Integrate Chat and Copilot into chat-sdk, and add SQL parse display (#166)

This commit is contained in:
williamhliu
2023-10-02 18:05:12 +08:00
committed by GitHub
parent 741ed4191b
commit 71cb20eb4f
68 changed files with 1353 additions and 882 deletions

View File

@@ -8,13 +8,17 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.7.0", "@ant-design/icons": "^4.7.0",
"@uiw/react-watermark": "^0.0.5", "@uiw/react-watermark": "^0.0.5",
"ahooks": "^3.7.8",
"antd": "^5.5.2", "antd": "^5.5.2",
"axios": "^1.4.0", "axios": "^0.21.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"echarts": "^5.4.2", "echarts": "^5.4.2",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"moment": "^2.29.4", "moment": "^2.29.4",
"react-copy-to-clipboard": "^5.1.0",
"react-spinners": "^0.13.8", "react-spinners": "^0.13.8",
"react-syntax-highlighter": "^15.5.0",
"sql-formatter": "^2.3.3",
"tslib": "^2.5.2" "tslib": "^2.5.2"
}, },
"peerDependencies": { "peerDependencies": {
@@ -72,6 +76,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/lodash": "^4.14.198",
"@types/node": "^16.18.31", "@types/node": "^16.18.31",
"@types/react": "^18.2.6", "@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
@@ -122,6 +127,7 @@
"rollup-plugin-exclude-dependencies-from-bundle": "^1.1.23", "rollup-plugin-exclude-dependencies-from-bundle": "^1.1.23",
"rollup-plugin-less": "^1.1.3", "rollup-plugin-less": "^1.1.3",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-styles": "^4.0.0",
"rollup-plugin-typescript2": "^0.34.1", "rollup-plugin-typescript2": "^0.34.1",
"sass-loader": "^12.3.0", "sass-loader": "^12.3.0",
"semver": "^7.3.5", "semver": "^7.3.5",

View File

@@ -28,7 +28,7 @@
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root" style="height: 100vh;"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -6,20 +6,10 @@
"src": "favicon.ico", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
} }
], ],
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "background_color": "#ffffff"
} }

View File

@@ -3,6 +3,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs' import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json' import json from '@rollup/plugin-json'
import less from 'rollup-plugin-less' import less from 'rollup-plugin-less'
import styles from "rollup-plugin-styles";
import postcss from 'rollup-plugin-postcss' import postcss from 'rollup-plugin-postcss'
import cssnano from 'cssnano' import cssnano from 'cssnano'
@@ -18,7 +19,12 @@ const config = {
commonjs(), commonjs(),
json(), json(),
typescript({ tsconfigOverride: overrides }), typescript({ tsconfigOverride: overrides }),
less({ output: 'dist/index.css' }), styles({
// mode: ["extract"],
// modules: true,
autoModules: id => id.includes(".module."),
}),
// less({ output: 'dist/index.css' }),
// postcss({ // postcss({
// plugins: [ // plugins: [
// cssnano() // cssnano()

View File

@@ -1,5 +1,6 @@
import basicConfig from './rollup.config.mjs' import basicConfig from './rollup.config.mjs'
import excludeDependenciesFromBundle from "rollup-plugin-exclude-dependencies-from-bundle" import excludeDependenciesFromBundle from "rollup-plugin-exclude-dependencies-from-bundle"
import terser from '@rollup/plugin-terser'
const config = { const config = {
...basicConfig, ...basicConfig,
@@ -12,6 +13,7 @@ const config = {
plugins: [ plugins: [
...basicConfig.plugins, ...basicConfig.plugins,
excludeDependenciesFromBundle(), excludeDependenciesFromBundle(),
terser()
] ]
} }

View File

@@ -1,9 +1,9 @@
import { PlusCircleOutlined } from '@ant-design/icons'; import { PlusCircleOutlined } from '@ant-design/icons';
import { AgentType } from '../type'; import { AgentType } from '../type';
import styles from './style.less'; import styles from './style.module.less';
import classNames from 'classnames'; import classNames from 'classnames';
import { message } from 'antd'; import { message } from 'antd';
import IconFont from '@/components/IconFont'; import IconFont from '../../components/IconFont';
import { AGENT_ICONS } from '../constants'; import { AGENT_ICONS } from '../constants';
type Props = { type Props = {

View File

@@ -1,14 +1,14 @@
import IconFont from '@/components/IconFont'; import IconFont from '../../components/IconFont';
import { getTextWidth, groupByColumn, isMobile } from '@/utils/utils'; import { getTextWidth, groupByColumn, isMobile } from '../../utils/utils';
import { AutoComplete, Select, Tag } from 'antd'; import { AutoComplete, Select, Tag } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import type { ForwardRefRenderFunction } from 'react'; import type { ForwardRefRenderFunction } from 'react';
import { searchRecommend } from 'supersonic-chat-sdk'; import { SemanticTypeEnum, SEMANTIC_TYPE_MAP, HOLDER_TAG } from '../constants';
import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants';
import styles from './style.less';
import { AgentType, ModelType } from '../type'; import { AgentType, ModelType } from '../type';
import { searchRecommend } from '../../service';
import styles from './style.module.less';
type Props = { type Props = {
inputMsg: string; inputMsg: string;
@@ -16,7 +16,7 @@ type Props = {
currentAgent?: AgentType; currentAgent?: AgentType;
agentList: AgentType[]; agentList: AgentType[];
onToggleHistoryVisible: () => void; onToggleHistoryVisible: () => void;
onOpenMobileAgents: () => void; onOpenAgents: () => void;
onInputMsgChange: (value: string) => void; onInputMsgChange: (value: string) => void;
onSendMsg: (msg: string, modelId?: number) => void; onSendMsg: (msg: string, modelId?: number) => void;
onAddConversation: (agent?: AgentType) => void; onAddConversation: (agent?: AgentType) => void;
@@ -42,13 +42,13 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
currentAgent, currentAgent,
agentList, agentList,
onToggleHistoryVisible, onToggleHistoryVisible,
onOpenMobileAgents, onOpenAgents,
onInputMsgChange, onInputMsgChange,
onSendMsg, onSendMsg,
onAddConversation, onAddConversation,
onSelectAgent, onSelectAgent,
}, },
ref, ref
) => { ) => {
const [modelOptions, setModelOptions] = useState<(ModelType | AgentType)[]>([]); const [modelOptions, setModelOptions] = useState<(ModelType | AgentType)[]>([]);
const [stepOptions, setStepOptions] = useState<Record<string, any[]>>({}); const [stepOptions, setStepOptions] = useState<Record<string, any[]>>({});
@@ -99,7 +99,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
.reduce((result, key) => { .reduce((result, key) => {
result[key] = data[key].slice( result[key] = data[key].slice(
0, 0,
Object.keys(data).length > 2 ? 2 : Object.keys(data).length > 1 ? 3 : 6, Object.keys(data).length > 2 ? 2 : Object.keys(data).length > 1 ? 3 : 6
); );
return result; return result;
}, {}) }, {})
@@ -110,7 +110,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
let msgValue = msg; let msgValue = msg;
let modelId: number | undefined; let modelId: number | undefined;
if (msg?.[0] === '/') { if (msg?.[0] === '/') {
const agent = agentList.find((item) => msg.includes(`/${item.name}`)); const agent = agentList.find(item => msg.includes(`/${item.name}`));
msgValue = agent ? msg.replace(`/${agent.name}`, '') : msg; msgValue = agent ? msg.replace(`/${agent.name}`, '') : msg;
} }
return { msgValue, modelId }; return { msgValue, modelId };
@@ -131,8 +131,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
if (fetchId !== fetchRef.current) { if (fetchId !== fetchRef.current) {
return; return;
} }
const recommends = msgValue ? res.data || [] : [];
const recommends = msgValue ? res.data.data || [] : [];
const stepOptionList = recommends.map((item: any) => item.subRecommend); const stepOptionList = recommends.map((item: any) => item.subRecommend);
if (stepOptionList.length > 0 && stepOptionList.every((item: any) => item !== null)) { if (stepOptionList.length > 0 && stepOptionList.every((item: any) => item !== null)) {
@@ -153,13 +152,11 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
setModelOptions(agentList); setModelOptions(agentList);
setStepOptions({}); setStepOptions({});
return; return;
} else { }
setOpen(false); if (modelOptions.length > 0) {
if (modelOptions.length > 0) { setTimeout(() => {
setTimeout(() => { setModelOptions([]);
setModelOptions([]); }, 50);
}, 50);
}
} }
if (!isSelect) { if (!isSelect) {
debounceGetWords(inputMsg, chatId, currentAgent); debounceGetWords(inputMsg, chatId, currentAgent);
@@ -180,7 +177,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
useEffect(() => { useEffect(() => {
const autoCompleteDropdown = document.querySelector( const autoCompleteDropdown = document.querySelector(
`.${styles.autoCompleteDropdown}`, `.${styles.autoCompleteDropdown}`
) as HTMLElement; ) as HTMLElement;
if (!autoCompleteDropdown) { if (!autoCompleteDropdown) {
return; return;
@@ -201,10 +198,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
result = result.concat(stepOptions[item]); result = result.concat(stepOptions[item]);
return result; return result;
}, []) }, [])
.find((item) => .find(item =>
Object.keys(stepOptions).length === 1 Object.keys(stepOptions).length === 1
? item.recommend === value ? item.recommend === value
: `${item.modelName || ''}${item.recommend}` === value, : `${item.modelName || ''}${item.recommend}` === value
); );
if (option && isSelect) { if (option && isSelect) {
onSendMsg(option.recommend, Object.keys(stepOptions).length > 1 ? option.modelId : undefined); onSendMsg(option.recommend, Object.keys(stepOptions).length > 1 ? option.modelId : undefined);
@@ -220,16 +217,16 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
const onSelect = (value: string) => { const onSelect = (value: string) => {
isSelect = true; isSelect = true;
if (modelOptions.length === 0) { if (modelOptions.length > 0) {
sendMsg(value); const agent = agentList.find(item => value.includes(item.name));
} else {
const agent = agentList.find((item) => value.includes(item.name));
if (agent) { if (agent) {
if (agent.id !== currentAgent?.id) { if (agent.id !== currentAgent?.id) {
onSelectAgent(agent); onSelectAgent(agent);
} }
onInputMsgChange(''); onInputMsgChange('');
} }
} else {
onInputMsgChange(value.replace(HOLDER_TAG, ''));
} }
setOpen(false); setOpen(false);
setTimeout(() => { setTimeout(() => {
@@ -241,7 +238,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
[styles.mobile]: isMobile, [styles.mobile]: isMobile,
}); });
const modelOptionNodes = modelOptions.map((model) => { const modelOptionNodes = modelOptions.map(model => {
return ( return (
<Option key={model.id} value={`/${model.name} `} className={styles.searchOption}> <Option key={model.id} value={`/${model.name} `} className={styles.searchOption}>
{model.name} {model.name}
@@ -249,22 +246,22 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
); );
}); });
const associateOptionNodes = Object.keys(stepOptions).map((key) => { const associateOptionNodes = Object.keys(stepOptions).map(key => {
return ( return (
<OptGroup key={key} label={key}> <OptGroup key={key} label={key}>
{stepOptions[key].map((option) => { {stepOptions[key].map(option => {
let optionValue = let optionValue =
Object.keys(stepOptions).length === 1 Object.keys(stepOptions).length === 1
? option.recommend ? option.recommend
: `${option.modelName || ''}${option.recommend}`; : `${option.modelName || ''}${option.recommend}`;
if (inputMsg[0] === '/') { if (inputMsg[0] === '/') {
const agent = agentList.find((item) => inputMsg.includes(item.name)); const agent = agentList.find(item => inputMsg.includes(item.name));
optionValue = agent ? `/${agent.name} ${option.recommend}` : optionValue; optionValue = agent ? `/${agent.name} ${option.recommend}` : optionValue;
} }
return ( return (
<Option <Option
key={`${option.recommend}${option.modelName ? `_${option.modelName}` : ''}`} key={`${option.recommend}${option.modelName ? `_${option.modelName}` : ''}`}
value={optionValue} value={`${optionValue}${HOLDER_TAG}`}
className={styles.searchOption} className={styles.searchOption}
> >
<div className={styles.optionContent}> <div className={styles.optionContent}>
@@ -312,12 +309,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
<div></div> <div></div>
</div> </div>
)} )}
{isMobile && ( <div className={styles.toolItem} onClick={onOpenAgents}>
<div className={styles.toolItem} onClick={onOpenMobileAgents}> <IconFont type="icon-zhinengzhuli" className={styles.toolIcon} />
<IconFont type="icon-zhinengzhuli" className={styles.toolIcon} /> <div></div>
<div></div> </div>
</div>
)}
</div> </div>
<div className={styles.composer}> <div className={styles.composer}>
<div className={styles.composerInputWrapper}> <div className={styles.composerInputWrapper}>
@@ -332,25 +327,24 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
}} }}
onSelect={onSelect} onSelect={onSelect}
autoFocus={!isMobile} autoFocus={!isMobile}
backfill
ref={inputRef} ref={inputRef}
id="chatInput" id="chatInput"
onKeyDown={(e) => { onKeyDown={e => {
if (e.code === 'Enter' || e.code === 'NumpadEnter') { if (e.code === 'Enter' || e.code === 'NumpadEnter') {
{ const chatInputEl: any = document.getElementById('chatInput');
const chatInputEl: any = document.getElementById('chatInput'); const agent = agentList.find(
if (!isSelect) { item => chatInputEl.value[0] === '/' && chatInputEl.value.includes(item.name)
sendMsg(chatInputEl.value); );
setOpen(false); if (agent) {
} else { if (agent.id !== currentAgent?.id) {
const agent = agentList.find((item) => chatInputEl.value.includes(item.name)); onSelectAgent(agent);
if (agent) {
if (agent.id !== currentAgent?.id) {
onSelectAgent(agent);
}
onInputMsgChange('');
}
} }
onInputMsgChange('');
return;
}
if (!isSelect) {
sendMsg(chatInputEl.value);
setOpen(false);
} }
} }
}} }}
@@ -364,7 +358,8 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
listHeight={500} listHeight={500}
allowClear allowClear
open={open} open={open}
getPopupContainer={(triggerNode) => triggerNode.parentNode} defaultActiveFirstOption={false}
getPopupContainer={triggerNode => triggerNode.parentNode}
> >
{modelOptions.length > 0 ? modelOptionNodes : associateOptionNodes} {modelOptions.length > 0 ? modelOptionNodes : associateOptionNodes}
</AutoComplete> </AutoComplete>

View File

@@ -81,7 +81,7 @@
.ant-select-selection-search-input { .ant-select-selection-search-input {
height: 40px !important; height: 40px !important;
padding: 0 16px; padding: 0 12px;
} }
.ant-select-selection-search { .ant-select-selection-search {
@@ -90,7 +90,7 @@
} }
.ant-select-selection-placeholder { .ant-select-selection-placeholder {
padding-left: 6px; padding-left: 2px;
line-height: 40px; line-height: 40px;
} }
} }

View File

@@ -6,52 +6,32 @@ import {
forwardRef, forwardRef,
ForwardRefRenderFunction, ForwardRefRenderFunction,
useImperativeHandle, useImperativeHandle,
memo,
} from 'react'; } from 'react';
import { useLocation } from 'umi';
import ConversationModal from '../components/ConversationModal'; import ConversationModal from '../components/ConversationModal';
import { deleteConversation, getAllConversations, saveConversation } from '../service'; import { deleteConversation, getAllConversations, saveConversation } from '../service';
import styles from './style.less'; import styles from './style.module.less';
import { AgentType, ConversationDetailType, DefaultEntityType } from '../type'; import { AgentType, ConversationDetailType } from '../type';
import { DEFAULT_CONVERSATION_NAME } from '../constants'; import { DEFAULT_CONVERSATION_NAME } from '../constants';
import moment from 'moment'; import moment from 'moment';
import { CloseOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons'; import { CloseOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
type Props = { type Props = {
agentList?: AgentType[];
currentAgent?: AgentType; currentAgent?: AgentType;
currentConversation?: ConversationDetailType; currentConversation?: ConversationDetailType;
historyVisible?: boolean; historyVisible?: boolean;
isCopilotMode?: boolean;
defaultEntityFilter?: DefaultEntityType;
triggerNewConversation?: boolean;
onNewConversationTriggered?: () => void;
onSelectConversation: ( onSelectConversation: (
conversation: ConversationDetailType, conversation: ConversationDetailType,
name?: string, sendMsgParams?: any,
modelId?: number, isAdd?: boolean
entityId?: string,
agent?: AgentType,
) => void; ) => void;
onCloseConversation: () => void; onCloseConversation: () => void;
}; };
const Conversation: ForwardRefRenderFunction<any, Props> = ( const Conversation: ForwardRefRenderFunction<any, Props> = (
{ { currentAgent, currentConversation, historyVisible, onSelectConversation, onCloseConversation },
agentList, ref
currentAgent,
currentConversation,
historyVisible,
isCopilotMode,
defaultEntityFilter,
triggerNewConversation,
onNewConversationTriggered,
onSelectConversation,
onCloseConversation,
},
ref,
) => { ) => {
const location = useLocation();
const { q, cid, modelId, entityId } = (location as any).query;
const [conversations, setConversations] = useState<ConversationDetailType[]>([]); const [conversations, setConversations] = useState<ConversationDetailType[]>([]);
const [editModalVisible, setEditModalVisible] = useState(false); const [editModalVisible, setEditModalVisible] = useState(false);
const [editConversation, setEditConversation] = useState<ConversationDetailType>(); const [editConversation, setEditConversation] = useState<ConversationDetailType>();
@@ -62,8 +42,8 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
onAddConversation, onAddConversation,
})); }));
const updateData = async (agent?: AgentType) => { const updateData = async (agentId?: number) => {
const { data } = await getAllConversations(agent?.id || currentAgent?.id); const { data } = await getAllConversations(agentId || currentAgent!.id);
const conversationList = data || []; const conversationList = data || [];
setConversations(conversationList.slice(0, 200)); setConversations(conversationList.slice(0, 200));
return conversationList; return conversationList;
@@ -72,42 +52,26 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
const initData = async () => { const initData = async () => {
const data = await updateData(); const data = await updateData();
if (data.length > 0) { if (data.length > 0) {
const chatId = cid; onSelectConversation(data[0]);
if (chatId) {
const conversation = data.find((item: any) => item.chatId === +chatId);
onSelectConversation(conversation || data[0]);
} else {
onSelectConversation(data[0]);
}
} else { } else {
onAddConversation(); onAddConversation();
} }
}; };
// useEffect(() => {
// if (triggerNewConversation) {
// return;
// }
// if (q && cid === undefined && window.location.href.includes('/chat')) {
// onAddConversation({
// name: q,
// modelId: modelId ? +modelId : undefined,
// entityId,
// });
// } else {
// initData();
// }
// }, [q]);
useEffect(() => { useEffect(() => {
if (currentAgent && !triggerNewConversation) { if (currentAgent) {
initData(); if (currentAgent.initialSendMsgParams) {
onAddConversation(currentAgent.initialSendMsgParams);
} else {
initData();
}
} }
}, [currentAgent]); }, [currentAgent]);
const addConversation = async (name?: string, agent?: AgentType) => { const addConversation = async (sendMsgParams?: any) => {
await saveConversation(name || DEFAULT_CONVERSATION_NAME, agent?.id || currentAgent!.id); const agentId = sendMsgParams?.agentId || currentAgent!.id;
return updateData(agent); await saveConversation(DEFAULT_CONVERSATION_NAME, agentId);
return updateData(agentId);
}; };
const onDeleteConversation = async (id: number) => { const onDeleteConversation = async (id: number) => {
@@ -115,28 +79,10 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
initData(); initData();
}; };
const onAddConversation = async ({ const onAddConversation = async (sendMsgParams?: any) => {
name, const data = await addConversation(sendMsgParams);
modelId,
entityId,
type,
agent,
}: {
name?: string;
modelId?: number;
entityId?: string;
type?: string;
agent?: AgentType;
} = {}) => {
const data = await addConversation(name, agent);
if (data.length > 0) { if (data.length > 0) {
onSelectConversation( onSelectConversation(data[0], sendMsgParams, true);
data[0],
type || name || DEFAULT_CONVERSATION_NAME,
modelId,
entityId,
agent,
);
} }
}; };
@@ -151,7 +97,6 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
const conversationClass = classNames(styles.conversation, { const conversationClass = classNames(styles.conversation, {
[styles.historyVisible]: historyVisible, [styles.historyVisible]: historyVisible,
[styles.copilotMode]: isCopilotMode,
}); });
const convertTime = (date: string) => { const convertTime = (date: string) => {
@@ -203,11 +148,11 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
<div className={styles.conversationList}> <div className={styles.conversationList}>
{conversations {conversations
.filter( .filter(
(conversation) => conversation =>
searchValue === '' || searchValue === '' ||
conversation.chatName.toLowerCase().includes(searchValue.toLowerCase()), conversation.chatName.toLowerCase().includes(searchValue.toLowerCase())
) )
.map((item) => { .map(item => {
const conversationItemClass = classNames(styles.conversationItem, { const conversationItemClass = classNames(styles.conversationItem, {
[styles.activeConversationItem]: currentConversation?.chatId === item.chatId, [styles.activeConversationItem]: currentConversation?.chatId === item.chatId,
}); });
@@ -249,7 +194,7 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
<div className={styles.subTitle}>{item.lastQuestion}</div> <div className={styles.subTitle}>{item.lastQuestion}</div>
<DeleteOutlined <DeleteOutlined
className={styles.deleteIcon} className={styles.deleteIcon}
onClick={(e) => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
onDeleteConversation(item.chatId); onDeleteConversation(item.chatId);
}} }}
@@ -277,4 +222,15 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
); );
}; };
export default forwardRef(Conversation); function areEqual(prevProps: Props, nextProps: Props) {
if (
prevProps.currentAgent?.id === nextProps.currentAgent?.id &&
prevProps.currentConversation?.chatId === nextProps.currentConversation?.chatId &&
prevProps.historyVisible === nextProps.historyVisible
) {
return true;
}
return false;
}
export default memo(forwardRef(Conversation), areEqual);

View File

@@ -166,11 +166,6 @@
width: 400px; width: 400px;
padding: 10px 16px; padding: 10px 16px;
border-left: 1px solid #f1f1f1; border-left: 1px solid #f1f1f1;
} z-index: 99;
&.copilotMode {
&.collapsed {
width: 0 !important;
}
} }
} }

View File

@@ -1,13 +1,13 @@
import Text from '../components/Text'; import Text from '../components/Text';
import { memo, useCallback, useEffect, useState } from 'react'; import { memo, useCallback, useEffect, useState } from 'react';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { ChatItem } from 'supersonic-chat-sdk';
import type { MsgDataType } from 'supersonic-chat-sdk';
import { AgentType, MessageItem, MessageTypeEnum } from '../type'; import { AgentType, MessageItem, MessageTypeEnum } from '../type';
import { isMobile, updateMessageContainerScroll } from '@/utils/utils'; import { isMobile, updateMessageContainerScroll } from '../../utils/utils';
import styles from './style.less'; import styles from './style.module.less';
import AgentTip from '../components/AgentTip'; import AgentTip from '../components/AgentTip';
import classNames from 'classnames'; import classNames from 'classnames';
import { MsgDataType } from '../../common/type';
import ChatItem from '../../components/ChatItem';
type Props = { type Props = {
id: string; id: string;
@@ -16,13 +16,14 @@ type Props = {
historyVisible: boolean; historyVisible: boolean;
currentAgent?: AgentType; currentAgent?: AgentType;
chatVisible?: boolean; chatVisible?: boolean;
isDeveloper?: boolean;
integrateSystem?: string;
onMsgDataLoaded: ( onMsgDataLoaded: (
data: MsgDataType, data: MsgDataType,
questionId: string | number, questionId: string | number,
question: string, question: string,
valid: boolean, valid: boolean
) => void; ) => void;
onApplyAuth: (model: string) => void;
onSendMsg: (value: string) => void; onSendMsg: (value: string) => void;
}; };
@@ -33,6 +34,8 @@ const MessageContainer: React.FC<Props> = ({
historyVisible, historyVisible,
currentAgent, currentAgent,
chatVisible, chatVisible,
isDeveloper,
integrateSystem,
onMsgDataLoaded, onMsgDataLoaded,
onSendMsg, onSendMsg,
}) => { }) => {
@@ -56,17 +59,6 @@ const MessageContainer: React.FC<Props> = ({
onResize(); onResize();
}, [historyVisible, chatVisible]); }, [historyVisible, chatVisible]);
const getFilters = (modelId?: number, entityId?: string) => {
if (!modelId || !entityId) {
return undefined;
}
return [
{
value: entityId,
},
];
};
const messageContainerClass = classNames(styles.messageContainer, { [styles.mobile]: isMobile }); const messageContainerClass = classNames(styles.messageContainer, { [styles.mobile]: isMobile });
return ( return (
@@ -77,15 +69,14 @@ const MessageContainer: React.FC<Props> = ({
id: msgId, id: msgId,
modelId, modelId,
agentId, agentId,
entityId,
type, type,
msg, msg,
msgValue, msgValue,
identityMsg, identityMsg,
msgData, msgData,
score, score,
isHistory,
parseOptions, parseOptions,
filters,
} = msgItem; } = msgItem;
return ( return (
@@ -104,14 +95,16 @@ const MessageContainer: React.FC<Props> = ({
conversationId={chatId} conversationId={chatId}
modelId={modelId} modelId={modelId}
agentId={agentId} agentId={agentId}
filter={getFilters(modelId, entityId)} filter={filters}
isLastMessage={index === messageList.length - 1} isLastMessage={index === messageList.length - 1}
isHistory={isHistory}
triggerResize={triggerResize} triggerResize={triggerResize}
isDeveloper={isDeveloper}
integrateSystem={integrateSystem}
onMsgDataLoaded={(data: MsgDataType, valid: boolean) => { onMsgDataLoaded={(data: MsgDataType, valid: boolean) => {
onMsgDataLoaded(data, msgId, msgValue || msg || '', valid); onMsgDataLoaded(data, msgId, msgValue || msg || '', valid);
}} }}
onUpdateMessageScroll={updateMessageContainerScroll} onUpdateMessageScroll={updateMessageContainerScroll}
onSendMsg={onSendMsg}
/> />
</> </>
)} )}
@@ -121,15 +114,16 @@ const MessageContainer: React.FC<Props> = ({
conversationId={chatId} conversationId={chatId}
modelId={modelId} modelId={modelId}
agentId={agentId} agentId={agentId}
filter={getFilters(modelId, entityId)} filter={filters}
isLastMessage={index === messageList.length - 1} isLastMessage={index === messageList.length - 1}
isHistory={isHistory}
triggerResize={triggerResize} triggerResize={triggerResize}
parseOptions={parseOptions} parseOptions={parseOptions}
integrateSystem={integrateSystem}
onMsgDataLoaded={(data: MsgDataType, valid: boolean) => { onMsgDataLoaded={(data: MsgDataType, valid: boolean) => {
onMsgDataLoaded(data, msgId, msgValue || msg || '', valid); onMsgDataLoaded(data, msgId, msgValue || msg || '', valid);
}} }}
onUpdateMessageScroll={updateMessageContainerScroll} onUpdateMessageScroll={updateMessageContainerScroll}
onSendMsg={onSendMsg}
/> />
)} )}
</div> </div>

View File

@@ -1,9 +1,9 @@
import IconFont from '@/components/IconFont'; import IconFont from '../../components/IconFont';
import { Drawer } from 'antd'; import { Drawer } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { AGENT_ICONS } from '../constants'; import { AGENT_ICONS } from '../constants';
import { AgentType } from '../type'; import { AgentType } from '../type';
import styles from './style.less'; import styles from './style.module.less';
type Props = { type Props = {
open: boolean; open: boolean;

View File

@@ -1,8 +1,8 @@
import LeftAvatar from '../CopilotAvatar'; import LeftAvatar from '../CopilotAvatar';
import Message from '../Message'; import Message from '../Message';
import styles from './style.less'; import styles from './style.module.less';
import { AgentType } from '../../type'; import { AgentType } from '../../type';
import { isMobile } from '@/utils/utils'; import { isMobile } from '../../../utils/utils';
type Props = { type Props = {
currentAgent?: AgentType; currentAgent?: AgentType;
@@ -24,7 +24,7 @@ const AgentTip: React.FC<Props> = ({ currentAgent, onSendMsg }) => {
<div className={styles.content}> <div className={styles.content}>
<div className={styles.examples}> <div className={styles.examples}>
{currentAgent.examples?.length > 0 ? ( {currentAgent.examples?.length > 0 ? (
currentAgent.examples.map((example) => ( currentAgent.examples.map(example => (
<div <div
key={example} key={example}
className={styles.example} className={styles.example}

View File

@@ -45,7 +45,7 @@ const ConversationModal: React.FC<Props> = ({ visible, editConversation, onClose
return ( return (
<Modal <Modal
title={`修改${CHAT_TITLE}问答名称`} title={`修改${CHAT_TITLE}问答名称`}
visible={visible} open={visible}
onCancel={onClose} onCancel={onClose}
onOk={onConfirm} onOk={onConfirm}
confirmLoading={loading} confirmLoading={loading}

View File

@@ -1,5 +1,5 @@
import IconFont from '@/components/IconFont'; import IconFont from '../../../components/IconFont';
import styles from './style.less'; import styles from './style.module.less';
const CopilotAvatar = () => { const CopilotAvatar = () => {
return <IconFont type="icon-zhinengsuanfa" className={styles.leftAvatar} />; return <IconFont type="icon-zhinengsuanfa" className={styles.leftAvatar} />;

View File

@@ -1,11 +1,13 @@
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './style.less'; import styles from './style.module.less';
import { ReactNode } from 'react';
type Props = { type Props = {
position: 'left' | 'right'; position: 'left' | 'right';
width?: number | string; width?: number | string;
height?: number | string; height?: number | string;
bubbleClassName?: string; bubbleClassName?: string;
children?: ReactNode;
}; };
const Message: React.FC<Props> = ({ position, width, height, children, bubbleClassName }) => { const Message: React.FC<Props> = ({ position, width, height, children, bubbleClassName }) => {
@@ -21,7 +23,7 @@ const Message: React.FC<Props> = ({ position, width, height, children, bubbleCla
<div <div
className={`${styles.bubble}${bubbleClassName ? ` ${bubbleClassName}` : ''}`} className={`${styles.bubble}${bubbleClassName ? ` ${bubbleClassName}` : ''}`}
style={{ height }} style={{ height }}
onClick={(e) => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
}} }}
> >

View File

@@ -1,10 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import LeftAvatar from '../CopilotAvatar'; import LeftAvatar from '../CopilotAvatar';
import Message from '../Message'; import Message from '../Message';
import styles from './style.less'; import styles from './style.module.less';
import { queryRecommendQuestions } from '../../service'; import { queryRecommendQuestions } from '../../service';
import Typing from '../Typing'; import { isMobile } from '../../../utils/utils';
import { isMobile } from '@/utils/utils';
type Props = { type Props = {
onSelectQuestion: (value: string) => void; onSelectQuestion: (value: string) => void;
@@ -25,7 +24,7 @@ const RecommendQuestions: React.FC<Props> = ({ onSelectQuestion }) => {
...item.recommendedQuestions.slice(0, 20).map((item: any) => item.question), ...item.recommendedQuestions.slice(0, 20).map((item: any) => item.question),
]; ];
return result; return result;
}, []) || [], }, []) || []
); );
}; };
@@ -37,7 +36,7 @@ const RecommendQuestions: React.FC<Props> = ({ onSelectQuestion }) => {
<div className={styles.recommendQuestions}> <div className={styles.recommendQuestions}>
{!isMobile && <LeftAvatar />} {!isMobile && <LeftAvatar />}
{loading ? ( {loading ? (
<Typing /> <></>
) : questions.length > 0 ? ( ) : questions.length > 0 ? (
<Message position="left" bubbleClassName={styles.recommendQuestionsMsg}> <Message position="left" bubbleClassName={styles.recommendQuestionsMsg}>
<div className={styles.title}></div> <div className={styles.title}></div>

View File

@@ -5,26 +5,26 @@
padding: 12px 20px 20px !important; padding: 12px 20px 20px !important;
.title { .title {
font-size: 14px;
font-weight: 500;
margin-bottom: 12px; margin-bottom: 12px;
font-weight: 500;
font-size: 14px;
} }
.content { .content {
display: flex; display: flex;
align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center;
column-gap: 16px; column-gap: 16px;
row-gap: 20px; row-gap: 20px;
.question { .question {
padding: 0 6px;
height: 22px; height: 22px;
line-height: 22px; padding: 0 6px;
font-size: 12px;
color: var(--text-color); color: var(--text-color);
border-radius:11px; font-size: 12px;
line-height: 22px;
background-color: #f4f4f4; background-color: #f4f4f4;
border-radius: 11px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {

View File

@@ -1,7 +1,9 @@
import { isMobile } from '../../utils/utils';
import { Avatar } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import LeftAvatar from './CopilotAvatar'; import LeftAvatar from './CopilotAvatar';
import Message from './Message'; import Message from './Message';
import styles from './style.less'; import styles from './style.module.less';
type Props = { type Props = {
position: 'left' | 'right'; position: 'left' | 'right';
@@ -15,7 +17,7 @@ const Text: React.FC<Props> = ({ position, data, quote }) => {
}); });
return ( return (
<div className={textWrapperClass}> <div className={textWrapperClass}>
{position === 'left' && <LeftAvatar />} {!isMobile && position === 'left' && <LeftAvatar />}
<Message position={position} bubbleClassName={styles.textBubble}> <Message position={position} bubbleClassName={styles.textBubble}>
{position === 'right' && quote && <div className={styles.quote}>{quote}</div>} {position === 'right' && quote && <div className={styles.quote}>{quote}</div>}
<div className={styles.text}>{data}</div> <div className={styles.text}>{data}</div>

View File

@@ -21,7 +21,7 @@ export enum SemanticTypeEnum {
} }
export const SEMANTIC_TYPE_MAP = { export const SEMANTIC_TYPE_MAP = {
[SemanticTypeEnum.MODEL]: '主题域', [SemanticTypeEnum.MODEL]: '数据来源',
[SemanticTypeEnum.DIMENSION]: '维度', [SemanticTypeEnum.DIMENSION]: '维度',
[SemanticTypeEnum.METRIC]: '指标', [SemanticTypeEnum.METRIC]: '指标',
[SemanticTypeEnum.VALUE]: '维度值', [SemanticTypeEnum.VALUE]: '维度值',
@@ -41,6 +41,8 @@ export const AGENT_ICONS = [
'icon-mendiankanban', 'icon-mendiankanban',
]; ];
export const HOLDER_TAG = '@_supersonic_@';
export const CHAT_TITLE = ''; export const CHAT_TITLE = '';
export const DEFAULT_CONVERSATION_NAME = '新问答对话'; export const DEFAULT_CONVERSATION_NAME = '新问答对话';
@@ -49,4 +51,8 @@ export const PAGE_TITLE = '问答对话';
export const WEB_TITLE = '问答对话'; export const WEB_TITLE = '问答对话';
export const PLACE_HOLDER = '请输入您的问题'; export const MOBILE_TITLE = '问答对话';
export const PLACE_HOLDER = '请输入您的问题,或输入“/”切换助理';
export const SIMPLE_PLACE_HOLDER = '请输入您的问题';

View File

@@ -1,52 +1,57 @@
import { updateMessageContainerScroll, isMobile, uuid } from '@/utils/utils'; import { updateMessageContainerScroll, isMobile, uuid, setToken } from '../utils/utils';
import { useEffect, useRef, useState } from 'react';
import { Helmet, useDispatch } from 'umi';
import MessageContainer from './MessageContainer';
import styles from './style.less';
import { import {
ConversationDetailType, ForwardRefRenderFunction,
DefaultEntityType, forwardRef,
ModelType, useEffect,
MessageItem, useImperativeHandle,
MessageTypeEnum, useRef,
AgentType, useState,
} from './type'; } from 'react';
import MessageContainer from './MessageContainer';
import styles from './style.module.less';
import { ConversationDetailType, MessageItem, MessageTypeEnum, AgentType } from './type';
import { queryAgentList } from './service'; import { queryAgentList } from './service';
import { useThrottleFn } from 'ahooks'; import { useThrottleFn } from 'ahooks';
import Conversation from './Conversation'; import Conversation from './Conversation';
import ChatFooter from './ChatFooter'; import ChatFooter from './ChatFooter';
import classNames from 'classnames'; import classNames from 'classnames';
import { CHAT_TITLE, DEFAULT_CONVERSATION_NAME, WEB_TITLE } from './constants'; import { CHAT_TITLE } from './constants';
import { HistoryMsgItemType, MsgDataType, getHistoryMsg } from 'supersonic-chat-sdk';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import 'supersonic-chat-sdk/dist/index.css';
import { setToken as setChatSdkToken } from 'supersonic-chat-sdk';
import AgentList from './AgentList'; import AgentList from './AgentList';
import { AUTH_TOKEN_KEY } from '@/common/constants';
import MobileAgents from './MobileAgents'; import MobileAgents from './MobileAgents';
import { HistoryMsgItemType, MsgDataType, SendMsgParamsType } from '../common/type';
import { getHistoryMsg } from '../service';
type Props = { type Props = {
isCopilotMode?: boolean; token?: string;
defaultModelName?: string; agentIds?: number[];
defaultEntityFilter?: DefaultEntityType; initialAgentId?: number;
copilotSendMsg?: string;
triggerNewConversation?: boolean;
chatVisible?: boolean; chatVisible?: boolean;
onNewConversationTriggered?: () => void; noInput?: boolean;
onCurrentModelChange?: (model?: ModelType) => void; isDeveloper?: boolean;
integrateSystem?: string;
isCopilot?: boolean;
apiUrl?: string;
onCurrentAgentChange?: (agent?: AgentType) => void;
onReportMsgEvent?: (msg: string, valid: boolean) => void;
}; };
const Chat: React.FC<Props> = ({ const Chat: ForwardRefRenderFunction<any, Props> = (
isCopilotMode, {
defaultModelName, token,
defaultEntityFilter, agentIds,
copilotSendMsg, initialAgentId,
triggerNewConversation, chatVisible,
chatVisible, noInput,
onNewConversationTriggered, isDeveloper,
}) => { integrateSystem,
const isMobileMode = isMobile || isCopilotMode; isCopilot,
apiUrl,
onCurrentAgentChange,
onReportMsgEvent,
},
ref
) => {
const [messageList, setMessageList] = useState<MessageItem[]>([]); const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [inputMsg, setInputMsg] = useState(''); const [inputMsg, setInputMsg] = useState('');
const [pageNo, setPageNo] = useState(1); const [pageNo, setPageNo] = useState(1);
@@ -56,95 +61,97 @@ const Chat: React.FC<Props> = ({
ConversationDetailType | undefined ConversationDetailType | undefined
>(isMobile ? { chatId: 0, chatName: `${CHAT_TITLE}问答` } : undefined); >(isMobile ? { chatId: 0, chatName: `${CHAT_TITLE}问答` } : undefined);
const [historyVisible, setHistoryVisible] = useState(false); const [historyVisible, setHistoryVisible] = useState(false);
const [defaultEntity, setDefaultEntity] = useState<DefaultEntityType>();
const [agentList, setAgentList] = useState<AgentType[]>([]); const [agentList, setAgentList] = useState<AgentType[]>([]);
const [currentAgent, setCurrentAgent] = useState<AgentType>(); const [currentAgent, setCurrentAgent] = useState<AgentType>();
const [mobileAgentsVisible, setMobileAgentsVisible] = useState(false); const [mobileAgentsVisible, setMobileAgentsVisible] = useState(false);
const dispatch = useDispatch(); const [agentListVisible, setAgentListVisible] = useState(true);
const conversationRef = useRef<any>(); const conversationRef = useRef<any>();
const chatFooterRef = useRef<any>(); const chatFooterRef = useRef<any>();
useImperativeHandle(ref, () => ({
sendCopilotMsg,
}));
const sendCopilotMsg = (params: SendMsgParamsType) => {
setAgentListVisible(false);
const { agentId, msg, modelId } = params;
if (currentAgent?.id !== agentId) {
setMessageList([]);
const agent = agentList.find(item => item.id === agentId) || ({} as AgentType);
updateCurrentAgent({ ...agent, initialSendMsgParams: params });
} else {
onSendMsg(msg, messageList, modelId, params);
}
};
const updateCurrentAgent = (agent?: AgentType) => {
setCurrentAgent(agent);
onCurrentAgentChange?.(agent);
localStorage.setItem('AGENT_ID', `${agent?.id}`);
if (!isCopilot) {
window.history.replaceState({}, '', `${window.location.pathname}?agentId=${agent?.id}`);
}
};
const initAgentList = async () => { const initAgentList = async () => {
const res = await queryAgentList(); const res = await queryAgentList();
const agentListValue = (res.data || []).filter((item) => item.status === 1); const agentListValue = (res.data || []).filter(
item => item.status === 1 && (agentIds === undefined || agentIds.includes(item.id))
);
setAgentList(agentListValue); setAgentList(agentListValue);
if (agentListValue.length > 0) { if (agentListValue.length > 0) {
setCurrentAgent(agentListValue[0]); const agentId = initialAgentId || localStorage.getItem('AGENT_ID');
if (agentId) {
const agent = agentListValue.find(item => item.id === +agentId);
updateCurrentAgent(agent || agentListValue[0]);
} else {
updateCurrentAgent(agentListValue[0]);
}
} }
}; };
useEffect(() => { useEffect(() => {
setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || '');
initAgentList(); initAgentList();
}, []); }, []);
useEffect(() => {
if (token) {
setToken(token);
}
}, [token]);
useEffect(() => {
if (apiUrl) {
localStorage.setItem('SUPERSONIC_CHAT_API_URL', apiUrl);
}
}, [apiUrl]);
useEffect(() => { useEffect(() => {
if (chatVisible) { if (chatVisible) {
inputFocus();
updateMessageContainerScroll(); updateMessageContainerScroll();
} }
}, [chatVisible]); }, [chatVisible]);
useEffect(() => {
if (triggerNewConversation) {
setCurrentAgent(agentList?.find((item) => item.name === '做分析'));
conversationRef.current?.onAddConversation({
type: 'CUSTOMIZE',
modelId: defaultEntityFilter?.modelId,
entityId: defaultEntityFilter?.entityId,
agent: agentList?.find((item) => item.name === '做分析'),
});
setTimeout(() => {
onNewConversationTriggered?.();
}, 200);
}
}, [triggerNewConversation]);
useEffect(() => { useEffect(() => {
if (!currentConversation) { if (!currentConversation) {
return; return;
} }
const { initMsg, modelId, entityId, agent } = currentConversation; const { initialMsgParams, isAdd } = currentConversation;
if (initMsg) { if (isAdd) {
inputFocus(); inputFocus();
if (initMsg === 'CUSTOMIZE' && copilotSendMsg) { if (initialMsgParams) {
onSendMsg( onSendMsg(initialMsgParams.msg, [], initialMsgParams.modelId, initialMsgParams);
copilotSendMsg,
[],
modelId,
entityId,
agentList.find((item) => item.name === '做分析'),
);
dispatch({
type: 'globalState/setCopilotSendMsg',
payload: '',
});
return; return;
} }
if (initMsg === DEFAULT_CONVERSATION_NAME || initMsg.includes('CUSTOMIZE')) { sendHelloRsp();
if (agent) {
setCurrentAgent(agent);
}
sendHelloRsp(agent);
return;
}
onSendMsg(
initMsg,
[],
modelId,
entityId,
initMsg.includes('商业线索') ? agentList.find((item) => item.name === '做分析') : undefined,
);
return; return;
} }
updateHistoryMsg(1); updateHistoryMsg(1);
setPageNo(1); setPageNo(1);
}, [currentConversation]); }, [currentConversation]);
useEffect(() => {
setDefaultEntity(defaultEntityFilter);
}, [defaultEntityFilter]);
useEffect(() => { useEffect(() => {
if (historyInited) { if (historyInited) {
const messageContainerEle = document.getElementById('messageContainer'); const messageContainerEle = document.getElementById('messageContainer');
@@ -157,11 +164,14 @@ const Chat: React.FC<Props> = ({
}, [historyInited]); }, [historyInited]);
const sendHelloRsp = (agent?: AgentType) => { const sendHelloRsp = (agent?: AgentType) => {
if (noInput) {
return;
}
setMessageList([ setMessageList([
{ {
id: uuid(), id: uuid(),
type: MessageTypeEnum.AGENT_LIST, type: MessageTypeEnum.AGENT_LIST,
msg: agent?.name || currentAgent?.name || '查信息', msg: agent?.name || currentAgent?.name || agentList?.[0].name,
}, },
]); ]);
}; };
@@ -173,13 +183,12 @@ const Chat: React.FC<Props> = ({
msg: item.queryText, msg: item.queryText,
msgData: item.queryResult, msgData: item.queryResult,
score: item.score, score: item.score,
isHistory: true,
})); }));
}; };
const updateHistoryMsg = async (page: number) => { const updateHistoryMsg = async (page: number) => {
const res = await getHistoryMsg(page, currentConversation!.chatId, 3); const res = await getHistoryMsg(page, currentConversation!.chatId, 3);
const { hasNextPage, list } = res.data?.data || { hasNextPage: false, list: [] }; const { hasNextPage, list } = res?.data || { hasNextPage: false, list: [] };
const msgList = [...convertHistoryMsg(list), ...(page === 1 ? [] : messageList)]; const msgList = [...convertHistoryMsg(list), ...(page === 1 ? [] : messageList)];
setMessageList(msgList); setMessageList(msgList);
setHasNextPage(hasNextPage); setHasNextPage(hasNextPage);
@@ -197,7 +206,7 @@ const Chat: React.FC<Props> = ({
}; };
const { run: handleScroll } = useThrottleFn( const { run: handleScroll } = useThrottleFn(
(e) => { e => {
if (e.target.scrollTop === 0 && hasNextPage) { if (e.target.scrollTop === 0 && hasNextPage) {
updateHistoryMsg(pageNo + 1); updateHistoryMsg(pageNo + 1);
setPageNo(pageNo + 1); setPageNo(pageNo + 1);
@@ -207,7 +216,7 @@ const Chat: React.FC<Props> = ({
leading: true, leading: true,
trailing: true, trailing: true,
wait: 200, wait: 200,
}, }
); );
const inputFocus = () => { const inputFocus = () => {
@@ -224,8 +233,7 @@ const Chat: React.FC<Props> = ({
msg?: string, msg?: string,
list?: MessageItem[], list?: MessageItem[],
modelId?: number, modelId?: number,
entityId?: string, sendMsgParams?: SendMsgParamsType
agent?: AgentType,
) => { ) => {
const currentMsg = msg || inputMsg; const currentMsg = msg || inputMsg;
if (currentMsg.trim() === '') { if (currentMsg.trim() === '') {
@@ -233,13 +241,14 @@ const Chat: React.FC<Props> = ({
return; return;
} }
const msgAgent = agentList.find((item) => currentMsg.indexOf(item.name) === 1); const msgAgent = agentList.find(item => currentMsg.indexOf(item.name) === 1);
const certainAgent = currentMsg[0] === '/' && msgAgent; const certainAgent = currentMsg[0] === '/' && msgAgent;
const agentIdValue = certainAgent ? msgAgent.id : undefined; const agentIdValue = certainAgent ? msgAgent.id : undefined;
if (agent || certainAgent) { const agent = agentList.find(item => item.id === sendMsgParams?.agentId);
setCurrentAgent(agent || msgAgent);
}
if (agent || certainAgent) {
updateCurrentAgent(agent || msgAgent);
}
const msgs = [ const msgs = [
...(list || messageList), ...(list || messageList),
{ {
@@ -250,8 +259,8 @@ const Chat: React.FC<Props> = ({
: currentMsg, : currentMsg,
modelId: modelId === -1 ? undefined : modelId, modelId: modelId === -1 ? undefined : modelId,
agentId: agent?.id || agentIdValue || currentAgent?.id, agentId: agent?.id || agentIdValue || currentAgent?.id,
entityId: entityId || defaultEntity?.entityId,
type: MessageTypeEnum.QUESTION, type: MessageTypeEnum.QUESTION,
filters: sendMsgParams?.filters,
}, },
]; ];
setMessageList(msgs); setMessageList(msgs);
@@ -276,35 +285,26 @@ const Chat: React.FC<Props> = ({
const onSelectConversation = ( const onSelectConversation = (
conversation: ConversationDetailType, conversation: ConversationDetailType,
name?: string, sendMsgParams?: SendMsgParamsType,
modelId?: number, isAdd?: boolean
entityId?: string,
agent?: AgentType,
) => { ) => {
if (!isMobileMode) {
window.history.replaceState('', '', `?q=${conversation.chatName}&cid=${conversation.chatId}`);
}
setCurrentConversation({ setCurrentConversation({
...conversation, ...conversation,
initMsg: name, initialMsgParams: sendMsgParams,
modelId, isAdd,
entityId,
agent,
}); });
saveConversationToLocal(conversation); saveConversationToLocal(conversation);
}; };
const reportMsgEvent = (msg: string, valid: boolean) => {};
const onMsgDataLoaded = ( const onMsgDataLoaded = (
data: MsgDataType, data: MsgDataType,
questionId: string | number, questionId: string | number,
question: string, question: string,
valid: boolean, valid: boolean
) => { ) => {
reportMsgEvent(question, valid); onReportMsgEvent?.(question, valid);
if (!isMobile) { if (!isMobile) {
conversationRef?.current?.updateData(); conversationRef?.current?.updateData(currentAgent?.id);
} }
if (!data) { if (!data) {
return; return;
@@ -319,7 +319,7 @@ const Chat: React.FC<Props> = ({
}; };
} }
const msgs = cloneDeep(messageList); const msgs = cloneDeep(messageList);
const msg = msgs.find((item) => item.id === questionId); const msg = msgs.find(item => item.id === questionId);
if (msg) { if (msg) {
msg.msgData = data; msg.msgData = data;
const msgList = [...msgs, ...(parseOptionsItem ? [parseOptionsItem] : [])]; const msgList = [...msgs, ...(parseOptionsItem ? [parseOptionsItem] : [])];
@@ -332,10 +332,8 @@ const Chat: React.FC<Props> = ({
setHistoryVisible(!historyVisible); setHistoryVisible(!historyVisible);
}; };
const onApplyAuth = (model: string) => {}; const onAddConversation = () => {
conversationRef.current?.onAddConversation();
const onAddConversation = (agent?: AgentType) => {
conversationRef.current?.onAddConversation({ agent });
inputFocus(); inputFocus();
}; };
@@ -343,7 +341,10 @@ const Chat: React.FC<Props> = ({
if (agent.id === currentAgent?.id) { if (agent.id === currentAgent?.id) {
return; return;
} }
setCurrentAgent(agent); if (messageList.length === 1 && messageList[0].type === MessageTypeEnum.AGENT_LIST) {
setMessageList([]);
}
updateCurrentAgent(agent);
updateMessageContainerScroll(); updateMessageContainerScroll();
}; };
@@ -365,9 +366,8 @@ const Chat: React.FC<Props> = ({
return ( return (
<div className={chatClass}> <div className={chatClass}>
{!isMobileMode && <Helmet title={WEB_TITLE} />}
<div className={styles.chatSection}> <div className={styles.chatSection}>
{!isMobile && ( {!isMobile && agentList.length > 1 && agentListVisible && (
<AgentList <AgentList
agentList={agentList} agentList={agentList}
currentAgent={currentAgent} currentAgent={currentAgent}
@@ -378,7 +378,7 @@ const Chat: React.FC<Props> = ({
{currentConversation && ( {currentConversation && (
<div className={styles.chatBody}> <div className={styles.chatBody}>
<div className={styles.chatContent}> <div className={styles.chatContent}>
{currentAgent && !isMobile && ( {currentAgent && !isMobile && !noInput && (
<div className={styles.chatHeader}> <div className={styles.chatHeader}>
<div className={styles.chatHeaderTitle}>{currentAgent.name}</div> <div className={styles.chatHeaderTitle}>{currentAgent.name}</div>
<div className={styles.chatHeaderTip}>{currentAgent.description}</div> <div className={styles.chatHeaderTip}>{currentAgent.description}</div>
@@ -391,38 +391,40 @@ const Chat: React.FC<Props> = ({
historyVisible={historyVisible} historyVisible={historyVisible}
currentAgent={currentAgent} currentAgent={currentAgent}
chatVisible={chatVisible} chatVisible={chatVisible}
isDeveloper={isDeveloper}
integrateSystem={integrateSystem}
onMsgDataLoaded={onMsgDataLoaded} onMsgDataLoaded={onMsgDataLoaded}
onApplyAuth={onApplyAuth}
onSendMsg={onSendMsg} onSendMsg={onSendMsg}
/> />
<ChatFooter {!noInput && (
inputMsg={inputMsg} <ChatFooter
chatId={currentConversation?.chatId} inputMsg={inputMsg}
agentList={agentList} chatId={currentConversation?.chatId}
currentAgent={currentAgent} agentList={agentList}
onToggleHistoryVisible={onToggleHistoryVisible} currentAgent={currentAgent}
onInputMsgChange={onInputMsgChange} onToggleHistoryVisible={onToggleHistoryVisible}
onSendMsg={sendMsg} onInputMsgChange={onInputMsgChange}
onAddConversation={onAddConversation} onSendMsg={sendMsg}
onSelectAgent={onSelectAgent} onAddConversation={onAddConversation}
onOpenMobileAgents={() => { onSelectAgent={onSelectAgent}
setMobileAgentsVisible(true); onOpenAgents={() => {
}} if (isMobile) {
ref={chatFooterRef} setMobileAgentsVisible(true);
/> } else {
setAgentListVisible(!agentListVisible);
}
}}
ref={chatFooterRef}
/>
)}
</div> </div>
</div> </div>
)} )}
</div> </div>
<Conversation <Conversation
agentList={agentList}
currentAgent={currentAgent} currentAgent={currentAgent}
currentConversation={currentConversation} currentConversation={currentConversation}
historyVisible={historyVisible} historyVisible={historyVisible}
isCopilotMode={isCopilotMode}
defaultEntityFilter={defaultEntityFilter}
triggerNewConversation={triggerNewConversation}
onNewConversationTriggered={onNewConversationTriggered}
onSelectConversation={onSelectConversation} onSelectConversation={onSelectConversation}
onCloseConversation={onCloseConversation} onCloseConversation={onCloseConversation}
ref={conversationRef} ref={conversationRef}
@@ -441,4 +443,4 @@ const Chat: React.FC<Props> = ({
); );
}; };
export default Chat; export default forwardRef(Chat);

View File

@@ -0,0 +1,51 @@
import axios from '../service/axiosInstance';
import { isMobile } from '../utils/utils';
import { AgentType, ModelType } from './type';
const prefix = isMobile ? '/openapi' : '/api';
export function saveConversation(chatName: string, agentId: number) {
return axios.post<any>(
`${prefix}/chat/manage/save?chatName=${chatName}&agentId=${agentId}`
);
}
export function updateConversationName(chatName: string, chatId: number = 0) {
return axios.post<any>(
`${prefix}/chat/manage/updateChatName?chatName=${chatName}&chatId=${chatId}`,
);
}
export function deleteConversation(chatId: number) {
return axios.post<any>(`${prefix}/chat/manage/delete?chatId=${chatId}`);
}
export function getAllConversations(agentId?: number) {
return axios.get<any>(`${prefix}/chat/manage/getAll`, { params: { agentId } });
}
export function getModelList() {
return axios.get<ModelType[]>(`${prefix}/chat/conf/modelList/view`);
}
export function updateQAFeedback(questionId: number, score: number) {
return axios.post<any>(
`${prefix}/chat/manage/updateQAFeedback?id=${questionId}&score=${score}&feedback=`,
);
}
export function queryMetricSuggestion(modelId: number) {
return axios.get<any>(`${prefix}/chat/recommend/metric/${modelId}`);
}
export function querySuggestion(modelId: number) {
return axios.get<any>(`${prefix}/chat/recommend/${modelId}`);
}
export function queryRecommendQuestions() {
return axios.get<any>(`${prefix}/chat/recommend/question`);
}
export function queryAgentList() {
return axios.get<AgentType[]>(`${prefix}/chat/agent/getAgentList`);
}

View File

@@ -1,5 +1,3 @@
@import '~antd/es/style/themes/default.less';
.chat { .chat {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
@@ -33,7 +31,7 @@
.chatHeader { .chatHeader {
position: absolute; position: absolute;
top: 0; top: 0;
z-index: 999; z-index: 9;
display: flex; display: flex;
align-items: baseline; align-items: baseline;
width: 100%; width: 100%;

View File

@@ -1,4 +1,4 @@
import { ChatContextType, MsgDataType } from 'supersonic-chat-sdk'; import { ChatContextType, MsgDataType, SendMsgParamsType } from "../common/type";
export enum MessageTypeEnum { export enum MessageTypeEnum {
TEXT = 'text', // 指标文本 TEXT = 'text', // 指标文本
@@ -27,8 +27,8 @@ export type MessageItem = {
quote?: string; quote?: string;
score?: number; score?: number;
feedback?: string; feedback?: string;
isHistory?: boolean;
parseOptions?: ChatContextType[]; parseOptions?: ChatContextType[];
filters?: any;
}; };
export type ConversationDetailType = { export type ConversationDetailType = {
@@ -38,10 +38,8 @@ export type ConversationDetailType = {
creator?: string; creator?: string;
lastQuestion?: string; lastQuestion?: string;
lastTime?: string; lastTime?: string;
initMsg?: string; initialMsgParams?: SendMsgParamsType;
modelId?: number; isAdd?: boolean;
entityId?: string;
agent?: AgentType;
}; };
export enum MessageModeEnum { export enum MessageModeEnum {
@@ -67,13 +65,6 @@ export type PluginType = {
comment: string; comment: string;
}; };
export type DefaultEntityType = {
entityId: string;
entityName: string;
modelName?: string;
modelId?: number;
};
export type SuggestionItemType = { export type SuggestionItemType = {
id: number; id: number;
model: number; model: number;
@@ -92,4 +83,5 @@ export type AgentType = {
description: string; description: string;
examples: string[]; examples: string[];
status: 0 | 1; status: 0 | 1;
initialSendMsgParams?: SendMsgParamsType;
}; };

View File

@@ -0,0 +1,148 @@
import IconFont from '../components/IconFont';
import { CaretRightOutlined, CloseOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import {
ForwardRefRenderFunction,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import Chat from '../Chat';
import { AgentType } from '../Chat/type';
import { setToken } from '../utils/utils';
import { SendMsgParamsType } from '../common/type';
import styles from './style.module.less';
type Props = {
token?: string;
agentIds?: number[];
noInput?: boolean;
isDeveloper?: boolean;
integrateSystem?: string;
apiUrl?: string;
onReportMsgEvent?: (msg: string, valid: boolean) => void;
onOpenChatPage?: (agentId?: number) => void;
};
const Copilot: ForwardRefRenderFunction<any, Props> = (
{
token,
agentIds,
noInput,
isDeveloper,
integrateSystem,
apiUrl,
onReportMsgEvent,
onOpenChatPage,
},
ref
) => {
const [chatVisible, setChatVisible] = useState(false);
const [copilotMinimized, setCopilotMinimized] = useState(false);
const [currentAgent, setCurrentAgent] = useState<AgentType>();
const chatRef = useRef<any>();
useImperativeHandle(ref, () => ({
sendCopilotMsg,
}));
useEffect(() => {
if (token) {
setToken(token);
}
}, [token]);
useEffect(() => {
if (apiUrl) {
localStorage.setItem('SUPERSONIC_CHAT_API_URL', apiUrl);
}
}, [apiUrl]);
const sendCopilotMsg = (params: SendMsgParamsType) => {
chatRef?.current?.sendCopilotMsg(params);
updateChatVisible(true);
};
const updateChatVisible = (visible: boolean) => {
setChatVisible(visible);
};
const onToggleChatVisible = () => {
updateChatVisible(!chatVisible);
};
const onCloseChat = () => {
updateChatVisible(false);
};
const onTransferChat = () => {
onOpenChatPage?.(currentAgent?.id);
};
const onMinimizeCopilot = (e: any) => {
e.stopPropagation();
updateChatVisible(false);
setCopilotMinimized(true);
};
const copilotClass = classNames(styles.copilot, {
[styles.copilotMinimized]: copilotMinimized,
});
const chatPopoverClass = classNames(styles.chatPopover, {
[styles.c2System]: integrateSystem === 'c2',
});
return (
<>
<div
className={copilotClass}
onMouseEnter={() => {
setCopilotMinimized(false);
}}
onClick={onToggleChatVisible}
>
<IconFont type="icon-copilot-fill" />
<div className={styles.minimizeWrapper} onClick={onMinimizeCopilot}>
<div className={styles.minimize}>-</div>
</div>
</div>
<div className={styles.copilotContent} style={{ display: chatVisible ? 'block' : 'none' }}>
<div className={chatPopoverClass}>
<div className={styles.header}>
<div className={styles.leftSection}>
<CloseOutlined className={styles.close} onClick={onCloseChat} />
{onOpenChatPage && (
<IconFont
type="icon-weibiaoti-"
className={styles.transfer}
onClick={onTransferChat}
/>
)}
</div>
<div className={styles.title}></div>
</div>
<div className={styles.chat}>
<Chat
chatVisible={chatVisible}
agentIds={agentIds}
noInput={noInput}
isDeveloper={isDeveloper}
integrateSystem={integrateSystem}
isCopilot
onCurrentAgentChange={setCurrentAgent}
onReportMsgEvent={onReportMsgEvent}
ref={chatRef}
/>
</div>
</div>
<CaretRightOutlined className={styles.rightArrow} />
</div>
</>
);
};
export default forwardRef(Copilot);

View File

@@ -25,24 +25,24 @@
} }
.minimizeWrapper { .minimizeWrapper {
position: absolute;
top: -18px;
right: -6px;
display: none; display: none;
cursor: pointer;
width: 22px; width: 22px;
height: 22px; height: 22px;
position: absolute;
right: -6px;
top: -18px;
padding: 4px; padding: 4px;
cursor: pointer;
.minimize { .minimize {
width: 100%;
height: 100%;
padding-bottom: 5px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 50%; width: 100%;
height: 100%;
padding-bottom: 5px;
background-color: var(--text-color-fifth-4); background-color: var(--text-color-fifth-4);
border-radius: 50%;
transition: all 0.1s ease-in-out; transition: all 0.1s ease-in-out;
} }
@@ -78,6 +78,10 @@
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out,
-webkit-transform 0.3s ease-in-out; -webkit-transform 0.3s ease-in-out;
&.c2System {
width: calc(100vw - 180px);
}
.header { .header {
position: relative; position: relative;
z-index: 99; z-index: 99;

View File

@@ -47,8 +47,8 @@ export type FilterItemType = {
elementID: number; elementID: number;
name: string; name: string;
bizName: string; bizName: string;
operator: string; operator?: string;
type: string; type?: string;
value: any; value: any;
}; };
@@ -69,6 +69,12 @@ export type EntityDimensionType = {
value: string; value: string;
} }
export type SqlInfoType = {
llmParseSql: string;
logicSql: string;
querySql: string;
}
export type ChatContextType = { export type ChatContextType = {
id: number; id: number;
queryId: number; queryId: number;
@@ -86,6 +92,7 @@ export type ChatContextType = {
queryMode: string; queryMode: string;
dimensionFilters: FilterItemType[]; dimensionFilters: FilterItemType[];
properties: any; properties: any;
sqlInfo: SqlInfoType;
}; };
export enum MsgValidTypeEnum { export enum MsgValidTypeEnum {
@@ -143,6 +150,7 @@ export type ParseDataType = {
state: ParseStateEnum; state: ParseStateEnum;
selectedParses: ChatContextType[]; selectedParses: ChatContextType[];
candidateParses: ChatContextType[]; candidateParses: ChatContextType[];
similarSolvedQuery: SimilarQuestionType[];
} }
export type QueryDataType = { export type QueryDataType = {
@@ -173,7 +181,7 @@ export enum SemanticTypeEnum {
}; };
export const SEMANTIC_TYPE_MAP = { export const SEMANTIC_TYPE_MAP = {
[SemanticTypeEnum.DOMAIN]: '数据模型', [SemanticTypeEnum.DOMAIN]: '数据来源',
[SemanticTypeEnum.DIMENSION]: '维度', [SemanticTypeEnum.DIMENSION]: '维度',
[SemanticTypeEnum.METRIC]: '指标', [SemanticTypeEnum.METRIC]: '指标',
[SemanticTypeEnum.VALUE]: '维度值', [SemanticTypeEnum.VALUE]: '维度值',
@@ -217,4 +225,17 @@ export type DrillDownDimensionType = {
model: number; model: number;
name: string; name: string;
bizName: string; bizName: string;
} }
export type SendMsgParamsType = {
msg: string;
agentId: number;
modelId: number;
filters?: FilterItemType[];
}
export type SimilarQuestionType = {
// queryId: number;
// parseId: number;
queryText: string;
}

View File

@@ -58,7 +58,7 @@ const ExecuteItem: React.FC<Props> = ({
<> <>
<div className={`${prefixCls}-title-bar`}> <div className={`${prefixCls}-title-bar`}>
<CheckCircleFilled className={`${prefixCls}-step-icon`} /> <CheckCircleFilled className={`${prefixCls}-step-icon`} />
<div className={`${prefixCls}-step-title`}></div> <div className={`${prefixCls}-step-title`}></div>
</div> </div>
<div className={`${prefixCls}-content-container ${prefixCls}-last-node`}> <div className={`${prefixCls}-content-container ${prefixCls}-last-node`}>
<Spin spinning={entitySwitchLoading}> <Spin spinning={entitySwitchLoading}>
@@ -68,7 +68,12 @@ const ExecuteItem: React.FC<Props> = ({
{data?.queryMode === 'WEB_PAGE' ? ( {data?.queryMode === 'WEB_PAGE' ? (
<WebPage id={queryId!} data={data} /> <WebPage id={queryId!} data={data} />
) : ( ) : (
<ChatMsg data={data} chartIndex={chartIndex} triggerResize={triggerResize} /> <ChatMsg
queryId={queryId}
data={data}
chartIndex={chartIndex}
triggerResize={triggerResize}
/>
)} )}
</Spin> </Spin>
</div> </div>

View File

@@ -23,7 +23,7 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
const initData = async () => { const initData = async () => {
const { data } = await queryDimensionValues(modelId, filter.bizName, ''); const { data } = await queryDimensionValues(modelId, filter.bizName, '');
setOptions( setOptions(
data?.data?.resultList.map((item: any) => ({ data?.resultList.map((item: any) => ({
label: item[filter.bizName], label: item[filter.bizName],
value: item[filter.bizName], value: item[filter.bizName],
})) || [] })) || []
@@ -47,9 +47,8 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
if (fetchId !== fetchRef.current) { if (fetchId !== fetchRef.current) {
return; return;
} }
setOptions( setOptions(
newOptions.data?.data?.resultList.map((item: any) => ({ newOptions.data?.resultList.map((item: any) => ({
label: item[filter.bizName], label: item[filter.bizName],
value: item[filter.bizName], value: item[filter.bizName],
})) || [] })) || []
@@ -76,7 +75,8 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
return ( return (
<span className={prefixCls}> <span className={prefixCls}>
{typeof filter.value === 'string' || isArray(filter.value) ? ( {(typeof filter.value === 'string' || isArray(filter.value)) &&
(filter.operator === '=' || filter.operator === 'IN') ? (
<Select <Select
bordered={false} bordered={false}
value={filter.value} value={filter.value}

View File

@@ -1,7 +1,7 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { AGG_TYPE_MAP, PREFIX_CLS } from '../../common/constants'; import { AGG_TYPE_MAP, PREFIX_CLS } from '../../common/constants';
import { ChatContextType, FilterItemType } from '../../common/type'; import { ChatContextType, FilterItemType } from '../../common/type';
import { CheckCircleFilled, InfoCircleOutlined } from '@ant-design/icons'; import { CheckCircleFilled } from '@ant-design/icons';
import classNames from 'classnames'; import classNames from 'classnames';
import SwicthEntity from './SwitchEntity'; import SwicthEntity from './SwitchEntity';
import Loading from './Loading'; import Loading from './Loading';
@@ -30,7 +30,7 @@ const ParseTip: React.FC<Props> = ({
}) => { }) => {
const prefixCls = `${PREFIX_CLS}-item`; const prefixCls = `${PREFIX_CLS}-item`;
const getNode = (tipTitle: string, tipNode?: ReactNode, parseSucceed?: boolean) => { const getNode = (tipTitle: ReactNode, tipNode?: ReactNode, parseSucceed?: boolean) => {
const contentContainerClass = classNames(`${prefixCls}-content-container`, { const contentContainerClass = classNames(`${prefixCls}-content-container`, {
[`${prefixCls}-content-container-succeed`]: parseSucceed, [`${prefixCls}-content-container-succeed`]: parseSucceed,
}); });
@@ -62,7 +62,6 @@ const ParseTip: React.FC<Props> = ({
const getTipNode = (parseInfo: ChatContextType, isOptions?: boolean, index?: number) => { const getTipNode = (parseInfo: ChatContextType, isOptions?: boolean, index?: number) => {
const { const {
modelId,
modelName, modelName,
dateInfo, dateInfo,
dimensionFilters, dimensionFilters,
@@ -76,8 +75,6 @@ const ParseTip: React.FC<Props> = ({
nativeQuery, nativeQuery,
} = parseInfo || {}; } = parseInfo || {};
const maxOptionCount = queryMode === 'DSL' ? 10 : MAX_OPTION_VALUES_COUNT;
const { startDate, endDate } = dateInfo || {}; const { startDate, endDate } = dateInfo || {};
const dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION'); const dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION');
const metric = metrics?.[0]; const metric = metrics?.[0];
@@ -92,44 +89,6 @@ const ParseTip: React.FC<Props> = ({
const fields = const fields =
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems; queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
const getFilterContent = (filters: any) => {
return (
<div className={`${prefixCls}-tip-item-filter-content`}>
{filters.map((filter: any) => (
<div className={`${prefixCls}-tip-item-option`}>
<span>
<span className={`${prefixCls}-tip-item-filter-name`}>{filter.name}</span>
{filter.operator !== '=' && filter.operator !== 'IN'
? ` ${filter.operator} `
: ''}
</span>
{queryMode !== 'DSL' && !filter.bizName?.includes('_id') ? (
<FilterItem
modelId={modelId}
filters={dimensionFilters}
filter={filter}
onFiltersChange={onFiltersChange}
/>
) : (
<span className={itemValueClass}>{filter.value}</span>
)}
</div>
))}
</div>
);
};
const getFiltersNode = () => {
return (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={`${prefixCls}-tip-item-content`}>
{getFilterContent(dimensionFilters)}
</div>
</div>
);
};
return ( return (
<div <div
className={`${prefixCls}-tip-content`} className={`${prefixCls}-tip-content`}
@@ -147,7 +106,7 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
) : ( ) : (
<> <>
{(queryMode.includes('ENTITY') || queryMode === 'DSL') && {(queryMode?.includes('ENTITY') || queryMode === 'DSL') &&
typeof entityId === 'string' && typeof entityId === 'string' &&
!!entityAlias && !!entityAlias &&
!!entityName ? ( !!entityName ? (
@@ -165,11 +124,11 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
) : ( ) : (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div> <div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}>{modelName}</div> <div className={itemValueClass}>{modelName}</div>
</div> </div>
)} )}
{!queryMode.includes('ENTITY') && metric && ( {!queryMode?.includes('ENTITY') && metric && (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div> <div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}>{metric.name}</div> <div className={itemValueClass}>{metric.name}</div>
@@ -199,24 +158,21 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
<div className={itemValueClass}> <div className={itemValueClass}>
{fields {fields
.slice(0, maxOptionCount) .slice(0, MAX_OPTION_VALUES_COUNT)
.map(field => field.name) .map(field => field.name)
.join('、')} .join('、')}
{fields.length > maxOptionCount && '...'} {fields.length > MAX_OPTION_VALUES_COUNT && '...'}
</div> </div>
</div> </div>
)} )}
{[ {queryMode !== 'ENTITY_ID' &&
'METRIC_FILTER', entityDimensions?.length > 0 &&
'METRIC_ENTITY', entityDimensions.map(dimension => (
'ENTITY_DETAIL', <div className={`${prefixCls}-tip-item`} key={dimension.itemId}>
'ENTITY_LIST_FILTER', <div className={`${prefixCls}-tip-item-name`}>{dimension.name}</div>
'ENTITY_ID', <div className={itemValueClass}>{dimension.value}</div>
'DSL', </div>
].includes(queryMode) && ))}
dimensionFilters &&
dimensionFilters?.length > 0 &&
getFiltersNode()}
{queryMode === 'METRIC_ORDERBY' && aggType && aggType !== 'NONE' && ( {queryMode === 'METRIC_ORDERBY' && aggType && aggType !== 'NONE' && (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div> <div className={`${prefixCls}-tip-item-name`}></div>
@@ -230,7 +186,8 @@ const ParseTip: React.FC<Props> = ({
}; };
const parseInfo = parseInfoOptions[0] || {}; const parseInfo = parseInfoOptions[0] || {};
const { properties, entity, entityInfo, elementMatches, queryMode } = parseInfo || {}; const { modelId, properties, entity, entityInfo, elementMatches, queryMode, dimensionFilters } =
parseInfo || {};
const { type } = properties || {}; const { type } = properties || {};
const entityAlias = entity?.alias?.[0]?.split('.')?.[0]; const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
@@ -249,31 +206,71 @@ const ParseTip: React.FC<Props> = ({
) )
); );
const getFilterContent = (filters: any) => {
const itemValueClass = `${prefixCls}-tip-item-value`;
return (
<div className={`${prefixCls}-tip-item-filter-content`}>
{filters.map((filter: any) => (
<div className={`${prefixCls}-tip-item-option`} key={filter.name}>
<span>
<span className={`${prefixCls}-tip-item-filter-name`}>{filter.name}</span>
{filter.operator !== '=' && filter.operator !== 'IN' ? ` ${filter.operator} ` : ''}
</span>
{/* {queryMode !== 'DSL' && !filter.bizName?.includes('_id') ? ( */}
{!filter.bizName?.includes('_id') ? (
<FilterItem
modelId={modelId}
filters={dimensionFilters}
filter={filter}
onFiltersChange={onFiltersChange}
/>
) : (
<span className={itemValueClass}>{filter.value}</span>
)}
</div>
))}
</div>
);
};
const getFiltersNode = () => {
return (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={`${prefixCls}-tip-item-content`}>{getFilterContent(dimensionFilters)}</div>
</div>
);
};
const tipNode = ( const tipNode = (
<div className={`${prefixCls}-tip`}> <div className={`${prefixCls}-tip`}>
{getTipNode(parseInfo)} {getTipNode(parseInfo)}
{queryMode !== 'ENTITY_ID' && entityDimensions?.length > 0 && ( {[
<div className={`${prefixCls}-entity-info`}> 'METRIC_FILTER',
{entityDimensions.map(dimension => ( 'METRIC_ENTITY',
<div className={`${prefixCls}-dimension-item`} key={dimension.itemId}> 'ENTITY_DETAIL',
<div className={`${prefixCls}-dimension-name`}>{dimension.name}</div> 'ENTITY_LIST_FILTER',
<div className={`${prefixCls}-dimension-value`}>{dimension.value}</div> 'ENTITY_ID',
</div> 'DSL',
))} ].includes(queryMode) &&
</div> dimensionFilters &&
)} dimensionFilters?.length > 0 &&
{(!type || queryMode === 'DSL') && entityAlias && entityName && ( getFiltersNode()}
<div className={`${prefixCls}-switch-entity-tip`}>
<InfoCircleOutlined />
<div>
{entityAlias}{entityAlias}
</div>
</div>
)}
</div> </div>
); );
return getNode('意图解析结果', tipNode, true); return getNode(
<div className={`${prefixCls}-title-bar`}>
{(!type || queryMode === 'DSL') && entityAlias && entityAlias !== '厂牌' && entityName && (
<div className={`${prefixCls}-switch-entity-tip`}>
({entityAlias}{entityAlias})
</div>
)}
</div>,
tipNode,
true
);
}; };
export default ParseTip; export default ParseTip;

View File

@@ -0,0 +1,60 @@
import { CheckCircleFilled, DownOutlined, UpOutlined } from '@ant-design/icons';
import { PREFIX_CLS } from '../../common/constants';
import { SimilarQuestionType } from '../../common/type';
import { useState } from 'react';
type Props = {
similarQuestions: SimilarQuestionType[];
defaultExpanded?: boolean;
onSelectQuestion: (question: SimilarQuestionType) => void;
};
const SimilarQuestions: React.FC<Props> = ({
similarQuestions,
defaultExpanded,
onSelectQuestion,
}) => {
const [expanded, setExpanded] = useState(defaultExpanded || false);
const tipPrefixCls = `${PREFIX_CLS}-item`;
const prefixCls = `${PREFIX_CLS}-similar-questions`;
const onToggleExpanded = () => {
setExpanded(!expanded);
};
return (
<div className={`${tipPrefixCls}-parse-tip`}>
<div className={`${tipPrefixCls}-title-bar`}>
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
<div className={`${tipPrefixCls}-step-title`}>
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onToggleExpanded}>
{expanded ? <UpOutlined /> : <DownOutlined />}
</span>
</div>
</div>
<div className={prefixCls}>
{expanded && (
<div className={`${prefixCls}-content`}>
{similarQuestions.slice(0, 5).map((question, index) => {
return (
<div
className={`${prefixCls}-question`}
key={question.queryText}
onClick={() => {
onSelectQuestion(question);
}}
>
{index + 1}. {question.queryText}
</div>
);
})}
</div>
)}
</div>
</div>
);
};
export default SimilarQuestions;

View File

@@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { format } from 'sql-formatter';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { solarizedlight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { message } from 'antd';
import { PREFIX_CLS } from '../../common/constants';
import { CheckCircleFilled, UpOutlined } from '@ant-design/icons';
import { SqlInfoType } from '../../common/type';
type Props = {
integrateSystem?: string;
sqlInfo: SqlInfoType;
};
const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
const [sqlType, setSqlType] = useState('');
const tipPrefixCls = `${PREFIX_CLS}-item`;
const prefixCls = `${PREFIX_CLS}-sql-item`;
const handleCopy = (text, result) => {
result ? message.success('复制SQL成功', 1) : message.error('复制SQL失败', 1);
};
const onCollapse = () => {
setSqlType('');
};
if (!sqlInfo.llmParseSql && !sqlInfo.logicSql && !sqlInfo.querySql) {
return null;
}
return (
<div className={`${tipPrefixCls}-parse-tip`}>
<div className={`${tipPrefixCls}-title-bar`}>
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
<div className={`${tipPrefixCls}-step-title`}>
SQL生成
{sqlType && (
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}>
<UpOutlined />
</span>
)}
</div>
<div className={`${prefixCls}-sql-options`}>
{sqlInfo.llmParseSql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'llmParseSql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'llmParseSql' ? '' : 'llmParseSql');
}}
>
LLM解析SQL
</div>
)}
{sqlInfo.logicSql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'logicSql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'logicSql' ? '' : 'logicSql');
}}
>
SQL
</div>
)}
{sqlInfo.querySql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'querySql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'querySql' ? '' : 'querySql');
}}
>
SQL
</div>
)}
</div>
</div>
<div
className={`${prefixCls} ${
!window.location.pathname.includes('/chat') &&
integrateSystem &&
integrateSystem !== 'wiki'
? `${prefixCls}-copilot`
: ''
}`}
>
{sqlType && (
<>
<SyntaxHighlighter
className={`${prefixCls}-code`}
language="sql"
style={solarizedlight}
>
{format(sqlInfo[sqlType])}
</SyntaxHighlighter>
<CopyToClipboard
text={format(sqlInfo[sqlType])}
onCopy={(text, result) => handleCopy(text, result)}
>
<button className={`${prefixCls}-copy-btn`}></button>
</CopyToClipboard>
</>
)}
</div>
</div>
);
};
export default SqlItem;

View File

@@ -1,4 +1,10 @@
import { ChatContextType, FilterItemType, MsgDataType, ParseStateEnum } from '../../common/type'; import {
ChatContextType,
FilterItemType,
MsgDataType,
ParseStateEnum,
SimilarQuestionType,
} from '../../common/type';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { chatExecute, chatParse, queryData, switchEntity } from '../../service'; import { chatExecute, chatParse, queryData, switchEntity } from '../../service';
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants'; import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
@@ -8,6 +14,8 @@ import ExecuteItem from './ExecuteItem';
import { isMobile } from '../../utils/utils'; import { isMobile } from '../../utils/utils';
import classNames from 'classnames'; import classNames from 'classnames';
import Tools from '../Tools'; import Tools from '../Tools';
import SqlItem from './SqlItem';
import SimilarQuestionItem from './SimilarQuestionItem';
type Props = { type Props = {
msg: string; msg: string;
@@ -17,11 +25,13 @@ type Props = {
filter?: any[]; filter?: any[];
isLastMessage?: boolean; isLastMessage?: boolean;
msgData?: MsgDataType; msgData?: MsgDataType;
isHistory?: boolean;
triggerResize?: boolean; triggerResize?: boolean;
parseOptions?: ChatContextType[]; parseOptions?: ChatContextType[];
isDeveloper?: boolean;
integrateSystem?: string;
onMsgDataLoaded?: (data: MsgDataType, valid: boolean) => void; onMsgDataLoaded?: (data: MsgDataType, valid: boolean) => void;
onUpdateMessageScroll?: () => void; onUpdateMessageScroll?: () => void;
onSendMsg?: (msg: string) => void;
}; };
const ChatItem: React.FC<Props> = ({ const ChatItem: React.FC<Props> = ({
@@ -31,12 +41,14 @@ const ChatItem: React.FC<Props> = ({
agentId, agentId,
filter, filter,
isLastMessage, isLastMessage,
isHistory,
triggerResize, triggerResize,
msgData, msgData,
parseOptions, parseOptions,
isDeveloper,
integrateSystem,
onMsgDataLoaded, onMsgDataLoaded,
onUpdateMessageScroll, onUpdateMessageScroll,
onSendMsg,
}) => { }) => {
const [data, setData] = useState<MsgDataType>(); const [data, setData] = useState<MsgDataType>();
const [parseLoading, setParseLoading] = useState(false); const [parseLoading, setParseLoading] = useState(false);
@@ -47,6 +59,7 @@ const ChatItem: React.FC<Props> = ({
const [executeTip, setExecuteTip] = useState(''); const [executeTip, setExecuteTip] = useState('');
const [executeMode, setExecuteMode] = useState(false); const [executeMode, setExecuteMode] = useState(false);
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false); const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
const [similarQuestions, setSimilarQuestions] = useState<SimilarQuestionType[]>([]);
const [chartIndex, setChartIndex] = useState(0); const [chartIndex, setChartIndex] = useState(0);
@@ -79,13 +92,13 @@ const ChatItem: React.FC<Props> = ({
setExecuteMode(true); setExecuteMode(true);
setExecuteLoading(true); setExecuteLoading(true);
try { try {
const { data } = await chatExecute(msg, conversationId!, parseInfoValue); const res: any = await chatExecute(msg, conversationId!, parseInfoValue);
setExecuteLoading(false); setExecuteLoading(false);
const valid = updateData(data); const valid = updateData(res);
if (onMsgDataLoaded) { if (onMsgDataLoaded) {
onMsgDataLoaded( onMsgDataLoaded(
{ {
...data.data, ...res.data,
chatContext: parseInfoValue, chatContext: parseInfoValue,
}, },
valid valid
@@ -97,12 +110,13 @@ const ChatItem: React.FC<Props> = ({
} }
}; };
const onSendMsg = async () => { const sendMsg = async () => {
setParseLoading(true); setParseLoading(true);
const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter); const parseData: any = await chatParse(msg, conversationId, modelId, agentId, filter);
setParseLoading(false); setParseLoading(false);
const { code, data } = parseData || {}; const { code, data } = parseData || {};
const { state, selectedParses, queryId } = data || {}; const { state, selectedParses, queryId, similarSolvedQuery } = data || {};
setSimilarQuestions(similarSolvedQuery || []);
if ( if (
code !== 200 || code !== 200 ||
state === ParseStateEnum.FAILED || state === ParseStateEnum.FAILED ||
@@ -115,7 +129,7 @@ const ChatItem: React.FC<Props> = ({
if (onUpdateMessageScroll) { if (onUpdateMessageScroll) {
onUpdateMessageScroll(); onUpdateMessageScroll();
} }
const parseInfos = selectedParses.map(item => ({ const parseInfos = selectedParses.map((item: any) => ({
...item, ...item,
queryId, queryId,
})); }));
@@ -126,15 +140,17 @@ const ChatItem: React.FC<Props> = ({
}; };
useEffect(() => { useEffect(() => {
if (data !== undefined || parseOptions !== undefined || executeTip !== '') { if (data !== undefined || parseOptions !== undefined || executeTip !== '' || parseLoading) {
return; return;
} }
if (msgData) { if (msgData) {
setParseInfoOptions([msgData.chatContext]); const parseInfoValue = { ...msgData.chatContext, queryId: msgData.queryId };
setParseInfoOptions([parseInfoValue]);
setParseInfo(parseInfoValue);
setExecuteMode(true); setExecuteMode(true);
updateData({ code: 200, data: msgData, msg: 'success' }); updateData({ code: 200, data: msgData, msg: 'success' });
} else if (msg) { } else if (msg) {
onSendMsg(); sendMsg();
} }
}, [msg, msgData]); }, [msg, msgData]);
@@ -142,19 +158,27 @@ const ChatItem: React.FC<Props> = ({
setEntitySwitchLoading(true); setEntitySwitchLoading(true);
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0); const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
setEntitySwitchLoading(false); setEntitySwitchLoading(false);
setData(res.data.data); setData(res.data);
const { chatContext } = res.data.data; const { chatContext } = res.data;
setParseInfo(chatContext); setParseInfo(chatContext);
setParseInfoOptions([chatContext]); setParseInfoOptions([chatContext]);
}; };
const onFiltersChange = async (dimensionFilters: FilterItemType[]) => { const onFiltersChange = async (dimensionFilters: FilterItemType[]) => {
setEntitySwitchLoading(true); setEntitySwitchLoading(true);
const chatContextValue = { ...(parseInfoOptions[0] || {}), dimensionFilters }; const { dimensions, metrics, dateInfo, id, queryId } = parseInfoOptions[0] || {};
const chatContextValue = {
dimensions,
metrics,
dateInfo,
dimensionFilters,
parseId: id,
queryId,
};
const res: any = await queryData(chatContextValue); const res: any = await queryData(chatContextValue);
setEntitySwitchLoading(false); setEntitySwitchLoading(false);
const resChatContext = res.data?.data?.chatContext; const resChatContext = res.data?.chatContext;
setData({ ...(res.data?.data || {}), chatContext: resChatContext || chatContextValue }); setData({ ...(res.data || {}), chatContext: resChatContext || chatContextValue });
setParseInfo(resChatContext || chatContextValue); setParseInfo(resChatContext || chatContextValue);
setParseInfoOptions([resChatContext || chatContextValue]); setParseInfoOptions([resChatContext || chatContextValue]);
}; };
@@ -167,6 +191,10 @@ const ChatItem: React.FC<Props> = ({
} }
}; };
const onSelectQuestion = (question: SimilarQuestionType) => {
onSendMsg?.(question.queryText);
};
const contentClass = classNames(`${prefixCls}-content`, { const contentClass = classNames(`${prefixCls}-content`, {
[`${prefixCls}-content-mobile`]: isMobile, [`${prefixCls}-content-mobile`]: isMobile,
}); });
@@ -189,17 +217,36 @@ const ChatItem: React.FC<Props> = ({
onSwitchEntity={onSwitchEntity} onSwitchEntity={onSwitchEntity}
onFiltersChange={onFiltersChange} onFiltersChange={onFiltersChange}
/> />
{executeMode && ( {parseTip && similarQuestions.length > 0 && (
<ExecuteItem <SimilarQuestionItem
queryId={parseInfo?.queryId} similarQuestions={similarQuestions}
executeLoading={executeLoading} defaultExpanded
entitySwitchLoading={entitySwitchLoading} onSelectQuestion={onSelectQuestion}
executeTip={executeTip}
chartIndex={chartIndex}
data={data}
triggerResize={triggerResize}
/> />
)} )}
{executeMode && (
<>
{parseInfoOptions?.[0]?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && (
<SqlItem integrateSystem={integrateSystem} sqlInfo={parseInfoOptions[0].sqlInfo} />
)}
{similarQuestions.length > 0 && (
<SimilarQuestionItem
similarQuestions={similarQuestions}
defaultExpanded={executeTip !== ''}
onSelectQuestion={onSelectQuestion}
/>
)}
<ExecuteItem
queryId={parseInfo?.queryId}
executeLoading={executeLoading}
entitySwitchLoading={entitySwitchLoading}
executeTip={executeTip}
chartIndex={chartIndex}
data={data}
triggerResize={triggerResize}
/>
</>
)}
</div> </div>
{!isMetricCard && data && ( {!isMetricCard && data && (
<Tools data={data} scoreValue={undefined} isLastMessage={isLastMessage} /> <Tools data={data} scoreValue={undefined} isLastMessage={isLastMessage} />

View File

@@ -2,6 +2,8 @@
@chat-item-prefix-cls: ~'@{supersonic-chat-prefix}-item'; @chat-item-prefix-cls: ~'@{supersonic-chat-prefix}-item';
@filter-item-prefix-cls: ~'@{supersonic-chat-prefix}-filter-item'; @filter-item-prefix-cls: ~'@{supersonic-chat-prefix}-filter-item';
@sql-item-prefix-cls: ~'@{supersonic-chat-prefix}-sql-item';
@similar-questions-prefix-cls: ~'@{supersonic-chat-prefix}-similar-questions';
.@{chat-item-prefix-cls} { .@{chat-item-prefix-cls} {
display: flex; display: flex;
@@ -17,7 +19,6 @@
display: inline-block; display: inline-block;
width: 4px; width: 4px;
height: 4px; height: 4px;
// border-radius: 50%;
background-color: var(--text-color); background-color: var(--text-color);
margin: 0 2px; margin: 0 2px;
opacity: 0; opacity: 0;
@@ -124,8 +125,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
column-gap: 6px; column-gap: 6px;
margin-top: 4px; color: var(--text-color-fourth);
color: var(--text-color-third);
font-size: 13px; font-size: 13px;
} }
@@ -169,7 +169,7 @@
&-tip { &-tip {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
row-gap: 6px; row-gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
color: var(--text-color-third); color: var(--text-color-third);
} }
@@ -178,7 +178,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
row-gap: 6px; row-gap: 10px;
column-gap: 12px; column-gap: 12px;
color: var(--text-color-third); color: var(--text-color-third);
} }
@@ -251,7 +251,6 @@
flex-wrap: wrap; flex-wrap: wrap;
row-gap: 6px; row-gap: 6px;
column-gap: 12px; column-gap: 12px;
margin-top: 4px;
color: var(--text-color-third); color: var(--text-color-third);
font-size: 14px; font-size: 14px;
} }
@@ -325,4 +324,93 @@
color: var(--chat-blue); color: var(--chat-blue);
font-weight: 500; font-weight: 500;
} }
} }
.@{sql-item-prefix-cls} {
position: relative;
margin: 2px 0 2px 7px;
padding: 2px 0 8px 18px;
border-left: 1px solid var(--green);
overflow: auto;
&-toggle-expand-btn {
margin-left: 4px;
color: var(--text-color-fourth);
font-size: 12px;
cursor: pointer;
}
&-sql-options {
margin-left: 4px;
display: flex;
align-items: center;
column-gap: 13px;
color: var(--text-color-third);
}
&-sql-option {
border-radius: 4px;
padding: 1px 4px;
cursor: pointer;
&:hover {
color: var(--chat-blue);
}
}
&-sql-option-active {
color: #fff !important;
background-color: var(--chat-blue);
}
&-code {
margin-top: 10px !important;
padding: 6px 14px 8px !important;
border: 1px solid var(--border-color-base) !important;
border-radius: 4px !important;
background: #f5f8fb !important;
}
&-copy-btn {
position: absolute;
top: 30px;
right: 20px;
background: transparent !important;
border: 0 !important;
color: var(--chat-blue);
cursor: pointer;
}
}
.@{sql-item-prefix-cls}-copilot {
width: 700px;
}
.@{similar-questions-prefix-cls} {
position: relative;
margin: 2px 0 2px 7px;
padding: 2px 0 8px 18px;
border-left: 1px solid var(--green);
overflow: auto;
&-toggle-expand-btn {
margin-left: 4px;
color: var(--text-color-fourth);
font-size: 12px;
cursor: pointer;
}
&-content {
display: flex;
flex-direction: column;
row-gap: 12px;
margin-top: 6px;
margin-bottom: 2px;
}
&-question {
width: fit-content;
color: var(--chat-blue);
cursor: pointer;
}
}

View File

@@ -68,7 +68,7 @@ const Message: React.FC<Props> = ({
<div className={`${prefixCls}-info-bar`}> <div className={`${prefixCls}-info-bar`}>
<div className={`${prefixCls}-main-entity-info`}> <div className={`${prefixCls}-main-entity-info`}>
<div className={`${prefixCls}-info-item`}> <div className={`${prefixCls}-info-item`}>
<div className={`${prefixCls}-info-name`}></div> <div className={`${prefixCls}-info-name`}></div>
<div className={`${prefixCls}-info-value`}>{modelName}</div> <div className={`${prefixCls}-info-value`}>{modelName}</div>
</div> </div>
<div className={`${prefixCls}-info-item`}> <div className={`${prefixCls}-info-item`}>

View File

@@ -19,33 +19,7 @@ const Text: React.FC<Props> = ({ columns, referenceColumn, dataSource }) => {
const initData = () => { const initData = () => {
let textValue = dataSource[0][columns[0].nameEn]; let textValue = dataSource[0][columns[0].nameEn];
let htmlCodeValue: string; setText(textValue === undefined ? '暂无数据,如有疑问请联系管理员' : textValue);
const match = textValue.match(/```html([\s\S]*?)```/);
htmlCodeValue = match && match[1].trim();
if (htmlCodeValue) {
textValue = textValue.replace(/```html([\s\S]*?)```/, '');
}
let scriptCode: string;
let scriptSrc: string;
if (htmlCodeValue) {
scriptSrc = htmlCodeValue.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
scriptCode =
htmlCodeValue.match(/<script type="text\/javascript">([\s\S]*?)<\/script>/)?.[1] || '';
if (scriptSrc) {
const script = document.createElement('script');
script.src = scriptSrc;
document.body.appendChild(script);
}
if (scriptCode) {
const script = document.createElement('script');
script.innerHTML = scriptCode;
setTimeout(() => {
document.body.appendChild(script);
}, 1500);
}
}
setText(textValue);
setHtmlCode(htmlCodeValue);
if (referenceColumn) { if (referenceColumn) {
const referenceDataValue = dataSource[0][referenceColumn.nameEn]; const referenceDataValue = dataSource[0][referenceColumn.nameEn];
setReferenceData(referenceDataValue || []); setReferenceData(referenceDataValue || []);

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { CLS_PREFIX } from '../../../common/constants'; import { CLS_PREFIX } from '../../../common/constants';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { isProd } from '../../../utils/utils'; import { getToken, isProd } from '../../../utils/utils';
type Props = { type Props = {
id: string | number; id: string | number;
@@ -89,10 +89,8 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
); );
urlValue = urlValue.replace( urlValue = urlValue.replace(
'?', '?',
`?miniProgram=true&reportName=${name}&filterData=${filterData}&` `?token=${getToken()}&miniProgram=true&reportName=${name}&filterData=${filterData}&`
); );
urlValue =
!isProd() && !urlValue.includes('http') ? `http://s2.tmeoa.com${urlValue}` : urlValue;
} else { } else {
const params = Object.keys(valueParams || {}).map(key => `${key}=${valueParams[key]}`); const params = Object.keys(valueParams || {}).map(key => `${key}=${valueParams[key]}`);
if (params.length > 0) { if (params.length > 0) {
@@ -103,7 +101,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
} }
} }
} }
// onReportLoaded(heightValue + 190);
setPluginUrl(urlValue); setPluginUrl(urlValue);
}; };
@@ -112,7 +109,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
}, []); }, []);
return ( return (
// <div className={prefixCls} style={{ height }}>
<iframe <iframe
id={`reportIframe_${id}`} id={`reportIframe_${id}`}
name={`reportIframe_${id}`} name={`reportIframe_${id}`}
@@ -121,7 +117,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
title="reportIframe" title="reportIframe"
allowFullScreen allowFullScreen
/> />
// </div>
); );
}; };

View File

@@ -12,12 +12,13 @@ import DrillDownDimensions from '../DrillDownDimensions';
import MetricOptions from '../MetricOptions'; import MetricOptions from '../MetricOptions';
type Props = { type Props = {
queryId?: number;
data: MsgDataType; data: MsgDataType;
chartIndex: number; chartIndex: number;
triggerResize?: boolean; triggerResize?: boolean;
}; };
const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => { const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize }) => {
const { queryColumns, queryResults, chatContext, queryMode } = data || {}; const { queryColumns, queryResults, chatContext, queryMode } = data || {};
const { dimensionFilters, elementMatches } = chatContext || {}; const { dimensionFilters, elementMatches } = chatContext || {};
@@ -127,14 +128,16 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
const onLoadData = async (value: any) => { const onLoadData = async (value: any) => {
setLoading(true); setLoading(true);
const { data } = await queryData({ const res: any = await queryData({
...chatContext, // ...chatContext,
queryId,
parseId: chatContext.id,
...value, ...value,
}); });
setLoading(false); setLoading(false);
if (data.code === 200) { if (res.code === 200) {
updateColummns(data.data?.queryColumns || []); updateColummns(res.data?.queryColumns || []);
setDataSource(data.data?.queryResults || []); setDataSource(res.data?.queryResults || []);
} }
}; };

View File

@@ -34,7 +34,7 @@ const DrillDownDimensions: React.FC<Props> = ({
const initData = async () => { const initData = async () => {
const res = await queryDrillDownDimensions(modelId); const res = await queryDrillDownDimensions(modelId);
setDimensions( setDimensions(
res.data.data.dimensions res.data.dimensions
.filter( .filter(
dimension => dimension =>
!dimensionFilters?.some(filter => filter.name === dimension.name) && !dimensionFilters?.some(filter => filter.name === dimension.name) &&

View File

@@ -1,7 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons'; import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4120566_x5c4www9bqm.js', scriptUrl: '//at.alicdn.com/t/c/font_4120566_46xw04fpzii.js',
}); });
export default IconFont; export default IconFont;

View File

@@ -25,7 +25,7 @@ const RecommendOptions: React.FC<Props> = ({ entityId, modelId, modelName, onSel
setLoading(true); setLoading(true);
const res = await queryEntities(entityId, modelId); const res = await queryEntities(entityId, modelId);
setLoading(false); setLoading(false);
setData(res.data.data); setData(res.data);
}; };
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,14 @@
import Chat from '../Chat';
import styles from './style.module.less';
type Props = {};
const ChatDemo: React.FC<Props> = ({}) => {
return (
<div className={styles.chatDemo}>
<Chat isDeveloper />
</div>
);
};
export default ChatDemo;

View File

@@ -0,0 +1,50 @@
import { Button, Space } from 'antd';
import styles from './style.module.less';
import Copilot from '../Copilot';
import { useRef } from 'react';
const buttonParams = [
{
msg: '周杰伦 艺人趋势解读',
agentId: 8,
modelId: 23,
filters: [{ bizName: 'singer_id', elementID: 283, value: 4558 }],
},
{
msg: '林俊杰 艺人趋势解读',
agentId: 8,
modelId: 23,
filters: [{ bizName: 'singer_id', elementID: 283, value: 4286 }],
},
];
const CopilotDemo = () => {
const copilotRef = useRef<any>();
return (
<div className={styles.copilotDemo}>
<Space>
{buttonParams.map(params => (
<Button
key={params.msg}
onClick={() => {
copilotRef?.current?.sendCopilotMsg(params);
}}
>
{params.msg}
</Button>
))}
</Space>
<Copilot
// token={localStorage.getItem('SUPERSONIC_TOKEN') || ''}
// agentIds={[8]}
isDeveloper
// integrateSystem="c2"
ref={copilotRef}
// noInput
/>
</div>
);
};
export default CopilotDemo;

View File

@@ -8,4 +8,8 @@
height: 100vh; height: 100vh;
overflow: auto; overflow: auto;
box-sizing: border-box; box-sizing: border-box;
} }
.chatDemo {
height: 100%;
}

View File

@@ -1,16 +1,15 @@
import './styles/index.less'; import './styles/index.less';
// import React from 'react';
// import ReactDOM from 'react-dom/client'; // import ReactDOM from 'react-dom/client';
// import Chat from './demo/Chat'; // import Chat from './demo/Chat';
// import ChatDemo from './demo/ChatDemo';
// import CopilotDemo from './demo/CopilotDemo';
// const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); // const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
// root.render(<ChatDemo />);
// root.render( export { default as Chat } from './Chat';
// <React.StrictMode>
// <Chat /> export { default as Copilot } from './Copilot';
// </React.StrictMode>
// );
export { default as ChatMsg } from './components/ChatMsg'; export { default as ChatMsg } from './components/ChatMsg';
@@ -33,6 +32,7 @@ export type {
FilterItemType, FilterItemType,
HistoryType, HistoryType,
HistoryMsgItemType, HistoryMsgItemType,
SendMsgParamsType,
} from './common/type'; } from './common/type';
export { getHistoryMsg, searchRecommend, queryContext } from './service'; export { getHistoryMsg, searchRecommend, queryContext } from './service';

View File

@@ -5,7 +5,7 @@ import { getToken } from '../utils/utils';
// 创建axios实例 // 创建axios实例
const axiosInstance: AxiosInstance = axios.create({ const axiosInstance: AxiosInstance = axios.create({
// 设置基本URL所有请求都会使用这个URL作为前缀 // 设置基本URL所有请求都会使用这个URL作为前缀
baseURL: '', baseURL: localStorage.getItem('SUPERSONIC_CHAT_API_URL') || '',
// 设置请求超时时间(毫秒) // 设置请求超时时间(毫秒)
timeout: 120000, timeout: 120000,
// 设置请求头 // 设置请求头
@@ -19,7 +19,7 @@ axiosInstance.interceptors.request.use(
(config: any) => { (config: any) => {
const token = getToken(); const token = getToken();
if (token && config?.headers) { if (token && config?.headers) {
config.headers.Auth = `Bearer ${token}`; // config.headers.Auth = `Bearer ${token}`;
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
return config; return config;
@@ -33,18 +33,18 @@ axiosInstance.interceptors.request.use(
// 响应拦截器 // 响应拦截器
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
(response: any) => { (response: any) => {
const redirect = response.headers.get('redirect'); const redirect = response.headers['redirect'];
if (redirect === 'REDIRECT') { if (redirect === 'REDIRECT') {
let win: any = window; let win: any = window;
while (win !== win.top) { while (win !== win.top) {
win = win.top; win = win.top;
} }
const contextpath = response.headers.get('contextpath'); const contextpath = response.headers['contextpath'];
win.location.href = win.location.href =
contextpath?.substring(0, contextpath?.indexOf('&')) + contextpath?.substring(0, contextpath?.indexOf('&')) +
`&redirect_uri=${encodeURIComponent(`http://${win.location.host}`)}`; `&redirect_uri=${encodeURIComponent(`http://${win.location.host}`)}`;
} }
return response; return response.data;
}, },
(error) => { (error) => {
// 对响应错误进行处理 // 对响应错误进行处理

View File

@@ -1,12 +1,12 @@
import axios from './axiosInstance'; import axios from './axiosInstance';
import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type'; import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type';
const DEFAULT_CHAT_ID = 12009993; const DEFAULT_CHAT_ID = 0;
const prefix = '/api'; const prefix = '/api';
export function searchRecommend(queryText: string, chatId?: number, modelId?: number, agentId?: number) { export function searchRecommend(queryText: string, chatId?: number, modelId?: number, agentId?: number) {
return axios.post<Result<SearchRecommendItem[]>>(`${prefix}/chat/query/search`, { return axios.post<SearchRecommendItem[]>(`${prefix}/chat/query/search`, {
queryText, queryText,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
modelId, modelId,
@@ -15,7 +15,7 @@ export function searchRecommend(queryText: string, chatId?: number, modelId?: nu
} }
export function chatQuery(queryText: string, chatId?: number, modelId?: number, filters?: any[]) { export function chatQuery(queryText: string, chatId?: number, modelId?: number, filters?: any[]) {
return axios.post<Result<MsgDataType>>(`${prefix}/chat/query/query`, { return axios.post<MsgDataType>(`${prefix}/chat/query/query`, {
queryText, queryText,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
modelId, modelId,
@@ -26,7 +26,7 @@ export function chatQuery(queryText: string, chatId?: number, modelId?: number,
} }
export function chatParse(queryText: string, chatId?: number, modelId?: number, agentId?: number, filters?: any[]) { export function chatParse(queryText: string, chatId?: number, modelId?: number, agentId?: number, filters?: any[]) {
return axios.post<Result<ParseDataType>>(`${prefix}/chat/query/parse`, { return axios.post<ParseDataType>(`${prefix}/chat/query/parse`, {
queryText, queryText,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
modelId, modelId,
@@ -38,7 +38,7 @@ export function chatParse(queryText: string, chatId?: number, modelId?: number,
} }
export function chatExecute(queryText: string, chatId: number, parseInfo: ChatContextType ) { export function chatExecute(queryText: string, chatId: number, parseInfo: ChatContextType ) {
return axios.post<Result<MsgDataType>>(`${prefix}/chat/query/execute`, { return axios.post<MsgDataType>(`${prefix}/chat/query/execute`, {
queryText, queryText,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
queryId: parseInfo.queryId, queryId: parseInfo.queryId,
@@ -47,7 +47,7 @@ export function chatExecute(queryText: string, chatId: number, parseInfo: ChatC
} }
export function switchEntity(entityId: string, modelId?: number, chatId?: number) { export function switchEntity(entityId: string, modelId?: number, chatId?: number) {
return axios.post<Result<any>>(`${prefix}/chat/query/switchQuery`, { return axios.post<any>(`${prefix}/chat/query/switchQuery`, {
queryText: entityId, queryText: entityId,
modelId, modelId,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
@@ -55,46 +55,46 @@ export function switchEntity(entityId: string, modelId?: number, chatId?: number
} }
export function queryData(chatContext: Partial<ChatContextType>) { export function queryData(chatContext: Partial<ChatContextType>) {
return axios.post<Result<MsgDataType>>(`${prefix}/chat/query/queryData`, chatContext); return axios.post<MsgDataType>(`${prefix}/chat/query/queryData`, chatContext);
} }
export function queryContext(queryText: string, chatId?: number) { export function queryContext(queryText: string, chatId?: number) {
return axios.post<Result<ChatContextType>>(`${prefix}/chat/query/queryContext`, { return axios.post<ChatContextType>(`${prefix}/chat/query/queryContext`, {
queryText, queryText,
chatId: chatId || DEFAULT_CHAT_ID, chatId: chatId || DEFAULT_CHAT_ID,
}); });
} }
export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) { export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) {
return axios.post<Result<HistoryType>>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, { return axios.post<HistoryType>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, {
current, current,
pageSize, pageSize,
}); });
} }
export function saveConversation(chatName: string) { export function saveConversation(chatName: string) {
return axios.post<Result<any>>(`${prefix}/chat/manage/save?chatName=${chatName}`); return axios.post<any>(`${prefix}/chat/manage/save?chatName=${chatName}`);
} }
export function getAllConversations() { export function getAllConversations() {
return axios.get<Result<any>>(`${prefix}/chat/manage/getAll`); return axios.get<any>(`${prefix}/chat/manage/getAll`);
} }
export function queryEntities(entityId: string | number, modelId: number) { export function queryEntities(entityId: string | number, modelId: number) {
return axios.post<Result<any>>(`${prefix}/chat/query/choice`, { return axios.post<any>(`${prefix}/chat/query/choice`, {
entityId, entityId,
modelId, modelId,
}); });
} }
export function updateQAFeedback(questionId: number, score: number) { export function updateQAFeedback(questionId: number, score: number) {
return axios.post<Result<any>>(`${prefix}/chat/manage/updateQAFeedback?id=${questionId}&score=${score}&feedback=`); return axios.post<any>(`${prefix}/chat/manage/updateQAFeedback?id=${questionId}&score=${score}&feedback=`);
} }
export function queryDrillDownDimensions(modelId: number) { export function queryDrillDownDimensions(modelId: number) {
return axios.get<Result<{ dimensions: DrillDownDimensionType[] }>>(`${prefix}/chat/recommend/metric/${modelId}`); return axios.get<{ dimensions: DrillDownDimensionType[] }>(`${prefix}/chat/recommend/metric/${modelId}`);
} }
export function queryDimensionValues(modelId: number, bizName: string, value: string) { export function queryDimensionValues(modelId: number, bizName: string, value: string) {
return axios.post<Result<any>>(`${prefix}/chat/query/queryDimensionValue`, { modelId, bizName, value}); return axios.post<any>(`${prefix}/chat/query/queryDimensionValue`, { modelId, bizName, value});
} }

View File

@@ -1,6 +1,7 @@
declare module 'slash2'; declare module 'slash2';
declare module '*.css'; declare module '*.css';
declare module '*.less'; declare module '*.less';
declare module '*.module.less';
declare module '*.scss'; declare module '*.scss';
declare module '*.sass'; declare module '*.sass';
declare module '*.svg'; declare module '*.svg';
@@ -18,12 +19,22 @@ declare module 'react-fittext';
declare module 'bizcharts-plugin-slider'; declare module 'bizcharts-plugin-slider';
declare module 'react-split-pane/lib/Pane'; declare module 'react-split-pane/lib/Pane';
// preview.pro.ant.design only do not use in your production ;
// preview.pro.ant.design Dedicated environment variable, please do not use it in your project.
declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined;
declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
declare module '*.module.less' {
const classes: {
readonly [key: string]: string
}
export default classes
declare module '*.less'
}
interface AxiosResponse<T = any> extends Promise<T> {
code: number;
data: T;
msg: string;
}
type Result<T> = { type Result<T> = {
code: number; code: number;
data: T; data: T;

View File

@@ -178,9 +178,76 @@ export function isProd() {
} }
export function setToken(token: string) { export function setToken(token: string) {
localStorage.setItem('SUPERSONIC_CHAT_TOKEN', token); localStorage.setItem('SUPERSONIC_TOKEN', token);
} }
export function getToken() { export function getToken() {
return localStorage.getItem('SUPERSONIC_CHAT_TOKEN'); return localStorage.getItem('SUPERSONIC_TOKEN');
} }
export const updateMessageContainerScroll = (nodeId?: string) => {
setTimeout(() => {
const ele: any = document.getElementById('messageContainer');
if (ele && ele.scrollHeight > ele.clientHeight) {
if (nodeId) {
const node = document.getElementById(nodeId);
if (node) {
ele.scrollTop = ele.scrollHeight - node.clientHeight - 130;
}
} else {
ele.scrollTop = ele.scrollHeight;
}
}
}, 100);
};
/**
* UUID生成器
* @param len 长度 number
* @param radix 随机数基数 number
* @returns {string}
*/
export const uuid = (len: number = 8, radix: number = 62) => {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
const uuid: string[] = [];
let i;
if (len) {
// Compact form
for (i = 0; i < len; i++) {
uuid[i] = chars[Math.floor(Math.random() * radix)];
}
} else {
// rfc4122, version 4 form
let r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = Math.floor(Math.random() * 16);
uuid[i] = chars[i === 19 ? ((r % 4) % 8) + 8 : r];
}
}
}
return uuid.join('');
};
let utilCanvas: any = null;
export const getTextWidth = (
text: string,
fontSize: string = '16px',
fontWeight: string = 'normal',
fontFamily: string = 'DINPro Medium',
): number => {
const canvas = utilCanvas || (utilCanvas = document.createElement('canvas'));
const context = canvas.getContext('2d');
context.font = `${fontWeight} ${fontSize} ${fontFamily}`;
const metrics = context.measureText(text);
return Math.ceil(metrics.width);
};

View File

@@ -7,7 +7,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"moduleResolution":"Node", "moduleResolution":"Node",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"importHelpers": true, "importHelpers": true
}, },
"include": [ "include": [
"src" "src"

View File

@@ -1,4 +1,4 @@
const ENV_CONFIG = { const ENV_CONFIG = {
tmeAvatarUrl: 'http://tpp.tmeoa.com/photo/48/', tmeAvatarUrl: '',
}; };
export default ENV_CONFIG; export default ENV_CONFIG;

View File

@@ -11,7 +11,7 @@ const ROUTES = [
{ {
path: '/chat/mobile', path: '/chat/mobile',
name: 'chat', name: 'chat',
component: './Chat', component: './ChatPage',
hideInMenu: true, hideInMenu: true,
layout: false, layout: false,
envEnableList: [ENV_KEY.CHAT], envEnableList: [ENV_KEY.CHAT],
@@ -19,7 +19,7 @@ const ROUTES = [
{ {
path: '/chat', path: '/chat',
name: 'chat', name: 'chat',
component: './Chat', component: './ChatPage',
envEnableList: [ENV_KEY.CHAT], envEnableList: [ENV_KEY.CHAT],
}, },
{ {

View File

@@ -1,23 +1,18 @@
import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants';
import RightContent from '@/components/RightContent'; import RightContent from '@/components/RightContent';
import S2Icon, { ICON } from '@/components/S2Icon'; import S2Icon, { ICON } from '@/components/S2Icon';
import type { Settings as LayoutSettings } from '@ant-design/pro-layout'; import type { Settings as LayoutSettings } from '@ant-design/pro-layout';
import { Space, Spin } from 'antd'; import { Space, Spin } from 'antd';
import qs from 'qs';
import ScaleLoader from 'react-spinners/ScaleLoader'; import ScaleLoader from 'react-spinners/ScaleLoader';
import type { RunTimeLayoutConfig } from 'umi'; import type { RunTimeLayoutConfig } from 'umi';
import { history } from 'umi'; import { history } from 'umi';
import defaultSettings from '../config/defaultSettings'; import defaultSettings from '../config/defaultSettings';
import settings from '../config/themeSettings'; import settings from '../config/themeSettings';
import { queryToken } from './services/login';
import { queryCurrentUser } from './services/user'; import { queryCurrentUser } from './services/user';
import { traverseRoutes, deleteUrlQuery, isMobile } from './utils/utils'; import { traverseRoutes, isMobile, getToken } from './utils/utils';
import { publicPath } from '../config/defaultSettings'; import { publicPath } from '../config/defaultSettings';
import Copilot from './pages/Copilot'; import { Copilot } from 'supersonic-chat-sdk';
export { request } from './services/request'; export { request } from './services/request';
const TOKEN_KEY = AUTH_TOKEN_KEY;
const replaceRoute = '/'; const replaceRoute = '/';
const getRunningEnv = async () => { const getRunningEnv = async () => {
@@ -42,25 +37,6 @@ export const initialStateConfig = {
), ),
}; };
const getToken = async () => {
let { search } = window.location;
if (search.length > 0) {
search = search.slice(1);
}
const data = qs.parse(search);
if (data.code) {
try {
const fromUrl = localStorage.getItem(FROM_URL_KEY);
const res = await queryToken(data.code as string);
localStorage.setItem(TOKEN_KEY, res.payload);
const newUrl = deleteUrlQuery(window.location.href, 'code');
window.location.href = fromUrl || newUrl;
} catch (err) {
console.log(err);
}
}
};
const getAuthCodes = () => { const getAuthCodes = () => {
return []; return [];
}; };
@@ -81,12 +57,6 @@ export async function getInitialState(): Promise<{
} catch (error) {} } catch (error) {}
return undefined; return undefined;
}; };
const { query } = history.location as any;
const currentToken = query[TOKEN_KEY] || localStorage.getItem(TOKEN_KEY);
if (window.location.host.includes('tmeoa') && !currentToken) {
await getToken();
}
let currentUser: any; let currentUser: any;
if (!window.location.pathname.includes('login')) { if (!window.location.pathname.includes('login')) {
@@ -162,7 +132,9 @@ export const layout: RunTimeLayoutConfig = (params) => {
style={{ height: location.pathname.includes('chat') ? 'calc(100vh - 48px)' : undefined }} style={{ height: location.pathname.includes('chat') ? 'calc(100vh - 48px)' : undefined }}
> >
{dom} {dom}
{history.location.pathname !== '/chat' && !isMobile && <Copilot />} {history.location.pathname !== '/chat' && !isMobile && (
<Copilot token={getToken() || ''} isDeveloper />
)}
</div> </div>
); );
}, },

View File

@@ -1,19 +0,0 @@
import { CHAT_BLUE } from '@/common/constants';
import { Spin } from 'antd';
import BeatLoader from 'react-spinners/BeatLoader';
import Message from './Message';
import styles from './style.less';
const Typing = () => {
return (
<Message position="left" bubbleClassName={styles.typingBubble}>
<Spin
spinning={true}
indicator={<BeatLoader color={CHAT_BLUE} size={10} />}
className={styles.typing}
/>
</Message>
);
};
export default Typing;

View File

@@ -1,78 +0,0 @@
import { isMobile } from '@/utils/utils';
import { request } from 'umi';
import { AgentType, ModelType } from './type';
const prefix = isMobile ? '/openapi' : '/api';
export function saveConversation(chatName: string, agentId: number) {
return request<Result<any>>(
`${prefix}/chat/manage/save?chatName=${chatName}&agentId=${agentId}`,
{
method: 'POST',
},
);
}
export function updateConversationName(chatName: string, chatId: number = 0) {
return request<Result<any>>(
`${prefix}/chat/manage/updateChatName?chatName=${chatName}&chatId=${chatId}`,
{ method: 'POST' },
);
}
export function deleteConversation(chatId: number) {
return request<Result<any>>(`${prefix}/chat/manage/delete?chatId=${chatId}`, { method: 'POST' });
}
export function getAllConversations(agentId?: number) {
return request<Result<any>>(`${prefix}/chat/manage/getAll`, { params: { agentId } });
}
export function getMiniProgramList(entityId: string, modelId: number) {
return request<Result<any>>(
`${prefix}/chat/plugin/extend/getAvailablePlugin/${entityId}/${modelId}`,
{
method: 'GET',
skipErrorHandler: true,
},
);
}
export function getModelList() {
return request<Result<ModelType[]>>(`${prefix}/chat/conf/modelList/view`, {
method: 'GET',
});
}
export function updateQAFeedback(questionId: number, score: number) {
return request<Result<any>>(
`${prefix}/chat/manage/updateQAFeedback?id=${questionId}&score=${score}&feedback=`,
{
method: 'POST',
},
);
}
export function queryMetricSuggestion(modelId: number) {
return request<Result<any>>(`${prefix}/chat/recommend/metric/${modelId}`, {
method: 'GET',
});
}
export function querySuggestion(modelId: number) {
return request<Result<any>>(`${prefix}/chat/recommend/${modelId}`, {
method: 'GET',
});
}
export function queryRecommendQuestions() {
return request<Result<any>>(`${prefix}/chat/recommend/question`, {
method: 'GET',
});
}
export function queryAgentList() {
return request<Result<AgentType[]>>(`${prefix}/chat/agent/getAgentList`, {
method: 'GET',
});
}

View File

@@ -0,0 +1,14 @@
import { useLocation } from 'umi';
import { getToken } from '@/utils/utils';
import { Chat } from 'supersonic-chat-sdk';
const ChatPage = () => {
const location = useLocation();
const { agentId } = (location as any).query;
return (
<Chat initialAgentId={agentId ? +agentId : undefined} token={getToken() || ''} isDeveloper />
);
};
export default ChatPage;

View File

@@ -1,119 +0,0 @@
import IconFont from '@/components/IconFont';
import { CaretRightOutlined, CloseOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import Chat from '../Chat';
import { DefaultEntityType, ModelType } from '../Chat/type';
import styles from './style.less';
type Props = {
globalCopilotFilter: DefaultEntityType;
copilotSendMsg: string;
};
const Copilot: React.FC<Props> = ({ globalCopilotFilter, copilotSendMsg }) => {
const [chatVisible, setChatVisible] = useState(false);
const [defaultModelName, setDefaultModelName] = useState('');
const [defaultEntityFilter, setDefaultEntityFilter] = useState<DefaultEntityType>();
const [triggerNewConversation, setTriggerNewConversation] = useState(false);
const [copilotMinimized, setCopilotMinimized] = useState(false);
useEffect(() => {
if (globalCopilotFilter && globalCopilotFilter.entityId !== defaultEntityFilter?.entityId) {
setTriggerNewConversation(true);
}
setDefaultEntityFilter(globalCopilotFilter);
if (globalCopilotFilter?.modelName) {
setDefaultModelName(globalCopilotFilter.modelName);
}
}, [globalCopilotFilter]);
useEffect(() => {
if (copilotSendMsg) {
updateChatVisible(true);
setTriggerNewConversation(true);
}
}, [copilotSendMsg]);
const updateChatVisible = (visible: boolean) => {
setChatVisible(visible);
};
const onToggleChatVisible = () => {
const chatVisibleValue = !chatVisible;
updateChatVisible(chatVisibleValue);
};
const onCloseChat = () => {
updateChatVisible(false);
};
const onTransferChat = () => {
window.open('/chat');
};
const onCurrentModelChange = (model?: ModelType) => {
setDefaultModelName(model?.name || '');
};
const onNewConversationTriggered = () => {
setTriggerNewConversation(false);
};
const onMinimizeCopilot = (e: any) => {
e.stopPropagation();
updateChatVisible(false);
setCopilotMinimized(true);
};
const copilotClass = classNames(styles.copilot, {
[styles.copilotMinimized]: copilotMinimized,
});
return (
<>
<div
className={copilotClass}
onMouseEnter={() => {
setCopilotMinimized(false);
}}
onClick={onToggleChatVisible}
>
<IconFont type="icon-copilot-fill" />
<div className={styles.minimizeWrapper} onClick={onMinimizeCopilot}>
<div className={styles.minimize}>-</div>
</div>
</div>
<div className={styles.copilotContent} style={{ display: chatVisible ? 'block' : 'none' }}>
<div className={styles.chatPopover}>
<div className={styles.header}>
<div className={styles.leftSection}>
<CloseOutlined className={styles.close} onClick={onCloseChat} />
<IconFont
type="icon-weibiaoti-"
className={styles.transfer}
onClick={onTransferChat}
/>
</div>
<div className={styles.title}>Copilot</div>
</div>
<div className={styles.chat}>
<Chat
copilotSendMsg={copilotSendMsg}
defaultModelName={defaultModelName}
defaultEntityFilter={defaultEntityFilter}
triggerNewConversation={triggerNewConversation}
chatVisible={chatVisible}
isCopilotMode
onNewConversationTriggered={onNewConversationTriggered}
onCurrentModelChange={onCurrentModelChange}
/>
</div>
</div>
<CaretRightOutlined className={styles.rightArrow} />
</div>
</>
);
};
export default Copilot;

View File

@@ -1,5 +1,3 @@
import { request } from 'umi';
export type LoginParamsType = { export type LoginParamsType = {
username: string; username: string;
password: string; password: string;
@@ -7,9 +5,3 @@ export type LoginParamsType = {
captcha: string; captcha: string;
type: string; type: string;
}; };
export async function queryToken(code: string) {
return request(`/davinciapi/login/tmeloginCallback`, {
params: { code },
});
}

191
webapp/pnpm-lock.yaml generated
View File

@@ -14,12 +14,15 @@ importers:
'@uiw/react-watermark': '@uiw/react-watermark':
specifier: ^0.0.5 specifier: ^0.0.5
version: 0.0.5(react-dom@18.2.0)(react@18.2.0) version: 0.0.5(react-dom@18.2.0)(react@18.2.0)
ahooks:
specifier: ^3.7.8
version: 3.7.8(react@18.2.0)
antd: antd:
specifier: ^5.5.2 specifier: ^5.5.2
version: 5.9.0(moment@2.29.4)(react-dom@18.2.0)(react@18.2.0) version: 5.9.0(moment@2.29.4)(react-dom@18.2.0)(react@18.2.0)
axios: axios:
specifier: ^1.4.0 specifier: ^0.21.1
version: 1.4.0 version: 0.21.4
classnames: classnames:
specifier: ^2.3.2 specifier: ^2.3.2
version: 2.3.2 version: 2.3.2
@@ -32,9 +35,18 @@ importers:
moment: moment:
specifier: ^2.29.4 specifier: ^2.29.4
version: 2.29.4 version: 2.29.4
react-copy-to-clipboard:
specifier: ^5.1.0
version: 5.1.0(react@18.2.0)
react-spinners: react-spinners:
specifier: ^0.13.8 specifier: ^0.13.8
version: 0.13.8(react-dom@18.2.0)(react@18.2.0) version: 0.13.8(react-dom@18.2.0)(react@18.2.0)
react-syntax-highlighter:
specifier: ^15.5.0
version: 15.5.0(react@18.2.0)
sql-formatter:
specifier: ^2.3.3
version: 2.3.4
tslib: tslib:
specifier: ^2.5.2 specifier: ^2.5.2
version: 2.6.2 version: 2.6.2
@@ -75,6 +87,9 @@ importers:
'@types/jest': '@types/jest':
specifier: ^27.5.2 specifier: ^27.5.2
version: 27.5.2 version: 27.5.2
'@types/lodash':
specifier: ^4.14.198
version: 4.14.198
'@types/node': '@types/node':
specifier: ^16.18.31 specifier: ^16.18.31
version: 16.18.50 version: 16.18.50
@@ -225,6 +240,9 @@ importers:
rollup-plugin-postcss: rollup-plugin-postcss:
specifier: ^4.0.2 specifier: ^4.0.2
version: 4.0.2(postcss@8.4.29) version: 4.0.2(postcss@8.4.29)
rollup-plugin-styles:
specifier: ^4.0.0
version: 4.0.0(rollup@3.29.1)
rollup-plugin-typescript2: rollup-plugin-typescript2:
specifier: ^0.34.1 specifier: ^0.34.1
version: 0.34.1(rollup@3.29.1)(typescript@4.9.5) version: 0.34.1(rollup@3.29.1)(typescript@4.9.5)
@@ -5502,6 +5520,15 @@ packages:
resolution: {integrity: sha512-t33RNmTu5ufG/sorROIafiCVJMx3jz95bXUMoPAZcUD14fxMXnuTzqzXZoxpR0tNx2xpw11Dlmem9vGCsrSOfA==} resolution: {integrity: sha512-t33RNmTu5ufG/sorROIafiCVJMx3jz95bXUMoPAZcUD14fxMXnuTzqzXZoxpR0tNx2xpw11Dlmem9vGCsrSOfA==}
dev: true dev: true
/@types/cssnano@5.1.0(postcss@8.4.29):
resolution: {integrity: sha512-ikR+18UpFGgvaWSur4og6SJYF/6QEYHXvrIt36dp81p1MG3cAPTYDMBJGeyWa3LCnqEbgNMHKRb+FP0NrXtoWQ==}
deprecated: This is a stub types definition. cssnano provides its own type definitions, so you do not need this installed.
dependencies:
cssnano: 6.0.1(postcss@8.4.29)
transitivePeerDependencies:
- postcss
dev: true
/@types/d3-timer@2.0.1: /@types/d3-timer@2.0.1:
resolution: {integrity: sha512-TF8aoF5cHcLO7W7403blM7L1T+6NF3XMyN3fxyUolq2uOcFeicG/khQg/dGxiCJWoAcmYulYN7LYSRKO54IXaA==} resolution: {integrity: sha512-TF8aoF5cHcLO7W7403blM7L1T+6NF3XMyN3fxyUolq2uOcFeicG/khQg/dGxiCJWoAcmYulYN7LYSRKO54IXaA==}
dev: false dev: false
@@ -7329,6 +7356,25 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/ahooks@3.7.8(react@18.2.0):
resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==}
engines: {node: '>=8.0.0'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@babel/runtime': 7.22.15
'@types/js-cookie': 2.2.7
ahooks-v3-count: 1.0.0
dayjs: 1.11.9
intersection-observer: 0.12.2
js-cookie: 2.2.1
lodash: 4.17.21
react: 18.2.0
resize-observer-polyfill: 1.5.1
screenfull: 5.2.0
tslib: 2.6.2
dev: false
/ajv-errors@1.0.1(ajv@6.12.6): /ajv-errors@1.0.1(ajv@6.12.6):
resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==} resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==}
peerDependencies: peerDependencies:
@@ -7887,6 +7933,7 @@ packages:
/asynckit@0.4.0: /asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/at-least-node@1.0.0: /at-least-node@1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
@@ -7953,12 +8000,10 @@ packages:
- supports-color - supports-color
dev: true dev: true
/axios@1.4.0: /axios@0.21.4:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies: dependencies:
follow-redirects: 1.15.2 follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
@@ -9146,6 +9191,7 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
dev: true
/comma-separated-tokens@1.0.8: /comma-separated-tokens@1.0.8:
resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
@@ -9555,7 +9601,7 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/css-blank-pseudo@3.0.3(postcss@8.4.29): /css-blank-pseudo@3.0.3(postcss@8.4.29):
resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==} resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==}
@@ -9587,7 +9633,7 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-selector-parser: 5.0.0 postcss-selector-parser: 5.0.0
/css-has-pseudo@3.0.4(postcss@8.4.29): /css-has-pseudo@3.0.4(postcss@8.4.29):
@@ -9664,7 +9710,7 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/css-prefers-color-scheme@6.0.3(postcss@8.4.29): /css-prefers-color-scheme@6.0.3(postcss@8.4.29):
resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==} resolution: {integrity: sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==}
@@ -10312,6 +10358,7 @@ packages:
/delayed-stream@1.0.0: /delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
dev: true
/depd@1.1.2: /depd@1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
@@ -12452,6 +12499,7 @@ packages:
asynckit: 0.4.0 asynckit: 0.4.0
combined-stream: 1.0.8 combined-stream: 1.0.8
mime-types: 2.1.35 mime-types: 2.1.35
dev: true
/format@0.2.2: /format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
@@ -17920,7 +17968,7 @@ packages:
/postcss-attribute-case-insensitive@4.0.2: /postcss-attribute-case-insensitive@4.0.2:
resolution: {integrity: sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==} resolution: {integrity: sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-selector-parser: 6.0.13 postcss-selector-parser: 6.0.13
/postcss-attribute-case-insensitive@5.0.2(postcss@8.4.29): /postcss-attribute-case-insensitive@5.0.2(postcss@8.4.29):
@@ -17979,7 +18027,7 @@ packages:
resolution: {integrity: sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==} resolution: {integrity: sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-color-functional-notation@4.2.4(postcss@8.4.29): /postcss-color-functional-notation@4.2.4(postcss@8.4.29):
@@ -17997,14 +18045,14 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
'@csstools/convert-colors': 1.4.0 '@csstools/convert-colors': 1.4.0
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-color-hex-alpha@5.0.3: /postcss-color-hex-alpha@5.0.3:
resolution: {integrity: sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==} resolution: {integrity: sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-color-hex-alpha@8.0.4(postcss@8.4.29): /postcss-color-hex-alpha@8.0.4(postcss@8.4.29):
@@ -18022,14 +18070,14 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
'@csstools/convert-colors': 1.4.0 '@csstools/convert-colors': 1.4.0
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-color-rebeccapurple@4.0.1: /postcss-color-rebeccapurple@4.0.1:
resolution: {integrity: sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==} resolution: {integrity: sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-color-rebeccapurple@7.1.1(postcss@8.4.29): /postcss-color-rebeccapurple@7.1.1(postcss@8.4.29):
@@ -18094,7 +18142,7 @@ packages:
resolution: {integrity: sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==} resolution: {integrity: sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-custom-media@8.0.2(postcss@8.4.29): /postcss-custom-media@8.0.2(postcss@8.4.29):
resolution: {integrity: sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==} resolution: {integrity: sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==}
@@ -18120,14 +18168,14 @@ packages:
resolution: {integrity: sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==} resolution: {integrity: sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-custom-selectors@5.1.2: /postcss-custom-selectors@5.1.2:
resolution: {integrity: sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==} resolution: {integrity: sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-selector-parser: 5.0.0 postcss-selector-parser: 5.0.0
/postcss-custom-selectors@6.0.3(postcss@8.4.29): /postcss-custom-selectors@6.0.3(postcss@8.4.29):
@@ -18144,7 +18192,7 @@ packages:
resolution: {integrity: sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==} resolution: {integrity: sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-selector-parser: 5.0.0 postcss-selector-parser: 5.0.0
/postcss-dir-pseudo-class@6.0.5(postcss@8.4.29): /postcss-dir-pseudo-class@6.0.5(postcss@8.4.29):
@@ -18233,7 +18281,7 @@ packages:
resolution: {integrity: sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==} resolution: {integrity: sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-double-position-gradients@3.1.2(postcss@8.4.29): /postcss-double-position-gradients@3.1.2(postcss@8.4.29):
@@ -18251,7 +18299,7 @@ packages:
resolution: {integrity: sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==} resolution: {integrity: sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-env-function@4.0.6(postcss@8.4.29): /postcss-env-function@4.0.6(postcss@8.4.29):
@@ -18267,7 +18315,7 @@ packages:
/postcss-flexbugs-fixes@4.2.1: /postcss-flexbugs-fixes@4.2.1:
resolution: {integrity: sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==} resolution: {integrity: sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-flexbugs-fixes@5.0.2(postcss@8.4.29): /postcss-flexbugs-fixes@5.0.2(postcss@8.4.29):
resolution: {integrity: sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==} resolution: {integrity: sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==}
@@ -18281,7 +18329,7 @@ packages:
resolution: {integrity: sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==} resolution: {integrity: sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-focus-visible@6.0.4(postcss@8.4.29): /postcss-focus-visible@6.0.4(postcss@8.4.29):
resolution: {integrity: sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==} resolution: {integrity: sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==}
@@ -18297,7 +18345,7 @@ packages:
resolution: {integrity: sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==} resolution: {integrity: sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-focus-within@5.0.4(postcss@8.4.29): /postcss-focus-within@5.0.4(postcss@8.4.29):
resolution: {integrity: sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==} resolution: {integrity: sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==}
@@ -18312,7 +18360,7 @@ packages:
/postcss-font-variant@4.0.1: /postcss-font-variant@4.0.1:
resolution: {integrity: sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==} resolution: {integrity: sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-font-variant@5.0.0(postcss@8.4.29): /postcss-font-variant@5.0.0(postcss@8.4.29):
resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==}
@@ -18326,7 +18374,7 @@ packages:
resolution: {integrity: sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==} resolution: {integrity: sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-gap-properties@3.0.5(postcss@8.4.29): /postcss-gap-properties@3.0.5(postcss@8.4.29):
resolution: {integrity: sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==} resolution: {integrity: sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==}
@@ -18352,7 +18400,7 @@ packages:
resolution: {integrity: sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==} resolution: {integrity: sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-image-set-function@4.0.7(postcss@8.4.29): /postcss-image-set-function@4.0.7(postcss@8.4.29):
@@ -18380,7 +18428,7 @@ packages:
/postcss-initial@3.0.4: /postcss-initial@3.0.4:
resolution: {integrity: sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==} resolution: {integrity: sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-initial@4.0.1(postcss@8.4.29): /postcss-initial@4.0.1(postcss@8.4.29):
resolution: {integrity: sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==} resolution: {integrity: sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==}
@@ -18412,7 +18460,7 @@ packages:
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
'@csstools/convert-colors': 1.4.0 '@csstools/convert-colors': 1.4.0
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-lab-function@4.2.1(postcss@8.4.29): /postcss-lab-function@4.2.1(postcss@8.4.29):
@@ -18486,7 +18534,7 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dependencies: dependencies:
loader-utils: 1.4.2 loader-utils: 1.4.2
postcss: 7.0.32 postcss: 7.0.39
postcss-load-config: 2.1.2 postcss-load-config: 2.1.2
schema-utils: 1.0.0 schema-utils: 1.0.0
@@ -18508,7 +18556,7 @@ packages:
resolution: {integrity: sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==} resolution: {integrity: sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-logical@5.0.4(postcss@8.4.29): /postcss-logical@5.0.4(postcss@8.4.29):
resolution: {integrity: sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==} resolution: {integrity: sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==}
@@ -18523,7 +18571,7 @@ packages:
resolution: {integrity: sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==} resolution: {integrity: sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-media-minmax@5.0.0(postcss@8.4.29): /postcss-media-minmax@5.0.0(postcss@8.4.29):
resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==} resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==}
@@ -18772,7 +18820,7 @@ packages:
resolution: {integrity: sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==} resolution: {integrity: sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-normalize-charset@5.1.0(postcss@8.4.29): /postcss-normalize-charset@5.1.0(postcss@8.4.29):
resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==}
@@ -19004,7 +19052,7 @@ packages:
resolution: {integrity: sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==} resolution: {integrity: sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-overflow-shorthand@3.0.4(postcss@8.4.29): /postcss-overflow-shorthand@3.0.4(postcss@8.4.29):
resolution: {integrity: sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==} resolution: {integrity: sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==}
@@ -19019,7 +19067,7 @@ packages:
/postcss-page-break@2.0.0: /postcss-page-break@2.0.0:
resolution: {integrity: sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==} resolution: {integrity: sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-page-break@3.0.4(postcss@8.4.29): /postcss-page-break@3.0.4(postcss@8.4.29):
resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==}
@@ -19033,7 +19081,7 @@ packages:
resolution: {integrity: sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==} resolution: {integrity: sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-values-parser: 2.0.1 postcss-values-parser: 2.0.1
/postcss-place@7.0.5(postcss@8.4.29): /postcss-place@7.0.5(postcss@8.4.29):
@@ -19057,7 +19105,7 @@ packages:
css-has-pseudo: 0.10.0 css-has-pseudo: 0.10.0
css-prefers-color-scheme: 3.1.1 css-prefers-color-scheme: 3.1.1
cssdb: 4.4.0 cssdb: 4.4.0
postcss: 7.0.32 postcss: 7.0.39
postcss-attribute-case-insensitive: 4.0.2 postcss-attribute-case-insensitive: 4.0.2
postcss-color-functional-notation: 2.0.1 postcss-color-functional-notation: 2.0.1
postcss-color-gray: 5.0.0 postcss-color-gray: 5.0.0
@@ -19150,7 +19198,7 @@ packages:
resolution: {integrity: sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==} resolution: {integrity: sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
postcss-selector-parser: 5.0.0 postcss-selector-parser: 5.0.0
/postcss-pseudo-class-any-link@7.1.6(postcss@8.4.29): /postcss-pseudo-class-any-link@7.1.6(postcss@8.4.29):
@@ -19208,7 +19256,7 @@ packages:
/postcss-replace-overflow-wrap@3.0.0: /postcss-replace-overflow-wrap@3.0.0:
resolution: {integrity: sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==} resolution: {integrity: sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==}
dependencies: dependencies:
postcss: 7.0.32 postcss: 7.0.39
/postcss-replace-overflow-wrap@4.0.0(postcss@8.4.29): /postcss-replace-overflow-wrap@4.0.0(postcss@8.4.29):
resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==}
@@ -19246,13 +19294,13 @@ packages:
resolution: {integrity: sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==} resolution: {integrity: sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==}
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
postcss: 7.0.32 postcss: 7.0.39
/postcss-selector-not@4.0.1: /postcss-selector-not@4.0.1:
resolution: {integrity: sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==} resolution: {integrity: sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ==}
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
postcss: 7.0.32 postcss: 7.0.39
/postcss-selector-not@6.0.1(postcss@8.4.29): /postcss-selector-not@6.0.1(postcss@8.4.29):
resolution: {integrity: sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==} resolution: {integrity: sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==}
@@ -19644,6 +19692,7 @@ packages:
/proxy-from-env@1.1.0: /proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
/prr@1.0.1: /prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
@@ -19792,6 +19841,16 @@ packages:
split-on-first: 1.1.0 split-on-first: 1.1.0
strict-uri-encode: 2.0.0 strict-uri-encode: 2.0.0
/query-string@7.1.3:
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
engines: {node: '>=6'}
dependencies:
decode-uri-component: 0.2.2
filter-obj: 1.1.0
split-on-first: 1.1.0
strict-uri-encode: 2.0.0
dev: true
/querystring-es3@0.2.1: /querystring-es3@0.2.1:
resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
engines: {node: '>=0.4.x'} engines: {node: '>=0.4.x'}
@@ -21140,6 +21199,16 @@ packages:
react: 17.0.2 react: 17.0.2
dev: false dev: false
/react-copy-to-clipboard@5.1.0(react@18.2.0):
resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==}
peerDependencies:
react: ^15.3.0 || 16 || 17 || 18
dependencies:
copy-to-clipboard: 3.3.3
prop-types: 15.8.1
react: 18.2.0
dev: false
/react-dev-inspector@1.9.0(eslint@7.32.0)(react@17.0.2)(typescript@4.9.5)(webpack@5.88.2): /react-dev-inspector@1.9.0(eslint@7.32.0)(react@17.0.2)(typescript@4.9.5)(webpack@5.88.2):
resolution: {integrity: sha512-1ZlraWRrDz+NgjHwOmTAn/wWoP+6gZt46DS1mRRILlST0iKg4FO2Zj9dDcG5XPaeIIr3OGKwsX5vM6vakmaftA==} resolution: {integrity: sha512-1ZlraWRrDz+NgjHwOmTAn/wWoP+6gZt46DS1mRRILlST0iKg4FO2Zj9dDcG5XPaeIIr3OGKwsX5vM6vakmaftA==}
peerDependencies: peerDependencies:
@@ -21623,6 +21692,19 @@ packages:
refractor: 3.6.0 refractor: 3.6.0
dev: false dev: false
/react-syntax-highlighter@15.5.0(react@18.2.0):
resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==}
peerDependencies:
react: '>= 0.14.0'
dependencies:
'@babel/runtime': 7.22.15
highlight.js: 10.7.3
lowlight: 1.20.0
prismjs: 1.29.0
react: 18.2.0
refractor: 3.6.0
dev: false
/react-tween-state@0.1.5: /react-tween-state@0.1.5:
resolution: {integrity: sha512-sJQpjsdn0wjlDIUpfpb7jQGnOG8hAEW2e8k0KPA+xmf5KFa6Xat2JldbmxBhaqP0S/uIXhVE5ymKyH/b9X8nYA==} resolution: {integrity: sha512-sJQpjsdn0wjlDIUpfpb7jQGnOG8hAEW2e8k0KPA+xmf5KFa6Xat2JldbmxBhaqP0S/uIXhVE5ymKyH/b9X8nYA==}
dependencies: dependencies:
@@ -22478,6 +22560,33 @@ packages:
- ts-node - ts-node
dev: true dev: true
/rollup-plugin-styles@4.0.0(rollup@3.29.1):
resolution: {integrity: sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
rollup: ^2.63.0
dependencies:
'@rollup/pluginutils': 4.2.1
'@types/cssnano': 5.1.0(postcss@8.4.29)
cosmiconfig: 7.1.0
cssnano: 5.1.15(postcss@8.4.29)
fs-extra: 10.1.0
icss-utils: 5.1.0(postcss@8.4.29)
mime-types: 2.1.35
p-queue: 6.6.2
postcss: 8.4.29
postcss-modules-extract-imports: 3.0.0(postcss@8.4.29)
postcss-modules-local-by-default: 4.0.3(postcss@8.4.29)
postcss-modules-scope: 3.0.0(postcss@8.4.29)
postcss-modules-values: 4.0.0(postcss@8.4.29)
postcss-value-parser: 4.2.0
query-string: 7.1.3
resolve: 1.22.4
rollup: 3.29.1
source-map-js: 1.0.2
tslib: 2.6.2
dev: true
/rollup-plugin-terser@7.0.2(rollup@2.79.1): /rollup-plugin-terser@7.0.2(rollup@2.79.1):
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser