Fix: Optimize code and fix ts type errors #9869 (#10666)

### What problem does this PR solve?

Fix: Optimize code and fix ts type errors #9869

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-10-20 15:59:56 +08:00
committed by GitHub
parent cc703da747
commit de46b0d46e
18 changed files with 377 additions and 267 deletions

View File

@ -16,7 +16,7 @@ interface EditTagsProps {
} }
const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>( const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
({ value = [], onChange }: EditTagsProps, ref) => { ({ value = [], onChange }: EditTagsProps) => {
const [inputVisible, setInputVisible] = useState(false); const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);

View File

@ -1,22 +1,32 @@
import Image from '@/components/image'; import Image from '@/components/image';
import SvgIcon from '@/components/svg-icon'; import SvgIcon from '@/components/svg-icon';
import { useFetchDocumentThumbnailsByIds, useGetDocumentUrl } from '@/hooks/document-hooks'; import {
useFetchDocumentThumbnailsByIds,
useGetDocumentUrl,
} from '@/hooks/document-hooks';
import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
import { preprocessLaTeX, replaceThinkToSection, showImage } from '@/utils/chat'; import {
preprocessLaTeX,
replaceThinkToSection,
showImage,
} from '@/utils/chat';
import { getExtension } from '@/utils/document-util'; import { getExtension } from '@/utils/document-util';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, Tooltip } from 'antd'; import { Button, Flex, Popover, Tooltip } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import 'katex/dist/katex.min.css';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { pipe } from 'lodash/fp'; import { pipe } from 'lodash/fp';
import 'katex/dist/katex.min.css';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Markdown from 'react-markdown'; import Markdown from 'react-markdown';
import reactStringReplace from 'react-string-replace'; import reactStringReplace from 'react-string-replace';
import SyntaxHighlighter from 'react-syntax-highlighter'; import SyntaxHighlighter from 'react-syntax-highlighter';
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; import {
oneDark,
oneLight,
} from 'react-syntax-highlighter/dist/esm/styles/prism';
import rehypeKatex from 'rehype-katex'; import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw'; import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
@ -39,7 +49,8 @@ const FloatingChatWidgetMarkdown = ({
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void; clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setDocumentIds, data: fileThumbnails } = useFetchDocumentThumbnailsByIds(); const { setDocumentIds, data: fileThumbnails } =
useFetchDocumentThumbnailsByIds();
const getDocumentUrl = useGetDocumentUrl(); const getDocumentUrl = useGetDocumentUrl();
const isDarkTheme = useIsDarkTheme(); const isDarkTheme = useIsDarkTheme();
@ -51,23 +62,37 @@ const FloatingChatWidgetMarkdown = ({
useEffect(() => { useEffect(() => {
const docAggs = reference?.doc_aggs; const docAggs = reference?.doc_aggs;
const docList = Array.isArray(docAggs) ? docAggs : Object.values(docAggs ?? {}); const docList = Array.isArray(docAggs)
? docAggs
: Object.values(docAggs ?? {});
setDocumentIds(docList.map((x: any) => x.doc_id).filter(Boolean)); setDocumentIds(docList.map((x: any) => x.doc_id).filter(Boolean));
}, [reference, setDocumentIds]); }, [reference, setDocumentIds]);
const handleDocumentButtonClick = useCallback((documentId: string, chunk: IReferenceChunk, isPdf: boolean, documentUrl?: string) => () => { const handleDocumentButtonClick = useCallback(
if (!documentId) return; (
if (!isPdf && documentUrl) { documentId: string,
window.open(documentUrl, '_blank'); chunk: IReferenceChunk,
} else if (clickDocumentButton) { isPdf: boolean,
clickDocumentButton(documentId, chunk); documentUrl?: string,
} ) =>
}, [clickDocumentButton]); () => {
if (!documentId) return;
if (!isPdf && documentUrl) {
window.open(documentUrl, '_blank');
} else if (clickDocumentButton) {
clickDocumentButton(documentId, chunk);
}
},
[clickDocumentButton],
);
const rehypeWrapReference = () => (tree: any) => { const rehypeWrapReference = () => (tree: any) => {
visitParents(tree, 'text', (node, ancestors) => { visitParents(tree, 'text', (node, ancestors) => {
const latestAncestor = ancestors[ancestors.length - 1]; const latestAncestor = ancestors[ancestors.length - 1];
if (latestAncestor.tagName !== 'custom-typography' && latestAncestor.tagName !== 'code') { if (
latestAncestor.tagName !== 'custom-typography' &&
latestAncestor.tagName !== 'code'
) {
node.type = 'element'; node.type = 'element';
node.tagName = 'custom-typography'; node.tagName = 'custom-typography';
node.properties = {}; node.properties = {};
@ -76,90 +101,173 @@ const FloatingChatWidgetMarkdown = ({
}); });
}; };
const getReferenceInfo = useCallback((chunkIndex: number) => { const getReferenceInfo = useCallback(
const chunkItem = reference?.chunks?.[chunkIndex]; (chunkIndex: number) => {
if (!chunkItem) return null; const chunkItem = reference?.chunks?.[chunkIndex];
const docAggsArray = Array.isArray(reference?.doc_aggs) ? reference.doc_aggs : Object.values(reference?.doc_aggs ?? {}); if (!chunkItem) return null;
const document = docAggsArray.find((x: any) => x?.doc_id === chunkItem?.document_id) as any; const docAggsArray = Array.isArray(reference?.doc_aggs)
const documentId = document?.doc_id; ? reference.doc_aggs
const documentUrl = document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined); : Object.values(reference?.doc_aggs ?? {});
const fileThumbnail = documentId ? fileThumbnails[documentId] : ''; const document = docAggsArray.find(
const fileExtension = documentId ? getExtension(document?.doc_name ?? '') : ''; (x: any) => x?.doc_id === chunkItem?.document_id,
return { documentUrl, fileThumbnail, fileExtension, imageId: chunkItem.image_id, chunkItem, documentId, document }; ) as any;
}, [fileThumbnails, reference, getDocumentUrl]); const documentId = document?.doc_id;
const documentUrl =
document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined);
const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
const fileExtension = documentId
? getExtension(document?.doc_name ?? '')
: '';
return {
documentUrl,
fileThumbnail,
fileExtension,
imageId: chunkItem.image_id,
chunkItem,
documentId,
document,
};
},
[fileThumbnails, reference, getDocumentUrl],
);
const getPopoverContent = useCallback((chunkIndex: number) => { const getPopoverContent = useCallback(
const info = getReferenceInfo(chunkIndex); (chunkIndex: number) => {
if (!info) {
return <div className="p-2 text-xs text-red-500">Error: Missing document information.</div>;
}
const { documentUrl, fileThumbnail, fileExtension, imageId, chunkItem, documentId, document } = info;
return (
<div key={`popover-content-${chunkItem.id}`} className="flex gap-2 widget-citation-content">
{imageId && (
<Popover placement="left" content={<Image id={imageId} className="max-w-[80vw] max-h-[60vh] rounded" />}>
<Image id={imageId} className="w-24 h-24 object-contain rounded m-1 cursor-pointer" />
</Popover>
)}
<div className="space-y-2 flex-1 min-w-0">
<div
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(chunkItem?.content ?? '') }}
className="max-h-[250px] overflow-y-auto text-xs leading-relaxed p-2 bg-gray-50 dark:bg-gray-800 rounded prose-sm"
></div>
{documentId && (
<Flex gap={'small'} align="center">
{fileThumbnail ? (
<img src={fileThumbnail} alt={document?.doc_name} className="w-6 h-6 rounded" />
) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)}
<Tooltip title={!documentUrl && fileExtension !== 'pdf' ? 'Document link unavailable' : document.doc_name}>
<Button
type="link"
size="small"
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(documentId, chunkItem, fileExtension === 'pdf', documentUrl)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">{document?.doc_name ?? 'Unnamed Document'}</span>
</Button>
</Tooltip>
</Flex>
)}
</div>
</div>
);
}, [getReferenceInfo, handleDocumentButtonClick]);
const renderReference = useCallback((text: string) => {
return reactStringReplace(text, currentReg, (match, i) => {
const chunkIndex = getChunkIndex(match);
const info = getReferenceInfo(chunkIndex); const info = getReferenceInfo(chunkIndex);
if (!info) { if (!info) {
return <Tooltip key={`err-tooltip-${i}`} title="Reference unavailable"><InfoCircleOutlined className={styles.referenceIcon} /></Tooltip>; return (
<div className="p-2 text-xs text-red-500">
Error: Missing document information.
</div>
);
} }
const { imageId, chunkItem, documentId, fileExtension, documentUrl } = info; const {
documentUrl,
if (showImage(chunkItem?.doc_type)) { fileThumbnail,
return <Image key={`img-${i}`} id={imageId} className="block object-contain max-w-full max-h-48 rounded my-2 cursor-pointer" onClick={handleDocumentButtonClick(documentId, chunkItem, fileExtension === 'pdf', documentUrl)} />; fileExtension,
} imageId,
chunkItem,
documentId,
document,
} = info;
return ( return (
<Popover <div
content={getPopoverContent(chunkIndex)} key={`popover-content-${chunkItem.id}`}
key={`popover-${i}`} className="flex gap-2 widget-citation-content"
> >
<InfoCircleOutlined className={styles.referenceIcon} /> {imageId && (
</Popover> <Popover
placement="left"
content={
<Image
id={imageId}
className="max-w-[80vw] max-h-[60vh] rounded"
/>
}
>
<Image
id={imageId}
className="w-24 h-24 object-contain rounded m-1 cursor-pointer"
/>
</Popover>
)}
<div className="space-y-2 flex-1 min-w-0">
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(chunkItem?.content ?? ''),
}}
className="max-h-[250px] overflow-y-auto text-xs leading-relaxed p-2 bg-gray-50 dark:bg-gray-800 rounded prose-sm"
></div>
{documentId && (
<Flex gap={'small'} align="center">
{fileThumbnail ? (
<img
src={fileThumbnail}
alt={document?.doc_name}
className="w-6 h-6 rounded"
/>
) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)}
<Tooltip
title={
!documentUrl && fileExtension !== 'pdf'
? 'Document link unavailable'
: document.doc_name
}
>
<Button
type="link"
size="small"
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">
{document?.doc_name ?? 'Unnamed Document'}
</span>
</Button>
</Tooltip>
</Flex>
)}
</div>
</div>
); );
}); },
}, [getPopoverContent, getReferenceInfo, handleDocumentButtonClick]); [getReferenceInfo, handleDocumentButtonClick],
);
const renderReference = useCallback(
(text: string) => {
return reactStringReplace(text, currentReg, (match, i) => {
const chunkIndex = getChunkIndex(match);
const info = getReferenceInfo(chunkIndex);
if (!info) {
return (
<Tooltip key={`err-tooltip-${i}`} title="Reference unavailable">
<InfoCircleOutlined className={styles.referenceIcon} />
</Tooltip>
);
}
const { imageId, chunkItem, documentId, fileExtension, documentUrl } =
info;
if (showImage(chunkItem?.doc_type)) {
return (
<Image
key={`img-${i}`}
id={imageId}
className="block object-contain max-w-full max-h-48 rounded my-2 cursor-pointer"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
/>
);
}
return (
<Popover content={getPopoverContent(chunkIndex)} key={`popover-${i}`}>
<InfoCircleOutlined className={styles.referenceIcon} />
</Popover>
);
});
},
[getPopoverContent, getReferenceInfo, handleDocumentButtonClick],
);
return ( return (
<div className="floating-chat-widget"> <div className="floating-chat-widget">
@ -167,28 +275,38 @@ const FloatingChatWidgetMarkdown = ({
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]} rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
className="text-sm leading-relaxed space-y-2 prose-sm max-w-full" className="text-sm leading-relaxed space-y-2 prose-sm max-w-full"
components={{ components={
'custom-typography': ({ children }: { children: string }) => renderReference(children), {
code(props: any) { 'custom-typography': ({ children }: { children: string }) =>
const { children, className, node, ...rest } = props; renderReference(children),
const match = /language-(\w+)/.exec(className || ''); code(props: any) {
return match ? ( // eslint-disable-next-line @typescript-eslint/no-unused-vars
<SyntaxHighlighter const { children, className, node, ...rest } = props;
{...omit(rest, 'inline')} const match = /language-(\w+)/.exec(className || '');
PreTag="div" return match ? (
language={match[1]} <SyntaxHighlighter
style={isDarkTheme ? oneDark : oneLight} {...omit(rest, 'inline')}
wrapLongLines PreTag="div"
> language={match[1]}
{String(children).replace(/\n$/, '')} style={isDarkTheme ? oneDark : oneLight}
</SyntaxHighlighter> wrapLongLines
) : ( >
<code {...rest} className={classNames(className, 'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded')}> {String(children).replace(/\n$/, '')}
{children} </SyntaxHighlighter>
</code> ) : (
); <code
}, {...rest}
} as any} className={classNames(
className,
'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded',
)}
>
{children}
</code>
);
},
} as any
}
> >
{contentWithCursor} {contentWithCursor}
</Markdown> </Markdown>
@ -196,4 +314,4 @@ const FloatingChatWidgetMarkdown = ({
); );
}; };
export default FloatingChatWidgetMarkdown; export default FloatingChatWidgetMarkdown;

View File

@ -1,18 +1,10 @@
import PdfDrawer from '@/components/pdf-drawer'; import PdfDrawer from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { MessageType, SharedFrom } from '@/constants/chat'; import { MessageType } from '@/constants/chat';
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
import { useFetchExternalChatInfo } from '@/hooks/use-chat-request'; import { useFetchExternalChatInfo } from '@/hooks/use-chat-request';
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import { MessageCircle, Minimize2, Send, X } from 'lucide-react'; import { MessageCircle, Minimize2, Send, X } from 'lucide-react';
import React, { import React, { useCallback, useEffect, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { import {
useGetSharedChatSearchParams, useGetSharedChatSearchParams,
useSendSharedMessage, useSendSharedMessage,
@ -28,12 +20,7 @@ const FloatingChatWidget = () => {
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const { const { sharedId: conversationId, locale } = useGetSharedChatSearchParams();
sharedId: conversationId,
from,
locale,
visibleAvatar,
} = useGetSharedChatSearchParams();
// Check if we're in button-only mode or window-only mode // Check if we're in button-only mode or window-only mode
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@ -58,14 +45,6 @@ const FloatingChatWidget = () => {
const { data: chatInfo } = useFetchExternalChatInfo(); const { data: chatInfo } = useFetchExternalChatInfo();
const useFetchAvatar = useMemo(() => {
return from === SharedFrom.Agent
? useFetchFlowSSE
: useFetchNextConversationSSE;
}, [from]);
const { data: avatarData } = useFetchAvatar();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer(); useClickDrawer();
@ -181,6 +160,40 @@ const FloatingChatWidget = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [displayMessages]); }, [displayMessages]);
// Render different content based on mode
// Master mode - handles everything and creates second iframe dynamically
useEffect(() => {
if (mode !== 'master') return;
// Create the chat window iframe dynamically when needed
const createChatWindow = () => {
// Check if iframe already exists in parent document
window.parent.postMessage(
{
type: 'CREATE_CHAT_WINDOW',
src: window.location.href.replace('mode=master', 'mode=window'),
},
'*',
);
};
createChatWindow();
// Listen for our own toggle events to show/hide the dynamic iframe
const handleToggle = (e: MessageEvent) => {
if (e.source === window) return; // Ignore our own messages
const chatWindow = document.getElementById(
'dynamic-chat-window',
) as HTMLIFrameElement;
if (chatWindow && e.data.type === 'TOGGLE_CHAT') {
chatWindow.style.display = e.data.isOpen ? 'block' : 'none';
}
};
window.addEventListener('message', handleToggle);
return () => window.removeEventListener('message', handleToggle);
}, [mode]);
// Play sound only when AI response is complete (not streaming chunks) // Play sound only when AI response is complete (not streaming chunks)
useEffect(() => { useEffect(() => {
if (derivedMessages && derivedMessages.length > 0 && !sendLoading) { if (derivedMessages && derivedMessages.length > 0 && !sendLoading) {
@ -271,41 +284,8 @@ const FloatingChatWidget = () => {
const messageCount = displayMessages?.length || 0; const messageCount = displayMessages?.length || 0;
// Render different content based on mode // Show just the button in master mode
if (mode === 'master') { if (mode === 'master') {
// Master mode - handles everything and creates second iframe dynamically
useEffect(() => {
// Create the chat window iframe dynamically when needed
const createChatWindow = () => {
// Check if iframe already exists in parent document
window.parent.postMessage(
{
type: 'CREATE_CHAT_WINDOW',
src: window.location.href.replace('mode=master', 'mode=window'),
},
'*',
);
};
createChatWindow();
// Listen for our own toggle events to show/hide the dynamic iframe
const handleToggle = (e: MessageEvent) => {
if (e.source === window) return; // Ignore our own messages
const chatWindow = document.getElementById(
'dynamic-chat-window',
) as HTMLIFrameElement;
if (chatWindow && e.data.type === 'TOGGLE_CHAT') {
chatWindow.style.display = e.data.isOpen ? 'block' : 'none';
}
};
window.addEventListener('message', handleToggle);
return () => window.removeEventListener('message', handleToggle);
}, []);
// Show just the button in master mode
return ( return (
<div <div
className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`} className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
@ -678,6 +658,7 @@ const FloatingChatWidget = () => {
/> />
</div> </div>
<button <button
type="button"
onClick={handleSendMessage} onClick={handleSendMessage}
disabled={!inputValue.trim() || sendLoading} disabled={!inputValue.trim() || sendLoading}
className="p-3 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="p-3 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"

View File

@ -70,7 +70,7 @@ export function UseGraphRagFormField({
<FormField <FormField
control={form.control} control={form.control}
name="parser_config.graphrag.use_graphrag" name="parser_config.graphrag.use_graphrag"
render={({ field }) => ( render={() => (
<FormItem defaultChecked={false} className=" items-center space-y-0 "> <FormItem defaultChecked={false} className=" items-center space-y-0 ">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FormLabel <FormLabel

View File

@ -5,7 +5,7 @@ import { Plus } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
interface ChunkResultBarProps { interface ChunkResultBarProps {
changeChunkTextMode: React.Dispatch<React.SetStateAction<string | number>>; changeChunkTextMode: (mode: ChunkTextMode) => void;
createChunk: (text: string) => void; createChunk: (text: string) => void;
isReadonly: boolean; isReadonly: boolean;
} }
@ -15,7 +15,7 @@ export default ({
isReadonly, isReadonly,
}: ChunkResultBarProps) => { }: ChunkResultBarProps) => {
const { t } = useTranslate('chunk'); const { t } = useTranslate('chunk');
const [textSelectValue, setTextSelectValue] = useState<string | number>( const [textSelectValue, setTextSelectValue] = useState<ChunkTextMode>(
ChunkTextMode.Full, ChunkTextMode.Full,
); );
const textSelectOptions = [ const textSelectOptions = [
@ -23,7 +23,7 @@ export default ({
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse }, { label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
]; ];
const changeTextSelectValue = (value: string | number) => { const changeTextSelectValue = (value: ChunkTextMode) => {
setTextSelectValue(value); setTextSelectValue(value);
changeChunkTextMode(value); changeChunkTextMode(value);
}; };

View File

@ -8,6 +8,7 @@ export interface FormatPreserveEditorProps {
key: keyof typeof parserKeyMap | 'text' | 'html'; key: keyof typeof parserKeyMap | 'text' | 'html';
type: string; type: string;
value: Array<{ [key: string]: string }>; value: Array<{ [key: string]: string }>;
params: ComponentParams;
}; };
onSave: (value: any) => void; onSave: (value: any) => void;
className?: string; className?: string;

View File

@ -4,6 +4,7 @@ import { isArray } from 'lodash';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { IChunk } from '../../interface';
import { useParserInit } from './hook'; import { useParserInit } from './hook';
import { IJsonContainerProps } from './interface'; import { IJsonContainerProps } from './interface';
export const parserKeyMap = { export const parserKeyMap = {
@ -17,8 +18,6 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
isChunck, isChunck,
handleCheck, handleCheck,
selectedChunkIds, selectedChunkIds,
unescapeNewlines,
escapeNewlines,
onSave, onSave,
className, className,
textMode, textMode,
@ -26,13 +25,8 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
isReadonly, isReadonly,
} = props; } = props;
const { const { content, activeEditIndex, setActiveEditIndex, editDivRef } =
content, useParserInit({ initialValue });
setContent,
activeEditIndex,
setActiveEditIndex,
editDivRef,
} = useParserInit({ initialValue });
const parserKey = useMemo(() => { const parserKey = useMemo(() => {
const key = const key =
@ -46,35 +40,39 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
(e?: any, index?: number) => { (e?: any, index?: number) => {
setActiveEditIndex(index); setActiveEditIndex(index);
}, },
[setContent, setActiveEditIndex], [setActiveEditIndex],
); );
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
const saveData = { if (Array.isArray(content.value)) {
...content, const saveData = {
value: content.value?.map((item, index) => { ...content,
if (index === activeEditIndex) { value: content.value?.map((item, index) => {
return { if (index === activeEditIndex) {
...item, return {
[parserKey]: e.target.textContent || '', ...item,
}; [parserKey]: e.target.textContent || '',
} else { };
return item; } else {
} return item;
}), }
}; }),
onSave(saveData); };
onSave(saveData as any);
}
setActiveEditIndex(undefined); setActiveEditIndex(undefined);
}, },
[content, onSave], [content, onSave, activeEditIndex, parserKey, setActiveEditIndex],
); );
useEffect(() => { useEffect(() => {
if (activeEditIndex !== undefined && editDivRef.current) { if (activeEditIndex !== undefined && editDivRef.current) {
editDivRef.current.focus(); editDivRef.current.focus();
editDivRef.current.textContent = if (typeof content.value !== 'string') {
content.value[activeEditIndex][parserKey]; editDivRef.current.textContent =
content.value[activeEditIndex][parserKey];
}
} }
}, [editDivRef, activeEditIndex, content, parserKey]); }, [editDivRef, activeEditIndex, content, parserKey]);
@ -122,7 +120,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
{activeEditIndex !== index && ( {activeEditIndex !== index && (
<div <div
className={cn( className={cn(
'text-text-secondary overflow-auto scrollbar-auto w-full', 'text-text-secondary overflow-auto scrollbar-auto w-full min-h-3',
{ {
[styles.contentEllipsis]: [styles.contentEllipsis]:
textMode === ChunkTextMode.Ellipse, textMode === ChunkTextMode.Ellipse,
@ -130,7 +128,8 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
)} )}
key={index} key={index}
onClick={(e) => { onClick={(e) => {
clickChunk(item); clickChunk(item as unknown as IChunk);
console.log('clickChunk', item, index);
if (!isReadonly) { if (!isReadonly) {
handleEdit(e, index); handleEdit(e, index);
} }

View File

@ -2,14 +2,13 @@ import { cn } from '@/lib/utils';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { IChunk } from '../../interface';
import { useParserInit } from './hook'; import { useParserInit } from './hook';
import { IObjContainerProps } from './interface'; import { IObjContainerProps } from './interface';
export const ObjectContainer = (props: IObjContainerProps) => { export const ObjectContainer = (props: IObjContainerProps) => {
const { const {
initialValue, initialValue,
isChunck, isChunck,
unescapeNewlines,
escapeNewlines,
onSave, onSave,
className, className,
textMode, textMode,
@ -19,7 +18,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
const { const {
content, content,
setContent, // setContent,
activeEditIndex, activeEditIndex,
setActiveEditIndex, setActiveEditIndex,
editDivRef, editDivRef,
@ -31,7 +30,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
// value: escapeNewlines(e.target.innerText), // value: escapeNewlines(e.target.innerText),
// })); // }));
setActiveEditIndex(1); setActiveEditIndex(1);
}, [setContent, setActiveEditIndex]); }, [setActiveEditIndex]);
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
@ -42,7 +41,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
onSave(saveData); onSave(saveData);
setActiveEditIndex(undefined); setActiveEditIndex(undefined);
}, },
[content, onSave], [content, onSave, setActiveEditIndex],
); );
useEffect(() => { useEffect(() => {
@ -81,14 +80,14 @@ export const ObjectContainer = (props: IObjContainerProps) => {
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse, [styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
}, },
)} )}
onClick={(e) => { onClick={() => {
clickChunk(content); clickChunk(content as unknown as IChunk);
if (!isReadonly) { if (!isReadonly) {
handleEdit(e); handleEdit();
} }
}} }}
> >
{content.value} {content.value as string}
</div> </div>
)} )}
</section> </section>

View File

@ -4,7 +4,6 @@ import { useCreateChunk, useDeleteChunk } from '@/hooks/chunk-hooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks'; import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useFetchMessageTrace } from '@/hooks/use-agent-request'; import { useFetchMessageTrace } from '@/hooks/use-agent-request';
import { IChunk } from '@/interfaces/database/knowledge';
import kbService from '@/services/knowledge-service'; import kbService from '@/services/knowledge-service';
import { formatSecondsToHumanReadable } from '@/utils/date'; import { formatSecondsToHumanReadable } from '@/utils/date';
import { buildChunkHighlights } from '@/utils/document-util'; import { buildChunkHighlights } from '@/utils/document-util';
@ -20,7 +19,7 @@ import {
PipelineResultSearchParams, PipelineResultSearchParams,
TimelineNodeType, TimelineNodeType,
} from './constant'; } from './constant';
import { IDslComponent, IPipelineFileLogDetail } from './interface'; import { IChunk, IDslComponent, IPipelineFileLogDetail } from './interface';
export const useFetchPipelineFileLogDetail = ({ export const useFetchPipelineFileLogDetail = ({
isAgent = false, isAgent = false,
@ -64,7 +63,6 @@ export const useHandleChunkCardClick = () => {
const [selectedChunk, setSelectedChunk] = useState<IChunk>(); const [selectedChunk, setSelectedChunk] = useState<IChunk>();
const handleChunkCardClick = useCallback((chunk: IChunk) => { const handleChunkCardClick = useCallback((chunk: IChunk) => {
console.log('click-chunk-->', chunk);
setSelectedChunk(chunk); setSelectedChunk(chunk);
}, []); }, []);
@ -75,7 +73,9 @@ export const useGetChunkHighlights = (selectedChunk?: IChunk) => {
const [size, setSize] = useState({ width: 849, height: 1200 }); const [size, setSize] = useState({ width: 849, height: 1200 });
const highlights: IHighlight[] = useMemo(() => { const highlights: IHighlight[] = useMemo(() => {
return selectedChunk ? buildChunkHighlights(selectedChunk, size) : []; return selectedChunk
? buildChunkHighlights(selectedChunk as any, size)
: [];
}, [selectedChunk, size]); }, [selectedChunk, size]);
const setWidthAndHeight = useCallback((width: number, height: number) => { const setWidthAndHeight = useCallback((width: number, height: number) => {

View File

@ -126,8 +126,17 @@ export interface IPipelineFileLogDetail {
} }
export interface IChunk { export interface IChunk {
available_int?: number; // Whether to enable, 0: not enabled, 1: enabled
chunk_id?: string;
content_with_weight?: string;
doc_id?: string;
doc_name?: string;
image_id?: string;
important_kwd?: string[];
question_kwd?: string[]; // keywords
tag_kwd?: string[];
positions: number[][]; positions: number[][];
image_id: string; tag_feas?: Record<string, number>;
text: string; text: string;
} }

View File

@ -40,12 +40,17 @@ const ParserContainer = (props: IProps) => {
const initialValue = useMemo(() => { const initialValue = useMemo(() => {
const outputs = data?.value?.obj?.params?.outputs; const outputs = data?.value?.obj?.params?.outputs;
const key = outputs?.output_format?.value; const key = outputs?.output_format?.value;
if (!outputs || !key) return { key: '', type: '', value: [] }; if (!outputs || !key)
const value = outputs[key]?.value; return {
const type = outputs[key]?.type; key: '' as 'text' | 'html' | 'json' | 'chunks',
type: '',
value: [],
};
const value = outputs[key as keyof typeof outputs]?.value;
const type = outputs[key as keyof typeof outputs]?.type;
console.log('outputs-->', outputs, data, key, value); console.log('outputs-->', outputs, data, key, value);
return { return {
key, key: key as 'text' | 'html' | 'json' | 'chunks',
type, type,
value, value,
params: data?.value?.obj?.params, params: data?.value?.obj?.params,
@ -95,7 +100,7 @@ const ParserContainer = (props: IProps) => {
const handleRemoveChunk = useCallback(async () => { const handleRemoveChunk = useCallback(async () => {
if (selectedChunkIds.length > 0) { if (selectedChunkIds.length > 0) {
initialText.value = initialText.value.filter( initialText.value = initialText.value.filter(
(item: any, index: number) => !selectedChunkIds.includes(index + ''), (_item: any, index: number) => !selectedChunkIds.includes(index + ''),
); );
setIsChange(true); setIsChange(true);
setSelectedChunkIds([]); setSelectedChunkIds([]);
@ -118,7 +123,7 @@ const ParserContainer = (props: IProps) => {
const selectAllChunk = useCallback( const selectAllChunk = useCallback(
(checked: boolean) => { (checked: boolean) => {
setSelectedChunkIds( setSelectedChunkIds(
checked ? initialText.value.map((x, index: number) => index) : [], checked ? initialText.value.map((_x: any, index: number) => index) : [],
); );
}, },
[initialText.value], [initialText.value],

View File

@ -12,6 +12,7 @@ import { RunningStatus } from '../dataset/constant';
import { LogTabs } from './dataset-common'; import { LogTabs } from './dataset-common';
import { DatasetFilter } from './dataset-filter'; import { DatasetFilter } from './dataset-filter';
import { useFetchFileLogList, useFetchOverviewTital } from './hook'; import { useFetchFileLogList, useFetchOverviewTital } from './hook';
import { DocumentLog, IFileLogItem } from './interface';
import FileLogsTable from './overview-table'; import FileLogsTable from './overview-table';
interface StatCardProps { interface StatCardProps {
@ -212,18 +213,25 @@ const FileLogsPage: FC = () => {
return tableOriginData.logs.map((item) => { return tableOriginData.logs.map((item) => {
return { return {
...item, ...item,
fileName: item.document_name, status: item.operation_status as RunningStatus,
statusName: item.operation_status, statusName: RunningStatusMap[item.operation_status as RunningStatus],
}; } as unknown as IFileLogItem & DocumentLog;
}); });
} }
return [];
}, [tableOriginData]); }, [tableOriginData]);
const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => { const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => {
setFilterValue({}); setFilterValue({});
setActive(active); setActive(active);
}; };
const handlePaginationChange = (page: number, pageSize: number) => { const handlePaginationChange = ({
page,
pageSize,
}: {
page: number;
pageSize: number;
}) => {
console.log('Pagination changed:', { page, pageSize }); console.log('Pagination changed:', { page, pageSize });
setPagination({ setPagination({
...pagination, ...pagination,

View File

@ -57,6 +57,6 @@ export interface IFileLogItem {
update_time: number; update_time: number;
} }
export interface IFileLogList { export interface IFileLogList {
logs: IFileLogItem[]; logs: Array<IFileLogItem & DocumentLog>;
total: number; total: number;
} }

View File

@ -1,7 +1,4 @@
import { import { DataFlowSelect } from '@/components/data-pipeline-select';
DataFlowSelect,
IDataPipelineSelectNode,
} from '@/components/data-pipeline-select';
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -22,7 +19,6 @@ import {
} from '../dataset/generate-button/generate'; } from '../dataset/generate-button/generate';
import { ChunkMethodForm } from './chunk-method-form'; import { ChunkMethodForm } from './chunk-method-form';
import ChunkMethodLearnMore from './chunk-method-learn-more'; import ChunkMethodLearnMore from './chunk-method-learn-more';
import { IDataPipelineNodeProps } from './components/link-data-pipeline';
import { MainContainer } from './configuration-form-container'; import { MainContainer } from './configuration-form-container';
import { ChunkMethodItem, ParseTypeItem } from './configuration/common-item'; import { ChunkMethodItem, ParseTypeItem } from './configuration/common-item';
import { formSchema } from './form-schema'; import { formSchema } from './form-schema';
@ -85,7 +81,7 @@ export default function DatasetSettings() {
}, },
}); });
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form); const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>(); // const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>();
const [graphRagGenerateData, setGraphRagGenerateData] = const [graphRagGenerateData, setGraphRagGenerateData] =
useState<IGenerateLogButtonProps>(); useState<IGenerateLogButtonProps>();
const [raptorGenerateData, setRaptorGenerateData] = const [raptorGenerateData, setRaptorGenerateData] =
@ -94,13 +90,13 @@ export default function DatasetSettings() {
useEffect(() => { useEffect(() => {
console.log('🚀 ~ DatasetSettings ~ knowledgeDetails:', knowledgeDetails); console.log('🚀 ~ DatasetSettings ~ knowledgeDetails:', knowledgeDetails);
if (knowledgeDetails) { if (knowledgeDetails) {
const data: IDataPipelineNodeProps = { // const data: IDataPipelineNodeProps = {
id: knowledgeDetails.pipeline_id, // id: knowledgeDetails.pipeline_id,
name: knowledgeDetails.pipeline_name, // name: knowledgeDetails.pipeline_name,
avatar: knowledgeDetails.pipeline_avatar, // avatar: knowledgeDetails.pipeline_avatar,
linked: true, // linked: true,
}; // };
setPipelineData(data); // setPipelineData(data);
setGraphRagGenerateData({ setGraphRagGenerateData({
finish_at: knowledgeDetails.graphrag_task_finish_at, finish_at: knowledgeDetails.graphrag_task_finish_at,
task_id: knowledgeDetails.graphrag_task_id, task_id: knowledgeDetails.graphrag_task_id,
@ -121,17 +117,17 @@ export default function DatasetSettings() {
console.error('An error occurred during submission:', error); console.error('An error occurred during submission:', error);
} }
} }
const handleLinkOrEditSubmit = ( // const handleLinkOrEditSubmit = (
data: IDataPipelineSelectNode | undefined, // data: IDataPipelineSelectNode | undefined,
) => { // ) => {
console.log('🚀 ~ DatasetSettings ~ data:', data); // console.log('🚀 ~ DatasetSettings ~ data:', data);
if (data) { // if (data) {
setPipelineData(data); // setPipelineData(data);
form.setValue('pipeline_id', data.id || ''); // form.setValue('pipeline_id', data.id || '');
// form.setValue('pipeline_name', data.name || ''); // // form.setValue('pipeline_name', data.name || '');
// form.setValue('pipeline_avatar', data.avatar || ''); // // form.setValue('pipeline_avatar', data.avatar || '');
} // }
}; // };
const handleDeletePipelineTask = (type: GenerateType) => { const handleDeletePipelineTask = (type: GenerateType) => {
if (type === GenerateType.KnowledgeGraph) { if (type === GenerateType.KnowledgeGraph) {
@ -203,11 +199,7 @@ export default function DatasetSettings() {
)} )}
<Divider /> <Divider />
{parseType === 1 && ( {parseType === 1 && <ChunkMethodForm />}
<ChunkMethodForm
selectedTag={selectedTag as DocumentParserType}
/>
)}
{/* <LinkDataPipeline {/* <LinkDataPipeline
data={pipelineData} data={pipelineData}

View File

@ -9,7 +9,7 @@ interface IProps extends IModalProps<any> {
data: any; data: any;
} }
const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => { const MindMapDrawer = ({ data, hideModal, loading }: IProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const percent = usePendingMindMap(); const percent = usePendingMindMap();
return ( return (

View File

@ -14,15 +14,14 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from '@/components/ui/popover'; } from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { import {
useAllTestingResult, useAllTestingResult,
useSelectTestingResult, useSelectTestingResult,
} from '@/hooks/knowledge-hooks'; } from '@/hooks/knowledge-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Separator } from '@radix-ui/react-select';
import { CheckIcon, ChevronDown, Files, XIcon } from 'lucide-react'; import { CheckIcon, ChevronDown, Files, XIcon } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
interface IProps { interface IProps {
onTesting(documentIds: string[]): void; onTesting(documentIds: string[]): void;
@ -35,11 +34,9 @@ const RetrievalDocuments = ({
selectedDocumentIds, selectedDocumentIds,
setSelectedDocumentIds, setSelectedDocumentIds,
}: IProps) => { }: IProps) => {
const { t } = useTranslation();
const { documents: documentsAll } = useAllTestingResult(); const { documents: documentsAll } = useAllTestingResult();
const { documents } = useSelectTestingResult(); const { documents } = useSelectTestingResult();
const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const maxCount = 3;
const { documents: useDocuments } = { const { documents: useDocuments } = {
documents: documents:
documentsAll?.length > documents?.length ? documentsAll : documents, documentsAll?.length > documents?.length ? documentsAll : documents,

View File

@ -337,6 +337,8 @@ export const useRenameSearch = () => {
}); });
const detail = reponse.data?.data; const detail = reponse.data?.data;
console.log('detail-->', detail); console.log('detail-->', detail);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, created_by, update_time, ...searchDataTemp } = detail; const { id, created_by, update_time, ...searchDataTemp } = detail;
res = await updateSearch({ res = await updateSearch({
...searchDataTemp, ...searchDataTemp,

View File

@ -5,14 +5,13 @@ import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useState } from 'react';
import { useFetchSearchList, useRenameSearch } from './hooks'; import { useFetchSearchList, useRenameSearch } from './hooks';
import { SearchCard } from './search-card'; import { SearchCard } from './search-card';
export default function SearchList() { export default function SearchList() {
// const { data } = useFetchFlowList(); // const { data } = useFetchFlowList();
const { t } = useTranslate('search'); const { t } = useTranslate('search');
const [isEdit, setIsEdit] = useState(false); // const [isEdit, setIsEdit] = useState(false);
const { const {
data: list, data: list,
searchParams, searchParams,
@ -36,11 +35,11 @@ export default function SearchList() {
}); });
}; };
const openCreateModalFun = () => { const openCreateModalFun = () => {
setIsEdit(false); // setIsEdit(false);
showSearchRenameModal(); showSearchRenameModal();
}; };
const handlePageChange = (page: number, pageSize: number) => { const handlePageChange = (page: number, pageSize: number) => {
setIsEdit(false); // setIsEdit(false);
setSearchListParams({ ...searchParams, page, page_size: pageSize }); setSearchListParams({ ...searchParams, page, page_size: pageSize });
}; };