From 805a59dddd07449d9d39bc702a97c6fa562593be Mon Sep 17 00:00:00 2001 From: williamhliu Date: Fri, 30 Jun 2023 17:42:03 +0800 Subject: [PATCH] [feature](webapp) upgrade chat version --- .gitignore | 1 + webapp/packages/chat-sdk/.gitignore | 3 - webapp/packages/chat-sdk/package.json | 8 +- .../chat-sdk/rollup/rollup.esm.config.mjs | 6 +- .../chat-sdk/rollup/rollup.umd.config.mjs | 2 +- webapp/packages/chat-sdk/src/common/type.ts | 20 +- .../chat-sdk/src/components/ChatItem/Text.tsx | 2 +- .../src/components/ChatItem/index.tsx | 131 +++-- .../src/components/ChatItem/style.less | 20 + .../src/components/ChatMsg/Bar/index.tsx | 11 +- .../src/components/ChatMsg/Message/index.tsx | 88 ++-- .../src/components/ChatMsg/Message/style.less | 43 +- .../components/ChatMsg/MetricCard/index.tsx | 2 +- .../ChatMsg/MetricTrend/MetricTrendChart.tsx | 31 +- .../components/ChatMsg/MetricTrend/index.tsx | 178 +++---- .../components/ChatMsg/MetricTrend/style.less | 11 + .../src/components/ChatMsg/Table/index.tsx | 11 +- .../src/components/ChatMsg/Table/style.less | 4 + .../chat-sdk/src/components/ChatMsg/index.tsx | 38 +- .../src/components/IconFont/index.tsx | 7 + .../src/components/SemanticDetail/index.tsx | 2 +- .../src/components/Suggestion/index.tsx | 2 +- .../chat-sdk/src/components/Tools/index.tsx | 43 +- webapp/packages/chat-sdk/src/demo/Chat.tsx | 36 +- .../chat-sdk/src/demo/style.module.less | 2 +- webapp/packages/chat-sdk/src/index.tsx | 1 + .../chat-sdk/src/service/axiosInstance.ts | 15 +- webapp/packages/chat-sdk/src/service/index.ts | 11 +- .../packages/chat-sdk/src/styles/global.less | 2 +- .../packages/chat-sdk/src/styles/index.less | 1 + .../chat-sdk/src/styles/variables.less | 2 +- webapp/packages/chat-sdk/src/utils/utils.ts | 1 - webapp/packages/supersonic-fe/.gitignore | 2 +- .../supersonic-fe/coding_build/build_prd.sh | 2 +- .../packages/supersonic-fe/config/config.ts | 2 + .../supersonic-fe/config/defaultSettings.ts | 3 +- .../supersonic-fe/config/envConfig.ts | 2 + webapp/packages/supersonic-fe/package.json | 4 +- webapp/packages/supersonic-fe/src/app.tsx | 31 -- .../src/pages/Chat/ChatFooter/index.tsx | 220 ++++++--- .../src/pages/Chat/ChatFooter/style.less | 58 ++- .../src/pages/Chat/MessageContainer.tsx | 105 ++-- .../Chat/RightSection/Context/DomainInfo.tsx | 22 + .../pages/Chat/RightSection/Context/index.tsx | 55 +-- .../Chat/RightSection/Context/style.less | 10 +- .../ConversationHistory/index.tsx | 2 +- .../ConversationHistory/style.less | 4 +- .../Conversation}/ConversationModal/index.tsx | 9 +- .../Conversation/index.tsx} | 40 +- .../Chat/RightSection/Conversation/style.less | 50 ++ .../pages/Chat/RightSection/Domains/index.tsx | 44 ++ .../Chat/RightSection/Domains/style.less | 70 +++ .../Chat/RightSection/Introduction/index.tsx | 14 +- .../Chat/RightSection/Introduction/style.less | 2 +- .../src/pages/Chat/RightSection/index.tsx | 39 +- .../src/pages/Chat/RightSection/style.less | 5 +- .../Chat/components/LeftAvatar/index.tsx | 8 + .../Chat/components/LeftAvatar/style.less | 13 + .../src/pages/Chat/components/Message.tsx | 33 +- .../src/pages/Chat/components/Text.tsx | 16 +- .../src/pages/Chat/components/style.less | 32 +- .../supersonic-fe/src/pages/Chat/constants.ts | 8 + .../supersonic-fe/src/pages/Chat/index.tsx | 221 ++++++--- .../supersonic-fe/src/pages/Chat/service.ts | 19 +- .../supersonic-fe/src/pages/Chat/style.less | 457 +++++++++--------- .../supersonic-fe/src/pages/Chat/type.ts | 13 +- .../supersonic-fe/src/pages/Chat/utils.ts | 16 - .../supersonic-fe/src/services/request.ts | 1 - .../packages/supersonic-fe/src/utils/utils.ts | 45 ++ 69 files changed, 1570 insertions(+), 842 deletions(-) create mode 100644 webapp/packages/chat-sdk/src/components/IconFont/index.tsx create mode 100644 webapp/packages/supersonic-fe/config/envConfig.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/DomainInfo.tsx rename webapp/packages/supersonic-fe/src/pages/Chat/{components => RightSection/Conversation}/ConversationHistory/index.tsx (95%) rename webapp/packages/supersonic-fe/src/pages/Chat/{components => RightSection/Conversation}/ConversationHistory/style.less (96%) rename webapp/packages/supersonic-fe/src/pages/Chat/{components => RightSection/Conversation}/ConversationModal/index.tsx (84%) rename webapp/packages/supersonic-fe/src/pages/Chat/{Conversation.tsx => RightSection/Conversation/index.tsx} (81%) create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/style.less create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Domains/index.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Domains/style.less create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/index.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/style.less delete mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/utils.ts diff --git a/.gitignore b/.gitignore index 52487a1c2..d0de5cca8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ assembly/runtime/* **/dist/ *.umi/ /assembly/deploy +/runtime .flattened-pom.xml \ No newline at end of file diff --git a/webapp/packages/chat-sdk/.gitignore b/webapp/packages/chat-sdk/.gitignore index 57b8cf1b0..bea504c15 100644 --- a/webapp/packages/chat-sdk/.gitignore +++ b/webapp/packages/chat-sdk/.gitignore @@ -13,9 +13,6 @@ /dist -package-lock.json -yarn.lock - # misc .DS_Store .env.local diff --git a/webapp/packages/chat-sdk/package.json b/webapp/packages/chat-sdk/package.json index a7a6130c9..de19d7f0b 100644 --- a/webapp/packages/chat-sdk/package.json +++ b/webapp/packages/chat-sdk/package.json @@ -1,12 +1,12 @@ { "name": "supersonic-chat-sdk", - "version": "0.1.0", + "version": "0.0.0", "main": "dist/index.es.js", "module": "dist/index.es.js", "unpkg": "dist/index.umd.js", "types": "dist/index.d.ts", "dependencies": { - "antd": "^5.5.0", + "antd": "^5.5.2", "axios": "^1.4.0", "classnames": "^2.3.2", "echarts": "^5.4.2", @@ -19,7 +19,7 @@ "react-dom": ">=16.8.0" }, "scripts": { - "start": "npm run build-es", + "start": "npm run start:dev", "start:dev": "node scripts/start.js", "clean": "rimraf ./dist", "build": "npm run clean && npm run build-es", @@ -191,4 +191,4 @@ "engines": { "node": ">=14.18.0" } -} +} \ No newline at end of file diff --git a/webapp/packages/chat-sdk/rollup/rollup.esm.config.mjs b/webapp/packages/chat-sdk/rollup/rollup.esm.config.mjs index 7d1fd43f5..7c4fb53fd 100644 --- a/webapp/packages/chat-sdk/rollup/rollup.esm.config.mjs +++ b/webapp/packages/chat-sdk/rollup/rollup.esm.config.mjs @@ -1,4 +1,5 @@ import basicConfig from './rollup.config.mjs' +// import { terser } from "rollup-plugin-terser" import excludeDependenciesFromBundle from "rollup-plugin-exclude-dependencies-from-bundle" const config = { @@ -6,7 +7,10 @@ const config = { output: [ { file: 'dist/index.es.js', - format: 'es', + format: 'es', + // plugins: [ + // terser() + // ], }, ], plugins: [ diff --git a/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs b/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs index 7388d65ac..062443458 100644 --- a/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs +++ b/webapp/packages/chat-sdk/rollup/rollup.umd.config.mjs @@ -1,5 +1,5 @@ import basicConfig from './rollup.config.mjs' -import terser from '@rollup/plugin-terser'; +import { terser } from '@rollup/plugin-terser' import replace from '@rollup/plugin-replace' const config = { diff --git a/webapp/packages/chat-sdk/src/common/type.ts b/webapp/packages/chat-sdk/src/common/type.ts index c52a078a1..fcbedf03a 100644 --- a/webapp/packages/chat-sdk/src/common/type.ts +++ b/webapp/packages/chat-sdk/src/common/type.ts @@ -46,7 +46,7 @@ export type FilterItemType = { name: string; operator: string; type: string; - value: string; + value: string[]; }; export type ChatContextType = { @@ -57,7 +57,7 @@ export type ChatContextType = { dimensions: FieldType[]; metrics: FieldType[]; entity: number; - filters: FilterItemType[]; + dimensionFilters: FilterItemType[]; }; export enum MsgValidTypeEnum { @@ -67,11 +67,22 @@ export enum MsgValidTypeEnum { INVALID = 3, }; +export type InstructionResonseType = { + description: string; + instructionConfig: { + showElements: { elementId: string, params: any }[]; + showType: string; + relaShowElements: { elementId: string, params: any }[]; + relaShowType: string; + }; + instructionId: number; + instructionType: string; + name: string; +} + export type MsgDataType = { id: number; question: string; - aggregateType: string; - appletResponse: string; chatContext: ChatContextType; entityInfo: EntityInfoType; queryAuthorization: any; @@ -80,6 +91,7 @@ export type MsgDataType = { queryId: number; queryMode: string; queryState: MsgValidTypeEnum; + response: InstructionResonseType; }; export type QueryDataType = { diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/Text.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/Text.tsx index 85a712af9..95c0b8cdf 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/Text.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/Text.tsx @@ -8,7 +8,7 @@ type Props = { const Text: React.FC = ({ data }) => { const prefixCls = `${PREFIX_CLS}-item`; return ( - +
{data}
); diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx index 3c992d8e6..961cff6c7 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx @@ -1,49 +1,48 @@ -import { MsgDataType, MsgValidTypeEnum, SuggestionDataType } from '../../common/type'; +import { MsgDataType, MsgValidTypeEnum } from '../../common/type'; import { useEffect, useState } from 'react'; import Typing from './Typing'; import ChatMsg from '../ChatMsg'; -import { querySuggestionInfo, chatQuery } from '../../service'; +import { chatQuery } from '../../service'; import { MSG_VALID_TIP, PARSE_ERROR_TIP, PREFIX_CLS } from '../../common/constants'; import Text from './Text'; -import Suggestion from '../Suggestion'; import Tools from '../Tools'; import SemanticDetail from '../SemanticDetail'; +import IconFont from '../IconFont'; type Props = { msg: string; + followQuestions?: string[]; conversationId?: number; - classId?: number; + domainId?: number; isLastMessage?: boolean; - suggestionEnable?: boolean; msgData?: MsgDataType; - onLastMsgDataLoaded?: (data: MsgDataType) => void; + isMobileMode?: boolean; + triggerResize?: boolean; + onMsgDataLoaded?: (data: MsgDataType) => void; onSelectSuggestion?: (value: string) => void; onUpdateMessageScroll?: () => void; }; const ChatItem: React.FC = ({ msg, + followQuestions, conversationId, - classId, + domainId, isLastMessage, - suggestionEnable, + isMobileMode, + triggerResize, msgData, - onLastMsgDataLoaded, + onMsgDataLoaded, onSelectSuggestion, onUpdateMessageScroll, }) => { const [data, setData] = useState(); - const [suggestionData, setSuggestionData] = useState(); const [loading, setLoading] = useState(false); const [metricInfoList, setMetricInfoList] = useState([]); const [tip, setTip] = useState(''); - const setMsgData = (value: MsgDataType) => { - setData(value); - }; - const updateData = (res: Result) => { - if (res.code === 401) { + if (res.code === 401 || res.code === 412) { setTip(res.msg); return false; } @@ -51,13 +50,13 @@ const ChatItem: React.FC = ({ setTip(PARSE_ERROR_TIP); return false; } - const { queryColumns, queryResults, queryState } = res.data || {}; + const { queryColumns, queryResults, queryState, queryMode } = res.data || {}; if (queryState !== MsgValidTypeEnum.NORMAL && queryState !== MsgValidTypeEnum.EMPTY) { setTip(MSG_VALID_TIP[queryState || MsgValidTypeEnum.INVALID]); return false; } - if (queryColumns && queryColumns.length > 0 && queryResults) { - setMsgData(res.data); + if ((queryColumns && queryColumns.length > 0 && queryResults) || queryMode === 'INSTRUCTION') { + setData(res.data); setTip(''); return true; } @@ -65,41 +64,20 @@ const ChatItem: React.FC = ({ return false; }; - const updateSuggestionData = (semanticRes: MsgDataType, suggestionRes: any) => { - const { aggregateType, queryColumns, entityInfo } = semanticRes; - setSuggestionData({ - currentAggregateType: aggregateType, - columns: queryColumns || [], - mainEntity: entityInfo, - suggestions: suggestionRes, - }); - }; - - const getSuggestions = async (domainId: number, semanticResData: MsgDataType) => { - if (!domainId) { - return; - } - const res = await querySuggestionInfo(domainId); - updateSuggestionData(semanticResData, res.data.data); - }; - const onSendMsg = async () => { setLoading(true); - const semanticRes = await chatQuery(msg, conversationId, classId); + const semanticRes = await chatQuery(msg, conversationId, domainId); updateData(semanticRes.data); - // if (suggestionEnable && semanticValid) { - // const semanticResData = semanticRes.data.data; - // await getSuggestions(semanticResData.entityInfo?.domainInfo?.itemId, semanticRes.data.data); - // } else { - // setSuggestionData(undefined); - // } - if (onLastMsgDataLoaded) { - onLastMsgDataLoaded(semanticRes.data.data); + if (onMsgDataLoaded) { + onMsgDataLoaded(semanticRes.data.data); } setLoading(false); }; useEffect(() => { + if (data !== undefined) { + return; + } if (msgData) { updateData({ code: 200, data: msgData, msg: 'success' }); } else if (msg) { @@ -107,15 +85,27 @@ const ChatItem: React.FC = ({ } }, [msg, msgData]); + const prefixCls = `${PREFIX_CLS}-item`; + if (loading) { - return ; + return ( +
+ + +
+ ); } if (tip) { - return ; + return ( +
+ + +
+ ); } - if (!data) { + if (!data || data.queryMode === 'INSTRUCTION') { return null; } @@ -126,26 +116,33 @@ const ChatItem: React.FC = ({ } }; - const prefixCls = `${PREFIX_CLS}-item`; - return ( -
- - - {suggestionEnable && suggestionData && isLastMessage && ( - - )} -
- {metricInfoList.map(item => ( - { - if (onSelectSuggestion) { - onSelectSuggestion(value); - } - }} - /> - ))} +
+ +
+ + + {metricInfoList.length > 0 && ( +
+ {metricInfoList.map(item => ( + { + if (onSelectSuggestion) { + onSelectSuggestion(value); + } + }} + /> + ))} +
+ )}
); diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/style.less b/webapp/packages/chat-sdk/src/components/ChatItem/style.less index 5ad1aaf19..0c9be0830 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatItem/style.less @@ -3,6 +3,26 @@ @chat-item-prefix-cls: ~'@{supersonic-chat-prefix}-item'; .@{chat-item-prefix-cls} { + display: flex; + + &-avatar { + display: flex; + align-items: center; + justify-content: center; + font-size: 40px; + width: 40px; + height: 40px; + margin-right: 6px; + border-radius: 50%; + color: var(--chat-blue); + background-color: #fff; + } + + &-content { + // flex: 1; + width: calc(100% - 50px); + } + &-metric-info-list { margin-top: 30px; display: flex; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx index c7fd99f51..85fd7ab30 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx @@ -8,10 +8,11 @@ import NoPermissionChart from '../NoPermissionChart'; type Props = { data: MsgDataType; + triggerResize?: boolean; onApplyAuth?: (domain: string) => void; }; -const BarChart: React.FC = ({ data, onApplyAuth }) => { +const BarChart: React.FC = ({ data, triggerResize, onApplyAuth }) => { const chartRef = useRef(); const [instance, setInstance] = useState(); @@ -133,7 +134,13 @@ const BarChart: React.FC = ({ data, onApplyAuth }) => { } }, [queryResults]); - if (!metricColumn?.authorized) { + useEffect(() => { + if (triggerResize && instance) { + instance.resize(); + } + }, [triggerResize]); + + if (metricColumn && !metricColumn?.authorized) { return ( = ({ position, width, height, + title, + followQuestions, children, bubbleClassName, chatContext, entityInfo, - aggregator, - noTime, + isMobileMode, }) => { - const { aggType, dateInfo, filters, metrics, domainName } = chatContext || {}; + const { dimensionFilters, domainName } = chatContext || {}; const prefixCls = `${PREFIX_CLS}-message`; - const timeSection = - !noTime && dateInfo?.text ? ( - dateInfo.text - ) : ( -
{`近${moment(dateInfo?.endDate).diff(dateInfo?.startDate, 'days') + 1}天`}
- ); + const entityInfoList = + entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || []; - const metricSection = - metrics && - metrics.map((metric, index) => { - let metricNode = {metric.name}; - return ( - <> - {metricNode} - {index < metrics.length - 1 && } - - ); - }); - - const aggregatorSection = aggregator !== 'tag' && aggType !== 'NONE' && aggType; - - const hasFilterSection = filters && filters.length > 0; + const hasFilterSection = + dimensionFilters && dimensionFilters.length > 0 && entityInfoList.length === 0; const filterSection = hasFilterSection && (
筛选条件:
- {filters.map(filterItem => { + {dimensionFilters.map(filterItem => { + const filterValue = + typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || []; return ( -
- {filterItem.name}:{filterItem.value} +
+ {filterItem.name}:{filterValue.join('、')}
); })} @@ -69,14 +57,15 @@ const Message: React.FC = ({
); - const entityInfoList = - entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || []; - - const hasEntityInfoSection = - entityInfoList.length > 0 && chatContext && chatContext.dimensions?.length > 0; + const leftTitle = title + ? followQuestions && followQuestions.length > 0 + ? `多轮对话:${[title, ...followQuestions].join(' ← ')}` + : `单轮对话:${title}` + : ''; return (
+ {domainName &&
{domainName}
}
= ({ e.stopPropagation(); }} > - {position === 'left' && chatContext && ( -
- {domainName} - {/* {dimensionSection} */} - {timeSection} - {metricSection} - {aggregatorSection} - {/* {tipSection} */} + {position === 'left' && title && ( +
+ {leftTitle}
)} - {(hasEntityInfoSection || hasFilterSection) && ( + {(entityInfoList.length > 0 || hasFilterSection) && (
- {hasEntityInfoSection && ( + {filterSection} + {entityInfoList.length > 0 && (
- {entityInfoList.slice(0, 3).map(dimension => { + {entityInfoList.slice(0, 4).map(dimension => { return (
{dimension.name}:
-
{dimension.value}
+ {dimension.bizName.includes('photo') ? ( + + ) : ( +
{dimension.value}
+ )}
); })}
)} - {filterSection}
)}
{children}
diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less index 87fb20dbb..d2b121475 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less @@ -3,6 +3,13 @@ @msg-prefix-cls: ~'@{supersonic-chat-prefix}-message'; .@{msg-prefix-cls} { + &-domain-name { + color: var(--text-color); + margin-bottom: 2px; + margin-left: 4px; + font-weight: 500; + } + &-content { display: flex; align-items: flex-start; @@ -24,15 +31,14 @@ } &-top-bar { - display: flex; - align-items: center; + position: relative; max-width: 100%; padding: 4px 0 8px; - overflow-x: auto; - color: var(--text-color); - font-weight: 500; - font-size: 14px; + color: var(--text-color-third); + font-size: 13px; white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; border-bottom: 1px solid rgba(0, 0, 0, 0.03); } @@ -44,11 +50,21 @@ font-size: 13px; } + &-filter-values { + display: flex; + align-items: center; + column-gap: 6px; + } + &-filter-item { padding: 2px 12px; color: var(--text-color-secondary); background-color: #edf2f2; border-radius: 13px; + max-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } &-tip { @@ -58,10 +74,16 @@ &-info-bar { display: flex; - flex-wrap: wrap; align-items: center; + row-gap: 12px; + flex-wrap: wrap; margin-top: 20px; column-gap: 20px; + color: var(--text-color-secondary); + background: rgba(133, 156, 241, 0.1); + padding: 4px 12px; + width: fit-content; + border-radius: 8px; } &-main-entity-info { @@ -70,6 +92,7 @@ align-items: center; font-size: 13px; column-gap: 20px; + row-gap: 10px; } &-info-item { @@ -77,12 +100,12 @@ align-items: center; } - &-info-Name { - color: var(--text-color-fourth); + &-info-name { + color: var(--text-color-third); } &-info-value { - color: var(--text-color-secondary); + color: var(--text-color); } } diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx index 42ee75f90..f26528f30 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx @@ -22,7 +22,7 @@ const MetricCard: React.FC = ({ data, onApplyAuth }) => { {/*
{startTime === endTime ? startTime : `${startTime} ~ ${endTime}`}
*/} - {!indicatorColumn?.authorized ? ( + {indicatorColumn && !indicatorColumn?.authorized ? ( ) : (
diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx index 89b8e1e12..692171d18 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx @@ -19,6 +19,7 @@ type Props = { categoryColumnName: string; metricField: ColumnType; resultList: any[]; + triggerResize?: boolean; onApplyAuth?: (domain: string) => void; }; @@ -28,6 +29,7 @@ const MetricTrendChart: React.FC = ({ categoryColumnName, metricField, resultList, + triggerResize, onApplyAuth, }) => { const chartRef = useRef(); @@ -40,6 +42,7 @@ const MetricTrendChart: React.FC = ({ setInstance(instanceObj); } else { instanceObj = instance; + instanceObj.clear(); } const valueColumnName = metricField.nameEn; @@ -51,13 +54,13 @@ const MetricTrendChart: React.FC = ({ endDate && (dateColumnName.includes('date') || dateColumnName.includes('month')) ? normalizeTrendData( - groupDataValue[key], - dateColumnName, - valueColumnName, - startDate, - endDate, - dateColumnName.includes('month') ? 'months' : 'days' - ) + groupDataValue[key], + dateColumnName, + valueColumnName, + startDate, + endDate, + dateColumnName.includes('month') ? 'months' : 'days' + ) : groupDataValue[key].reverse(); return result; }, {}); @@ -114,8 +117,8 @@ const MetricTrendChart: React.FC = ({ return value === 0 ? 0 : metricField.dataFormatType === 'percent' - ? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%` - : getFormattedValue(value); + ? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%` + : getFormattedValue(value); }, }, }, @@ -135,11 +138,11 @@ const MetricTrendChart: React.FC = ({ item.value === '' ? '-' : metricField.dataFormatType === 'percent' - ? `${formatByDecimalPlaces( + ? `${formatByDecimalPlaces( item.value, metricField.dataFormat?.decimalPlaces || 2 )}%` - : getFormattedValue(item.value) + : getFormattedValue(item.value) }
` ) .join(''); @@ -181,6 +184,12 @@ const MetricTrendChart: React.FC = ({ } }, [resultList, metricField]); + useEffect(() => { + if (triggerResize && instance) { + instance.resize(); + } + }, [triggerResize]); + const prefixCls = `${CLS_PREFIX}-metric-trend`; return ( diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx index 8446d81e3..ee3acb445 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx @@ -1,30 +1,27 @@ import { useEffect, useState } from 'react'; import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants'; -import { ColumnType, MsgDataType } from '../../../common/type'; -import { groupByColumn, isMobile } from '../../../utils/utils'; +import { ColumnType, FieldType, MsgDataType } from '../../../common/type'; +import { isMobile } from '../../../utils/utils'; import { queryData } from '../../../service'; import MetricTrendChart from './MetricTrendChart'; import classNames from 'classnames'; import { Spin } from 'antd'; import Table from '../Table'; -import SemanticInfoPopover from '../SemanticInfoPopover'; type Props = { data: MsgDataType; + triggerResize?: boolean; onApplyAuth?: (domain: string) => void; onCheckMetricInfo?: (data: any) => void; }; -const MetricTrend: React.FC = ({ data, onApplyAuth, onCheckMetricInfo }) => { +const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onCheckMetricInfo }) => { const { queryColumns, queryResults, entityInfo, chatContext } = data; const [columns, setColumns] = useState(queryColumns); - const metricFields = columns.filter((column: any) => column.showType === 'NUMBER') || []; + const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER'); - const [currentMetricField, setCurrentMetricField] = useState(metricFields[0]); - const [onlyOneDate, setOnlyOneDate] = useState(false); - const [trendData, setTrendData] = useState(data); + const [activeMetricField, setActiveMetricField] = useState(chatContext.metrics?.[0]); const [dataSource, setDataSource] = useState(queryResults); - const [mergeMetric, setMergeMetric] = useState(false); const [currentDateOption, setCurrentDateOption] = useState(); const [loading, setLoading] = useState(false); @@ -35,66 +32,17 @@ const MetricTrend: React.FC = ({ data, onApplyAuth, onCheckMetricInfo }) const categoryColumnName = columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || ''; - const getColumns = () => { - const categoryFieldData = groupByColumn(dataSource, categoryColumnName); - const result = [dateField]; - const columnsValue = Object.keys(categoryFieldData).map(item => ({ - authorized: currentMetricField.authorized, - name: item !== 'undefined' ? item : currentMetricField.name, - nameEn: `${item}${currentMetricField.name}`, - showType: 'NUMBER', - type: 'NUMBER', - })); - return result.concat(columnsValue); - }; - - const getResultList = () => { - return [ - { - [dateField.nameEn]: dataSource[0][dateField.nameEn], - ...dataSource.reduce((result, item) => { - result[`${item[categoryColumnName]}${currentMetricField.name}`] = - item[currentMetricField.nameEn]; - return result; - }, {}), - }, - ]; - }; - useEffect(() => { setDataSource(queryResults); }, [queryResults]); - useEffect(() => { - let onlyOneDateValue = false; - let dataValue = trendData; - if (dateColumnName && dataSource.length > 0) { - const dateFieldData = groupByColumn(dataSource, dateColumnName); - onlyOneDateValue = - Object.keys(dateFieldData).length === 1 && Object.keys(dateFieldData)[0] !== undefined; - if (onlyOneDateValue) { - if (categoryColumnName !== '') { - dataValue = { - ...trendData, - queryColumns: getColumns(), - queryResults: getResultList(), - }; - } else { - setMergeMetric(true); - } - } - } - setOnlyOneDate(onlyOneDateValue); - setTrendData(dataValue); - }, [currentMetricField]); + const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES[0]; - const dateOptions = DATE_TYPES[chatContext.dateInfo?.period] || DATE_TYPES[0]; - - const onLoadData = async (value: number) => { + const onLoadData = async (value: any) => { setLoading(true); const { data } = await queryData({ ...chatContext, - dateInfo: { ...chatContext.dateInfo, unit: value }, + ...value, }); setLoading(false); if (data.code === 200) { @@ -105,20 +53,21 @@ const MetricTrend: React.FC = ({ data, onApplyAuth, onCheckMetricInfo }) const selectDateOption = (dateOption: number) => { setCurrentDateOption(dateOption); - // const { domainName, dimensions, metrics, aggType, filters } = chatContext || {}; - // const dimensionSection = dimensions?.join('、') || ''; - // const metricSection = metrics?.join('、') || ''; - // const aggregatorSection = aggType || ''; - // const filterSection = filters - // .reduce((result, dimensionName) => { - // result = result.concat(dimensionName); - // return result; - // }, []) - // .join('、'); - onLoadData(dateOption); + onLoadData({ + metrics: [activeMetricField], + dateInfo: { ...chatContext?.dateInfo, unit: dateOption }, + }); }; - if (metricFields.length === 0) { + const onSwitchMetric = (metricField: FieldType) => { + setActiveMetricField(metricField); + onLoadData({ + dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit }, + metrics: [metricField], + }); + }; + + if (!currentMetricField) { return null; } @@ -127,64 +76,66 @@ const MetricTrend: React.FC = ({ data, onApplyAuth, onCheckMetricInfo }) return (
- {!onlyOneDate && ( -
- {dateOptions.map((dateOption: { label: string; value: number }, index: number) => { - const dateOptionClass = classNames(`${prefixCls}-date-option`, { - [`${prefixCls}-date-active`]: dateOption.value === currentDateOption, - [`${prefixCls}-date-mobile`]: isMobile, - }); - return ( - <> -
{ - selectDateOption(dateOption.value); - }} - > - {dateOption.label} - {dateOption.value === currentDateOption && ( -
- )} -
- {index !== dateOptions.length - 1 && ( -
+
+ {dateOptions.map((dateOption: { label: string; value: number }, index: number) => { + const dateOptionClass = classNames(`${prefixCls}-date-option`, { + [`${prefixCls}-date-active`]: dateOption.value === currentDateOption, + [`${prefixCls}-date-mobile`]: isMobile, + }); + return ( + <> +
{ + selectDateOption(dateOption.value); + }} + > + {dateOption.label} + {dateOption.value === currentDateOption && ( +
)} - - ); - })} -
- )} - {metricFields.length > 1 && !mergeMetric && ( +
+ {index !== dateOptions.length - 1 && ( +
+ )} + + ); + })} +
+ {chatContext.metrics.length > 0 && (
- {metricFields.map((metricField: ColumnType) => { + {chatContext.metrics.map((metricField: FieldType) => { const metricFieldClass = classNames(`${prefixCls}-metric-field`, { [`${prefixCls}-metric-field-active`]: - currentMetricField?.nameEn === metricField.nameEn, + activeMetricField?.bizName === metricField.bizName && + chatContext.metrics.length > 1, + [`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1, }); return (
{ - setCurrentMetricField(metricField); + if (chatContext.metrics.length > 1) { + onSwitchMetric(metricField); + } }} > - - {metricField.name} - + > */} + {metricField.name} + {/* */}
); })}
)} - {onlyOneDate ? ( - + {dataSource?.length === 1 ? ( +
) : ( = ({ data, onApplyAuth, onCheckMetricInfo }) categoryColumnName={categoryColumnName} metricField={currentMetricField} resultList={dataSource} + triggerResize={triggerResize} onApplyAuth={onApplyAuth} /> diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less index 38cf12cfb..927f31316 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less @@ -81,6 +81,17 @@ background-color: var(--chat-blue); } + &-metric-field-single { + padding-left: 0; + font-weight: 500; + cursor: default; + color: var(--text-color-secondary); + + &:hover { + color: var(--text-color-secondary); + } + } + &-date-options { display: flex; align-items: center; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx index 759fd62bc..b7f1e48f8 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx @@ -56,14 +56,21 @@ const Table: React.FC = ({ data, onApplyAuth }) => { } ); + const getRowClassName = (_: any, index: number) => { + return index % 2 !== 0 ? `${prefixCls}-even-row` : ''; + }; + return (
); diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less index 3314f9c06..06fd26c4d 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less @@ -16,6 +16,10 @@ width: 100%; } + &-even-row { + background-color: #fbfbfb; + } + .ant-table-container table > thead > tr:first-child th:first-child { border-top-left-radius: 12px !important; border-bottom-left-radius: 12px !important; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx index af4637a7c..296204124 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx @@ -7,12 +7,23 @@ import Table from './Table'; import { MsgDataType } from '../../common/type'; type Props = { + question: string; + followQuestions?: string[]; data: MsgDataType; + isMobileMode?: boolean; + triggerResize?: boolean; onCheckMetricInfo?: (data: any) => void; }; -const ChatMsg: React.FC = ({ data, onCheckMetricInfo }) => { - const { aggregateType, queryColumns, queryResults, chatContext, entityInfo } = data; +const ChatMsg: React.FC = ({ + question, + followQuestions, + data, + isMobileMode, + triggerResize, + onCheckMetricInfo, +}) => { + const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data; if (!queryColumns || !queryResults) { return null; @@ -24,20 +35,30 @@ const ChatMsg: React.FC = ({ data, onCheckMetricInfo }) => { const metricFields = queryColumns.filter(item => item.showType === 'NUMBER'); const getMsgContent = () => { - if (categoryField.length > 1 || aggregateType === 'tag') { + if ( + categoryField.length > 1 || + queryMode === 'ENTITY_DETAIL' || + queryMode === 'ENTITY_DIMENSION' + ) { return
; } if (dateField && metricFields.length > 0) { - return ; + return ( + + ); } if (singleData) { return ; } - return ; + return ; }; let width = '100%'; - if ((categoryField.length > 1 || aggregateType === 'tag') && !isMobile) { + if (categoryField.length > 1 && !isMobile && !isMobileMode) { if (queryColumns.length === 1) { width = '600px'; } else if (queryColumns.length === 2) { @@ -50,8 +71,9 @@ const ChatMsg: React.FC = ({ data, onCheckMetricInfo }) => { position="left" chatContext={chatContext} entityInfo={entityInfo} - aggregator={aggregateType} - tip={''} + title={question} + followQuestions={followQuestions} + isMobileMode={isMobileMode} width={width} > {getMsgContent()} diff --git a/webapp/packages/chat-sdk/src/components/IconFont/index.tsx b/webapp/packages/chat-sdk/src/components/IconFont/index.tsx new file mode 100644 index 000000000..709314cc9 --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/IconFont/index.tsx @@ -0,0 +1,7 @@ +import { createFromIconfontCN } from '@ant-design/icons'; + +const IconFont = createFromIconfontCN({ + scriptUrl: '//at.alicdn.com/t/c/font_4120566_hsbqfckf8tl.js', +}); + +export default IconFont; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx b/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx index 5b3d670af..4155bf24b 100644 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx +++ b/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx @@ -17,7 +17,7 @@ const SemanticDetail: React.FC = ({ dataSource, onDimensionSelect }) => { const semanticDetailCls = `${CLS_PREFIX}-semantic-detail`; return ( - +
diff --git a/webapp/packages/chat-sdk/src/components/Suggestion/index.tsx b/webapp/packages/chat-sdk/src/components/Suggestion/index.tsx index e50a0da27..388f6c858 100644 --- a/webapp/packages/chat-sdk/src/components/Suggestion/index.tsx +++ b/webapp/packages/chat-sdk/src/components/Suggestion/index.tsx @@ -79,7 +79,7 @@ const Suggestion: React.FC = ({ return (
- +
问答支持多轮对话,您可以继续输入:
{metricList.length > 0 && (
diff --git a/webapp/packages/chat-sdk/src/components/Tools/index.tsx b/webapp/packages/chat-sdk/src/components/Tools/index.tsx index a60b05b01..4a8413904 100644 --- a/webapp/packages/chat-sdk/src/components/Tools/index.tsx +++ b/webapp/packages/chat-sdk/src/components/Tools/index.tsx @@ -2,12 +2,15 @@ import { isMobile } from '../../utils/utils'; import { DislikeOutlined, LikeOutlined } from '@ant-design/icons'; import { Button, message } from 'antd'; import { CLS_PREFIX } from '../../common/constants'; +import { MsgDataType } from '../../common/type'; type Props = { + data: MsgDataType; isLastMessage?: boolean; + isMobileMode?: boolean; }; -const Tools: React.FC = ({ isLastMessage }) => { +const Tools: React.FC = ({ data, isLastMessage, isMobileMode }) => { const prefixCls = `${CLS_PREFIX}-tools`; const changeChart = () => { @@ -18,10 +21,6 @@ const Tools: React.FC = ({ isLastMessage }) => { message.info('正在开发中,敬请期待'); }; - const lockDomain = () => { - message.info('正在开发中,敬请期待'); - }; - const like = () => { message.info('正在开发中,敬请期待'); }; @@ -30,12 +29,6 @@ const Tools: React.FC = ({ isLastMessage }) => { message.info('正在开发中,敬请期待'); }; - const lockDomainSection = isLastMessage && ( - - ); - const feedbackSection = isLastMessage && (
这个回答正确吗?
@@ -44,25 +37,19 @@ const Tools: React.FC = ({ isLastMessage }) => {
); - if (isMobile) { - return ( -
- {isLastMessage &&
{lockDomainSection}
} - {feedbackSection} -
- ); - } - return (
- - - {lockDomainSection} - {feedbackSection} + {!isMobile && !isMobileMode && ( + <> + + + {feedbackSection} + + )}
); }; diff --git a/webapp/packages/chat-sdk/src/demo/Chat.tsx b/webapp/packages/chat-sdk/src/demo/Chat.tsx index 432d35e0e..981993f46 100644 --- a/webapp/packages/chat-sdk/src/demo/Chat.tsx +++ b/webapp/packages/chat-sdk/src/demo/Chat.tsx @@ -1,24 +1,44 @@ import { Input } from 'antd'; import styles from './style.module.less'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import ChatItem from '../components/ChatItem'; import { queryContext, searchRecommend } from '../service'; const { Search } = Input; const Chat = () => { + const [data, setData] = useState(); const [inputMsg, setInputMsg] = useState(''); const [msg, setMsg] = useState(''); + const [followQuestions, setFollowQuestions] = useState([]); + const [triggerResize, setTriggerResize] = useState(false); + + const onWindowResize = () => { + setTriggerResize(true); + setTimeout(() => { + setTriggerResize(false); + }, 0); + }; + + useEffect(() => { + window.addEventListener('resize', onWindowResize); + return () => { + window.removeEventListener('resize', onWindowResize); + }; + }, []); const onInputChange = (e: React.ChangeEvent) => { const { value } = e.target; setInputMsg(value); - searchRecommend(value); }; const onSearch = () => { setMsg(inputMsg); - queryContext(inputMsg); + }; + + const onMsgDataLoaded = (msgData: any) => { + setData(msgData); + setFollowQuestions(['测试1234测试', '测试1234测试', '测试1234测试']); }; return ( @@ -32,7 +52,15 @@ const Chat = () => { />
- +
); diff --git a/webapp/packages/chat-sdk/src/demo/style.module.less b/webapp/packages/chat-sdk/src/demo/style.module.less index 91c8e940b..e6c0ff84c 100644 --- a/webapp/packages/chat-sdk/src/demo/style.module.less +++ b/webapp/packages/chat-sdk/src/demo/style.module.less @@ -2,7 +2,7 @@ display: flex; flex-direction: column; row-gap: 20px; - padding: 30px; + padding: 20px; background: linear-gradient(180deg,rgba(23,74,228,0) 29.44%,rgba(23,74,228,.06)),linear-gradient(90deg,#f3f3f7,#f3f3f7 20%,#ebf0f9 60%,#f3f3f7 80%,#f3f3f7); height: 100vh; diff --git a/webapp/packages/chat-sdk/src/index.tsx b/webapp/packages/chat-sdk/src/index.tsx index 5ab34216a..f94e59c17 100644 --- a/webapp/packages/chat-sdk/src/index.tsx +++ b/webapp/packages/chat-sdk/src/index.tsx @@ -25,6 +25,7 @@ export type { ChatContextType, MsgValidTypeEnum, MsgDataType, + InstructionResonseType, ColumnType, SuggestionItemType, SuggestionType, diff --git a/webapp/packages/chat-sdk/src/service/axiosInstance.ts b/webapp/packages/chat-sdk/src/service/axiosInstance.ts index ab2008036..c7050a089 100644 --- a/webapp/packages/chat-sdk/src/service/axiosInstance.ts +++ b/webapp/packages/chat-sdk/src/service/axiosInstance.ts @@ -19,7 +19,7 @@ axiosInstance.interceptors.request.use( (config: any) => { const token = getToken(); if (token && config?.headers) { - config.headers.auth = `Bearer ${token}`; + config.headers.Auth = `Bearer ${token}`; } return config; }, @@ -32,9 +32,16 @@ axiosInstance.interceptors.request.use( // 响应拦截器 axiosInstance.interceptors.response.use( (response: any) => { - if (Number(response.data.code) === 403) { - window.location.href = '/#/login'; - return response; + const redirect = response.headers.get('redirect'); + if (redirect === 'REDIRECT') { + let win: any = window; + while (win !== win.top) { + win = win.top; + } + const contextpath = response.headers.get('contextpath'); + win.location.href = + contextpath?.substring(0, contextpath?.indexOf('&')) + + `&redirect_uri=${encodeURIComponent(`http://${win.location.host}`)}`; } return response; }, diff --git a/webapp/packages/chat-sdk/src/service/index.ts b/webapp/packages/chat-sdk/src/service/index.ts index defff0c06..d584943a6 100644 --- a/webapp/packages/chat-sdk/src/service/index.ts +++ b/webapp/packages/chat-sdk/src/service/index.ts @@ -2,7 +2,7 @@ import axios from './axiosInstance'; import { ChatContextType, HistoryType, MsgDataType, SearchRecommendItem } from '../common/type'; import { QueryDataType } from '../common/type'; -const DEFAULT_CHAT_ID = 2; +const DEFAULT_CHAT_ID = 0; const prefix = '/api'; @@ -57,7 +57,7 @@ export function getRelatedDimensionFromStatInfo(data: any) { export function getMetricQueryInfo(data: any) { return axios.get( - `getMetricQueryInfo/${data.classId}/${data.metricName}` + `/openapi/bd-bi/api/polaris/intelligentQuery/getMetricQueryInfo/${data.classId}/${data.metricName}` ); } @@ -67,4 +67,11 @@ export function saveConversation(chatName: string) { export function getAllConversations() { return axios.get>(`${prefix}/chat/manage/getAll`); +} + +export function queryEntities(entityId: string | number, domainId: number) { + return axios.post>(`${prefix}/chat/query/choice`, { + entityId, + domainId, + }); } \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/styles/global.less b/webapp/packages/chat-sdk/src/styles/global.less index 22ae164be..b04b56788 100644 --- a/webapp/packages/chat-sdk/src/styles/global.less +++ b/webapp/packages/chat-sdk/src/styles/global.less @@ -26,7 +26,7 @@ &-metric { &::after { - background: #31c462; + background: var(--primary-green); } } diff --git a/webapp/packages/chat-sdk/src/styles/index.less b/webapp/packages/chat-sdk/src/styles/index.less index 4b457f152..c246be1eb 100644 --- a/webapp/packages/chat-sdk/src/styles/index.less +++ b/webapp/packages/chat-sdk/src/styles/index.less @@ -27,3 +27,4 @@ @import "../components/Tools/style.less"; @import "../components/Suggestion/style.less"; + diff --git a/webapp/packages/chat-sdk/src/styles/variables.less b/webapp/packages/chat-sdk/src/styles/variables.less index 47cfe0e56..797e286a8 100644 --- a/webapp/packages/chat-sdk/src/styles/variables.less +++ b/webapp/packages/chat-sdk/src/styles/variables.less @@ -50,7 +50,7 @@ --link-active-color: #2748d9; --link-bg-color: rgba(58, 100, 255, 0.1); --text-accent-color: #3a64ff; - --primary-green: #00b354; + --primary-green: #31c462; --link-hover-bg-color: rgba(58, 100, 255, 0.06); --success-2: rgba(82, 196, 26, 0.2); --success-pink: #ff8193; diff --git a/webapp/packages/chat-sdk/src/utils/utils.ts b/webapp/packages/chat-sdk/src/utils/utils.ts index 544aa4fb4..04ed8d8a7 100644 --- a/webapp/packages/chat-sdk/src/utils/utils.ts +++ b/webapp/packages/chat-sdk/src/utils/utils.ts @@ -159,7 +159,6 @@ export function getChartLightenColor(col) { export const isMobile = window.navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i); - export function setToken(token: string) { localStorage.setItem('SUPERSONIC_CHAT_TOKEN', token); } diff --git a/webapp/packages/supersonic-fe/.gitignore b/webapp/packages/supersonic-fe/.gitignore index e53b81e0a..8e11232af 100644 --- a/webapp/packages/supersonic-fe/.gitignore +++ b/webapp/packages/supersonic-fe/.gitignore @@ -18,8 +18,8 @@ yarn-error.log /coverage .idea -package-lock.json yarn.lock +package-lock.json *bak .vscode diff --git a/webapp/packages/supersonic-fe/coding_build/build_prd.sh b/webapp/packages/supersonic-fe/coding_build/build_prd.sh index cac545a79..9912dcbab 100644 --- a/webapp/packages/supersonic-fe/coding_build/build_prd.sh +++ b/webapp/packages/supersonic-fe/coding_build/build_prd.sh @@ -6,7 +6,7 @@ if [ $? -ne 0 ]; then exit 1 fi -npm run build +npm run build:inner if [ $? -ne 0 ]; then echo "build failed" exit 1 diff --git a/webapp/packages/supersonic-fe/config/config.ts b/webapp/packages/supersonic-fe/config/config.ts index 7e97cc026..acd46c5cf 100644 --- a/webapp/packages/supersonic-fe/config/config.ts +++ b/webapp/packages/supersonic-fe/config/config.ts @@ -5,6 +5,7 @@ import themeSettings from './themeSettings'; import proxy from './proxy'; import routes from './routes'; import moment from 'moment'; +import ENV_CONFIG from './envConfig'; const { REACT_APP_ENV, RUN_TYPE } = process.env; @@ -19,6 +20,7 @@ export default defineConfig({ API_BASE_URL: '/api/semantic/', // 直接在define中挂载裸露的全局变量还需要配置eslint,ts相关配置才能导致在使用中不会飘红,冗余较高,这里挂在进程环境下 CHAT_API_BASE_URL: '/api/chat/', AUTH_API_BASE_URL: '/api/auth/', + ...ENV_CONFIG, }, }, metas: [ diff --git a/webapp/packages/supersonic-fe/config/defaultSettings.ts b/webapp/packages/supersonic-fe/config/defaultSettings.ts index 14945506d..b88f21a0f 100644 --- a/webapp/packages/supersonic-fe/config/defaultSettings.ts +++ b/webapp/packages/supersonic-fe/config/defaultSettings.ts @@ -13,8 +13,7 @@ const Settings: LayoutSettings & { colorWeak: false, title: '', pwa: false, - // logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', - iconfontUrl: '//at.alicdn.com/t/c/font_3201979_rncj6jun6k.js', + iconfontUrl: '//at.alicdn.com/t/c/font_3201979_drwu4z3kkbi.js', splitMenus: true, menu: { defaultOpenAll: true, diff --git a/webapp/packages/supersonic-fe/config/envConfig.ts b/webapp/packages/supersonic-fe/config/envConfig.ts new file mode 100644 index 000000000..d28529eca --- /dev/null +++ b/webapp/packages/supersonic-fe/config/envConfig.ts @@ -0,0 +1,2 @@ +const ENV_CONFIG = {}; +export default ENV_CONFIG; diff --git a/webapp/packages/supersonic-fe/package.json b/webapp/packages/supersonic-fe/package.json index 4f4f88f33..9a3620754 100644 --- a/webapp/packages/supersonic-fe/package.json +++ b/webapp/packages/supersonic-fe/package.json @@ -65,7 +65,7 @@ "@antv/layout": "^0.3.20", "@antv/xflow": "^1.0.55", "@babel/runtime": "^7.22.5", - "supersonic-chat-sdk": "^0.1.0", + "supersonic-chat-sdk": "^0.0.0", "@types/numeral": "^2.0.2", "@types/react-draft-wysiwyg": "^1.13.2", "@types/react-syntax-highlighter": "^13.5.0", @@ -144,4 +144,4 @@ "@types/react": "17.0.0" }, "__npminstall_done": false -} +} \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/src/app.tsx b/webapp/packages/supersonic-fe/src/app.tsx index 2ccfb6ed9..7954d1969 100644 --- a/webapp/packages/supersonic-fe/src/app.tsx +++ b/webapp/packages/supersonic-fe/src/app.tsx @@ -5,18 +5,12 @@ import { history } from 'umi'; import type { RunTimeLayoutConfig } from 'umi'; import RightContent from '@/components/RightContent'; import S2Icon, { ICON } from '@/components/S2Icon'; -import qs from 'qs'; import { queryCurrentUser } from './services/user'; -import { queryToken } from './services/login'; import defaultSettings from '../config/defaultSettings'; import settings from '../config/themeSettings'; -import { deleteUrlQuery } from './utils/utils'; -import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants'; export { request } from './services/request'; import { ROUTE_AUTH_CODES } from '../config/routes'; -const TOKEN_KEY = AUTH_TOKEN_KEY; - const replaceRoute = '/'; const getRuningEnv = async () => { @@ -40,25 +34,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 { RUN_TYPE, APP_TARGET } = process.env; if (RUN_TYPE === 'local') { @@ -89,12 +64,6 @@ export async function getInitialState(): Promise<{ } catch (error) {} 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(); - } const currentUser = await fetchUserInfo(); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx index 672ee9e17..67f9b991c 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx @@ -1,6 +1,6 @@ import IconFont from '@/components/IconFont'; -import { getTextWidth, groupByColumn, isMobile } from '@/utils/utils'; -import { AutoComplete, Select, Tag } from 'antd'; +import { getTextWidth, groupByColumn } from '@/utils/utils'; +import { AutoComplete, Select, Tag, Tooltip } from 'antd'; import classNames from 'classnames'; import { debounce } from 'lodash'; import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; @@ -8,13 +8,18 @@ import type { ForwardRefRenderFunction } from 'react'; import { searchRecommend } from 'supersonic-chat-sdk'; import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants'; import styles from './style.less'; -import { PLACE_HOLDER } from '@/common/constants'; +import { PLACE_HOLDER } from '../constants'; +import { DomainType } from '../type'; type Props = { inputMsg: string; chatId?: number; + currentDomain?: DomainType; + domains: DomainType[]; + isMobileMode?: boolean; onInputMsgChange: (value: string) => void; onSendMsg: (msg: string, domainId?: number) => void; + onAddConversation: () => void; }; const { OptGroup, Option } = Select; @@ -30,9 +35,19 @@ const compositionEndEvent = () => { }; const ChatFooter: ForwardRefRenderFunction = ( - { inputMsg, chatId, onInputMsgChange, onSendMsg }, + { + inputMsg, + chatId, + currentDomain, + domains, + isMobileMode, + onInputMsgChange, + onSendMsg, + onAddConversation, + }, ref, ) => { + const [domainOptions, setDomainOptions] = useState([]); const [stepOptions, setStepOptions] = useState>({}); const [open, setOpen] = useState(false); const [focused, setFocused] = useState(false); @@ -73,39 +88,61 @@ const ChatFooter: ForwardRefRenderFunction = ( }; }, []); + const getStepOptions = (recommends: any[]) => { + const data = groupByColumn(recommends, 'domainName'); + return isMobileMode && recommends.length > 6 + ? Object.keys(data) + .slice(0, 4) + .reduce((result, key) => { + result[key] = data[key].slice( + 0, + Object.keys(data).length > 2 ? 2 : Object.keys(data).length > 1 ? 3 : 6, + ); + return result; + }, {}) + : data; + }; + + const processMsg = (msg: string, domains: DomainType[]) => { + let msgValue = msg; + let domainId: number | undefined; + if (msg?.[0] === '@') { + const domain = domains.find((item) => msg.includes(`@${item.name}`)); + msgValue = domain ? msg.replace(`@${domain.name}`, '') : msg; + domainId = domain?.id; + } + return { msgValue, domainId }; + }; + const debounceGetWordsFunc = useCallback(() => { - const getAssociateWords = async (msg: string, chatId?: number) => { + const getAssociateWords = async ( + msg: string, + domains: DomainType[], + chatId?: number, + domain?: DomainType, + ) => { if (isPinyin) { return; } + if (msg === '' || (msg.length === 1 && msg[0] === '@')) { + return; + } fetchRef.current += 1; const fetchId = fetchRef.current; - const res = await searchRecommend(msg, chatId); + const { msgValue, domainId } = processMsg(msg, domains); + const res = await searchRecommend(msgValue.trim(), chatId, domainId || domain?.id); if (fetchId !== fetchRef.current) { return; } - const recommends = msg ? res.data.data || [] : []; + + const recommends = msgValue ? res.data.data || [] : []; const stepOptionList = recommends.map((item: any) => item.subRecommend); if (stepOptionList.length > 0 && stepOptionList.every((item: any) => item !== null)) { - const data = groupByColumn(recommends, 'domainName'); - const optionsData = - isMobile && recommends.length > 6 - ? Object.keys(data) - .slice(0, 4) - .reduce((result, key) => { - result[key] = data[key].slice( - 0, - Object.keys(data).length > 2 ? 2 : Object.keys(data).length > 1 ? 3 : 6, - ); - return result; - }, {}) - : data; - setStepOptions(optionsData); + setStepOptions(getStepOptions(recommends)); } else { setStepOptions({}); } - setOpen(recommends.length > 0); }; return debounce(getAssociateWords, 20); @@ -114,13 +151,27 @@ const ChatFooter: ForwardRefRenderFunction = ( const [debounceGetWords] = useState(debounceGetWordsFunc); useEffect(() => { + if (inputMsg.length === 1 && inputMsg[0] === '@') { + setOpen(true); + setDomainOptions(domains); + setStepOptions({}); + return; + } else { + setOpen(false); + if (domainOptions.length > 0) { + setTimeout(() => { + setDomainOptions([]); + }, 500); + } + } if (!isSelect) { - debounceGetWords(inputMsg, chatId); + debounceGetWords(inputMsg, domains, chatId, currentDomain); } else { isSelect = false; } if (!inputMsg) { setStepOptions({}); + fetchRef.current = 0; } }, [inputMsg]); @@ -140,6 +191,10 @@ const ChatFooter: ForwardRefRenderFunction = ( const textWidth = getTextWidth(inputMsg); if (Object.keys(stepOptions).length > 0) { autoCompleteDropdown.style.marginLeft = `${textWidth}px`; + } else { + setTimeout(() => { + autoCompleteDropdown.style.marginLeft = `0px`; + }, 200); } }, [stepOptions]); @@ -157,18 +212,20 @@ const ChatFooter: ForwardRefRenderFunction = ( if (option && isSelect) { onSendMsg(option.recommend, option.domainId); } else { - onSendMsg(value); + onSendMsg(value.trim()); } }; const autoCompleteDropdownClass = classNames(styles.autoCompleteDropdown, { - [styles.external]: true, - [styles.mobile]: isMobile, + [styles.mobile]: isMobileMode, + [styles.domainOptions]: domainOptions.length > 0, }); const onSelect = (value: string) => { isSelect = true; - sendMsg(value); + if (domainOptions.length === 0) { + sendMsg(value); + } setOpen(false); setTimeout(() => { isSelect = false; @@ -176,20 +233,31 @@ const ChatFooter: ForwardRefRenderFunction = ( }; const chatFooterClass = classNames(styles.chatFooter, { - [styles.mobile]: isMobile, + [styles.mobile]: isMobileMode, }); return (
+ + +
= ( listHeight={500} allowClear open={open} - getPopupContainer={isMobile ? (triggerNode) => triggerNode.parentNode : undefined} + getPopupContainer={(triggerNode) => triggerNode.parentNode} > - {Object.keys(stepOptions).map((key) => { - return ( - - {stepOptions[key].map((option) => ( + {domainOptions.length > 0 + ? domainOptions.map((domain) => { + return ( - ))} - - ); - })} + ); + }) + : Object.keys(stepOptions).map((key) => { + return ( + + {stepOptions[key].map((option) => { + let optionValue = + Object.keys(stepOptions).length === 1 + ? option.recommend + : `${option.domainName || ''}${option.recommend}`; + if (inputMsg[0] === '@') { + const domain = domains.find((item) => inputMsg.includes(item.name)); + optionValue = domain + ? `@${domain.name} ${option.recommend}` + : optionValue; + } + return ( + + ); + })} + + ); + })}
void; - onMsgDataLoaded: (data: MsgDataType) => void; + onMsgDataLoaded: (data: MsgDataType, questionId: string | number) => void; onSelectSuggestion: (value: string) => void; + onCheckMore: (data: MsgDataType) => void; onUpdateMessageScroll: () => void; }; @@ -22,52 +25,92 @@ const MessageContainer: React.FC = ({ id, chatId, messageList, - dispatch, + miniProgramLoading, + isMobileMode, onClickMessageContainer, onMsgDataLoaded, onSelectSuggestion, onUpdateMessageScroll, }) => { - const onWindowResize = useCallback(() => { - dispatch({ - type: 'windowResize/setTriggerResize', - payload: true, - }); + const [triggerResize, setTriggerResize] = useState(false); + + const onResize = useCallback(() => { + setTriggerResize(true); setTimeout(() => { - dispatch({ - type: 'windowResize/setTriggerResize', - payload: false, - }); + setTriggerResize(false); }, 0); }, []); useEffect(() => { - window.addEventListener('resize', onWindowResize); + window.addEventListener('resize', onResize); return () => { - window.removeEventListener('resize', onWindowResize); + window.removeEventListener('resize', onResize); }; }, []); + const messageListClass = classNames(styles.messageList, { + [styles.miniProgramLoading]: miniProgramLoading, + }); + + const getFollowQuestions = (index: number) => { + const followQuestions: string[] = []; + const currentMsg = messageList[index]; + const currentMsgData = currentMsg.msgData; + const msgs = messageList.slice(0, index).reverse(); + + for (let i = 0; i < msgs.length; i++) { + const msg = msgs[i]; + const msgDomainId = msg.msgData?.chatContext?.domainId; + const msgEntityId = msg.msgData?.entityInfo?.entityId; + const currentMsgDomainId = currentMsgData?.chatContext?.domainId; + const currentMsgEntityId = currentMsgData?.entityInfo?.entityId; + + if ( + (msg.type === MessageTypeEnum.QUESTION || msg.type === MessageTypeEnum.INSTRUCTION) && + !!currentMsgDomainId && + !!currentMsgEntityId && + msgDomainId === currentMsgDomainId && + msgEntityId === currentMsgEntityId && + msg.msg + ) { + followQuestions.push(msg.msg); + } else { + break; + } + } + return followQuestions; + }; + return (
-
+ {miniProgramLoading && } +
{messageList.map((msgItem: MessageItem, index: number) => { + const { id: msgId, domainId, type, msg, msgValue, identityMsg, msgData } = msgItem; + + const followQuestions = getFollowQuestions(index); + return ( -
- {msgItem.type === MessageTypeEnum.TEXT && } - {msgItem.type === MessageTypeEnum.QUESTION && ( +
+ {type === MessageTypeEnum.TEXT && } + {type === MessageTypeEnum.QUESTION && ( <> - + + {identityMsg && } { + onMsgDataLoaded(data, msgId); + }} onSelectSuggestion={onSelectSuggestion} onUpdateMessageScroll={onUpdateMessageScroll} - suggestionEnable /> )} @@ -80,10 +123,14 @@ const MessageContainer: React.FC = ({ }; function areEqual(prevProps: Props, nextProps: Props) { - if (prevProps.id === nextProps.id && isEqual(prevProps.messageList, nextProps.messageList)) { + if ( + prevProps.id === nextProps.id && + isEqual(prevProps.messageList, nextProps.messageList) && + prevProps.miniProgramLoading === nextProps.miniProgramLoading + ) { return true; } return false; } -export default connect()(memo(MessageContainer, areEqual)); +export default memo(MessageContainer, areEqual); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/DomainInfo.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/DomainInfo.tsx new file mode 100644 index 000000000..f4366e177 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/DomainInfo.tsx @@ -0,0 +1,22 @@ +import { DomainType } from '../../type'; +import styles from './style.less'; + +type Props = { + domain: DomainType; +}; + +const DomainInfo: React.FC = ({ domain }) => { + return ( +
+
相关信息
+
+
+ 主题域: + {domain.name} +
+
+
+ ); +}; + +export default DomainInfo; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/index.tsx index fe7860a17..1812c9e12 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/index.tsx @@ -1,13 +1,14 @@ import moment from 'moment'; import styles from './style.less'; -import type { ChatContextType } from 'supersonic-chat-sdk'; +import type { ChatContextType, EntityInfoType } from 'supersonic-chat-sdk'; type Props = { chatContext: ChatContextType; + entityInfo?: EntityInfoType; }; -const Context: React.FC = ({ chatContext }) => { - const { domainName, metrics, dateInfo, filters } = chatContext; +const Context: React.FC = ({ chatContext, entityInfo }) => { + const { domainName, metrics, dateInfo, dimensionFilters } = chatContext; return (
@@ -17,17 +18,15 @@ const Context: React.FC = ({ chatContext }) => { 主题域: {domainName}
- { - dateInfo && ( -
- 时间范围: - - {dateInfo.text || - `近${moment(dateInfo.endDate).diff(moment(dateInfo.startDate), 'days') + 1}天`} - -
- ) - } + {dateInfo && ( +
+ 时间范围: + + {dateInfo.text || + `近${moment(dateInfo.endDate).diff(moment(dateInfo.startDate), 'days') + 1}天`} + +
+ )} {metrics && metrics.length > 0 && (
指标: @@ -36,20 +35,22 @@ const Context: React.FC = ({ chatContext }) => {
)} - {filters && filters.length > 0 && ( -
-
筛选条件:
-
- {filters.map((filter) => { - return ( -
- {filter.name}:{filter.value} -
- ); - })} + {dimensionFilters && + dimensionFilters.length > 0 && + !(entityInfo?.dimensions && entityInfo.dimensions.length > 0) && ( +
+
筛选条件:
+
+ {dimensionFilters.map((filter) => { + return ( +
+ {filter.name}:{filter.value} +
+ ); + })} +
-
- )} + )}
); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/style.less index ae8a95e35..b0121bc0d 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Context/style.less @@ -1,6 +1,8 @@ .context { display: flex; flex-direction: column; + padding: 20px 10px 0; + border-top: 1px solid #ccc; .title { margin-bottom: 22px; @@ -45,11 +47,11 @@ } .fieldValue { + max-width: 150px; + overflow: hidden; color: var(--text-color); - - &.switchField { - cursor: pointer; - } + white-space: nowrap; + text-overflow: ellipsis; } .filterValues { diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx similarity index 95% rename from webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/index.tsx rename to webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx index 126607209..fad3e774c 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx @@ -1,6 +1,6 @@ import { CloseOutlined } from '@ant-design/icons'; import moment from 'moment'; -import type { ConversationDetailType } from '../../type'; +import type { ConversationDetailType } from '../../../type'; import styles from './style.less'; type Props = { diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less similarity index 96% rename from webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/style.less rename to webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less index f98e4de05..29876f5d9 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationHistory/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less @@ -5,8 +5,8 @@ z-index: 10; display: flex; flex-direction: column; - width: 215px; - height: calc(100vh - 48px); + width: 100%; + height: calc(100vh - 78px); overflow: hidden; background: #f3f3f7; border-right: 1px solid var(--border-color-base); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationModal/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationModal/index.tsx similarity index 84% rename from webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationModal/index.tsx rename to webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationModal/index.tsx index 78bd7ccae..3a2cb2275 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/ConversationModal/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationModal/index.tsx @@ -1,7 +1,8 @@ import { Form, Input, Modal } from 'antd'; import { useEffect, useRef, useState } from 'react'; -import { updateConversationName } from '../../service'; -import type { ConversationDetailType } from '../../type'; +import { updateConversationName } from '../../../service'; +import type { ConversationDetailType } from '../../../type'; +import { CHAT_TITLE } from '../../../constants'; const FormItem = Form.Item; @@ -43,7 +44,7 @@ const ConversationModal: React.FC = ({ visible, editConversation, onClose return ( = ({ visible, editConversation, onClose
diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/Conversation.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx similarity index 81% rename from webapp/packages/supersonic-fe/src/pages/Chat/Conversation.tsx rename to webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx index 30d2fc18a..15c91f4b7 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/Conversation.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx @@ -1,5 +1,5 @@ import IconFont from '@/components/IconFont'; -import { Dropdown, Menu, message } from 'antd'; +import { Dropdown, Menu } from 'antd'; import classNames from 'classnames'; import { useEffect, @@ -9,11 +9,12 @@ import { useImperativeHandle, } from 'react'; import { useLocation } from 'umi'; -import ConversationHistory from './components/ConversationHistory'; -import ConversationModal from './components/ConversationModal'; -import { deleteConversation, getAllConversations, saveConversation } from './service'; +import ConversationHistory from './ConversationHistory'; +import ConversationModal from './ConversationModal'; +import { deleteConversation, getAllConversations, saveConversation } from '../../service'; import styles from './style.less'; -import { ConversationDetailType } from './type'; +import { ConversationDetailType } from '../../type'; +import { DEFAULT_CONVERSATION_NAME } from '../../constants'; type Props = { currentConversation?: ConversationDetailType; @@ -65,7 +66,7 @@ const Conversation: ForwardRefRenderFunction = ( }; useEffect(() => { - if (q && cid === undefined) { + if (q && cid === undefined && location.pathname === '/workbench/chat') { onAddConversation(q); } else { initData(); @@ -73,7 +74,7 @@ const Conversation: ForwardRefRenderFunction = ( }, [q]); const addConversation = async (name?: string) => { - await saveConversation(name || '新问答对话'); + await saveConversation(name || DEFAULT_CONVERSATION_NAME); return updateData(); }; @@ -96,21 +97,14 @@ const Conversation: ForwardRefRenderFunction = ( } }; - const onNewChat = () => { - onAddConversation('新问答对话'); - }; - const onShowHistory = () => { setHistoryVisible(true); }; - const onShare = () => { - message.info('正在开发中,敬请期待'); - }; - return (
-
+
+
对话管理
{conversations.map((item) => { const conversationItemClass = classNames(styles.conversationItem, { @@ -133,7 +127,6 @@ const Conversation: ForwardRefRenderFunction = ( trigger={['contextMenu']} >
{ onSelectConversation(item); @@ -159,19 +152,6 @@ const Conversation: ForwardRefRenderFunction = (
-
-
- -
新建对话
-
-
- -
分享
-
-
{historyVisible && ( void; +}; + +const Domains: React.FC = ({ domains, currentDomain, onSelectDomain }) => { + return ( +
+
+
主题列表
+
(可在输入框@)
+
+
+ {domains + .filter((domain) => domain.id !== -1) + .map((domain) => { + const domainItemClass = classNames(styles.domainItem, { + [styles.activeDomainItem]: currentDomain?.id === domain.id, + }); + return ( +
+
{ + onSelectDomain(domain); + }} + > + {/* */} +
{domain.name}
+
+
+ ); + })} +
+
+ ); +}; + +export default Domains; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Domains/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Domains/style.less new file mode 100644 index 000000000..92fd3441f --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Domains/style.less @@ -0,0 +1,70 @@ +.domains { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #ccc; + + .titleBar { + display: flex; + align-items: center; + column-gap: 4px; + margin-bottom: 12px; + + .title { + padding-left: 10px; + color: var(--text-color); + font-size: 16px; + line-height: 24px; + } + + .subTitle { + font-size: 13px; + color: var(--text-color-third); + } + } + + .domainList { + display: flex; + flex-direction: column; + + .domainItem { + display: flex; + align-items: center; + padding: 4px 10px; + font-size: 14px; + cursor: pointer; + + .loadingIcon { + margin-right: 6px; + color: var(--text-color-fifth); + font-size: 12px; + } + + .arrowIcon { + margin-right: 6px; + color: var(--text-color-fifth); + font-size: 12px; + } + + .domainIcon { + margin-right: 6px; + color: var(--blue); + } + + .domainName { + width: 150px; + overflow: hidden; + color: var(--text-color-secondary); + white-space: nowrap; + text-overflow: ellipsis; + } + + &:hover { + background-color: var(--link-hover-bg-color); + } + + &.activeDomainItem { + background-color: var(--link-hover-bg-color); + } + } + } +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/index.tsx index 264f0eac4..783c5d172 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/index.tsx @@ -20,11 +20,15 @@ const Introduction: React.FC = ({ currentEntity }) => { return (
{dimension.name}: - - {dimension.bizName.includes('publish_time') - ? moment(dimension.value).format('YYYY-MM-DD') - : dimension.value} - + {dimension.bizName.includes('photo') ? ( + + ) : ( + + {dimension.bizName.includes('publish_time') + ? moment(dimension.value).format('YYYY-MM-DD') + : dimension.value} + + )}
); })} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/style.less index 0c2ecd439..9126093c1 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Introduction/style.less @@ -1,7 +1,7 @@ .introduction { display: flex; flex-direction: column; - padding-bottom: 4px; + padding: 0 10px 4px; .title { margin-bottom: 22px; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx index b441a0931..cea054fbe 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx @@ -3,24 +3,55 @@ import Context from './Context'; import Introduction from './Introduction'; import styles from './style.less'; import type { MsgDataType } from 'supersonic-chat-sdk'; +import Domains from './Domains'; +import { ConversationDetailType, DomainType } from '../type'; +import DomainInfo from './Context/DomainInfo'; +import Conversation from './Conversation'; type Props = { + domains: DomainType[]; currentEntity?: MsgDataType; + currentConversation?: ConversationDetailType; + currentDomain?: DomainType; + conversationRef: any; + onSelectConversation: (conversation: ConversationDetailType, name?: string) => void; + onSelectDomain: (domain: DomainType) => void; }; -const RightSection: React.FC = ({ currentEntity }) => { +const RightSection: React.FC = ({ + domains, + currentEntity, + currentDomain, + currentConversation, + conversationRef, + onSelectConversation, + onSelectDomain, +}) => { const rightSectionClass = classNames(styles.rightSection, { - [styles.external]: true, + [styles.external]: false, }); return (
- {currentEntity && ( + + {currentDomain && !currentEntity && (
- {currentEntity?.chatContext && } + +
+ )} + {!!currentEntity?.chatContext?.domainId && ( +
+
)} + {domains && domains.length > 0 && ( + + )}
); }; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/style.less index 0f9702475..40adbf630 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/style.less @@ -1,13 +1,12 @@ .rightSection { width: 225px; height: calc(100vh - 48px); - padding-right: 10px; + margin-right: 12px; padding-bottom: 10px; - padding-left: 20px; overflow-y: auto; .entityInfo { - margin-top: 30px; + margin-top: 20px; .topInfo { margin-bottom: 20px; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/index.tsx new file mode 100644 index 000000000..ebd93ee72 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/index.tsx @@ -0,0 +1,8 @@ +import IconFont from '@/components/IconFont'; +import styles from './style.less'; + +const LeftAvatar = () => { + return ; +}; + +export default LeftAvatar; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/style.less new file mode 100644 index 000000000..b6842f859 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/LeftAvatar/style.less @@ -0,0 +1,13 @@ +.leftAvatar { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + margin-right: 6px; + margin-right: 6px; + color: var(--chat-blue); + font-size: 40px; + background-color: #fff; + border-radius: 50%; +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/Message.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/components/Message.tsx index fae962c11..1582ccfc9 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/Message.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/Message.tsx @@ -3,27 +3,52 @@ import styles from './style.less'; type Props = { position: 'left' | 'right'; + width?: number | string; + height?: number | string; bubbleClassName?: string; - aggregator?: string; - noTime?: boolean; + domainName?: string; + question?: string; + followQuestions?: string[]; }; -const Message: React.FC = ({ position, children, bubbleClassName }) => { +const Message: React.FC = ({ + position, + width, + height, + children, + bubbleClassName, + domainName, + question, + followQuestions, +}) => { const messageClass = classNames(styles.message, { [styles.left]: position === 'left', [styles.right]: position === 'right', }); + const leftTitle = question + ? followQuestions && followQuestions.length > 0 + ? `多轮对话:${[question, ...followQuestions].join(' ← ')}` + : `单轮对话:${question}` + : ''; + return ( -
+
+ {!!domainName &&
{domainName}
}
{ e.stopPropagation(); }} > + {position === 'left' && question && ( +
+ {leftTitle} +
+ )} {children}
diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/Text.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/components/Text.tsx index a6026af65..5d0f5fa6c 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/Text.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/Text.tsx @@ -1,3 +1,5 @@ +import classNames from 'classnames'; +import LeftAvatar from './LeftAvatar'; import Message from './Message'; import styles from './style.less'; @@ -8,11 +10,17 @@ type Props = { }; const Text: React.FC = ({ position, data, quote }) => { + const textWrapperClass = classNames(styles.textWrapper, { + [styles.rightTextWrapper]: position === 'right', + }); return ( - - {position === 'right' && quote &&
{quote}
} -
{data}
-
+
+ {position === 'left' && } + + {position === 'right' && quote &&
{quote}
} +
{data}
+
+
); }; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less index b855ea3c0..0fc5f2dac 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less @@ -1,10 +1,28 @@ .message { + .domainName { + margin-bottom: 2px; + margin-left: 4px; + color: var(--text-color); + font-weight: 500; + } .messageContent { display: flex; align-items: flex-start; .messageBody { width: 100%; + + .messageTopBar { + max-width: 90%; + margin: 0 16px; + padding: 12px 0 8px; + overflow: hidden; + color: var(--text-color-third); + font-size: 13px; + white-space: nowrap; + text-overflow: ellipsis; + background-color: #fff; + } } .avatar { @@ -73,7 +91,6 @@ font-size: 16px; background: linear-gradient(81.62deg, #2870ea 8.72%, var(--chat-blue) 85.01%); border: 1px solid transparent; - border-radius: 12px 4px 12px 12px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12); .text { @@ -275,3 +292,16 @@ } } } + +.textWrapper { + display: flex; + align-items: center; + + &.rightTextWrapper { + justify-content: flex-end; + } + + .rightAvatar { + margin-left: 6px; + } +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/constants.ts b/webapp/packages/supersonic-fe/src/pages/Chat/constants.ts index fb56e5ddf..142c44a2f 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/constants.ts +++ b/webapp/packages/supersonic-fe/src/pages/Chat/constants.ts @@ -26,3 +26,11 @@ export const SEMANTIC_TYPE_MAP = { [SemanticTypeEnum.METRIC]: '指标', [SemanticTypeEnum.VALUE]: '维度值', }; + +export const DEFAULT_CONVERSATION_NAME = '新问答对话' + +export const WEB_TITLE = '问答对话' + +export const CHAT_TITLE = '问答' + +export const PLACE_HOLDER = '请输入您的问题' diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx index 41189b8d3..f12c15f38 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx @@ -1,27 +1,28 @@ -import { updateMessageContainerScroll, isMobile, uuid } from '@/utils/utils'; +import { updateMessageContainerScroll, isMobile, uuid, getLeafList } from '@/utils/utils'; import { useEffect, useRef, useState } from 'react'; import { Helmet } from 'umi'; import MessageContainer from './MessageContainer'; import styles from './style.less'; -import { ConversationDetailType, MessageItem, MessageTypeEnum } from './type'; -import { updateConversationName } from './service'; +import { ConversationDetailType, DomainType, MessageItem, MessageTypeEnum } from './type'; +import { getDomainList, updateConversationName } from './service'; import { useThrottleFn } from 'ahooks'; -import Conversation from './Conversation'; import RightSection from './RightSection'; import ChatFooter from './ChatFooter'; import classNames from 'classnames'; -import { AUTH_TOKEN_KEY, DEFAULT_CONVERSATION_NAME, WEB_TITLE } from '@/common/constants'; -import { - HistoryMsgItemType, - MsgDataType, - getHistoryMsg, - queryContext, - setToken as setChatSdkToken, -} from 'supersonic-chat-sdk'; -import { getConversationContext } from './utils'; +import { CHAT_TITLE, DEFAULT_CONVERSATION_NAME, WEB_TITLE } from './constants'; +import { cloneDeep } from 'lodash'; +import { HistoryMsgItemType, MsgDataType, getHistoryMsg } from 'supersonic-chat-sdk'; import 'supersonic-chat-sdk/dist/index.css'; +import { setToken as setChatSdkToken } from 'supersonic-chat-sdk'; +import { TOKEN_KEY } from '@/services/request'; + +type Props = { + isCopilotMode?: boolean; +}; + +const Chat: React.FC = ({ isCopilotMode }) => { + const isMobileMode = (isMobile || isCopilotMode) as boolean; -const Chat = () => { const [messageList, setMessageList] = useState([]); const [inputMsg, setInputMsg] = useState(''); const [pageNo, setPageNo] = useState(1); @@ -29,15 +30,14 @@ const Chat = () => { const [historyInited, setHistoryInited] = useState(false); const [currentConversation, setCurrentConversation] = useState< ConversationDetailType | undefined - >(isMobile ? { chatId: 0, chatName: '问答对话' } : undefined); + >(isMobile ? { chatId: 0, chatName: `${CHAT_TITLE}问答` } : undefined); const [currentEntity, setCurrentEntity] = useState(); + const [miniProgramLoading, setMiniProgramLoading] = useState(false); + const [domains, setDomains] = useState([]); + const [currentDomain, setCurrentDomain] = useState(); const conversationRef = useRef(); const chatFooterRef = useRef(); - useEffect(() => { - setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || ''); - }, []); - const sendHelloRsp = () => { setMessageList([ { @@ -48,15 +48,35 @@ const Chat = () => { ]); }; + const existInstuctionMsg = (list: HistoryMsgItemType[]) => { + return list.some((msg) => msg.queryResponse.queryMode === MessageTypeEnum.INSTRUCTION); + }; + + const updateScroll = (list: HistoryMsgItemType[]) => { + if (existInstuctionMsg(list)) { + setMiniProgramLoading(true); + setTimeout(() => { + setMiniProgramLoading(false); + updateMessageContainerScroll(); + }, 3000); + } else { + updateMessageContainerScroll(); + } + }; + const updateHistoryMsg = async (page: number) => { - const res = await getHistoryMsg(page, currentConversation!.chatId); - const { hasNextPage, list } = res.data.data; + const res = await getHistoryMsg(page, currentConversation!.chatId, 3); + const { hasNextPage, list } = res.data?.data || { hasNextPage: false, list: [] }; setMessageList([ ...list.map((item: HistoryMsgItemType) => ({ id: item.questionId, - type: MessageTypeEnum.QUESTION, + type: + item.queryResponse?.queryMode === MessageTypeEnum.INSTRUCTION + ? MessageTypeEnum.INSTRUCTION + : MessageTypeEnum.QUESTION, msg: item.queryText, msgData: item.queryResponse, + isHistory: true, })), ...(page === 1 ? [] : messageList), ]); @@ -67,8 +87,9 @@ const Chat = () => { } else { setCurrentEntity(list[list.length - 1].queryResponse); } - updateMessageContainerScroll(); + updateScroll(list); setHistoryInited(true); + inputFocus(); } if (page > 1) { const msgEle = document.getElementById(`${messageList[0]?.id}`); @@ -90,6 +111,21 @@ const Chat = () => { }, ); + const initDomains = async () => { + try { + const res = await getDomainList(); + const domainList = getLeafList(res.data); + setDomains( + [{ id: -1, name: '全部', bizName: 'all', parentId: 0 }, ...domainList].slice(0, 11), + ); + } catch (e) {} + }; + + useEffect(() => { + setChatSdkToken(localStorage.getItem(TOKEN_KEY) || ''); + initDomains(); + }, []); + useEffect(() => { if (historyInited) { const messageContainerEle = document.getElementById('messageContainer'); @@ -123,7 +159,7 @@ const Chat = () => { sendHelloRsp(); return; } - onSendMsg(currentConversation.initMsg, [], domainId, true); + onSendMsg(currentConversation.initMsg, [], domainId); return; } updateHistoryMsg(1); @@ -132,32 +168,36 @@ const Chat = () => { const modifyConversationName = async (name: string) => { await updateConversationName(name, currentConversation!.chatId); - conversationRef?.current?.updateData(); - window.history.replaceState('', '', `?q=${name}&cid=${currentConversation!.chatId}`); + if (!isMobileMode) { + conversationRef?.current?.updateData(); + window.history.replaceState('', '', `?q=${name}&cid=${currentConversation!.chatId}`); + } }; - const onSendMsg = async ( - msg?: string, - list?: MessageItem[], - domainId?: number, - firstMsg?: boolean, - ) => { + const onSendMsg = async (msg?: string, list?: MessageItem[], domainId?: number) => { const currentMsg = msg || inputMsg; if (currentMsg.trim() === '') { setInputMsg(''); return; } - let quote = ''; - if (currentEntity && !firstMsg) { - const { data } = await queryContext(currentMsg, currentConversation!.chatId); - if (data.code === 200 && data.data.domainId === currentEntity.chatContext?.domainId) { - quote = getConversationContext(data.data); - } + const msgDomain = domains.find((item) => currentMsg.includes(item.name)); + const certainDomain = currentMsg[0] === '@' && msgDomain; + if (certainDomain) { + setCurrentDomain(msgDomain.id === -1 ? undefined : msgDomain); } - setMessageList([ + const domainIdValue = domainId || msgDomain?.id || currentDomain?.id; + const msgs = [ ...(list || messageList), - { id: uuid(), msg: currentMsg, domainId, type: MessageTypeEnum.QUESTION, quote }, - ]); + { + id: uuid(), + msg: currentMsg, + msgValue: certainDomain ? currentMsg.replace(`@${msgDomain.name}`, '').trim() : currentMsg, + domainId: domainIdValue === -1 ? undefined : domainIdValue, + identityMsg: certainDomain ? getIdentityMsgText(msgDomain) : undefined, + type: MessageTypeEnum.QUESTION, + }, + ]; + setMessageList(msgs); updateMessageContainerScroll(); setInputMsg(''); modifyConversationName(currentMsg); @@ -179,36 +219,89 @@ const Chat = () => { }; const onSelectConversation = (conversation: ConversationDetailType, name?: string) => { - window.history.replaceState('', '', `?q=${conversation.chatName}&cid=${conversation.chatId}`); + if (!isMobileMode) { + window.history.replaceState('', '', `?q=${conversation.chatName}&cid=${conversation.chatId}`); + } setCurrentConversation({ ...conversation, initMsg: name, }); saveConversationToLocal(conversation); + setCurrentDomain(undefined); }; - const onMsgDataLoaded = (data: MsgDataType) => { + const onMsgDataLoaded = (data: MsgDataType, questionId: string | number) => { + if (!data) { + return; + } + if (data.queryMode === 'INSTRUCTION') { + setMessageList([ + ...messageList.slice(0, messageList.length - 1), + { + id: uuid(), + msg: data.response.name || messageList[messageList.length - 1]?.msg, + type: MessageTypeEnum.INSTRUCTION, + msgData: data, + }, + ]); + } else { + const msgs = cloneDeep(messageList); + const msg = msgs.find((item) => item.id === questionId); + if (msg) { + msg.msgData = data; + setMessageList(msgs); + } + updateMessageContainerScroll(); + } setCurrentEntity(data); + }; + + const onCheckMore = (data: MsgDataType) => { + setMessageList([ + ...messageList, + { + id: uuid(), + msg: data.response.name, + type: MessageTypeEnum.INSTRUCTION, + msgData: data, + }, + ]); updateMessageContainerScroll(); }; + const getIdentityMsgText = (domain?: DomainType) => { + return domain + ? `您好,我当前身份是【${domain.name}】主题专家,我将尽力帮您解答相关问题~` + : '您好,我将尽力帮您解答所有主题相关问题~'; + }; + + const getIdentityMsg = (domain?: DomainType) => { + return { + id: uuid(), + type: MessageTypeEnum.TEXT, + msg: getIdentityMsgText(domain), + }; + }; + + const onSelectDomain = (domain: DomainType) => { + const domainValue = currentDomain?.id === domain.id ? undefined : domain; + setCurrentDomain(domainValue); + setCurrentEntity(undefined); + setMessageList([...messageList, getIdentityMsg(domainValue)]); + updateMessageContainerScroll(); + inputFocus(); + }; + const chatClass = classNames(styles.chat, { - [styles.external]: true, - [styles.mobile]: isMobile, + [styles.mobile]: isMobileMode, + [styles.copilot]: isCopilotMode, }); return (
- + {!isMobileMode && }
- {!isMobile && ( - - )}
{currentConversation && (
@@ -217,16 +310,22 @@ const Chat = () => { id="messageContainer" messageList={messageList} chatId={currentConversation?.chatId} + miniProgramLoading={miniProgramLoading} + isMobileMode={isMobileMode} onClickMessageContainer={() => { inputFocus(); }} onMsgDataLoaded={onMsgDataLoaded} onSelectSuggestion={onSendMsg} + onCheckMore={onCheckMore} onUpdateMessageScroll={updateMessageContainerScroll} /> { onSendMsg(msg, messageList, domainId); @@ -234,13 +333,27 @@ const Chat = () => { inputBlur(); } }} + onAddConversation={() => { + conversationRef.current?.onAddConversation(); + inputFocus(); + }} ref={chatFooterRef} />
)}
- {!isMobile && } + {!isMobileMode && ( + + )}
); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/service.ts b/webapp/packages/supersonic-fe/src/pages/Chat/service.ts index 65458bb31..3874e8b3b 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/Chat/service.ts @@ -1,9 +1,12 @@ import { request } from 'umi'; +import { DomainType } from './type'; const prefix = '/api'; export function saveConversation(chatName: string) { - return request>(`${prefix}/chat/manage/save?chatName=${chatName}`, { method: 'POST' }); + return request>(`${prefix}/chat/manage/save?chatName=${chatName}`, { + method: 'POST', + }); } export function updateConversationName(chatName: string, chatId: number = 0) { @@ -20,3 +23,17 @@ export function deleteConversation(chatId: number) { export function getAllConversations() { return request>(`${prefix}/chat/manage/getAll`); } + +export function getMiniProgramList(id: string, type: string) { + return request>(`/openapi/bd-bi/api/polaris/sql/getInterpretList/${id}/${type}`, { + method: 'GET', + skipErrorHandler: true, + }); +} + +export function getDomainList() { + return request>(`${prefix}/semantic/domain/getDomainList`, { + method: 'GET', + skipErrorHandler: true, + }); +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/style.less index 739ee3314..b972e9357 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/style.less @@ -1,167 +1,258 @@ +@import '~antd/es/style/themes/default.less'; + .chat { height: calc(100vh - 48px) !important; - overflow-y: hidden; + overflow: hidden; background: linear-gradient(180deg, rgba(23, 74, 228, 0) 29.44%, rgba(23, 74, 228, 0.06) 100%), linear-gradient(90deg, #f3f3f7 0%, #f3f3f7 20%, #ebf0f9 60%, #f3f3f7 80%, #f3f3f7 100%); - &.external { - .chatApp { - width: calc(100vw - 450px) !important; - height: calc(100vh - 58px) !important; + .chatSection { + display: flex; + width: 100vw !important; + height: calc(100vh - 48px) !important; + overflow: hidden; + } + + .chatApp { + display: flex; + flex-direction: column; + width: calc(100vw - 225px); + height: calc(100vh - 48px); + padding-left: 20px; + color: rgba(0, 0, 0, 0.87); + + .emptyHolder { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + } + + .navBar { + position: relative; + z-index: 10; + display: flex; + align-items: center; + height: 40px; + padding: 0 10px; + background: rgb(243 243 243); + border-bottom: 1px solid rgb(228, 228, 228); + + .conversationNameWrapper { + display: flex; + align-items: center; + + .conversationName { + padding: 4px 12px; + color: var(--text-color-third) !important; + font-size: 14px !important; + border-radius: 4px; + cursor: pointer; + + .editIcon { + margin-left: 10px; + color: var(--text-color-fourth); + font-size: 14px; + } + + &:hover { + background-color: rgba(0, 0, 0, 0.03); + } + } + + .divider { + width: 1px; + height: 16px; + margin-right: 4px; + margin-left: 12px; + background-color: var(--text-color-fourth); + } + } + + .conversationInput { + width: 300px; + color: var(--text-color-third) !important; + font-size: 14px !important; + cursor: default !important; + } + } + + .chatBody { + display: flex; + flex: 1; + height: 100%; + + .chatContent { + display: flex; + flex-direction: column; + width: 100%; + + .messageContainer { + position: relative; + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + + .messageList { + display: flex; + flex-direction: column; + padding: 20px 20px 90px 4px; + row-gap: 10px; + + .messageItem { + display: flex; + flex-direction: column; + row-gap: 10px; + + :global { + .ant-table-row { + background-color: #fff; + } + + .ant-table-tbody > tr > td { + border-bottom: 1px solid #f0f0f0; + transition: background 0.2s, border-color 0.2s; + } + + .ss-chat-table-even-row { + background-color: #fbfbfb; + } + + .ant-table-wrapper .ant-table-pagination { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin: 16px 0; + row-gap: 8px; + } + + .ant-pagination .ant-pagination-prev, + .ant-pagination .ant-pagination-next { + display: inline-block; + min-width: 32px; + height: 32px; + color: rgba(0, 0, 0, 0.88); + line-height: 32px; + text-align: center; + vertical-align: middle; + list-style: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + + .ant-pagination-item-link { + display: block; + width: 100%; + height: 100%; + padding: 0; + font-size: 12px; + text-align: center; + background-color: transparent; + border: 1px solid transparent; + border-radius: 6px; + outline: none; + transition: border 0.2s; + } + } + + .ant-pagination-jump-prev, + .ant-pagination-jump-next { + .ant-pagination-item-link { + display: inline-block; + min-width: 32px; + height: 32px; + color: rgba(0, 0, 0, 0.25); + line-height: 32px; + text-align: center; + vertical-align: middle; + list-style: none; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + } + } + + .ant-pagination-options { + display: inline-block; + margin-left: 16px; + vertical-align: middle; + } + + .ant-pagination .ant-pagination-item { + display: inline-block; + min-width: 32px; + height: 32px; + line-height: 30px; + text-align: center; + vertical-align: middle; + list-style: none; + background-color: transparent; + border: 1px solid transparent; + border-radius: 6px; + outline: 0; + cursor: pointer; + user-select: none; + margin-inline-end: 8px; + } + + .ant-pagination .ant-pagination-item-active { + font-weight: 600; + background-color: #ffffff; + border-color: var(--primary-color); + } + + .ant-pagination { + box-sizing: border-box; + margin: 0; + padding: 0; + color: #606266; + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5715; + list-style: none; + font-feature-settings: 'tnum', 'tnum'; + } + } + } + + &.miniProgramLoading { + position: absolute; + bottom: 10000px; + width: 100%; + } + } + } + } } } &.mobile { - height: 100vh !important; + height: 100% !important; .chatSection { - // height: 100vh !important; + width: 100% !important; height: 100% !important; } .conversation { - // height: 100vh !important; height: 100% !important; } .chatApp { - width: 100vw !important; - // height: 100vh !important; + width: calc(100% - 225px) !important; height: 100% !important; - } - } -} - -.chatSection { - display: flex; - height: calc(100vh - 48px) !important; - overflow-y: hidden; -} - -.chatBody { - height: 100%; -} - -.conversation { - position: relative; - width: 225px; - height: calc(100vh - 48px); - - .leftSection { - width: 100%; - height: 100%; - } -} - -.chatApp { - display: flex; - flex-direction: column; - width: calc(100vw - 510px); - height: calc(100vh - 58px) !important; - margin-top: 10px; - color: rgba(0, 0, 0, 0.87); - - .emptyHolder { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - } - - .navBar { - position: relative; - z-index: 10; - display: flex; - align-items: center; - height: 40px; - padding: 0 10px; - background: rgb(243 243 243); - border-bottom: 1px solid rgb(228, 228, 228); - - .conversationNameWrapper { - display: flex; - align-items: center; - - .conversationName { - padding: 4px 12px; - color: var(--text-color-third) !important; - font-size: 14px !important; - border-radius: 4px; - cursor: pointer; - - .editIcon { - margin-left: 10px; - color: var(--text-color-fourth); - font-size: 14px; - } - - &:hover { - background-color: rgba(0, 0, 0, 0.03); - } - } - - .divider { - width: 1px; - height: 16px; - margin-right: 4px; - margin-left: 12px; - background-color: var(--text-color-fourth); - } - } - - .conversationInput { - width: 300px; - color: var(--text-color-third) !important; - font-size: 14px !important; - cursor: default !important; - } - } - - .chatBody { - display: flex; - flex: 1; - - .chatContent { - display: flex; - flex-direction: column; - width: 100%; - - .messageContainer { - position: relative; - display: flex; - flex: 1; - flex-direction: column; - min-height: 0; - overflow-x: hidden; - overflow-y: scroll; - - .messageList { - display: flex; - flex-direction: column; - padding: 0 20px 90px 4px; - row-gap: 20px; - - .messageItem { - display: flex; - flex-direction: column; - row-gap: 20px; - } - - &.reportLoading { - position: absolute; - bottom: 10000px; - width: 100%; - } - } - } + margin-top: 0 !important; } } } .mobile { .messageList { - padding: 0 12px 20px !important; + padding: 20px 12px 20px !important; } } @@ -235,7 +326,7 @@ } :global { - button[ant-click-animating-without-extra-node]::after { + button[ant-click-animating-without-extra-node]:after { border: 0 none; opacity: 0; animation: none 0 ease 0 1 normal; @@ -358,42 +449,6 @@ } } -.conversationList { - padding-top: 20px; - .conversationItem { - padding-left: 16px; - cursor: pointer; - - .conversationItemContent { - display: flex; - align-items: center; - padding: 12px 0; - color: var(--text-color-third); - - .conversationIcon { - margin-right: 10px; - color: var(--text-color-fourth); - font-size: 20px; - } - - .conversationContent { - width: 160px; - overflow: hidden; - color: var(--text-color-third); - white-space: nowrap; - text-overflow: ellipsis; - } - } - - &.activeConversationItem, - &:hover { - .conversationContent { - color: var(--chat-blue); - } - } - } -} - .addConversation { display: flex; align-items: center; @@ -437,17 +492,6 @@ } } -.collapseBtn { - margin: 0 10px; - color: var(--text-color-third); - font-size: 16px; - cursor: pointer; - - &:hover { - color: var(--primary-color); - } -} - .autoCompleteDropdown { width: 650px !important; min-width: 650px !important; @@ -462,10 +506,6 @@ background: #f5f5f5 !important; } } - - // .ant-select-item-option-active:not(.ant-select-item-option-disabled) { - // background-color: #fff; - // } } } @@ -545,35 +585,20 @@ font-size: 12px; } -.operateSection { - margin-top: 20px; - padding-left: 15px; +.messageLoading { + margin-top: 30px; + padding: 0 20px; } -.operateItem { - display: flex; - align-items: center; - padding: 10px 0; - cursor: pointer; +:global { + .ss-chat-recommend-options { + .ant-table-thead .ant-table-cell { + padding: 8px !important; + } - .operateIcon { - margin-right: 10px; - color: var(--text-color-fourth); - font-size: 20px; - } - - .operateLabel { - color: var(--text-color-third); - font-size: 14px; - } - - &:hover { - .operateLabel { - color: var(--chat-blue); + .ant-table-tbody .ant-table-cell { + padding: 8px !important; + border-bottom: 1px solid #f0f0f0; } } } - -.messageLoading { - margin-top: 30px; -} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/type.ts b/webapp/packages/supersonic-fe/src/pages/Chat/type.ts index 2ed7dc6cb..975098d17 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/type.ts +++ b/webapp/packages/supersonic-fe/src/pages/Chat/type.ts @@ -3,19 +3,21 @@ import { MsgDataType } from 'supersonic-chat-sdk'; export enum MessageTypeEnum { TEXT = 'text', // 指标文本 QUESTION = 'question', - TAG = 'tag', // 标签 - SUGGESTION = 'suggestion', // 建议 NO_PERMISSION = 'no_permission', // 无权限 SEMANTIC_DETAIL = 'semantic_detail', // 语义指标/维度等信息详情 + INSTRUCTION = 'INSTRUCTION', // 插件 } export type MessageItem = { id: string | number; type?: MessageTypeEnum; msg?: string; + msgValue?: string; + identityMsg?: string; domainId?: number; msgData?: MsgDataType; quote?: string; + isHistory?: boolean; }; export type ConversationDetailType = { @@ -32,3 +34,10 @@ export type ConversationDetailType = { export enum MessageModeEnum { INTERPRET = 'interpret', } + +export type DomainType = { + id: number; + parentId: number; + name: string; + bizName: string; +}; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/utils.ts b/webapp/packages/supersonic-fe/src/pages/Chat/utils.ts deleted file mode 100644 index 1de58d3f3..000000000 --- a/webapp/packages/supersonic-fe/src/pages/Chat/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ChatContextType } from 'supersonic-chat-sdk'; -import moment from 'moment'; - -export function getConversationContext(chatContext: ChatContextType) { - if (!chatContext) return ''; - const { domainName, metrics, dateInfo } = chatContext; - // const dimensionStr = - // dimensions?.length > 0 ? dimensions.map((dimension) => dimension.name).join('、') : ''; - const timeStr = - dateInfo?.text || - `近${moment(dateInfo?.endDate).diff(moment(dateInfo?.startDate), 'days') + 1}天`; - - return `${domainName}${ - metrics?.length > 0 ? `${timeStr}${metrics.map((metric) => metric.name).join('、')}` : '' - }`; -} diff --git a/webapp/packages/supersonic-fe/src/services/request.ts b/webapp/packages/supersonic-fe/src/services/request.ts index ddba11cab..ce3588267 100644 --- a/webapp/packages/supersonic-fe/src/services/request.ts +++ b/webapp/packages/supersonic-fe/src/services/request.ts @@ -1,4 +1,3 @@ -import { FROM_URL_KEY } from '@/common/constants'; import type { RequestOptionsInit, RequestOptionsWithoutResponse, diff --git a/webapp/packages/supersonic-fe/src/utils/utils.ts b/webapp/packages/supersonic-fe/src/utils/utils.ts index 99ab664b9..7165aaae7 100644 --- a/webapp/packages/supersonic-fe/src/utils/utils.ts +++ b/webapp/packages/supersonic-fe/src/utils/utils.ts @@ -343,3 +343,48 @@ export const getFormattedValueData = (value: number | string, remainZero?: boole } return `${formattedValue}${unit === NumericUnit.None ? '' : unit}`; }; + +function getLeafNodes(treeNodes: any[]): any[] { + const leafNodes: any[] = []; + + function traverse(node: any) { + if (!node.children || node.children.length === 0) { + leafNodes.push(node); + } else { + node.children.forEach((child: any) => traverse(child)); + } + } + + treeNodes.forEach((node) => traverse(node)); + + return leafNodes; +} + +function buildTree(nodes: any[]): any[] { + const map: Record = {}; + const roots: any[] = []; + + nodes.forEach((node) => { + map[node.id] = node; + node.children = []; + }); + + nodes.forEach((node) => { + if (node.parentId) { + const parent = map[node.parentId]; + if (parent) { + parent.children.push(node); + } + } else { + roots.push(node); + } + }); + + return roots; +} + +export function getLeafList(flatNodes: any[]): any[] { + const treeNodes = buildTree(flatNodes); + const leafNodes = getLeafNodes(treeNodes); + return leafNodes; +}