From aa218898ffeba358ea985c6e2ed218880e119157 Mon Sep 17 00:00:00 2001 From: williamhliu Date: Sun, 20 Aug 2023 18:17:00 +0800 Subject: [PATCH] [feature](weaapp) add agent --- webapp/packages/chat-sdk/package.json | 2 +- .../packages/chat-sdk/src/common/constants.ts | 6 +- webapp/packages/chat-sdk/src/common/type.ts | 11 + .../src/components/ChatItem/ExecuteItem.tsx | 19 +- .../src/components/ChatItem/ParseTip.tsx | 35 +-- .../src/components/ChatItem/index.tsx | 83 ++--- .../src/components/ChatItem/style.less | 5 +- .../src/components/ChatMsg/Message/index.tsx | 4 +- .../components/ChatMsg/MetricCard/index.tsx | 6 +- .../components/ChatMsg/MetricCard/style.less | 4 +- .../components/ChatMsg/MetricTrend/index.tsx | 154 +++++---- .../components/ChatMsg/MetricTrend/style.less | 7 + .../chat-sdk/src/components/ChatMsg/index.tsx | 74 ++++- .../chat-sdk/src/components/Tools/index.tsx | 16 +- webapp/packages/chat-sdk/src/demo/Chat.tsx | 1 + .../chat-sdk/src/service/axiosInstance.ts | 2 +- webapp/packages/chat-sdk/src/service/index.ts | 23 +- .../packages/supersonic-fe/config/routes.ts | 6 + webapp/packages/supersonic-fe/package.json | 2 +- webapp/packages/supersonic-fe/src/app.tsx | 14 +- .../supersonic-fe/src/locales/zh-CN/menu.ts | 1 + .../src/pages/Agent/AgentModal.tsx | 109 +++++++ .../src/pages/Agent/AgentsSection.tsx | 144 +++++++++ .../src/pages/Agent/ToolModal.tsx | 264 ++++++++++++++++ .../src/pages/Agent/ToolsSection.tsx | 196 ++++++++++++ .../supersonic-fe/src/pages/Agent/index.tsx | 94 ++++++ .../supersonic-fe/src/pages/Agent/service.ts | 36 +++ .../supersonic-fe/src/pages/Agent/style.less | 292 ++++++++++++++++++ .../supersonic-fe/src/pages/Agent/type.ts | 119 +++++++ .../src/pages/Chat/ChatFooter/index.tsx | 229 ++++++++------ .../src/pages/Chat/Conversation.tsx | 7 +- .../src/pages/Chat/MessageContainer.tsx | 30 +- .../pages/Chat/components/AgentList/index.tsx | 63 ++++ .../Chat/components/AgentList/style.less | 63 ++++ .../supersonic-fe/src/pages/Chat/index.tsx | 79 ++++- .../supersonic-fe/src/pages/Chat/service.ts | 8 +- .../supersonic-fe/src/pages/Chat/style.less | 15 +- .../supersonic-fe/src/pages/Chat/type.ts | 12 +- .../src/pages/ChatPlugin/DetailModal.tsx | 4 +- .../src/pages/ChatPlugin/constants.ts | 5 +- 40 files changed, 1928 insertions(+), 316 deletions(-) create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/AgentModal.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/AgentsSection.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/ToolModal.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/ToolsSection.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/index.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/service.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/style.less create mode 100644 webapp/packages/supersonic-fe/src/pages/Agent/type.ts create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/components/AgentList/index.tsx create mode 100644 webapp/packages/supersonic-fe/src/pages/Chat/components/AgentList/style.less diff --git a/webapp/packages/chat-sdk/package.json b/webapp/packages/chat-sdk/package.json index fbfac40fc..79169865b 100644 --- a/webapp/packages/chat-sdk/package.json +++ b/webapp/packages/chat-sdk/package.json @@ -1,6 +1,6 @@ { "name": "supersonic-chat-sdk", - "version": "0.3.0", + "version": "0.4.32", "main": "dist/index.es.js", "module": "dist/index.es.js", "unpkg": "dist/index.umd.js", diff --git a/webapp/packages/chat-sdk/src/common/constants.ts b/webapp/packages/chat-sdk/src/common/constants.ts index 0e678feb7..737363f2d 100644 --- a/webapp/packages/chat-sdk/src/common/constants.ts +++ b/webapp/packages/chat-sdk/src/common/constants.ts @@ -40,13 +40,13 @@ export const THEME_COLOR_LIST = [ '#5CA9E6', ]; -export const PARSE_ERROR_TIP = '小Q不太懂您说什么呐,回去一定补充知识'; +export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识'; -export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能小Q还不够聪明,请您换个表达再试试'; +export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试'; export const MSG_VALID_TIP = { [MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常', - [MsgValidTypeEnum.INVALID]: '小Q不太懂您说什么呐,回去一定补充知识', + [MsgValidTypeEnum.INVALID]: '智能助理不太懂您说什么呐,回去一定补充知识', }; export const PREFIX_CLS = 'ss-chat'; diff --git a/webapp/packages/chat-sdk/src/common/type.ts b/webapp/packages/chat-sdk/src/common/type.ts index 8b92a5f9c..276a4970e 100644 --- a/webapp/packages/chat-sdk/src/common/type.ts +++ b/webapp/packages/chat-sdk/src/common/type.ts @@ -51,10 +51,21 @@ export type FilterItemType = { value: string[]; }; +export type ModelType = { + alias: string; + bizName: string; + id: number; + model: number; + name: string; + type: string; + useCnt: number; +} + export type ChatContextType = { aggType: string; modelId: number; modelName: string; + model: ModelType; dateInfo: DateInfoType; dimensions: FieldType[]; metrics: FieldType[]; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx index 9e29fa3d2..e7f23c9f2 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx @@ -1,3 +1,4 @@ +import { Spin } from 'antd'; import { PREFIX_CLS } from '../../common/constants'; import { MsgDataType } from '../../common/type'; import ChatMsg from '../ChatMsg'; @@ -8,6 +9,7 @@ import Typing from './Typing'; type Props = { question: string; executeLoading: boolean; + entitySwitchLoading: boolean; chartIndex: number; executeTip?: string; data?: MsgDataType; @@ -21,6 +23,7 @@ type Props = { const ExecuteItem: React.FC = ({ question, executeLoading, + entitySwitchLoading, chartIndex, executeTip, data, @@ -50,13 +53,15 @@ const ExecuteItem: React.FC = ({ return (
- + + + {!isMetricCard && ( = ({ const entityAlias = entity?.alias?.[0]?.split('.')?.[0]; const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name; - const pluginName = properties?.CONTEXT?.plugin?.name; - - const modeName = pluginName - ? '调插件' - : queryMode.includes('METRIC') - ? '算指标' - : queryMode === 'ENTITY_DETAIL' - ? '查明细' - : queryMode === 'ENTITY_LIST_FILTER' - ? '做圈选' - : ''; + const { type: agentType, name: agentName } = properties || {}; const fields = queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems; @@ -101,11 +91,10 @@ const ParseTip: React.FC = ({ }} > {index !== undefined &&
{index + 1}.
} - {!pluginName && isOptions &&
{modeName}:
} - {!!pluginName ? ( + {!!agentType ? (
- 将由问答插件 - {pluginName}来解答 + 将由{agentType === 'plugin' ? '插件' : '内置'}工具 + {agentName}来解答
) : ( <> @@ -123,7 +112,7 @@ const ParseTip: React.FC = ({
{modelName}
)} - {modeName === '算指标' && metric && ( + {metric && (
指标:
{metric.name}
@@ -153,9 +142,13 @@ const ParseTip: React.FC = ({
)} - {['METRIC_FILTER', 'METRIC_ENTITY', 'ENTITY_DETAIL', 'ENTITY_LIST_FILTER'].includes( - queryMode - ) && + {[ + 'METRIC_FILTER', + 'METRIC_ENTITY', + 'ENTITY_DETAIL', + 'ENTITY_LIST_FILTER', + 'ENTITY_ID', + ].includes(queryMode) && dimensionFilters && dimensionFilters?.length > 0 && (
@@ -198,10 +191,10 @@ const ParseTip: React.FC = ({
); } else { - const pluginName = parseInfoOptions[0]?.properties?.CONTEXT?.plugin?.name; + const agentType = parseInfoOptions[0]?.properties?.type; tipNode = (
-
{!!pluginName ? '您的问题' : '您的问题解析为:'}
+
{!!agentType ? '您的问题' : '您的问题解析为:'}
{getTipNode(parseInfoOptions[0])}
); diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx index 5dedfc9fe..55f8466a6 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx @@ -5,11 +5,14 @@ import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/ import IconFont from '../IconFont'; import ParseTip from './ParseTip'; import ExecuteItem from './ExecuteItem'; +import { isMobile } from '../../utils/utils'; +import classNames from 'classnames'; type Props = { msg: string; conversationId?: number; modelId?: number; + agentId?: number; filter?: any[]; isLastMessage?: boolean; msgData?: MsgDataType; @@ -24,6 +27,7 @@ const ChatItem: React.FC = ({ msg, conversationId, modelId, + agentId, filter, isLastMessage, isMobileMode, @@ -41,7 +45,7 @@ const ChatItem: React.FC = ({ const [executeLoading, setExecuteLoading] = useState(false); const [executeTip, setExecuteTip] = useState(''); const [executeMode, setExecuteMode] = useState(false); - const [entitySwitching, setEntitySwitching] = useState(false); + const [entitySwitchLoading, setEntitySwitchLoading] = useState(false); const [chartIndex, setChartIndex] = useState(0); @@ -76,37 +80,42 @@ const ChatItem: React.FC = ({ ) => { setExecuteMode(true); setExecuteLoading(true); - const { data } = await chatExecute(msg, conversationId!, parseInfoValue); - setExecuteLoading(false); - const valid = updateData(data); - if (onMsgDataLoaded) { - let parseOptions: ChatContextType[] = parseInfoOptions || []; - if ( - parseInfoOptions && - parseInfoOptions.length > 1 && - (parseInfoOptions[0].queryMode.includes('METRIC') || - parseInfoOptions[0].queryMode.includes('ENTITY')) - ) { - parseOptions = parseInfoOptions.filter( - (item, index) => - index === 0 || - (!item.queryMode.includes('METRIC') && !item.queryMode.includes('ENTITY')) + try { + const { data } = await chatExecute(msg, conversationId!, parseInfoValue); + setExecuteLoading(false); + const valid = updateData(data); + if (onMsgDataLoaded) { + let parseOptions: ChatContextType[] = parseInfoOptions || []; + if ( + parseInfoOptions && + parseInfoOptions.length > 1 && + (parseInfoOptions[0].queryMode.includes('METRIC') || + parseInfoOptions[0].queryMode.includes('ENTITY')) + ) { + parseOptions = parseInfoOptions.filter( + (item, index) => + index === 0 || + (!item.queryMode.includes('METRIC') && !item.queryMode.includes('ENTITY')) + ); + } + onMsgDataLoaded( + { + ...data.data, + chatContext: parseInfoValue, + parseOptions: parseOptions.length > 1 ? parseOptions.slice(1) : undefined, + }, + valid ); } - onMsgDataLoaded( - { - ...data.data, - chatContext: parseInfoValue, - parseOptions: parseOptions.length > 1 ? parseOptions.slice(1) : undefined, - }, - valid - ); + } catch (e) { + setExecuteLoading(false); + setExecuteTip(SEARCH_EXCEPTION_TIP); } }; const onSendMsg = async () => { setParseLoading(true); - const { data: parseData } = await chatParse(msg, conversationId, modelId, filter); + const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter); setParseLoading(false); const { code, data } = parseData || {}; const { state, selectedParses } = data || {}; @@ -115,10 +124,9 @@ const ChatItem: React.FC = ({ state === ParseStateEnum.FAILED || selectedParses == null || selectedParses.length === 0 || - (selectedParses.length === 1 && - !selectedParses[0]?.modelName && - !selectedParses[0]?.properties?.CONTEXT?.plugin?.name && - selectedParses[0]?.queryMode !== 'WEB_PAGE') + (selectedParses.length > 0 && + !selectedParses[0]?.properties?.type && + !selectedParses[0]?.queryMode) ) { setParseTip(PARSE_ERROR_TIP); return; @@ -146,9 +154,9 @@ const ChatItem: React.FC = ({ }, [msg, msgData]); const onSwitchEntity = async (entityId: string) => { - setEntitySwitching(true); + setEntitySwitchLoading(true); const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0); - setEntitySwitching(false); + setEntitySwitchLoading(false); setData(res.data.data); }; @@ -164,11 +172,15 @@ const ChatItem: React.FC = ({ } }; + const contentClass = classNames(`${prefixCls}-content`, { + [`${prefixCls}-content-mobile`]: isMobile, + }); + return (
- -
+ {!isMobile && } +
= ({
{executeMode && data?.queryMode !== 'WEB_PAGE' && (
- -
+ {!isMobile && } +
= ({ width, + maxWidth, height, children, bubbleClassName, @@ -38,7 +40,7 @@ const Message: React.FC = ({
{ e.stopPropagation(); }} 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 428e1f752..1cf240d3c 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx @@ -43,7 +43,11 @@ const MetricCard: React.FC = ({ return (
-
{indicatorColumn?.name}
+ {indicatorColumn?.name ? ( +
{indicatorColumn?.name}
+ ) : ( +
+ )} {(hasFilterSection || drillDownDimension) && (
( diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less index 158d691a3..3fa55d1d9 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less @@ -109,7 +109,7 @@ &-drill-down-dimensions { position: absolute; - bottom: -38px; - left: 0; + bottom: -44px; + left: -16; } } \ No newline at end of file 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 e3b5275a3..a70c2db0b 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx @@ -19,25 +19,19 @@ type Props = { }; const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApplyAuth }) => { - const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data; - - const { dateMode, unit } = chatContext?.dateInfo || {}; - + const { entityInfo, chatContext, queryMode } = data; + const { dateInfo, dimensionFilters, elementMatches } = chatContext || {}; + const { dateMode, unit } = dateInfo || {}; const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY; - const initialDateOption = dateOptions.find((option: any) => { - return dateMode === 'RECENT' && option.value === unit; - })?.value; - const [columns, setColumns] = useState(queryColumns || []); - const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER'); - - const [activeMetricField, setActiveMetricField] = useState(chatContext.metrics?.[0]); - const [dataSource, setDataSource] = useState(queryResults); - const [currentDateOption, setCurrentDateOption] = useState(initialDateOption); - const [dimensions, setDimensions] = useState(chatContext?.dimensions); + const [columns, setColumns] = useState([]); + const [activeMetricField, setActiveMetricField] = useState(); + const [dataSource, setDataSource] = useState([]); + const [currentDateOption, setCurrentDateOption] = useState(); + const [dimensions, setDimensions] = useState(); const [drillDownDimension, setDrillDownDimension] = useState(); - const [aggregateInfoValue, setAggregateInfoValue] = useState(aggregateInfo); - const [dateModeValue, setDateModeValue] = useState(dateMode); + const [aggregateInfoValue, setAggregateInfoValue] = useState(); + const [dateModeValue, setDateModeValue] = useState(); const [loading, setLoading] = useState(false); const dateField: any = columns.find( @@ -47,9 +41,31 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply const categoryColumnName = columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || ''; + const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined; + const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element + ?.name; + + const isEntityMode = + (queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') && + typeof entityId === 'string' && + entityName !== undefined; + useEffect(() => { + const { queryColumns, queryResults, chatContext, aggregateInfo } = data; + + const initialDateOption = dateOptions.find((option: any) => { + return dateMode === 'RECENT' && option.value === unit; + })?.value; + + setColumns(queryColumns || []); + setActiveMetricField(chatContext?.metrics?.[0]); setDataSource(queryResults); - }, [queryResults]); + setCurrentDateOption(initialDateOption); + setDimensions(chatContext?.dimensions); + setDrillDownDimension(undefined); + setAggregateInfoValue(aggregateInfo); + setDateModeValue(chatContext?.dateInfo?.dateMode); + }, [data]); useEffect(() => { if (queryMode === 'METRIC_GROUPBY') { @@ -117,14 +133,14 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply }); }; + const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER'); + if (!currentMetricField) { return null; } const prefixCls = `${CLS_PREFIX}-metric-trend`; - const { dimensionFilters } = chatContext || {}; - const hasFilterSection = dimensionFilters?.length > 0; return ( @@ -174,59 +190,61 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply
)}
- {aggregateInfoValue?.metricInfos?.length > 0 && ( - - )} -
- {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 && ( -
- )} - - ); - })} -
- {dataSource?.length === 1 || chartIndex % 2 === 1 ? ( - - ) : ( - + {aggregateInfoValue?.metricInfos?.length > 0 && ( + + )} +
+ {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 && ( +
+ )} + + ); + })} +
+ {dataSource?.length === 1 || chartIndex % 2 === 1 ? ( +
+ ) : ( + + )} + + {queryMode.includes('METRIC') && !isEntityMode && ( + )} - {queryMode.includes('METRIC') && ( - - )} ); 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 88cf224ff..07a193f2b 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less @@ -48,6 +48,13 @@ font-weight: 500; } + &-content { + display: flex; + flex-direction: column; + width: 100%; + row-gap: 12px; + } + &-indicator { display: flex; flex-direction: column; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx index b701f75c8..b8a5ce585 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx @@ -5,7 +5,7 @@ import MetricCard from './MetricCard'; import MetricTrend from './MetricTrend'; import Table from './Table'; import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { queryData } from '../../service'; type Props = { @@ -25,6 +25,11 @@ const ChatMsg: React.FC = ({ question, data, chartIndex, isMobileMode, tr const [drillDownDimension, setDrillDownDimension] = useState(); const [loading, setLoading] = useState(false); + useEffect(() => { + setColumns(queryColumns); + setDataSource(queryResults); + }, [queryColumns, queryResults]); + if (!queryColumns || !queryResults) { return null; } @@ -35,14 +40,15 @@ const ChatMsg: React.FC = ({ question, data, chartIndex, isMobileMode, tr const metricFields = columns.filter(item => item.showType === 'NUMBER'); const isMetricCard = - queryMode.includes('METRIC') && + (queryMode.includes('METRIC') || + (queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1)) && (singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate); const isText = columns.length === 1 && columns[0].showType === 'CATEGORY' && - !queryMode.includes('METRIC') && - !queryMode.includes('ENTITY') && + ((!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) || + queryMode === 'METRIC_INTERPRET') && singleData; const onLoadData = async (value: any) => { @@ -68,9 +74,43 @@ const ChatMsg: React.FC = ({ question, data, chartIndex, isMobileMode, tr const getMsgContent = () => { if (isText) { + let text = dataSource[0][columns[0].nameEn]; + let htmlCode: string; + const match = text.match(/```html([\s\S]*?)```/); + htmlCode = match && match[1].trim(); + if (htmlCode) { + text = text.replace(/```html([\s\S]*?)```/, ''); + } + let scriptCode: string; + let scriptSrc: string; + if (htmlCode) { + scriptSrc = htmlCode.match(/