Skip to content

Commit 7be8ece

Browse files
ctrlz526FinleyGe
andauthored
feat: add new Markdown components and enhance i18n support (#5622)
* feat: add new Markdown components and enhance i18n support (cherry picked from commit b0b6cc7ad49ac35f070389744a764928d7103074) * feat: support structured data render --------- Co-authored-by: FinleyGe <m13203533462@163.com>
1 parent 9f8f8dd commit 7be8ece

File tree

15 files changed

+945
-50
lines changed

15 files changed

+945
-50
lines changed

packages/web/i18n/en/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@
372372
"core.chat.feedback.Feedback Close": "Close Feedback",
373373
"core.chat.feedback.No Content": "User Did Not Provide Specific Feedback Content",
374374
"core.chat.feedback.Read User dislike": "User Disagrees\nClick to View Content",
375+
"core.chat.indicator.no_data": "There is no data yet",
376+
"core.chat.table.per_page": "{{num}} per page",
375377
"core.chat.logs.api": "API Call",
376378
"core.chat.logs.feishu": "Feishu",
377379
"core.chat.logs.free_login": "No login link",
@@ -392,6 +394,7 @@
392394
"core.chat.quote.beforeUpdate": "Before update",
393395
"core.chat.response.Complete Response": "Complete Response",
394396
"core.chat.response.Extension model": "Question Optimization Model",
397+
"core.chat.response.Fold response": "Fold",
395398
"core.chat.response.Read complete response": "View Details",
396399
"core.chat.response.Read complete response tips": "Click to View Detailed Process",
397400
"core.chat.response.Tool call input tokens": "Tool Call Input Tokens Consumption",

packages/web/i18n/zh-CN/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@
372372
"core.chat.feedback.Feedback Close": "关闭反馈",
373373
"core.chat.feedback.No Content": "用户没有填写具体反馈内容",
374374
"core.chat.feedback.Read User dislike": "用户表示反对\n点击查看内容",
375+
"core.chat.indicator.no_data": "暂时没有该数据",
376+
"core.chat.table.per_page": "{{num}} 条/页",
375377
"core.chat.logs.api": "API 调用",
376378
"core.chat.logs.feishu": "飞书",
377379
"core.chat.logs.free_login": "免登录链接",
@@ -392,6 +394,7 @@
392394
"core.chat.quote.beforeUpdate": "更新前",
393395
"core.chat.response.Complete Response": "完整响应",
394396
"core.chat.response.Extension model": "问题优化模型",
397+
"core.chat.response.Fold response": "收起",
395398
"core.chat.response.Read complete response": "查看详情",
396399
"core.chat.response.Read complete response tips": "点击查看详细流程",
397400
"core.chat.response.Tool call input tokens": "工具调用输入 Tokens",

packages/web/i18n/zh-Hant/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@
372372
"core.chat.feedback.Feedback Close": "關閉回饋",
373373
"core.chat.feedback.No Content": "使用者未提供具體回饋內容",
374374
"core.chat.feedback.Read User dislike": "使用者表示反對\n點選檢視內容",
375+
"core.chat.indicator.no_data": "暫時沒有該數據",
376+
"core.chat.table.per_page": "{{num}} 筆/頁",
375377
"core.chat.logs.api": "API 呼叫",
376378
"core.chat.logs.feishu": "飛書",
377379
"core.chat.logs.free_login": "免登入連結",
@@ -392,6 +394,7 @@
392394
"core.chat.quote.beforeUpdate": "更新前",
393395
"core.chat.response.Complete Response": "完整回應",
394396
"core.chat.response.Extension model": "問題最佳化模型",
397+
"core.chat.response.Fold response": "收起",
395398
"core.chat.response.Read complete response": "檢視詳細資料",
396399
"core.chat.response.Read complete response tips": "點選檢視詳細流程",
397400
"core.chat.response.Tool call input tokens": "工具呼叫輸入 Token 消耗",

pnpm-lock.yaml

Lines changed: 93 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

projects/app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"axios": "^1.12.1",
3030
"date-fns": "2.30.0",
3131
"dayjs": "^1.11.7",
32+
"dompurify": "^3.2.7",
3233
"echarts": "5.4.1",
3334
"echarts-gl": "2.0.9",
3435
"framer-motion": "9.1.7",
@@ -58,6 +59,7 @@
5859
"recharts": "^2.15.0",
5960
"rehype-external-links": "^3.0.0",
6061
"rehype-katex": "^7.0.0",
62+
"rehype-raw": "^7.0.0",
6163
"remark-breaks": "^4.0.0",
6264
"remark-gfm": "^4.0.0",
6365
"remark-math": "^6.0.0",
@@ -68,6 +70,7 @@
6870
},
6971
"devDependencies": {
7072
"@svgr/webpack": "^6.5.1",
73+
"@types/dompurify": "^3.2.0",
7174
"@types/js-yaml": "^4.0.9",
7275
"@types/jsonwebtoken": "^9.0.3",
7376
"@types/lodash": "^4.14.191",

projects/app/src/components/Markdown/A.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import Markdown from '.';
2323
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
2424
import { Types } from 'mongoose';
2525
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
26-
import { useCreation } from 'ahooks';
2726

2827
export type AProps = {
2928
chatAuthData?: {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
import { Box } from '@chakra-ui/react';
3+
4+
const Divider: React.FC = () => {
5+
return <Box width="100%" height="1px" bg="gray.200" my={4} mx="auto" />;
6+
};
7+
8+
export default Divider;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react';
2+
import { Box, Text, VStack, Flex } from '@chakra-ui/react';
3+
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
4+
5+
type IndicatorCardProps = {
6+
dataList: {
7+
name: string;
8+
value: string | number;
9+
}[];
10+
};
11+
12+
const IndicatorCard: React.FC<IndicatorCardProps> = ({ dataList }) => {
13+
const { t } = useSafeTranslation();
14+
if (!dataList || !Array.isArray(dataList) || dataList.length === 0) {
15+
return <Box>No indicator data available</Box>;
16+
}
17+
18+
return (
19+
<VStack align="stretch">
20+
{dataList.map((indicator, index) => (
21+
<Flex align="stretch" w="250px" key={index} gap={1} mt={1}>
22+
<Flex w="5px" bg="blue.500"></Flex>
23+
<Flex
24+
flex="1"
25+
borderRadius="md"
26+
bg="gray.100"
27+
display="flex"
28+
flexDirection="column"
29+
overflow="hidden"
30+
>
31+
{/* indicator name */}
32+
<Flex w="full">
33+
<Text
34+
color="gray.800"
35+
fontSize="sm"
36+
fontWeight="normal"
37+
textAlign="right"
38+
flex="1"
39+
noOfLines={1}
40+
>
41+
{indicator.name}
42+
</Text>
43+
</Flex>
44+
45+
{/* indicator value and unit */}
46+
<Flex w="full">
47+
<Text
48+
color="blue.500"
49+
fontSize="lg"
50+
fontWeight="bold"
51+
textAlign="right"
52+
flex="1"
53+
noOfLines={1}
54+
>
55+
{indicator.value || t('common:core.chat.indicator.no_data')}
56+
</Text>
57+
</Flex>
58+
</Flex>
59+
</Flex>
60+
))}
61+
</VStack>
62+
);
63+
};
64+
65+
export default IndicatorCard;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { Box, Text, Link, Flex } from '@chakra-ui/react';
3+
4+
const LinkBlock: React.FC<{ data: { text: string; url: string } }> = ({ data }) => {
5+
const handleClick = () => {
6+
window.open(data.url, '_blank', 'noopener,noreferrer');
7+
};
8+
9+
return (
10+
<Box my={4}>
11+
<Link
12+
onClick={handleClick}
13+
cursor="pointer"
14+
textDecoration="none"
15+
_hover={{ textDecoration: 'none' }}
16+
>
17+
<Text
18+
fontWeight="medium"
19+
color="blue.600"
20+
_hover={{ color: 'blue.700' }}
21+
noOfLines={1}
22+
flex="1"
23+
>
24+
{data.text}
25+
</Text>
26+
</Link>
27+
</Box>
28+
);
29+
};
30+
31+
export default LinkBlock;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React, { useState, useMemo } from 'react';
2+
import { Box, Flex, Text, Grid } from '@chakra-ui/react';
3+
import Icon from '@fastgpt/web/components/common/Icon';
4+
import MySelect from '@fastgpt/web/components/common/MySelect';
5+
import { useSafeTranslation } from '@fastgpt/web/hooks/useSafeTranslation';
6+
7+
const TableBlock: React.FC<{ code: string }> = ({ code }) => {
8+
const { t } = useSafeTranslation();
9+
const tableData = JSON.parse(code);
10+
const [currentPage, setCurrentPage] = useState(1);
11+
const [perPage, setPerPage] = useState(10);
12+
13+
const headers = Object.keys(tableData[0]);
14+
15+
// calculate paginated data
16+
const { paginatedData, totalPages } = useMemo(() => {
17+
const total = Math.ceil(tableData.length / perPage);
18+
const startIndex = (currentPage - 1) * perPage;
19+
const endIndex = startIndex + perPage;
20+
const paginated = tableData.slice(startIndex, endIndex);
21+
22+
return {
23+
paginatedData: paginated,
24+
totalPages: total
25+
};
26+
}, [tableData, currentPage, perPage]);
27+
28+
const handlePrevPage = () => {
29+
setCurrentPage((prev) => Math.max(prev - 1, 1));
30+
};
31+
32+
const handleNextPage = () => {
33+
setCurrentPage((prev) => Math.min(prev + 1, totalPages));
34+
};
35+
36+
const handlePerPageChange = (value: string) => {
37+
setPerPage(Number(value));
38+
setCurrentPage(1); // reset to first page
39+
};
40+
41+
return (
42+
<Box my={4}>
43+
<Flex overflowX="auto">
44+
<table style={{ width: '100%', borderCollapse: 'collapse', border: '1px solid #e2e8f0' }}>
45+
<thead>
46+
<tr style={{ backgroundColor: '#f7fafc' }}>
47+
{headers.map((header, index) => (
48+
<th
49+
key={index}
50+
style={{
51+
padding: '12px',
52+
border: '1px solid #e2e8f0',
53+
textAlign: 'left',
54+
fontWeight: 'bold',
55+
whiteSpace: 'nowrap'
56+
}}
57+
>
58+
{header}
59+
</th>
60+
))}
61+
</tr>
62+
</thead>
63+
<tbody>
64+
{paginatedData.map((row: any, rowIndex: number) => (
65+
<tr
66+
key={rowIndex}
67+
style={{ backgroundColor: rowIndex % 2 === 0 ? '#ffffff' : '#f9f9f9' }}
68+
>
69+
{headers.map((header, colIndex) => (
70+
<td
71+
key={colIndex}
72+
style={{
73+
padding: '12px',
74+
border: '1px solid #e2e8f0',
75+
whiteSpace: 'nowrap'
76+
}}
77+
>
78+
{row[header] || ''}
79+
</td>
80+
))}
81+
</tr>
82+
))}
83+
</tbody>
84+
</table>
85+
</Flex>
86+
87+
<Grid width="full" gridTemplateColumns="1fr auto 1fr" alignItems="center" gap={4}>
88+
<Flex gap={1} align="center" gridColumn="2">
89+
<Icon
90+
name="core/chat/chevronLeft"
91+
w="16px"
92+
height="16px"
93+
cursor={currentPage === 1 ? 'not-allowed' : 'pointer'}
94+
opacity={currentPage === 1 ? 0.5 : 1}
95+
onClick={currentPage === 1 ? undefined : handlePrevPage}
96+
/>
97+
<Text>
98+
{currentPage} / {totalPages}
99+
</Text>
100+
<Icon
101+
name="core/chat/chevronRight"
102+
w="16px"
103+
height="16px"
104+
cursor={currentPage === totalPages ? 'not-allowed' : 'pointer'}
105+
opacity={currentPage === totalPages ? 0.5 : 1}
106+
onClick={currentPage === totalPages ? undefined : handleNextPage}
107+
/>
108+
</Flex>
109+
110+
{totalPages > 1 && (
111+
<Flex gridColumn="3">
112+
<MySelect
113+
value={perPage.toString()}
114+
onChange={handlePerPageChange}
115+
list={[
116+
{ label: t('common:core.chat.table.per_page', { num: 5 }), value: '5' },
117+
{
118+
label: t('common:core.chat.table.per_page', { num: 10 }),
119+
value: '10'
120+
},
121+
{
122+
label: t('common:core.chat.table.per_page', { num: 20 }),
123+
value: '20'
124+
},
125+
{
126+
label: t('common:core.chat.table.per_page', { num: 50 }),
127+
value: '50'
128+
},
129+
{
130+
label: t('common:core.chat.table.per_page', { num: 100 }),
131+
value: '100'
132+
}
133+
]}
134+
/>
135+
</Flex>
136+
)}
137+
</Grid>
138+
</Box>
139+
);
140+
};
141+
142+
export default TableBlock;

0 commit comments

Comments
 (0)