@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,156 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button } from 'antd';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import CustomParamsRenderer from './CustomParamsRenderer';
|
|
5
|
+
import { CustomParamSchema } from './CustomParamsRenderer/types';
|
|
6
|
+
|
|
7
|
+
// ==================== Styled Components ====================
|
|
8
|
+
|
|
9
|
+
const HistoryCardContainer = styled.div`
|
|
10
|
+
margin-top: 12px;
|
|
11
|
+
width: 400px;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const StatusContainer = styled.div<{ $approved: boolean }>`
|
|
16
|
+
margin-top: 16px;
|
|
17
|
+
padding: 12px 16px;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 12px;
|
|
22
|
+
background-color: ${props => props.$approved ? 'rgb(238, 244, 248)' : 'rgb(252, 250, 245)'};
|
|
23
|
+
border: 1px solid ${props => props.$approved ? 'rgb(238, 244, 248)' : 'rgb(252, 250, 245)'};
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const StatusContent = styled.div`
|
|
27
|
+
flex: 1;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const StatusText = styled.div`
|
|
31
|
+
font-size: 14px;
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
margin-bottom: 4px;
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
// ==================== Types ====================
|
|
37
|
+
|
|
38
|
+
export interface HistoryCardProps {
|
|
39
|
+
/** 审批状态 */
|
|
40
|
+
approvalStatus?: string;
|
|
41
|
+
/** 表单值 */
|
|
42
|
+
formValues?: Record<string, any>;
|
|
43
|
+
/** 表单 Schema */
|
|
44
|
+
formSchema?: CustomParamSchema;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 将后端返回的小写 schema 转换为组件需要的大写格式
|
|
49
|
+
* @param schema 后端返回的 schema(小写字段名)
|
|
50
|
+
* @returns 转换后的 schema(大写字段名)
|
|
51
|
+
*/
|
|
52
|
+
export const convertSchemaToUpperCase = (schema: any): CustomParamSchema | undefined => {
|
|
53
|
+
if (!schema) return undefined;
|
|
54
|
+
|
|
55
|
+
const result: CustomParamSchema = {
|
|
56
|
+
Type: schema.type || 'string',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (schema.title) {
|
|
60
|
+
result.Title = schema.title;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (schema.description) {
|
|
64
|
+
result.Description = schema.description;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
68
|
+
result.Required = schema.required;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (schema.properties && typeof schema.properties === 'object') {
|
|
72
|
+
result.Properties = {};
|
|
73
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
74
|
+
const converted = convertSchemaToUpperCase(value);
|
|
75
|
+
if (converted) {
|
|
76
|
+
result.Properties[key] = converted;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (schema.items) {
|
|
82
|
+
result.Items = convertSchemaToUpperCase(schema.items);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* HistoryCard 历史卡片组件 (SDK 版本)
|
|
90
|
+
* 用于展示历史对话中的 card 类型消息(只读模式)
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* <HistoryCard
|
|
95
|
+
* approvalStatus="approved"
|
|
96
|
+
* formValues={{ name: 'test', age: 18 }}
|
|
97
|
+
* formSchema={schema}
|
|
98
|
+
* />
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export const HistoryCard: React.FC<HistoryCardProps> = ({
|
|
102
|
+
approvalStatus,
|
|
103
|
+
formValues = {},
|
|
104
|
+
formSchema,
|
|
105
|
+
}) => {
|
|
106
|
+
// 判断是否已提交
|
|
107
|
+
const isApproved = approvalStatus === 'approved';
|
|
108
|
+
|
|
109
|
+
// 如果没有表单 schema,只显示状态
|
|
110
|
+
if (!formSchema) {
|
|
111
|
+
return (
|
|
112
|
+
<HistoryCardContainer>
|
|
113
|
+
<StatusContainer $approved={isApproved}>
|
|
114
|
+
<StatusContent>
|
|
115
|
+
<StatusText>
|
|
116
|
+
{isApproved ? '已提交' : '待提交'}
|
|
117
|
+
</StatusText>
|
|
118
|
+
</StatusContent>
|
|
119
|
+
<Button
|
|
120
|
+
color="primary"
|
|
121
|
+
variant="filled"
|
|
122
|
+
disabled={true}
|
|
123
|
+
>
|
|
124
|
+
{isApproved ? '已提交' : '提交'}
|
|
125
|
+
</Button>
|
|
126
|
+
</StatusContainer>
|
|
127
|
+
</HistoryCardContainer>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<HistoryCardContainer>
|
|
133
|
+
<CustomParamsRenderer
|
|
134
|
+
schema={formSchema}
|
|
135
|
+
value={formValues}
|
|
136
|
+
disabled={true}
|
|
137
|
+
/>
|
|
138
|
+
<StatusContainer $approved={isApproved}>
|
|
139
|
+
<StatusContent>
|
|
140
|
+
<StatusText>
|
|
141
|
+
{isApproved ? '已提交' : '待提交'}
|
|
142
|
+
</StatusText>
|
|
143
|
+
</StatusContent>
|
|
144
|
+
<Button
|
|
145
|
+
color="primary"
|
|
146
|
+
variant="filled"
|
|
147
|
+
disabled={true}
|
|
148
|
+
>
|
|
149
|
+
{isApproved ? '已提交' : '提交'}
|
|
150
|
+
</Button>
|
|
151
|
+
</StatusContainer>
|
|
152
|
+
</HistoryCardContainer>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export default HistoryCard;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { Button, message } from 'antd';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import CustomParamsRenderer, { validateCustomParams, CustomParamSchema } from './CustomParamsRenderer';
|
|
5
|
+
|
|
6
|
+
// ==================== Styled Components ====================
|
|
7
|
+
|
|
8
|
+
const HumanVerifyContainer = styled.div`
|
|
9
|
+
margin-top: 12px;
|
|
10
|
+
width: 400px;
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const StatusContainer = styled.div<{ $approved: boolean }>`
|
|
15
|
+
margin-top: 16px;
|
|
16
|
+
padding: 12px 16px;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 12px;
|
|
21
|
+
background-color: ${props => props.$approved ? 'rgb(238, 244, 248)' : 'rgb(252, 250, 245)'};
|
|
22
|
+
border: 1px solid ${props => props.$approved ? 'rgb(238, 244, 248)' : 'rgb(252, 250, 245)'};
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const StatusContent = styled.div`
|
|
26
|
+
flex: 1;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const StatusText = styled.div`
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
font-weight: 500;
|
|
32
|
+
margin-bottom: 4px;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
// ==================== Types ====================
|
|
36
|
+
|
|
37
|
+
export interface HumanVerifyProps {
|
|
38
|
+
/** 验证ID */
|
|
39
|
+
verifyId?: string;
|
|
40
|
+
/** 会话 Webhook */
|
|
41
|
+
sessionWebhook?: string;
|
|
42
|
+
/** 是否已提交 */
|
|
43
|
+
approved?: boolean;
|
|
44
|
+
/** CustomParams 的 schema */
|
|
45
|
+
customParamsSchema?: CustomParamSchema;
|
|
46
|
+
/** CustomParams 的 key(用于提交时的字段名) */
|
|
47
|
+
customParamsKey?: string;
|
|
48
|
+
/** 提交回调函数 */
|
|
49
|
+
onSubmit?: (data: {
|
|
50
|
+
verifyId: string;
|
|
51
|
+
sessionWebhook: string;
|
|
52
|
+
status: string;
|
|
53
|
+
customParamsKey: string;
|
|
54
|
+
customParamsValue: Record<string, any>;
|
|
55
|
+
}) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* HumanVerify 人工审核组件 (SDK 版本)
|
|
60
|
+
* 用于展示需要人工审核的表单,并处理提交逻辑
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <HumanVerify
|
|
65
|
+
* verifyId="xxx"
|
|
66
|
+
* sessionWebhook="https://..."
|
|
67
|
+
* approved={false}
|
|
68
|
+
* customParamsSchema={schema}
|
|
69
|
+
* customParamsKey="customParams"
|
|
70
|
+
* onSubmit={(data) => {
|
|
71
|
+
* bot.postMessage({
|
|
72
|
+
* msgType: 'cardCallBack',
|
|
73
|
+
* data: {
|
|
74
|
+
* sessionWebhook: data.sessionWebhook,
|
|
75
|
+
* content: JSON.stringify({
|
|
76
|
+
* verifyId: data.verifyId,
|
|
77
|
+
* status: data.status,
|
|
78
|
+
* [data.customParamsKey]: data.customParamsValue,
|
|
79
|
+
* }),
|
|
80
|
+
* },
|
|
81
|
+
* });
|
|
82
|
+
* }}
|
|
83
|
+
* />
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const HumanVerify: React.FC<HumanVerifyProps> = ({
|
|
87
|
+
verifyId,
|
|
88
|
+
sessionWebhook,
|
|
89
|
+
approved = false,
|
|
90
|
+
customParamsSchema,
|
|
91
|
+
customParamsKey = 'customParams',
|
|
92
|
+
onSubmit,
|
|
93
|
+
}) => {
|
|
94
|
+
// CustomParams表单状态管理
|
|
95
|
+
const [customParamsValue, setCustomParamsValue] = useState<Record<string, any>>({});
|
|
96
|
+
// CustomParams验证错误状态
|
|
97
|
+
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
|
98
|
+
|
|
99
|
+
// 实时验证:当表单值变化时,如果已经有错误信息,则重新验证
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
// 只有在已经显示过错误的情况下才进行实时验证
|
|
102
|
+
if (Object.keys(validationErrors).length > 0 && customParamsSchema) {
|
|
103
|
+
const validation = validateCustomParams(customParamsSchema, customParamsValue);
|
|
104
|
+
|
|
105
|
+
if (validation.valid) {
|
|
106
|
+
// 如果验证通过,清空所有错误
|
|
107
|
+
setValidationErrors({});
|
|
108
|
+
} else {
|
|
109
|
+
// 如果仍有错误,更新错误信息
|
|
110
|
+
const errors: Record<string, string> = {};
|
|
111
|
+
validation.errors.forEach((err) => {
|
|
112
|
+
errors[err.field] = err.message;
|
|
113
|
+
});
|
|
114
|
+
setValidationErrors(errors);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}, [customParamsValue, customParamsSchema, validationErrors]);
|
|
118
|
+
|
|
119
|
+
// 处理审批按钮点击
|
|
120
|
+
const handleApprove = useCallback(() => {
|
|
121
|
+
if (!verifyId || !onSubmit) return;
|
|
122
|
+
|
|
123
|
+
// 验证表单
|
|
124
|
+
if (customParamsSchema) {
|
|
125
|
+
const validation = validateCustomParams(customParamsSchema, customParamsValue);
|
|
126
|
+
|
|
127
|
+
if (!validation.valid) {
|
|
128
|
+
// 转换错误格式
|
|
129
|
+
const errors: Record<string, string> = {};
|
|
130
|
+
validation.errors.forEach((err) => {
|
|
131
|
+
errors[err.field] = err.message;
|
|
132
|
+
});
|
|
133
|
+
setValidationErrors(errors);
|
|
134
|
+
|
|
135
|
+
// 提示用户
|
|
136
|
+
message.error('请填写所有必填项');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 清空错误
|
|
141
|
+
setValidationErrors({});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 调用提交回调
|
|
145
|
+
onSubmit({
|
|
146
|
+
verifyId,
|
|
147
|
+
sessionWebhook: sessionWebhook || '',
|
|
148
|
+
status: 'approve',
|
|
149
|
+
customParamsKey,
|
|
150
|
+
customParamsValue,
|
|
151
|
+
});
|
|
152
|
+
}, [verifyId, sessionWebhook, onSubmit, customParamsValue, customParamsSchema, customParamsKey]);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<HumanVerifyContainer>
|
|
156
|
+
{customParamsSchema && (
|
|
157
|
+
<CustomParamsRenderer
|
|
158
|
+
schema={customParamsSchema}
|
|
159
|
+
value={customParamsValue}
|
|
160
|
+
onChange={setCustomParamsValue}
|
|
161
|
+
errors={validationErrors}
|
|
162
|
+
disabled={approved}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
<StatusContainer $approved={approved}>
|
|
166
|
+
<StatusContent>
|
|
167
|
+
<StatusText>
|
|
168
|
+
{approved ? '已提交' : '待提交'}
|
|
169
|
+
</StatusText>
|
|
170
|
+
</StatusContent>
|
|
171
|
+
<Button
|
|
172
|
+
color="primary"
|
|
173
|
+
variant="filled"
|
|
174
|
+
onClick={handleApprove}
|
|
175
|
+
disabled={approved}
|
|
176
|
+
>
|
|
177
|
+
{'提交'}
|
|
178
|
+
</Button>
|
|
179
|
+
</StatusContainer>
|
|
180
|
+
</HumanVerifyContainer>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default HumanVerify;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { HumanVerify, type HumanVerifyProps } from './HumanVerify';
|
|
2
|
+
export { HistoryCard, type HistoryCardProps, convertSchemaToUpperCase } from './HistoryCard';
|
|
3
|
+
export {
|
|
4
|
+
CustomParamsRenderer,
|
|
5
|
+
useCustomParamsRenderer,
|
|
6
|
+
validateCustomParams,
|
|
7
|
+
type CustomParamSchema,
|
|
8
|
+
type CustomParamsRendererProps,
|
|
9
|
+
type ValidationResult,
|
|
10
|
+
type ValidationError,
|
|
11
|
+
} from './CustomParamsRenderer';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarkdownRenderer - SDK封装的Markdown渲染组件
|
|
3
|
+
* 内部复用 MarkdownView,提供简化的接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect } from 'react';
|
|
7
|
+
import styled from 'styled-components';
|
|
8
|
+
import { MarkdownView } from '@/markdown';
|
|
9
|
+
import { loadEchartsScript } from '@/utils/loadEcharts';
|
|
10
|
+
|
|
11
|
+
export interface MarkdownRendererProps {
|
|
12
|
+
/** Markdown内容 */
|
|
13
|
+
content: string;
|
|
14
|
+
/** 渲染状态,Running时显示加载动画 */
|
|
15
|
+
status?: 'Running' | 'Success' | 'Error';
|
|
16
|
+
/** 自定义类名 */
|
|
17
|
+
className?: string;
|
|
18
|
+
/** 自定义样式 */
|
|
19
|
+
style?: React.CSSProperties;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 样式隔离容器
|
|
23
|
+
const StyledContainer = styled.div`
|
|
24
|
+
/* 样式隔离 - 确保内部样式不受外部影响 */
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
line-height: 1.6;
|
|
28
|
+
color: #333;
|
|
29
|
+
|
|
30
|
+
/* 确保内部元素正常显示 */
|
|
31
|
+
* {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Markdown内容样式 */
|
|
36
|
+
p {
|
|
37
|
+
margin: 0 0 8px 0;
|
|
38
|
+
&:last-child {
|
|
39
|
+
margin-bottom: 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ul, ol {
|
|
44
|
+
margin: 8px 0;
|
|
45
|
+
padding-left: 20px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
li {
|
|
49
|
+
margin: 4px 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
code {
|
|
53
|
+
background: rgba(0, 0, 0, 0.05);
|
|
54
|
+
padding: 2px 6px;
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
57
|
+
font-size: 13px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pre {
|
|
61
|
+
background: #f6f8fa;
|
|
62
|
+
padding: 12px;
|
|
63
|
+
border-radius: 6px;
|
|
64
|
+
overflow-x: auto;
|
|
65
|
+
|
|
66
|
+
code {
|
|
67
|
+
background: transparent;
|
|
68
|
+
padding: 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
blockquote {
|
|
73
|
+
margin: 8px 0;
|
|
74
|
+
padding: 8px 16px;
|
|
75
|
+
border-left: 4px solid #ddd;
|
|
76
|
+
background: #f9f9f9;
|
|
77
|
+
color: #666;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
table {
|
|
81
|
+
border-collapse: collapse;
|
|
82
|
+
width: 100%;
|
|
83
|
+
margin: 8px 0;
|
|
84
|
+
|
|
85
|
+
th, td {
|
|
86
|
+
border: 1px solid #ddd;
|
|
87
|
+
padding: 8px 12px;
|
|
88
|
+
text-align: left;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
th {
|
|
92
|
+
background: #f6f8fa;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
tr:nth-child(even) {
|
|
97
|
+
background: #f9f9f9;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
img {
|
|
102
|
+
max-width: 100%;
|
|
103
|
+
height: auto;
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
a {
|
|
108
|
+
color: #1890ff;
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
|
|
111
|
+
&:hover {
|
|
112
|
+
text-decoration: underline;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
h1, h2, h3, h4, h5, h6 {
|
|
117
|
+
margin: 16px 0 8px 0;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
line-height: 1.4;
|
|
120
|
+
|
|
121
|
+
&:first-child {
|
|
122
|
+
margin-top: 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
h1 { font-size: 24px; }
|
|
127
|
+
h2 { font-size: 20px; }
|
|
128
|
+
h3 { font-size: 18px; }
|
|
129
|
+
h4 { font-size: 16px; }
|
|
130
|
+
h5 { font-size: 14px; }
|
|
131
|
+
h6 { font-size: 14px; color: #666; }
|
|
132
|
+
|
|
133
|
+
hr {
|
|
134
|
+
border: none;
|
|
135
|
+
border-top: 1px solid #ddd;
|
|
136
|
+
margin: 16px 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* 深度思考样式 */
|
|
140
|
+
details {
|
|
141
|
+
margin: 8px 0;
|
|
142
|
+
padding: 8px 12px;
|
|
143
|
+
background: #f6f8fa;
|
|
144
|
+
border-radius: 6px;
|
|
145
|
+
|
|
146
|
+
summary {
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
color: #666;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
pre.think {
|
|
153
|
+
margin: 8px 0 0 0;
|
|
154
|
+
padding: 8px;
|
|
155
|
+
background: #fff;
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
white-space: pre-wrap;
|
|
158
|
+
font-size: 13px;
|
|
159
|
+
color: #666;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* MarkdownRenderer - Markdown渲染组件
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```tsx
|
|
169
|
+
* <MarkdownRenderer
|
|
170
|
+
* content="# 标题\n这是正文内容,支持**加粗**和*斜体*"
|
|
171
|
+
* status="Success"
|
|
172
|
+
* />
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
|
|
176
|
+
content,
|
|
177
|
+
status = 'Success',
|
|
178
|
+
className,
|
|
179
|
+
style,
|
|
180
|
+
}) => {
|
|
181
|
+
// 自动加载ECharts(如果内容中包含echarts代码块)
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (content?.includes('```echarts')) {
|
|
184
|
+
loadEchartsScript().catch(console.error);
|
|
185
|
+
}
|
|
186
|
+
}, [content]);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<StyledContainer className={`appflow-sdk-markdown ${className || ''}`} style={style}>
|
|
190
|
+
<MarkdownView content={content} status={status} />
|
|
191
|
+
</StyledContainer>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export default MarkdownRenderer;
|