mirror of
https://github.com/tencentmusic/supersonic.git
synced 2026-04-29 12:34:28 +08:00
Compare commits
6 Commits
f973faba04
...
2169941926
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2169941926 | ||
|
|
f6622319a4 | ||
|
|
4a6938956b | ||
|
|
848b4a1e44 | ||
|
|
a298c670ed | ||
|
|
5b45cfbad7 |
22
.github/workflows/centos-ci.yml
vendored
22
.github/workflows/centos-ci.yml
vendored
@@ -15,11 +15,17 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [8, 11, 21] # 定义要测试的JDK版本
|
||||
java-version: [21] # 定义要测试的JDK版本
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK ${{ matrix.java-version }}
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Reset DNF repositories
|
||||
run: |
|
||||
cd /etc/yum.repos.d/
|
||||
@@ -29,21 +35,11 @@ jobs:
|
||||
- name: Update DNF package index
|
||||
run: dnf makecache
|
||||
|
||||
- name: Install Java and Maven with retry
|
||||
- name: Install Maven with retry
|
||||
run: |
|
||||
if [ ${{ matrix.java-version }} -eq 8 ]; then
|
||||
for i in {1..5}; do
|
||||
dnf install -y java-1.8.0-openjdk-devel maven && break || sleep 15
|
||||
dnf install -y maven && break || sleep 15
|
||||
done
|
||||
elif [ ${{ matrix.java-version }} -eq 11 ]; then
|
||||
for i in {1..5}; do
|
||||
dnf install -y java-11-openjdk-devel maven && break || sleep 15
|
||||
done
|
||||
elif [ ${{ matrix.java-version }} -eq 21 ]; then
|
||||
for i in {1..5}; do
|
||||
dnf install -y java-21-openjdk-devel maven && break || sleep 15
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Verify Java and Maven installation
|
||||
run: |
|
||||
|
||||
2
.github/workflows/mac-ci.yml
vendored
2
.github/workflows/mac-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [8, 11, 21] # Define the JDK versions to test
|
||||
java-version: [21] # Define the JDK versions to test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/ubuntu-ci.yml
vendored
2
.github/workflows/ubuntu-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [8, 11, 21] # 定义要测试的JDK版本
|
||||
java-version: [21] # 定义要测试的JDK版本
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/windows-ci.yml
vendored
2
.github/workflows/windows-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java-version: [8, 11, 21] # Add JDK 21 to the matrix
|
||||
java-version: [21] # Add JDK 21 to the matrix
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -18,6 +18,9 @@ public class AuthenticationConfig {
|
||||
@Value("${s2.authentication.include.path:/api}")
|
||||
private String includePath;
|
||||
|
||||
@Value("${s2.authentication.strategy:http}")
|
||||
private String strategy;
|
||||
|
||||
@Value("${s2.authentication.enable:false}")
|
||||
private boolean enabled;
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.tencent.supersonic.common.pojo.User;
|
||||
|
||||
public interface UserStrategy {
|
||||
|
||||
String getStrategyName();
|
||||
|
||||
boolean accept(boolean isEnableAuthentication);
|
||||
|
||||
User findUser(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
@@ -10,6 +10,13 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class FakeUserStrategy implements UserStrategy {
|
||||
|
||||
public static final String STRATEGY_NAME = "fake";
|
||||
|
||||
@Override
|
||||
public String getStrategyName() {
|
||||
return STRATEGY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(boolean isEnableAuthentication) {
|
||||
return !isEnableAuthentication;
|
||||
|
||||
@@ -15,12 +15,18 @@ import java.util.Optional;
|
||||
@Service
|
||||
public class HttpHeaderUserStrategy implements UserStrategy {
|
||||
|
||||
public static final String STRATEGY_NAME = "http";
|
||||
private final TokenService tokenService;
|
||||
|
||||
public HttpHeaderUserStrategy(TokenService tokenService) {
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStrategyName() {
|
||||
return STRATEGY_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(boolean isEnableAuthentication) {
|
||||
return isEnableAuthentication;
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.Data;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Configuration
|
||||
@Data
|
||||
@@ -26,10 +27,26 @@ public class UserStrategyFactory {
|
||||
|
||||
@PostConstruct
|
||||
public void setUserStrategy() {
|
||||
for (UserStrategy userStrategy : userStrategyList) {
|
||||
if (userStrategy.accept(authenticationConfig.isEnabled())) {
|
||||
UserHolder.setStrategy(userStrategy);
|
||||
|
||||
boolean enabled = authenticationConfig.isEnabled();
|
||||
if (!enabled) {
|
||||
for (UserStrategy userStrategy : userStrategyList) {
|
||||
if (userStrategy.accept(authenticationConfig.isEnabled())) {
|
||||
UserHolder.setStrategy(userStrategy);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String strategy = authenticationConfig.getStrategy();
|
||||
Optional<UserStrategy> strategyOptional = userStrategyList.stream()
|
||||
.filter(t -> t.accept(true) && strategy.equalsIgnoreCase(t.getStrategyName()))
|
||||
.findAny();
|
||||
|
||||
if (strategyOptional.isPresent()) {
|
||||
UserHolder.setStrategy(strategyOptional.get());
|
||||
} else {
|
||||
throw new IllegalStateException("strategy is not found: " + strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.List;
|
||||
|
||||
/** extended information command about model */
|
||||
@Data
|
||||
@ToString
|
||||
public class ChatConfigBaseReq {
|
||||
|
||||
private Long modelId;
|
||||
|
||||
@@ -4,11 +4,15 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.enums.MemoryReviewResult;
|
||||
import com.tencent.supersonic.chat.api.pojo.enums.MemoryStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChatMemoryUpdateReq {
|
||||
|
||||
@NotNull(message = "id不可为空")
|
||||
|
||||
@@ -4,7 +4,6 @@ import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class ItemNameVisibility {
|
||||
|
||||
private ItemNameVisibilityInfo aggVisibilityInfo;
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class ItemNameVisibilityInfo {
|
||||
|
||||
/** invisible dimensions */
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class ItemVisibility {
|
||||
|
||||
/** invisible dimensions */
|
||||
|
||||
@@ -6,7 +6,6 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RecommendedQuestionReq {
|
||||
|
||||
@@ -6,7 +6,6 @@ import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@ToString
|
||||
@Data
|
||||
public class DictLatestTaskResp {
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ package com.tencent.supersonic.chat.api.pojo.response;
|
||||
import com.tencent.supersonic.chat.api.pojo.request.RecommendedQuestionReq;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RecommendQuestionResp {
|
||||
private Long modelId;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.tencent.supersonic.chat.api.pojo.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SimilarQueryRecallResp {
|
||||
|
||||
private Long queryId;
|
||||
|
||||
@@ -230,7 +230,8 @@ public class ChatQueryServiceImpl implements ChatQueryService {
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
private boolean checkMetricReplace(ChatQueryDataReq chatQueryDataReq, SemanticParseInfo parseInfo) {
|
||||
private boolean checkMetricReplace(ChatQueryDataReq chatQueryDataReq,
|
||||
SemanticParseInfo parseInfo) {
|
||||
List<String> oriFields = getFieldsFromSql(parseInfo);
|
||||
Set<SchemaElement> metrics = chatQueryDataReq.getMetrics();
|
||||
if (CollectionUtils.isEmpty(oriFields) || CollectionUtils.isEmpty(metrics)) {
|
||||
@@ -242,7 +243,7 @@ public class ChatQueryServiceImpl implements ChatQueryService {
|
||||
}
|
||||
|
||||
private String replaceFilters(ChatQueryDataReq queryData, SemanticParseInfo parseInfo,
|
||||
DataSetSchema dataSetSchema) {
|
||||
DataSetSchema dataSetSchema) {
|
||||
String correctorSql = parseInfo.getSqlInfo().getCorrectedS2SQL();
|
||||
log.info("correctorSql before replacing:{}", correctorSql);
|
||||
// get where filter and having filter
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
RangeValue,
|
||||
SimilarQuestionType,
|
||||
} from '../../common/type';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createContext, useEffect, useRef, useState } from 'react';
|
||||
import { chatExecute, chatParse, queryData, deleteQuery, switchEntity } from '../../service';
|
||||
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
|
||||
import { Spin } from 'antd';
|
||||
import { message, Spin } from 'antd';
|
||||
import IconFont from '../IconFont';
|
||||
import ExpandParseTip from './ExpandParseTip';
|
||||
import ParseTip from './ParseTip';
|
||||
@@ -25,6 +25,7 @@ import SimilarQuestionItem from './SimilarQuestionItem';
|
||||
import { AgentType } from '../../Chat/type';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { exportCsvFile } from '../../utils/utils';
|
||||
import { useMethodRegister } from '../../hooks';
|
||||
|
||||
type Props = {
|
||||
msg: string;
|
||||
@@ -51,6 +52,11 @@ type Props = {
|
||||
onSendMsg?: (msg: string) => void;
|
||||
};
|
||||
|
||||
export const ChartItemContext = createContext({
|
||||
register: (...args: any[]) => {},
|
||||
call: (...args: any[]) => {},
|
||||
});
|
||||
|
||||
const ChatItem: React.FC<Props> = ({
|
||||
msg,
|
||||
conversationId,
|
||||
@@ -433,126 +439,135 @@ const ChatItem: React.FC<Props> = ({
|
||||
|
||||
const { llmReq, llmResp } = parseInfo?.properties?.CONTEXT || {};
|
||||
|
||||
const { register, call } = useMethodRegister(() => message.error('该条消息暂不支持该操作'));
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||
<div className={isMobile ? `${prefixCls}-mobile-msg-card` : ''}>
|
||||
<div className={`${prefixCls}-time`}>
|
||||
{parseTimeCost?.parseStartTime
|
||||
? dayjs(parseTimeCost.parseStartTime).format('M月D日 HH:mm')
|
||||
: ''}
|
||||
</div>
|
||||
<div className={contentClass}>
|
||||
<>
|
||||
{currentAgent?.enableFeedback === 1 && !questionId && showExpandParseTip && (
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<ExpandParseTip
|
||||
<ChartItemContext.Provider value={{ register, call }}>
|
||||
<div className={prefixCls}>
|
||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||
<div className={isMobile ? `${prefixCls}-mobile-msg-card` : ''}>
|
||||
<div className={`${prefixCls}-time`}>
|
||||
{parseTimeCost?.parseStartTime
|
||||
? dayjs(parseTimeCost.parseStartTime).format('M月D日 HH:mm')
|
||||
: ''}
|
||||
</div>
|
||||
<div className={contentClass}>
|
||||
<>
|
||||
{currentAgent?.enableFeedback === 1 && !questionId && showExpandParseTip && (
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<ExpandParseTip
|
||||
isSimpleMode={isSimpleMode}
|
||||
parseInfoOptions={preParseInfoOptions}
|
||||
agentId={agentId}
|
||||
integrateSystem={integrateSystem}
|
||||
parseTimeCost={parseTimeCost?.parseTime}
|
||||
isDeveloper={isDeveloper}
|
||||
onSelectParseInfo={onExpandSelectParseInfo}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
onFiltersChange={onFiltersChange}
|
||||
onDateInfoChange={onDateInfoChange}
|
||||
onRefresh={onRefresh}
|
||||
handlePresetClick={handlePresetClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!preParseMode && (
|
||||
<ParseTip
|
||||
isSimpleMode={isSimpleMode}
|
||||
parseInfoOptions={preParseInfoOptions}
|
||||
parseLoading={parseLoading}
|
||||
parseInfoOptions={parseInfoOptions}
|
||||
parseTip={parseTip}
|
||||
currentParseInfo={parseInfo}
|
||||
agentId={agentId}
|
||||
dimensionFilters={dimensionFilters}
|
||||
dateInfo={dateInfo}
|
||||
entityInfo={entityInfo}
|
||||
integrateSystem={integrateSystem}
|
||||
parseTimeCost={parseTimeCost?.parseTime}
|
||||
isDeveloper={isDeveloper}
|
||||
onSelectParseInfo={onExpandSelectParseInfo}
|
||||
onSelectParseInfo={onSelectParseInfo}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
onFiltersChange={onFiltersChange}
|
||||
onDateInfoChange={onDateInfoChange}
|
||||
onRefresh={onRefresh}
|
||||
onRefresh={() => {
|
||||
onRefresh();
|
||||
}}
|
||||
handlePresetClick={handlePresetClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</>
|
||||
|
||||
{!preParseMode && (
|
||||
<ParseTip
|
||||
isSimpleMode={isSimpleMode}
|
||||
parseLoading={parseLoading}
|
||||
parseInfoOptions={parseInfoOptions}
|
||||
parseTip={parseTip}
|
||||
currentParseInfo={parseInfo}
|
||||
agentId={agentId}
|
||||
dimensionFilters={dimensionFilters}
|
||||
dateInfo={dateInfo}
|
||||
entityInfo={entityInfo}
|
||||
integrateSystem={integrateSystem}
|
||||
parseTimeCost={parseTimeCost?.parseTime}
|
||||
isDeveloper={isDeveloper}
|
||||
onSelectParseInfo={onSelectParseInfo}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
onFiltersChange={onFiltersChange}
|
||||
onDateInfoChange={onDateInfoChange}
|
||||
onRefresh={() => {
|
||||
onRefresh();
|
||||
}}
|
||||
handlePresetClick={handlePresetClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{executeMode && (
|
||||
<Spin spinning={entitySwitchLoading}>
|
||||
<div style={{ minHeight: 50 }}>
|
||||
{!isMobile && parseInfo?.sqlInfo && isDeveloper && isDebugMode && !isSimpleMode && (
|
||||
<SqlItem
|
||||
agentId={agentId}
|
||||
queryId={parseInfo.queryId}
|
||||
{executeMode && (
|
||||
<Spin spinning={entitySwitchLoading}>
|
||||
<div style={{ minHeight: 50 }}>
|
||||
{!isMobile &&
|
||||
parseInfo?.sqlInfo &&
|
||||
isDeveloper &&
|
||||
isDebugMode &&
|
||||
!isSimpleMode && (
|
||||
<SqlItem
|
||||
agentId={agentId}
|
||||
queryId={parseInfo.queryId}
|
||||
question={msg}
|
||||
llmReq={llmReq}
|
||||
llmResp={llmResp}
|
||||
integrateSystem={integrateSystem}
|
||||
queryMode={parseInfo.queryMode}
|
||||
sqlInfo={parseInfo.sqlInfo}
|
||||
sqlTimeCost={parseTimeCost?.sqlTime}
|
||||
executeErrorMsg={executeErrorMsg}
|
||||
/>
|
||||
)}
|
||||
<ExecuteItem
|
||||
isSimpleMode={isSimpleMode}
|
||||
queryId={parseInfo?.queryId}
|
||||
question={msg}
|
||||
llmReq={llmReq}
|
||||
llmResp={llmResp}
|
||||
integrateSystem={integrateSystem}
|
||||
queryMode={parseInfo.queryMode}
|
||||
sqlInfo={parseInfo.sqlInfo}
|
||||
sqlTimeCost={parseTimeCost?.sqlTime}
|
||||
queryMode={parseInfo?.queryMode}
|
||||
executeLoading={executeLoading}
|
||||
executeTip={executeTip}
|
||||
executeErrorMsg={executeErrorMsg}
|
||||
chartIndex={0}
|
||||
data={data}
|
||||
triggerResize={triggerResize}
|
||||
executeItemNode={executeItemNode}
|
||||
isDeveloper={isDeveloper}
|
||||
renderCustomExecuteNode={renderCustomExecuteNode}
|
||||
/>
|
||||
)}
|
||||
<ExecuteItem
|
||||
isSimpleMode={isSimpleMode}
|
||||
</div>
|
||||
</Spin>
|
||||
)}
|
||||
{executeMode &&
|
||||
!executeLoading &&
|
||||
!isSimpleMode &&
|
||||
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
||||
<SimilarQuestionItem
|
||||
queryId={parseInfo?.queryId}
|
||||
question={msg}
|
||||
queryMode={parseInfo?.queryMode}
|
||||
executeLoading={executeLoading}
|
||||
executeTip={executeTip}
|
||||
executeErrorMsg={executeErrorMsg}
|
||||
chartIndex={0}
|
||||
data={data}
|
||||
triggerResize={triggerResize}
|
||||
executeItemNode={executeItemNode}
|
||||
isDeveloper={isDeveloper}
|
||||
renderCustomExecuteNode={renderCustomExecuteNode}
|
||||
defaultExpanded={parseTip !== '' || executeTip !== ''}
|
||||
similarQueries={data?.similarQueries}
|
||||
onSelectQuestion={onSelectQuestion}
|
||||
/>
|
||||
</div>
|
||||
</Spin>
|
||||
)}
|
||||
{executeMode &&
|
||||
!executeLoading &&
|
||||
!isSimpleMode &&
|
||||
)}
|
||||
</div>
|
||||
{(parseTip !== '' || (executeMode && !executeLoading)) &&
|
||||
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
||||
<SimilarQuestionItem
|
||||
queryId={parseInfo?.queryId}
|
||||
defaultExpanded={parseTip !== '' || executeTip !== ''}
|
||||
similarQueries={data?.similarQueries}
|
||||
onSelectQuestion={onSelectQuestion}
|
||||
<Tools
|
||||
isLastMessage={isLastMessage}
|
||||
queryId={parseInfo?.queryId || 0}
|
||||
scoreValue={score}
|
||||
isParserError={isParserError}
|
||||
onExportData={() => {
|
||||
onExportData();
|
||||
}}
|
||||
isSimpleMode={isSimpleMode}
|
||||
onReExecute={queryId => {
|
||||
deleteQueryInfo(queryId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{(parseTip !== '' || (executeMode && !executeLoading)) &&
|
||||
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
||||
<Tools
|
||||
isLastMessage={isLastMessage}
|
||||
queryId={parseInfo?.queryId || 0}
|
||||
scoreValue={score}
|
||||
isParserError={isParserError}
|
||||
onExportData={() => {
|
||||
onExportData();
|
||||
}}
|
||||
onReExecute={queryId => {
|
||||
deleteQueryInfo(queryId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ChartItemContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,10 +7,19 @@ import {
|
||||
} from '../../../utils/utils';
|
||||
import type { ECharts } from 'echarts';
|
||||
import * as echarts from 'echarts';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
ForwardRefRenderFunction,
|
||||
useContext,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import NoPermissionChart from '../NoPermissionChart';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import { Spin } from 'antd';
|
||||
import { ChartItemContext } from '../../ChatItem';
|
||||
import { useExportByEcharts } from '../../../hooks';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
@@ -30,7 +39,7 @@ const BarChart: React.FC<Props> = ({
|
||||
onApplyAuth,
|
||||
}) => {
|
||||
const chartRef = useRef<any>();
|
||||
const [instance, setInstance] = useState<ECharts>();
|
||||
const instanceRef = useRef<ECharts>();
|
||||
|
||||
const { queryColumns, queryResults, entityInfo } = data;
|
||||
|
||||
@@ -41,11 +50,11 @@ const BarChart: React.FC<Props> = ({
|
||||
|
||||
const renderChart = () => {
|
||||
let instanceObj: any;
|
||||
if (!instance) {
|
||||
if (!instanceRef.current) {
|
||||
instanceObj = echarts.init(chartRef.current);
|
||||
setInstance(instanceObj);
|
||||
instanceRef.current = instanceObj;
|
||||
} else {
|
||||
instanceObj = instance;
|
||||
instanceObj = instanceRef.current;
|
||||
}
|
||||
const data = (queryResults || []).sort(
|
||||
(a: any, b: any) => b[metricColumnName] - a[metricColumnName]
|
||||
@@ -163,8 +172,8 @@ const BarChart: React.FC<Props> = ({
|
||||
}, [queryResults]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerResize && instance) {
|
||||
instance.resize();
|
||||
if (triggerResize && instanceRef.current) {
|
||||
instanceRef.current.resize();
|
||||
}
|
||||
}, [triggerResize]);
|
||||
|
||||
@@ -180,6 +189,15 @@ const BarChart: React.FC<Props> = ({
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-bar`;
|
||||
|
||||
const { downloadChartAsImage } = useExportByEcharts({
|
||||
instanceRef,
|
||||
question,
|
||||
});
|
||||
|
||||
const { register } = useContext(ChartItemContext);
|
||||
|
||||
register('downloadChartAsImage', downloadChartAsImage);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
} from '../../../utils/utils';
|
||||
import type { ECharts } from 'echarts';
|
||||
import * as echarts from 'echarts';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import NoPermissionChart from '../NoPermissionChart';
|
||||
import classNames from 'classnames';
|
||||
import { isArray } from 'lodash';
|
||||
import { useExportByEcharts } from '../../../hooks';
|
||||
import { ChartItemContext } from '../../ChatItem';
|
||||
|
||||
type Props = {
|
||||
model?: string;
|
||||
@@ -37,15 +39,15 @@ const MetricTrendChart: React.FC<Props> = ({
|
||||
chartType,
|
||||
}) => {
|
||||
const chartRef = useRef<any>();
|
||||
const [instance, setInstance] = useState<ECharts>();
|
||||
const instanceRef = useRef<ECharts>();
|
||||
|
||||
const renderChart = () => {
|
||||
let instanceObj: any;
|
||||
if (!instance) {
|
||||
if (!instanceRef.current) {
|
||||
instanceObj = echarts.init(chartRef.current);
|
||||
setInstance(instanceObj);
|
||||
instanceRef.current = instanceObj;
|
||||
} else {
|
||||
instanceObj = instance;
|
||||
instanceObj = instanceRef.current;
|
||||
instanceObj.clear();
|
||||
}
|
||||
|
||||
@@ -195,6 +197,15 @@ const MetricTrendChart: React.FC<Props> = ({
|
||||
instanceObj.resize();
|
||||
};
|
||||
|
||||
const { downloadChartAsImage } = useExportByEcharts({
|
||||
instanceRef,
|
||||
question: metricField.name,
|
||||
});
|
||||
|
||||
const { register } = useContext(ChartItemContext);
|
||||
|
||||
register('downloadChartAsImage', downloadChartAsImage);
|
||||
|
||||
useEffect(() => {
|
||||
if (metricField.authorized) {
|
||||
renderChart();
|
||||
@@ -202,8 +213,8 @@ const MetricTrendChart: React.FC<Props> = ({
|
||||
}, [resultList, metricField, chartType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerResize && instance) {
|
||||
instance.resize();
|
||||
if (triggerResize && instanceRef.current) {
|
||||
instanceRef.current.resize();
|
||||
}
|
||||
}, [triggerResize]);
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../co
|
||||
import { getFormattedValue } from '../../../utils/utils';
|
||||
import type { ECharts } from 'echarts';
|
||||
import * as echarts from 'echarts';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import { isArray } from 'lodash';
|
||||
import { ChartItemContext } from '../../ChatItem';
|
||||
import { useExportByEcharts } from '../../../hooks';
|
||||
|
||||
type Props = {
|
||||
dateColumnName: string;
|
||||
@@ -13,6 +15,7 @@ type Props = {
|
||||
resultList: any[];
|
||||
triggerResize?: boolean;
|
||||
chartType?: string;
|
||||
question: string;
|
||||
};
|
||||
|
||||
const MultiMetricsTrendChart: React.FC<Props> = ({
|
||||
@@ -21,17 +24,17 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
|
||||
resultList,
|
||||
triggerResize,
|
||||
chartType,
|
||||
question,
|
||||
}) => {
|
||||
const chartRef = useRef<any>();
|
||||
const [instance, setInstance] = useState<ECharts>();
|
||||
|
||||
const instanceRef = useRef<ECharts>();
|
||||
const renderChart = () => {
|
||||
let instanceObj: any;
|
||||
if (!instance) {
|
||||
if (!instanceRef.current) {
|
||||
instanceObj = echarts.init(chartRef.current);
|
||||
setInstance(instanceObj);
|
||||
instanceRef.current = instanceObj;
|
||||
} else {
|
||||
instanceObj = instance;
|
||||
instanceObj = instanceRef.current;
|
||||
instanceObj.clear();
|
||||
}
|
||||
|
||||
@@ -132,13 +135,22 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
|
||||
instanceObj.resize();
|
||||
};
|
||||
|
||||
const { downloadChartAsImage } = useExportByEcharts({
|
||||
instanceRef,
|
||||
question,
|
||||
});
|
||||
|
||||
const { register } = useContext(ChartItemContext);
|
||||
|
||||
register('downloadChartAsImage', downloadChartAsImage);
|
||||
|
||||
useEffect(() => {
|
||||
renderChart();
|
||||
}, [resultList, chartType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerResize && instance) {
|
||||
instance.resize();
|
||||
if (triggerResize && instanceRef.current) {
|
||||
instanceRef.current.resize();
|
||||
}
|
||||
}, [triggerResize]);
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ const MetricTrend: React.FC<Props> = ({
|
||||
<MultiMetricsTrendChart
|
||||
dateColumnName={dateColumnName}
|
||||
metricFields={metricFields}
|
||||
question={question}
|
||||
resultList={queryResults}
|
||||
triggerResize={triggerResize}
|
||||
chartType={chartType}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import { isMobile } from '../../utils/utils';
|
||||
import { DislikeOutlined, LikeOutlined, DownloadOutlined, RedoOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
DislikeOutlined,
|
||||
LikeOutlined,
|
||||
DownloadOutlined,
|
||||
RedoOutlined,
|
||||
FileJpgOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import { CLS_PREFIX } from '../../common/constants';
|
||||
import { useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { updateQAFeedback } from '../../service';
|
||||
import { useMethodRegister } from '../../hooks';
|
||||
import { ChartItemContext } from '../ChatItem';
|
||||
|
||||
type Props = {
|
||||
queryId: number;
|
||||
scoreValue?: number;
|
||||
isLastMessage?: boolean;
|
||||
isParserError?: boolean;
|
||||
isSimpleMode?: boolean;
|
||||
onExportData?: () => void;
|
||||
onReExecute?: (queryId: number) => void;
|
||||
};
|
||||
@@ -20,6 +29,7 @@ const Tools: React.FC<Props> = ({
|
||||
scoreValue,
|
||||
isLastMessage,
|
||||
isParserError = false,
|
||||
isSimpleMode = false,
|
||||
onExportData,
|
||||
onReExecute,
|
||||
}) => {
|
||||
@@ -44,6 +54,8 @@ const Tools: React.FC<Props> = ({
|
||||
[`${prefixCls}-feedback-active`]: score === 1,
|
||||
});
|
||||
|
||||
const { call } = useContext(ChartItemContext);
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
{!isMobile && (
|
||||
@@ -68,6 +80,18 @@ const Tools: React.FC<Props> = ({
|
||||
<DownloadOutlined />
|
||||
<span className={`${prefixCls}-font-style`}>导出数据</span>
|
||||
</Button>
|
||||
{!isSimpleMode && (
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
call('downloadChartAsImage');
|
||||
}}
|
||||
type="text"
|
||||
>
|
||||
<FileJpgOutlined />
|
||||
<span className={`${prefixCls}-font-style`}>导出图片</span>
|
||||
</Button>
|
||||
)}
|
||||
{isLastMessage && (
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
3
webapp/packages/chat-sdk/src/hooks/index.ts
Normal file
3
webapp/packages/chat-sdk/src/hooks/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './useMethodRegister';
|
||||
export * from './useComposing';
|
||||
export * from './useExportByEcharts';
|
||||
41
webapp/packages/chat-sdk/src/hooks/useExportByEcharts.ts
Normal file
41
webapp/packages/chat-sdk/src/hooks/useExportByEcharts.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { message } from 'antd';
|
||||
import { ECharts } from 'echarts';
|
||||
|
||||
export interface ExportByEchartsProps {
|
||||
instanceRef: React.MutableRefObject<ECharts | undefined>;
|
||||
question: string;
|
||||
options?: Parameters<ECharts['getConnectedDataURL']>[0];
|
||||
}
|
||||
|
||||
export const useExportByEcharts = ({ instanceRef, question, options }: ExportByEchartsProps) => {
|
||||
const handleSaveAsImage = () => {
|
||||
if (instanceRef.current) {
|
||||
return instanceRef.current.getConnectedDataURL({
|
||||
type: 'png',
|
||||
pixelRatio: 2,
|
||||
backgroundColor: '#fff',
|
||||
excludeComponents: ['toolbox'],
|
||||
...options,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const downloadImage = (url: string) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${question}.png`;
|
||||
a.click();
|
||||
};
|
||||
|
||||
const downloadChartAsImage = () => {
|
||||
const url = handleSaveAsImage();
|
||||
if (url) {
|
||||
downloadImage(url);
|
||||
message.success('导出图片成功');
|
||||
} else {
|
||||
message.error('该条消息暂不支持导出图片');
|
||||
}
|
||||
};
|
||||
|
||||
return { downloadChartAsImage };
|
||||
};
|
||||
25
webapp/packages/chat-sdk/src/hooks/useMethodRegister.ts
Normal file
25
webapp/packages/chat-sdk/src/hooks/useMethodRegister.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
export const useMethodRegister = (fallback?: (...args: any[]) => any) => {
|
||||
const methodStore = useRef<Map<string, (...args: any[]) => any>>(new Map());
|
||||
|
||||
const register = useCallback<(key: string, method: (...args: any[]) => any) => any>(
|
||||
(key, method) => {
|
||||
methodStore.current.set(key, method);
|
||||
},
|
||||
[methodStore]
|
||||
);
|
||||
|
||||
const call = useCallback<(key: string, ...args: any[]) => any>(
|
||||
(key, ...args) => {
|
||||
const method = methodStore.current.get(key);
|
||||
if (method) {
|
||||
return method(...args);
|
||||
}
|
||||
return fallback?.(...args);
|
||||
},
|
||||
[methodStore]
|
||||
);
|
||||
|
||||
return { register, call };
|
||||
};
|
||||
Reference in New Issue
Block a user