Skip to content

Commit 2ddfcc7

Browse files
authored
Images that appear consecutively in the dialogue are displayed using a carousel. #12076 (#12077)
### What problem does this PR solve? Images that appear consecutively in the dialogue are displayed using a carousel. #12076 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
1 parent 5ba51b2 commit 2ddfcc7

File tree

5 files changed

+194
-84
lines changed

5 files changed

+194
-84
lines changed

web/src/components/next-markdown-content/index.tsx

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { getExtension } from '@/utils/document-util';
55
import DOMPurify from 'dompurify';
66
import { memo, useCallback, useEffect, useMemo } from 'react';
77
import Markdown from 'react-markdown';
8-
import reactStringReplace from 'react-string-replace';
98
import SyntaxHighlighter from 'react-syntax-highlighter';
109
import rehypeKatex from 'rehype-katex';
1110
import rehypeRaw from 'rehype-raw';
@@ -18,7 +17,6 @@ import { useTranslation } from 'react-i18next';
1817
import 'katex/dist/katex.min.css'; // `rehype-katex` does not import the CSS for you
1918

2019
import {
21-
currentReg,
2220
preprocessLaTeX,
2321
replaceTextByOldReg,
2422
replaceThinkToSection,
@@ -31,6 +29,11 @@ import classNames from 'classnames';
3129
import { omit } from 'lodash';
3230
import { pipe } from 'lodash/fp';
3331
import { CircleAlert } from 'lucide-react';
32+
import { ImageCarousel } from '../markdown-content/image-carousel';
33+
import {
34+
groupConsecutiveReferences,
35+
shouldShowCarousel,
36+
} from '../markdown-content/reference-utils';
3437
import { Button } from '../ui/button';
3538
import {
3639
HoverCard,
@@ -39,6 +42,19 @@ import {
3942
} from '../ui/hover-card';
4043
import styles from './index.less';
4144

45+
// Helper function to convert IReferenceObject to IReference
46+
const convertReferenceObjectToReference = (
47+
referenceObject: IReferenceObject,
48+
) => {
49+
const chunks = Object.values(referenceObject.chunks);
50+
const docAggs = Object.values(referenceObject.doc_aggs);
51+
return {
52+
chunks,
53+
doc_aggs: docAggs,
54+
total: chunks.length,
55+
};
56+
};
57+
4258
const getChunkIndex = (match: string) => Number(match);
4359
// TODO: The display of the table is inconsistent with the display previously placed in the MessageItem.
4460
function MarkdownContent({
@@ -211,47 +227,95 @@ function MarkdownContent({
211227

212228
const renderReference = useCallback(
213229
(text: string) => {
214-
let replacedText = reactStringReplace(text, currentReg, (match, i) => {
215-
const chunkIndex = getChunkIndex(match);
230+
const groups = groupConsecutiveReferences(text);
231+
const elements = [];
232+
let lastIndex = 0;
216233

217-
const { documentUrl, fileExtension, imageId, chunkItem, documentId } =
218-
getReferenceInfo(chunkIndex);
234+
const convertedReference = reference
235+
? convertReferenceObjectToReference(reference)
236+
: null;
219237

220-
const docType = chunkItem?.doc_type;
238+
groups.forEach((group, groupIndex) => {
239+
if (group[0].start > lastIndex) {
240+
elements.push(text.substring(lastIndex, group[0].start));
241+
}
221242

222-
return showImage(docType) ? (
223-
<section>
224-
<Image
225-
id={imageId}
226-
className={styles.referenceInnerChunkImage}
227-
onClick={
228-
documentId
229-
? handleDocumentButtonClick(
230-
documentId,
231-
chunkItem,
232-
fileExtension === 'pdf',
233-
documentUrl,
234-
)
235-
: () => {}
236-
}
237-
></Image>
238-
<span className="text-accent-primary">{imageId}</span>
239-
</section>
240-
) : (
241-
<HoverCard key={i}>
242-
<HoverCardTrigger>
243-
<CircleAlert className="size-4 inline-block" />
244-
</HoverCardTrigger>
245-
<HoverCardContent className="max-w-3xl">
246-
{renderPopoverContent(chunkIndex)}
247-
</HoverCardContent>
248-
</HoverCard>
249-
);
243+
if (
244+
convertedReference &&
245+
shouldShowCarousel(group, convertedReference)
246+
) {
247+
elements.push(
248+
<ImageCarousel
249+
key={`carousel-${groupIndex}`}
250+
group={group}
251+
reference={convertedReference}
252+
fileThumbnails={fileThumbnails}
253+
onImageClick={handleDocumentButtonClick}
254+
/>,
255+
);
256+
} else {
257+
group.forEach((ref) => {
258+
const chunkIndex = getChunkIndex(ref.id);
259+
const {
260+
documentUrl,
261+
fileExtension,
262+
imageId,
263+
chunkItem,
264+
documentId,
265+
} = getReferenceInfo(chunkIndex);
266+
const docType = chunkItem?.doc_type;
267+
268+
if (showImage(docType)) {
269+
elements.push(
270+
<section key={ref.id}>
271+
<Image
272+
id={imageId}
273+
className={styles.referenceInnerChunkImage}
274+
onClick={
275+
documentId
276+
? handleDocumentButtonClick(
277+
documentId,
278+
chunkItem,
279+
fileExtension === 'pdf',
280+
documentUrl,
281+
)
282+
: () => {}
283+
}
284+
/>
285+
<span className="text-accent-primary"> {imageId}</span>
286+
</section>,
287+
);
288+
} else {
289+
elements.push(
290+
<HoverCard key={ref.id}>
291+
<HoverCardTrigger>
292+
<CircleAlert className="size-4 inline-block" />
293+
</HoverCardTrigger>
294+
<HoverCardContent className="max-w-3xl">
295+
{renderPopoverContent(chunkIndex)}
296+
</HoverCardContent>
297+
</HoverCard>,
298+
);
299+
}
300+
});
301+
}
302+
303+
lastIndex = group[group.length - 1].end;
250304
});
251305

252-
return replacedText;
306+
if (lastIndex < text.length) {
307+
elements.push(text.substring(lastIndex));
308+
}
309+
310+
return elements;
253311
},
254-
[renderPopoverContent, getReferenceInfo, handleDocumentButtonClick],
312+
[
313+
renderPopoverContent,
314+
getReferenceInfo,
315+
handleDocumentButtonClick,
316+
reference,
317+
fileThumbnails,
318+
],
255319
);
256320

257321
return (

web/src/locales/en.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2084,12 +2084,14 @@ Important structured information may include: names, dates, locations, events, k
20842084
schema: 'Schema',
20852085
response: 'Response',
20862086
executionMode: 'Execution mode',
2087+
executionModeTip:
2088+
'Accepted Response: The system returns an acknowledgment immediately after the request is validated, while the workflow continues to execute asynchronously in the background. /Final Response: The system returns a response only after the workflow execution is completed.',
20872089
authMethods: 'Authentication methods',
20882090
authType: 'Authentication type',
20892091
limit: 'Request limit',
20902092
per: 'Time period',
20912093
maxBodySize: 'Maximum body size',
2092-
ipWhitelist: 'Ip whitelist',
2094+
ipWhitelist: 'IP whitelist',
20932095
tokenHeader: 'Token header',
20942096
tokenValue: 'Token value',
20952097
username: 'Username',
@@ -2109,6 +2111,8 @@ Important structured information may include: names, dates, locations, events, k
21092111
queryParameters: 'Query parameters',
21102112
headerParameters: 'Header parameters',
21112113
requestBodyParameters: 'Request body parameters',
2114+
streaming: 'Accepted response',
2115+
immediately: 'Final response',
21122116
},
21132117
},
21142118
llmTools: {

web/src/locales/zh.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,8 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
18351835
schema: '模式',
18361836
response: '响应',
18371837
executionMode: '执行模式',
1838+
executionModeTip:
1839+
'Accepted Response:请求校验通过后立即返回接收成功响应,工作流在后台异步执行;Final Response:系统在工作流执行完成后返回最终处理结果',
18381840
authMethods: '认证方法',
18391841
authType: '认证类型',
18401842
limit: '请求限制',

web/src/pages/agent/form/begin-form/webhook/response.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ export function WebhookResponse() {
2424
<RAGFlowFormItem
2525
name="execution_mode"
2626
label={t('flow.webhook.executionMode')}
27+
tooltip={t('flow.webhook.executionModeTip')}
2728
>
2829
<SelectWithSearch
29-
options={buildOptions(WebhookExecutionMode)}
30+
options={buildOptions(WebhookExecutionMode, t, 'flow.webhook')}
3031
></SelectWithSearch>
3132
</RAGFlowFormItem>
3233
{executionMode === WebhookExecutionMode.Immediately && (

0 commit comments

Comments
 (0)