@alicloud/appflow-chat 0.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-ZH.md +188 -0
- package/README.md +190 -0
- package/dist/appflow-chat.cjs.js +1903 -0
- package/dist/appflow-chat.esm.js +36965 -0
- package/dist/types/index.d.ts +862 -0
- package/package.json +87 -0
- package/src/components/DocReferences.tsx +64 -0
- package/src/components/HumanVerify/CustomParamsRenderer/ArrayField.tsx +394 -0
- package/src/components/HumanVerify/CustomParamsRenderer/FieldRenderer.tsx +202 -0
- package/src/components/HumanVerify/CustomParamsRenderer/ObjectField.tsx +126 -0
- package/src/components/HumanVerify/CustomParamsRenderer/index.tsx +166 -0
- package/src/components/HumanVerify/CustomParamsRenderer/types.ts +203 -0
- package/src/components/HumanVerify/HistoryCard.tsx +156 -0
- package/src/components/HumanVerify/HumanVerify.tsx +184 -0
- package/src/components/HumanVerify/index.ts +11 -0
- package/src/components/MarkdownRenderer.tsx +195 -0
- package/src/components/MessageBubble.tsx +400 -0
- package/src/components/RichMessageBubble.tsx +283 -0
- package/src/components/WebSearchPanel.tsx +68 -0
- package/src/context/RichBubble.tsx +21 -0
- package/src/core/BubbleContent.tsx +75 -0
- package/src/core/RichBubbleContent.tsx +324 -0
- package/src/core/SourceContent.tsx +285 -0
- package/src/core/WebSearchContent.tsx +219 -0
- package/src/core/index.ts +16 -0
- package/src/hooks/usePreSignUpload.ts +36 -0
- package/src/index.ts +80 -0
- package/src/markdown/components/Chart.tsx +120 -0
- package/src/markdown/components/Error.tsx +39 -0
- package/src/markdown/components/FileDisplay.tsx +246 -0
- package/src/markdown/components/Loading.tsx +41 -0
- package/src/markdown/components/SyntaxHighlight.tsx +182 -0
- package/src/markdown/index.tsx +250 -0
- package/src/markdown/styled.ts +234 -0
- package/src/markdown/utils/dataProcessor.ts +89 -0
- package/src/services/ChatService.ts +926 -0
- package/src/utils/fetchEventSource.ts +65 -0
- package/src/utils/loadEcharts.ts +32 -0
- package/src/utils/loadPrism.ts +156 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import ReactMarkdown, { Options } from 'react-markdown';
|
|
3
|
+
import remarkMath from 'remark-math';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import rehypeKatex from 'rehype-katex';
|
|
6
|
+
import { Image } from 'antd';
|
|
7
|
+
import { ASyntaxHighLight } from './components/SyntaxHighlight';
|
|
8
|
+
import rehypeRaw from 'rehype-raw';
|
|
9
|
+
import remarkBreaks from 'remark-breaks';
|
|
10
|
+
import { StyledContentRender } from './styled';
|
|
11
|
+
import FileDisplay from './components/FileDisplay';
|
|
12
|
+
import Chart from './components/Chart';
|
|
13
|
+
import { convertTableDataToMarkdown } from './utils/dataProcessor';
|
|
14
|
+
import Loading from './components/Loading';
|
|
15
|
+
import Error from './components/Error';
|
|
16
|
+
|
|
17
|
+
export interface MarkdownViewProps {
|
|
18
|
+
/** Markdown 内容 */
|
|
19
|
+
content: any;
|
|
20
|
+
/** 状态 */
|
|
21
|
+
status?: string;
|
|
22
|
+
/** 完整数据 */
|
|
23
|
+
fullData?: any;
|
|
24
|
+
/** 事件类型 */
|
|
25
|
+
eventType?: string;
|
|
26
|
+
/** 等待消息提示 */
|
|
27
|
+
waitingMessage?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const MarkdownView: React.FC<MarkdownViewProps> = ({
|
|
31
|
+
content,
|
|
32
|
+
waitingMessage = '',
|
|
33
|
+
}) => {
|
|
34
|
+
|
|
35
|
+
// markdown扩展配置
|
|
36
|
+
const markdownOptions: Options = useMemo(() => ({
|
|
37
|
+
remarkPlugins: [remarkMath, remarkGfm, remarkBreaks],
|
|
38
|
+
rehypePlugins: [rehypeKatex, rehypeRaw],
|
|
39
|
+
components: {
|
|
40
|
+
// 自定义组件渲染
|
|
41
|
+
img: ({ node, ...props }: any) => {
|
|
42
|
+
return (
|
|
43
|
+
<Image
|
|
44
|
+
style={{
|
|
45
|
+
maxWidth: '200px',
|
|
46
|
+
maxHeight: '150px',
|
|
47
|
+
objectFit: 'contain',
|
|
48
|
+
}}
|
|
49
|
+
src={props.src}
|
|
50
|
+
alt={props.alt}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
code: ({ node, className, ...props }: any) => {
|
|
55
|
+
// 元信息中解析语言类型
|
|
56
|
+
const languageType = /language-(\w+)/.exec(node?.properties?.className || '')?.[0];
|
|
57
|
+
const langFromMeta = node?.position?.start?.line === 2 ? 'language-file' : null;
|
|
58
|
+
const finalLang = languageType || langFromMeta;
|
|
59
|
+
|
|
60
|
+
// 文件信息块特殊处理
|
|
61
|
+
if (finalLang === 'language-file') {
|
|
62
|
+
// 解析原始文本内容
|
|
63
|
+
const content = String(props.children).replace(/\n$/, '');
|
|
64
|
+
const fileInfo = parseFileInfo(content);
|
|
65
|
+
return <FileDisplay fileInfo={fileInfo} />;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ant_table信息块特殊处理
|
|
69
|
+
if (finalLang === 'language-ant_table') {
|
|
70
|
+
try {
|
|
71
|
+
// 解析原始文本内容
|
|
72
|
+
const content = String(props?.children).replace(/\n$/, '');
|
|
73
|
+
const tableData = JSON.parse(content);
|
|
74
|
+
const markdownTable = convertTableDataToMarkdown(tableData);
|
|
75
|
+
return <MarkdownView content={markdownTable} />;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// 检查是否是流式输出导致的解析错误
|
|
78
|
+
if (!isValidJSON(String(props?.children))) {
|
|
79
|
+
return (
|
|
80
|
+
<Loading text={'表格加载中...'} />
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// 真实错误的处理
|
|
84
|
+
console.error("ant_table数据解析错误:", error);
|
|
85
|
+
return <Error text={'表格数据加载失败,请检查数据格式'} />;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// echarts图表信息块特殊处理
|
|
90
|
+
if (finalLang === 'language-echarts') {
|
|
91
|
+
try {
|
|
92
|
+
// 确保children是字符串
|
|
93
|
+
const codeContent = Array.isArray(props.children) ? props.children.join('') : props.children.toString();
|
|
94
|
+
|
|
95
|
+
// 检查是否是完整的JSON字符串
|
|
96
|
+
if (!isValidJSON(codeContent)) {
|
|
97
|
+
return <Loading />;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const chartContent = JSON.parse(codeContent);
|
|
101
|
+
|
|
102
|
+
// 检查图表配置是否完整
|
|
103
|
+
let chartOptions;
|
|
104
|
+
try {
|
|
105
|
+
if (typeof chartContent?.option === 'string') {
|
|
106
|
+
chartOptions = JSON.parse(chartContent?.option);
|
|
107
|
+
} else {
|
|
108
|
+
chartOptions = chartContent?.option;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 检查图表配置是否有必要的属性
|
|
112
|
+
if (!chartOptions || Object.keys(chartOptions).length === 0) {
|
|
113
|
+
return <Loading />;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div style={{ minWidth: '300px' }}>
|
|
118
|
+
<Chart options={chartOptions} />
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
} catch (configError) {
|
|
122
|
+
// 图表配置解析错误
|
|
123
|
+
console.error("图表配置解析错误:", configError);
|
|
124
|
+
return <Error text={'图表数据加载失败,请检查数据格式'} />;
|
|
125
|
+
}
|
|
126
|
+
} catch (error: any) {
|
|
127
|
+
// 如果是流式输出导致的json解析错误,显示加载状态
|
|
128
|
+
if (!isValidJSON(String(props.children))) {
|
|
129
|
+
return <Loading />;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 其他真实错误的处理
|
|
133
|
+
console.error("图表数据解析错误:", error);
|
|
134
|
+
return <Error text={'图表数据加载失败,请检查数据格式'} />;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return <ASyntaxHighLight>{props.children}</ASyntaxHighLight>;
|
|
139
|
+
},
|
|
140
|
+
p: (paragraph: any) => {
|
|
141
|
+
const { node }: any = paragraph || {};
|
|
142
|
+
if (node?.children[0]?.tagName === 'img') {
|
|
143
|
+
const image = node.children[0];
|
|
144
|
+
const isSlice = (Array.isArray(paragraph?.children) || typeof paragraph?.children?.slice === 'function');
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<>
|
|
148
|
+
<Image
|
|
149
|
+
style={{
|
|
150
|
+
maxWidth: '200px',
|
|
151
|
+
maxHeight: '150px',
|
|
152
|
+
objectFit: 'contain',
|
|
153
|
+
}}
|
|
154
|
+
src={image.properties.src}
|
|
155
|
+
className="max-w-full h-auto align-middle border-none rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 ease-in-out mt-2 mb-2"
|
|
156
|
+
alt={image.properties.alt}
|
|
157
|
+
/>
|
|
158
|
+
{isSlice && <p>
|
|
159
|
+
{paragraph?.children?.slice(1)}
|
|
160
|
+
</p>}
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return (
|
|
165
|
+
<p>
|
|
166
|
+
{paragraph?.children}
|
|
167
|
+
</p>
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
a: ({ children, href }) => (
|
|
171
|
+
<a href={href} target="_blank">
|
|
172
|
+
{children}
|
|
173
|
+
</a>
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
}), []);
|
|
177
|
+
|
|
178
|
+
// 检查是否是完整的JSON字符串
|
|
179
|
+
const isValidJSON = (str: string) => {
|
|
180
|
+
try {
|
|
181
|
+
const firstChar = str?.trim()[0];
|
|
182
|
+
const lastChar = str?.trim()[str?.trim().length - 1];
|
|
183
|
+
return (firstChar === '{' && lastChar === '}') || (firstChar === '[' && lastChar === ']');
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// 将think标签转换为details标签
|
|
190
|
+
const convertThinkToDetails = (content: string) => {
|
|
191
|
+
return content?.replace(
|
|
192
|
+
/<think>([\s\S]*?)<\/think>/g,
|
|
193
|
+
'<details open><summary>深度思考</summary><pre class="think">$1</pre></details>'
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// 将数学公式转换为markdown
|
|
198
|
+
const coverMath2Markdown = (v: string) => {
|
|
199
|
+
if (!v) {
|
|
200
|
+
return `<span><span class="prompt-message">${waitingMessage}</span><span class="prompt-loading-cursor">...</span></span>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const mathContent = `${v?.replace(/\\\[.*?\\\]/gims, (str) => {
|
|
204
|
+
const line = str.split(/[\r\n]/g);
|
|
205
|
+
|
|
206
|
+
if (line[0].trim() === '\\[') {
|
|
207
|
+
return '$$' + str.slice(2, -2) + '$$';
|
|
208
|
+
} else {
|
|
209
|
+
return '$' + str.slice(2, -2) + '$';
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
.replace(/\\\(.*?\\\)/gims, (str) => {
|
|
213
|
+
return '$' + str.slice(2, -2) + '$';
|
|
214
|
+
})}`
|
|
215
|
+
|
|
216
|
+
return convertThinkToDetails(mathContent);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 解析文件信息
|
|
220
|
+
const parseFileInfo = (content: string) => {
|
|
221
|
+
const lines = content.split('\n');
|
|
222
|
+
const fileInfo = {
|
|
223
|
+
name: '',
|
|
224
|
+
size: '',
|
|
225
|
+
type: 'unknown'
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
lines.forEach(line => {
|
|
229
|
+
const [key, value] = line.split(':').map(part => part.trim());
|
|
230
|
+
|
|
231
|
+
if (key && value) {
|
|
232
|
+
if (key.toLowerCase() === '名称') fileInfo.name = value;
|
|
233
|
+
if (key.toLowerCase() === '大小') fileInfo.size = value;
|
|
234
|
+
if (key.toLowerCase() === '类型') fileInfo.type = value;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return fileInfo;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<StyledContentRender>
|
|
243
|
+
<ReactMarkdown {...markdownOptions}>
|
|
244
|
+
{`${coverMath2Markdown(content)}`}
|
|
245
|
+
</ReactMarkdown>
|
|
246
|
+
</StyledContentRender>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export default MarkdownView;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const StyledContentRender = styled.div<{
|
|
4
|
+
color?: string;
|
|
5
|
+
bg_color?: string;
|
|
6
|
+
second_color?: string;
|
|
7
|
+
primary_color?: string;
|
|
8
|
+
loading_color?: string;
|
|
9
|
+
}>`
|
|
10
|
+
position: relative;
|
|
11
|
+
|
|
12
|
+
> p {
|
|
13
|
+
margin-bottom: 16px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
> p:last-child {
|
|
17
|
+
margin-bottom: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@keyframes dots {
|
|
21
|
+
0%, 20% {
|
|
22
|
+
color: ${({ loading_color }) => loading_color};
|
|
23
|
+
text-shadow: ${({ loading_color }) => `.25em 0 0 ${loading_color}, .5em 0 0 ${loading_color}`};
|
|
24
|
+
}
|
|
25
|
+
40% {
|
|
26
|
+
color: ${({ primary_color }) => primary_color};
|
|
27
|
+
text-shadow: ${({ loading_color }) => `.25em 0 0 ${loading_color}, .5em 0 0 ${loading_color}`};
|
|
28
|
+
}
|
|
29
|
+
60% {
|
|
30
|
+
text-shadow: ${({ primary_color, loading_color }) => `.25em 0 0 ${primary_color}, .5em 0 0 ${loading_color}`};
|
|
31
|
+
}
|
|
32
|
+
80%, 100% {
|
|
33
|
+
text-shadow: ${({ primary_color }) => `.25em 0 0 ${primary_color}, .5em 0 0 ${primary_color}`};
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@keyframes blink-dots {
|
|
39
|
+
0%,
|
|
40
|
+
80%,
|
|
41
|
+
100% {
|
|
42
|
+
box-shadow: 0 -1em 0 -1.3em #5584ff;
|
|
43
|
+
}
|
|
44
|
+
40% {
|
|
45
|
+
box-shadow: 0 -1em 0 0 #5584ff;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.prompt-message {
|
|
50
|
+
font-size: 12px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.prompt-loading {
|
|
54
|
+
margin-left: 2px;
|
|
55
|
+
color: ${({ second_color }) => second_color};
|
|
56
|
+
position: relative;
|
|
57
|
+
|
|
58
|
+
.dots {
|
|
59
|
+
//text-align: center;
|
|
60
|
+
//font-size: 1.5em;
|
|
61
|
+
//letter-spacing: 2px;
|
|
62
|
+
//animation: dots 1s steps(5, end) infinite;
|
|
63
|
+
|
|
64
|
+
display: inline-block;
|
|
65
|
+
position: relative;
|
|
66
|
+
text-indent: -9999em;
|
|
67
|
+
animation-delay: -0.16s;
|
|
68
|
+
margin: 0 auto !important;
|
|
69
|
+
transform: scale(0.5);
|
|
70
|
+
top: 0.6em;
|
|
71
|
+
left: 1.5em;
|
|
72
|
+
|
|
73
|
+
&:before,
|
|
74
|
+
&:after,
|
|
75
|
+
& {
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
width: 1em;
|
|
78
|
+
height: 1em;
|
|
79
|
+
animation: 1.8s ease-in-out 0s infinite normal none running blink-dots;
|
|
80
|
+
}
|
|
81
|
+
&:before {
|
|
82
|
+
left: -2em;
|
|
83
|
+
animation-delay: -0.32s;
|
|
84
|
+
}
|
|
85
|
+
&:after {
|
|
86
|
+
left: 2em;
|
|
87
|
+
}
|
|
88
|
+
&:before,
|
|
89
|
+
&:after {
|
|
90
|
+
content: '';
|
|
91
|
+
position: absolute;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
h1,h2,h3,h4,h5,h6 {
|
|
97
|
+
padding: 10px;
|
|
98
|
+
color: ${({ color }) => color};
|
|
99
|
+
// background: ${({ bg_color }) => bg_color}; // TODO dark mode ?
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
h1 {
|
|
103
|
+
font-size: 18px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
h2 {
|
|
107
|
+
font-size: 16px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
h3,h4 {
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ul,ol {
|
|
115
|
+
padding-left: 2em;
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
gap: 8px;
|
|
119
|
+
list-style-type: disc;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ul ul,
|
|
123
|
+
ol ul {
|
|
124
|
+
list-style-type: circle;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ul ul ul,
|
|
128
|
+
ul ol ul,
|
|
129
|
+
ol ul ul,
|
|
130
|
+
ol ol ul {
|
|
131
|
+
list-style-type: square;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ol {
|
|
135
|
+
list-style-type: decimal;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
li {
|
|
139
|
+
white-space: normal;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
details {
|
|
143
|
+
font-size: 12px;
|
|
144
|
+
color: rgb(106 105 105);
|
|
145
|
+
white-space: normal;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
details:has(pre:empty) {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
summary {
|
|
153
|
+
margin-left: -4px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
pre.think {
|
|
157
|
+
font-size: 12px;
|
|
158
|
+
color: #a6a6a6;
|
|
159
|
+
border-left: 1px #a6a6a6 solid;
|
|
160
|
+
display: inline-block;
|
|
161
|
+
white-space: pre-wrap;
|
|
162
|
+
overflow-wrap: break-word;
|
|
163
|
+
padding-left: 10px;
|
|
164
|
+
margin-top: 8px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pre.think > p {
|
|
168
|
+
margin-top: 10px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
table {
|
|
172
|
+
display: block;
|
|
173
|
+
overflow-x: auto;
|
|
174
|
+
width: max-content;
|
|
175
|
+
max-width: 100%;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
th, td {
|
|
179
|
+
border: 1px solid #d9d9d9;
|
|
180
|
+
padding: 12px;
|
|
181
|
+
text-align: center;
|
|
182
|
+
white-space: nowrap;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* 用于 coverMath2Markdown 的加载动画 */
|
|
186
|
+
@keyframes cursorImg {
|
|
187
|
+
0%, 100% {
|
|
188
|
+
opacity: 1;
|
|
189
|
+
}
|
|
190
|
+
50% {
|
|
191
|
+
opacity: 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.prompt-loading-cursor {
|
|
196
|
+
color: #5584ff;
|
|
197
|
+
animation: cursorImg 1s infinite steps(1, start);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* KaTeX 数学公式样式 */
|
|
201
|
+
.katex {
|
|
202
|
+
font: inherit;
|
|
203
|
+
text-rendering: auto;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.katex-display {
|
|
207
|
+
display: block;
|
|
208
|
+
margin: 1em 0;
|
|
209
|
+
text-align: center;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* 数学符号和布局样式 */
|
|
213
|
+
.katex .mord,
|
|
214
|
+
.katex .mbin,
|
|
215
|
+
.katex .mrel,
|
|
216
|
+
.katex .mopen,
|
|
217
|
+
.katex .mclose,
|
|
218
|
+
.katex .mpunct,
|
|
219
|
+
.katex .minner {
|
|
220
|
+
font-family: inherit;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* 数学布局样式 */
|
|
224
|
+
.katex .vlist-t,
|
|
225
|
+
.katex .vlist-r,
|
|
226
|
+
.katex .vlist {
|
|
227
|
+
display: inline-block;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.katex .mfrac {
|
|
231
|
+
display: inline-block;
|
|
232
|
+
vertical-align: middle;
|
|
233
|
+
}
|
|
234
|
+
`;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// 将表格数据转换为Markdown表格格式
|
|
2
|
+
export const convertTableDataToMarkdown = (tableData: any): string => {
|
|
3
|
+
try {
|
|
4
|
+
const { column, data } = tableData;
|
|
5
|
+
|
|
6
|
+
if (!Array.isArray(column) || !Array.isArray(data)) {
|
|
7
|
+
return '表格数据格式错误';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (column.length === 0 || data.length === 0) {
|
|
11
|
+
return '暂无数据';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 构建Markdown表格,添加序号列
|
|
15
|
+
let markdown = '';
|
|
16
|
+
|
|
17
|
+
// 表头 - 添加序号列
|
|
18
|
+
markdown += '| 序号 | ' + column.join(' | ') + ' |\n';
|
|
19
|
+
|
|
20
|
+
// 分隔线 - 序号列居中对齐,其他列默认左对齐
|
|
21
|
+
markdown += '| :---: | ' + column.map(() => '---').join(' | ') + ' |\n';
|
|
22
|
+
|
|
23
|
+
// 数据行 - 添加序号
|
|
24
|
+
data.forEach((row, index) => {
|
|
25
|
+
const rowData = column.map(col => {
|
|
26
|
+
const value = row[col];
|
|
27
|
+
return value !== undefined ? String(value) : '-';
|
|
28
|
+
});
|
|
29
|
+
// 序号从1开始
|
|
30
|
+
markdown += '| ' + (index + 1) + ' | ' + rowData.join(' | ') + ' |\n';
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return markdown;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('转换表格数据失败:', error);
|
|
36
|
+
return '表格数据转换失败';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// 检查内容是否为表格数据
|
|
41
|
+
const isTableData = (content: string): boolean => {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(content);
|
|
44
|
+
return parsed && typeof parsed === 'object' &&
|
|
45
|
+
Array.isArray(parsed.column) &&
|
|
46
|
+
Array.isArray(parsed.data);
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// 处理步骤内容
|
|
53
|
+
export const processStepContent = (content: any, stepType?: string): string | null => {
|
|
54
|
+
if (!content) return null;
|
|
55
|
+
|
|
56
|
+
// 如果是表格类型,转换为Markdown表格格式
|
|
57
|
+
if (stepType === 'ant_table' && isTableData(content)) {
|
|
58
|
+
try {
|
|
59
|
+
const tableData = JSON.parse(content);
|
|
60
|
+
const markdownTable = convertTableDataToMarkdown(tableData);
|
|
61
|
+
return markdownTable;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('解析表格数据失败:', error);
|
|
64
|
+
return '```json\n' + content + '\n```';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 如果是echart类型,包装为echarts代码块
|
|
69
|
+
if (stepType === 'echart') {
|
|
70
|
+
return '```echarts\n' + content + '\n```';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 如果是code类型,包装为sql代码块
|
|
74
|
+
if (stepType === 'code') {
|
|
75
|
+
return '```sql\n' + content + '\n```';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 如果是JSON格式,格式化显示
|
|
79
|
+
try {
|
|
80
|
+
JSON.parse(content);
|
|
81
|
+
const parsed = JSON.parse(content);
|
|
82
|
+
return '```json\n' + JSON.stringify(parsed, null, 2) + '\n```';
|
|
83
|
+
} catch {
|
|
84
|
+
// 不是JSON,继续其他处理
|
|
85
|
+
console.log('不是JSON');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return content;
|
|
89
|
+
};
|