Fix: Added read-only mode support and optimized navigation logic #9869 (#10370)

### What problem does this PR solve?

Fix: Added read-only mode support and optimized navigation logic #9869

- Added the `isReadonly` property to the parseResult component to
control the enabled state of editing and interactive features
- Added the `navigateToDataFile` navigation method to navigate to the
data file details page
- Refactored the `navigateToDataflowResult` method to use an object
parameter to support more flexible query parameter configuration
- Unified the `var(--accent-primary)` CSS variable format to
`rgb(var(--accent-primary))` to accommodate more styling scenarios
- Extracted the parser initialization logic into a separate hook
(`useParserInit`)

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-09-30 12:00:29 +08:00
committed by GitHub
parent fb19e24f8a
commit 4d6ff672eb
27 changed files with 468 additions and 253 deletions

View File

@ -1,5 +1,6 @@
(window._iconfont_svg_string_4909832 = (window._iconfont_svg_string_4909832 =
'<svg>' + '<svg>' +
'<symbol id="icon-play" viewBox="0 0 1024 1024"><path d="M0 0h1024v1024H0z" fill="#00BEB4" opacity=".01" ></path><path d="M161.206857 839.972571V185.929143a72.850286 72.850286 0 0 1 109.275429-63.049143l566.345143 326.948571a72.850286 72.850286 0 0 1 0 126.244572l-566.418286 326.948571a72.850286 72.850286 0 0 1-109.202286-63.049143z" fill="#00BEB4" ></path></symbol>' +
'<symbol id="icon-Pipeline" viewBox="0 0 1024 1024"><path d="M610.9184 729.6a59.392 59.392 0 0 1 59.3408 59.392v79.104a59.3408 59.3408 0 0 1-59.392 59.3408H413.1328a59.392 59.392 0 0 1-59.3408-59.392V788.992a59.392 59.392 0 0 1 59.392-59.3408h197.7856z m0-316.4672a59.392 59.392 0 0 1 59.3408 59.3408v79.104a59.3408 59.3408 0 0 1-59.392 59.392H413.1328a59.3408 59.3408 0 0 1-59.3408-59.392V472.4736a59.392 59.392 0 0 1 59.392-59.392h197.7856z m0-316.5184a59.392 59.392 0 0 1 59.3408 59.3408V235.008a59.3408 59.3408 0 0 1-59.392 59.392H413.1328A59.3408 59.3408 0 0 1 353.792 235.008V155.9552a59.392 59.392 0 0 1 59.392-59.392h197.7856z" fill="#00BEB4" ></path><path d="M749.3632 472.4224a197.8368 197.8368 0 0 1 0 395.6224l-4.608-0.256a39.5776 39.5776 0 0 1 4.608-78.848l6.9632-0.2048a118.6816 118.6816 0 0 0-6.9632-237.2096l-4.608-0.256a39.5776 39.5776 0 0 1 4.608-78.848zM274.6368 155.904l4.608 0.256a39.5776 39.5776 0 0 1-4.608 78.848 118.6816 118.6816 0 1 0 0 237.4144 39.5776 39.5776 0 1 1 0 79.104 197.7856 197.7856 0 1 1 0-395.6224z" fill="#1177D7" ></path></symbol>' + '<symbol id="icon-Pipeline" viewBox="0 0 1024 1024"><path d="M610.9184 729.6a59.392 59.392 0 0 1 59.3408 59.392v79.104a59.3408 59.3408 0 0 1-59.392 59.3408H413.1328a59.392 59.392 0 0 1-59.3408-59.392V788.992a59.392 59.392 0 0 1 59.392-59.3408h197.7856z m0-316.4672a59.392 59.392 0 0 1 59.3408 59.3408v79.104a59.3408 59.3408 0 0 1-59.392 59.392H413.1328a59.3408 59.3408 0 0 1-59.3408-59.392V472.4736a59.392 59.392 0 0 1 59.392-59.392h197.7856z m0-316.5184a59.392 59.392 0 0 1 59.3408 59.3408V235.008a59.3408 59.3408 0 0 1-59.392 59.392H413.1328A59.3408 59.3408 0 0 1 353.792 235.008V155.9552a59.392 59.392 0 0 1 59.392-59.392h197.7856z" fill="#00BEB4" ></path><path d="M749.3632 472.4224a197.8368 197.8368 0 0 1 0 395.6224l-4.608-0.256a39.5776 39.5776 0 0 1 4.608-78.848l6.9632-0.2048a118.6816 118.6816 0 0 0-6.9632-237.2096l-4.608-0.256a39.5776 39.5776 0 0 1 4.608-78.848zM274.6368 155.904l4.608 0.256a39.5776 39.5776 0 0 1-4.608 78.848 118.6816 118.6816 0 1 0 0 237.4144 39.5776 39.5776 0 1 1 0 79.104 197.7856 197.7856 0 1 1 0-395.6224z" fill="#1177D7" ></path></symbol>' +
'<symbol id="icon-dataflow-01" viewBox="0 0 1024 1024"><path d="M636.202667 214.954667c-18.688 1.493333-28.288 4.266667-34.944 7.68a85.333333 85.333333 0 0 0-37.290667 37.290666c-3.413333 6.656-6.186667 16.213333-7.68 34.944C554.666667 314.069333 554.666667 338.901333 554.666667 375.466667V469.333333h135.253333a128.042667 128.042667 0 1 1 0 85.333334H554.666667v93.866666c0 36.565333 0 61.397333 1.621333 80.597334 1.493333 18.688 4.266667 28.288 7.68 34.944a85.333333 85.333333 0 0 0 37.290667 37.290666c6.656 3.413333 16.213333 6.186667 34.944 7.68 14.08 1.152 31.232 1.493333 53.76 1.578667A128.042667 128.042667 0 0 1 938.666667 853.333333a128 128 0 0 1-248.746667 42.666667 814.037333 814.037333 0 0 1-60.672-1.877333c-23.978667-1.962667-46.037333-6.186667-66.730667-16.725334a170.666667 170.666667 0 0 1-74.581333-74.581333c-10.538667-20.693333-14.762667-42.752-16.725333-66.730667C469.333333 712.96 469.333333 684.629333 469.333333 650.325333V554.666667H334.08a128.042667 128.042667 0 1 1 0-85.333334H469.333333V373.717333c0-34.346667 0-62.72 1.877334-85.76 1.962667-24.021333 6.186667-46.08 16.725333-66.773333a170.666667 170.666667 0 0 1 74.581333-74.581333c20.693333-10.538667 42.752-14.762667 66.730667-16.725334a813.653333 813.653333 0 0 1 60.714667-1.834666 128.042667 128.042667 0 1 1 0 85.333333c-22.528 0.085333-39.68 0.426667-53.76 1.578667z" ></path></symbol>' + '<symbol id="icon-dataflow-01" viewBox="0 0 1024 1024"><path d="M636.202667 214.954667c-18.688 1.493333-28.288 4.266667-34.944 7.68a85.333333 85.333333 0 0 0-37.290667 37.290666c-3.413333 6.656-6.186667 16.213333-7.68 34.944C554.666667 314.069333 554.666667 338.901333 554.666667 375.466667V469.333333h135.253333a128.042667 128.042667 0 1 1 0 85.333334H554.666667v93.866666c0 36.565333 0 61.397333 1.621333 80.597334 1.493333 18.688 4.266667 28.288 7.68 34.944a85.333333 85.333333 0 0 0 37.290667 37.290666c6.656 3.413333 16.213333 6.186667 34.944 7.68 14.08 1.152 31.232 1.493333 53.76 1.578667A128.042667 128.042667 0 0 1 938.666667 853.333333a128 128 0 0 1-248.746667 42.666667 814.037333 814.037333 0 0 1-60.672-1.877333c-23.978667-1.962667-46.037333-6.186667-66.730667-16.725334a170.666667 170.666667 0 0 1-74.581333-74.581333c-10.538667-20.693333-14.762667-42.752-16.725333-66.730667C469.333333 712.96 469.333333 684.629333 469.333333 650.325333V554.666667H334.08a128.042667 128.042667 0 1 1 0-85.333334H469.333333V373.717333c0-34.346667 0-62.72 1.877334-85.76 1.962667-24.021333 6.186667-46.08 16.725333-66.773333a170.666667 170.666667 0 0 1 74.581333-74.581333c20.693333-10.538667 42.752-14.762667 66.730667-16.725334a813.653333 813.653333 0 0 1 60.714667-1.834666 128.042667 128.042667 0 1 1 0 85.333333c-22.528 0.085333-39.68 0.426667-53.76 1.578667z" ></path></symbol>' +
'<symbol id="icon-knowledgegraph" viewBox="0 0 1024 1024"><path d="M190.464 489.472h327.68v40.96h-327.68z" ></path><path d="M482.34496 516.5056l111.26784-308.20352 38.54336 13.9264L520.86784 530.432z" ></path><path d="M620.544 196.608m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M182.272 509.952m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M558.65344 520.9088l283.77088 163.84-20.48 35.47136-283.77088-163.84z" ></path><path d="M841.728 686.08m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M448.67584 803.77856l49.60256-323.91168 40.48896 6.20544-49.60256 323.91168z" ></path><path d="M512 530.432m-143.36 0a143.36 143.36 0 1 0 286.72 0 143.36 143.36 0 1 0-286.72 0Z" ></path><path d="M462.848 843.776m-102.4 0a102.4 102.4 0 1 0 204.8 0 102.4 102.4 0 1 0-204.8 0Z"></path></symbol>' + '<symbol id="icon-knowledgegraph" viewBox="0 0 1024 1024"><path d="M190.464 489.472h327.68v40.96h-327.68z" ></path><path d="M482.34496 516.5056l111.26784-308.20352 38.54336 13.9264L520.86784 530.432z" ></path><path d="M620.544 196.608m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M182.272 509.952m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M558.65344 520.9088l283.77088 163.84-20.48 35.47136-283.77088-163.84z" ></path><path d="M841.728 686.08m-122.88 0a122.88 122.88 0 1 0 245.76 0 122.88 122.88 0 1 0-245.76 0Z" ></path><path d="M448.67584 803.77856l49.60256-323.91168 40.48896 6.20544-49.60256 323.91168z" ></path><path d="M512 530.432m-143.36 0a143.36 143.36 0 1 0 286.72 0 143.36 143.36 0 1 0-286.72 0Z" ></path><path d="M462.848 843.776m-102.4 0a102.4 102.4 0 1 0 204.8 0 102.4 102.4 0 1 0-204.8 0Z"></path></symbol>' +

View File

@ -246,7 +246,7 @@ const CustomTimeline = ({
orientation = 'horizontal', orientation = 'horizontal',
lineStyle = 'solid', lineStyle = 'solid',
lineColor = 'var(--text-secondary)', lineColor = 'var(--text-secondary)',
indicatorColor = 'var(--accent-primary)', indicatorColor = 'rgb(var(--accent-primary))',
defaultValue = 1, defaultValue = 1,
className, className,
activeStyle, activeStyle,

View File

@ -1,3 +1,4 @@
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useNavigate, useParams, useSearchParams } from 'umi'; import { useNavigate, useParams, useSearchParams } from 'umi';
@ -23,6 +24,13 @@ export const useNavigatePage = () => {
[navigate], [navigate],
); );
const navigateToDataFile = useCallback(
(id: string) => () => {
navigate(`${Routes.DatasetBase}${Routes.DatasetBase}/${id}`);
},
[navigate],
);
const navigateToHome = useCallback(() => { const navigateToHome = useCallback(() => {
navigate(Routes.Root); navigate(Routes.Root);
}, [navigate]); }, [navigate]);
@ -133,10 +141,16 @@ export const useNavigatePage = () => {
); );
const navigateToDataflowResult = useCallback( const navigateToDataflowResult = useCallback(
(id: string, knowledgeId: string, doc_id?: string) => () => { (props: NavigateToDataflowResultProps) => () => {
let params: string[] = [];
Object.keys(props).forEach((key) => {
if (props[key]) {
params.push(`${key}=${props[key]}`);
}
});
navigate( navigate(
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`, // `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
`${Routes.DataflowResult}?id=${id}&doc_id=${doc_id}&${QueryStringMap.KnowledgeId}=${knowledgeId}&type=dataflow`, `${Routes.DataflowResult}?${params.join('&')}`,
); );
}, },
[navigate], [navigate],
@ -163,5 +177,6 @@ export const useNavigatePage = () => {
navigateToOldProfile, navigateToOldProfile,
navigateToDataflowResult, navigateToDataflowResult,
navigateToDataflow, navigateToDataflow,
navigateToDataFile,
}; };
}; };

View File

@ -39,7 +39,9 @@ function InnerButtonEdge({
targetPosition, targetPosition,
}); });
const selectedStyle = useMemo(() => { const selectedStyle = useMemo(() => {
return selected ? { strokeWidth: 1, stroke: 'var(--accent-primary)' } : {}; return selected
? { strokeWidth: 1, stroke: 'rgb(var(--accent-primary))' }
: {};
}, [selected]); }, [selected]);
const onEdgeClick = () => { const onEdgeClick = () => {
@ -56,7 +58,7 @@ function InnerButtonEdge({
let index = idx - 1; let index = idx - 1;
while (index >= 0) { while (index >= 0) {
if (path[index] === source) { if (path[index] === source) {
return { strokeWidth: 1, stroke: 'var(--accent-primary)' }; return { strokeWidth: 1, stroke: 'rgb(var(--accent-primary))' };
} }
index--; index--;
} }

View File

@ -72,7 +72,7 @@ const Chunk = () => {
chunkUpdatingVisible, chunkUpdatingVisible,
documentId, documentId,
} = useUpdateChunk(); } = useUpdateChunk();
const { navigateToDataset, getQueryString, navigateToDatasetList } = const { navigateToDataFile, getQueryString, navigateToDatasetList } =
useNavigatePage(); useNavigatePage();
const fileUrl = useGetDocumentUrl(); const fileUrl = useGetDocumentUrl();
useEffect(() => { useEffect(() => {
@ -188,7 +188,7 @@ const Chunk = () => {
<BreadcrumbSeparator /> <BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbLink <BreadcrumbLink
onClick={navigateToDataset( onClick={navigateToDataFile(
getQueryString(QueryStringMap.id) as string, getQueryString(QueryStringMap.id) as string,
)} )}
> >

View File

@ -6,8 +6,10 @@ import {
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
import { import {
ArrowUpRight, ArrowUpRight,
CirclePause, CirclePause,
@ -41,7 +43,7 @@ export function LogSheet({
const { t } = useTranslation(); const { t } = useTranslation();
const { handleDownloadJson } = useDownloadOutput(logs); const { handleDownloadJson } = useDownloadOutput(logs);
const { navigateToDataflowResult } = useNavigatePage();
return ( return (
<Sheet open onOpenChange={hideModal} modal={false}> <Sheet open onOpenChange={hideModal} modal={false}>
<SheetContent <SheetContent
@ -51,7 +53,20 @@ export function LogSheet({
<SheetHeader> <SheetHeader>
<SheetTitle className="flex items-center gap-2.5"> <SheetTitle className="flex items-center gap-2.5">
<Logs className="size-4" /> {t('flow.log')} <Logs className="size-4" /> {t('flow.log')}
<Button variant={'ghost'} disabled={!isCompleted}> <Button
variant={'ghost'}
disabled={!isCompleted}
onClick={navigateToDataflowResult({
id: 'cfc28d6c9c4911f088bf047c16ec874f', // 'log_id',
[PipelineResultSearchParams.AgentId]:
'cfc28d6c9c4911f088bf047c16ec874f', // 'agent_id',
[PipelineResultSearchParams.DocumentId]:
'05b0e19a9d9d11f0b674047c16ec874f', //'doc_id',
[PipelineResultSearchParams.AgentTitle]: 'full', //'title',
[PipelineResultSearchParams.IsReadOnly]: 'true',
[PipelineResultSearchParams.Type]: 'dataflow',
})}
>
{t('dataflow.viewResult')} <ArrowUpRight /> {t('dataflow.viewResult')} <ArrowUpRight />
</Button> </Button>
</SheetTitle> </SheetTitle>

View File

@ -7,28 +7,17 @@ import { ChunkTextMode } from '../../constant';
interface ChunkResultBarProps { interface ChunkResultBarProps {
changeChunkTextMode: React.Dispatch<React.SetStateAction<string | number>>; changeChunkTextMode: React.Dispatch<React.SetStateAction<string | number>>;
createChunk: (text: string) => void; createChunk: (text: string) => void;
isReadonly: boolean;
} }
export default ({ changeChunkTextMode, createChunk }: ChunkResultBarProps) => { export default ({
changeChunkTextMode,
createChunk,
isReadonly,
}: ChunkResultBarProps) => {
const { t } = useTranslate('chunk'); const { t } = useTranslate('chunk');
const [textSelectValue, setTextSelectValue] = useState<string | number>( const [textSelectValue, setTextSelectValue] = useState<string | number>(
ChunkTextMode.Full, ChunkTextMode.Full,
); );
// const handleFilterChange = (e: string | number) => {
// const value = e === -1 ? undefined : (e as number);
// selectAllChunk(false);
// handleSetAvailable(value);
// };
// const filterContent = (
// <div className="w-[200px]">
// <Radio.Group onChange={handleFilterChange} value={available}>
// <div className="flex flex-col gap-2 p-4">
// <Radio value={-1}>{t('all')}</Radio>
// <Radio value={1}>{t('enabled')}</Radio>
// <Radio value={0}>{t('disabled')}</Radio>
// </div>
// </Radio.Group>
// </div>
// );
const textSelectOptions = [ const textSelectOptions = [
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full }, { label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse }, { label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
@ -57,31 +46,15 @@ export default ({ changeChunkTextMode, createChunk }: ChunkResultBarProps) => {
</div> </div>
))} ))}
</div> </div>
{/* <Input {!isReadonly && (
className="bg-bg-card text-muted-foreground" <Button
style={{ width: 200 }} onClick={() => createChunk('')}
placeholder={t('search')} variant={'secondary'}
icon={<SearchOutlined />} className="bg-bg-card text-muted-foreground hover:bg-card"
onChange={handleInputChange} >
value={searchString} <Plus size={44} />
/> </Button>
<Popover> )}
<PopoverTrigger asChild>
<Button className="bg-bg-card text-muted-foreground hover:bg-card">
<ListFilter />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[200px]">
{filterContent}
</PopoverContent>
</Popover> */}
<Button
onClick={() => createChunk('')}
variant={'secondary'}
className="bg-bg-card text-muted-foreground hover:bg-card"
>
<Plus size={44} />
</Button>
</div> </div>
); );
}; };

View File

@ -0,0 +1,30 @@
import { useEffect, useRef, useState } from 'react';
import { IJsonContainerProps, IObjContainerProps } from './interface';
export const useParserInit = ({
initialValue,
}: {
initialValue:
| Pick<IJsonContainerProps, 'initialValue'>
| Pick<IObjContainerProps, 'initialValue'>;
}) => {
const [content, setContent] = useState(initialValue);
useEffect(() => {
setContent(initialValue);
console.log('initialValue json parse', initialValue);
}, [initialValue]);
const [activeEditIndex, setActiveEditIndex] = useState<number | undefined>(
undefined,
);
const editDivRef = useRef<HTMLDivElement>(null);
return {
content,
setContent,
activeEditIndex,
setActiveEditIndex,
editDivRef,
};
};

View File

@ -1,22 +1,8 @@
import { CheckedState } from '@radix-ui/react-checkbox'; import { CheckedState } from '@radix-ui/react-checkbox';
import { ChunkTextMode } from '../../constant'; import { FormatPreserveEditorProps } from './interface';
import { ArrayContainer, parserKeyMap } from './json-parser'; import { ArrayContainer } from './json-parser';
import { ObjectContainer } from './object-parser'; import { ObjectContainer } from './object-parser';
interface FormatPreserveEditorProps {
initialValue: {
key: keyof typeof parserKeyMap | 'text' | 'html';
type: string;
value: Array<{ [key: string]: string }>;
};
onSave: (value: any) => void;
className?: string;
isSelect?: boolean;
isDelete?: boolean;
isChunck?: boolean;
handleCheckboxClick?: (id: string | number, checked: boolean) => void;
selectedChunkIds?: string[];
textMode?: ChunkTextMode;
}
const FormatPreserveEditor = ({ const FormatPreserveEditor = ({
initialValue, initialValue,
onSave, onSave,
@ -25,6 +11,8 @@ const FormatPreserveEditor = ({
handleCheckboxClick, handleCheckboxClick,
selectedChunkIds, selectedChunkIds,
textMode, textMode,
clickChunk,
isReadonly,
}: FormatPreserveEditorProps) => { }: FormatPreserveEditorProps) => {
console.log('initialValue', initialValue); console.log('initialValue', initialValue);
@ -42,6 +30,7 @@ const FormatPreserveEditor = ({
<div className="editor-container"> <div className="editor-container">
{['json', 'chunks'].includes(initialValue.key) && ( {['json', 'chunks'].includes(initialValue.key) && (
<ArrayContainer <ArrayContainer
isReadonly={isReadonly}
className={className} className={className}
initialValue={initialValue} initialValue={initialValue}
handleCheck={handleCheck} handleCheck={handleCheck}
@ -51,11 +40,13 @@ const FormatPreserveEditor = ({
unescapeNewlines={unescapeNewlines} unescapeNewlines={unescapeNewlines}
textMode={textMode} textMode={textMode}
isChunck={isChunck} isChunck={isChunck}
clickChunk={clickChunk}
/> />
)} )}
{['text', 'html'].includes(initialValue.key) && ( {['text', 'html'].includes(initialValue.key) && (
<ObjectContainer <ObjectContainer
isReadonly={isReadonly}
className={className} className={className}
initialValue={initialValue} initialValue={initialValue}
handleCheck={handleCheck} handleCheck={handleCheck}
@ -65,6 +56,7 @@ const FormatPreserveEditor = ({
unescapeNewlines={unescapeNewlines} unescapeNewlines={unescapeNewlines}
textMode={textMode} textMode={textMode}
isChunck={isChunck} isChunck={isChunck}
clickChunk={clickChunk}
/> />
)} )}
</div> </div>

View File

@ -0,0 +1,65 @@
import { CheckedState } from '@radix-ui/react-checkbox';
import { ChunkTextMode } from '../../constant';
import { IChunk } from '../../interface';
import { parserKeyMap } from './json-parser';
export interface FormatPreserveEditorProps {
initialValue: {
key: keyof typeof parserKeyMap | 'text' | 'html';
type: string;
value: Array<{ [key: string]: string }>;
};
onSave: (value: any) => void;
className?: string;
isSelect?: boolean;
isDelete?: boolean;
isChunck?: boolean;
handleCheckboxClick?: (id: string | number, checked: boolean) => void;
selectedChunkIds?: string[];
textMode?: ChunkTextMode;
clickChunk: (chunk: IChunk) => void;
isReadonly: boolean;
}
export type IJsonContainerProps = {
initialValue: {
key: keyof typeof parserKeyMap;
type: string;
value: {
[key: string]: string;
}[];
};
isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void;
selectedChunkIds: string[] | undefined;
unescapeNewlines: (text: string) => string;
escapeNewlines: (text: string) => string;
onSave: (data: {
value: {
text: string;
}[];
key: string;
type: string;
}) => void;
className?: string;
textMode?: ChunkTextMode;
clickChunk: (chunk: IChunk) => void;
isReadonly: boolean;
};
export type IObjContainerProps = {
initialValue: {
key: string;
type: string;
value: string;
};
isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void;
unescapeNewlines: (text: string) => string;
escapeNewlines: (text: string) => string;
onSave: (data: { value: string; key: string; type: string }) => void;
className?: string;
textMode?: ChunkTextMode;
clickChunk: (chunk: IChunk) => void;
isReadonly: boolean;
};

View File

@ -1,37 +1,16 @@
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { CheckedState } from '@radix-ui/react-checkbox'; import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { useParserInit } from './hook';
import { IJsonContainerProps } from './interface';
export const parserKeyMap = { export const parserKeyMap = {
json: 'text', json: 'text',
chunks: 'content_with_weight', chunks: 'text',
}; };
type IProps = {
initialValue: { export const ArrayContainer = (props: IJsonContainerProps) => {
key: keyof typeof parserKeyMap;
type: string;
value: {
[key: string]: string;
}[];
};
isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void;
selectedChunkIds: string[] | undefined;
unescapeNewlines: (text: string) => string;
escapeNewlines: (text: string) => string;
onSave: (data: {
value: {
text: string;
}[];
key: string;
type: string;
}) => void;
className?: string;
textMode?: ChunkTextMode;
};
export const ArrayContainer = (props: IProps) => {
const { const {
initialValue, initialValue,
isChunck, isChunck,
@ -42,29 +21,27 @@ export const ArrayContainer = (props: IProps) => {
onSave, onSave,
className, className,
textMode, textMode,
clickChunk,
isReadonly,
} = props; } = props;
const [content, setContent] = useState(initialValue); const {
content,
setContent,
activeEditIndex,
setActiveEditIndex,
editDivRef,
} = useParserInit({ initialValue });
useEffect(() => {
setContent(initialValue);
console.log('initialValue json parse', initialValue);
}, [initialValue]);
const [activeEditIndex, setActiveEditIndex] = useState<number | undefined>(
undefined,
);
const editDivRef = useRef<HTMLDivElement>(null);
const handleEdit = useCallback( const handleEdit = useCallback(
(e?: any, index?: number) => { (e?: any, index?: number) => {
console.log(e, e.target.innerText);
setContent((pre) => ({ setContent((pre) => ({
...pre, ...pre,
value: pre.value.map((item, i) => { value: pre.value.map((item, i) => {
if (i === index) { if (i === index) {
return { return {
...item, ...item,
[parserKeyMap[content.key]]: e.target.innerText, [parserKeyMap[content.key]]: unescapeNewlines(e.target.innerText),
}; };
} }
return item; return item;
@ -76,14 +53,13 @@ export const ArrayContainer = (props: IProps) => {
); );
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
console.log(e, e.target.innerText);
const saveData = { const saveData = {
...content, ...content,
value: content.value?.map((item, index) => { value: content.value?.map((item, index) => {
if (index === activeEditIndex) { if (index === activeEditIndex) {
return { return {
...item, ...item,
[parserKeyMap[content.key]]: unescapeNewlines(e.target.innerText), [parserKeyMap[content.key]]: e.target.innerText,
}; };
} else { } else {
return item; return item;
@ -99,8 +75,9 @@ export const ArrayContainer = (props: IProps) => {
useEffect(() => { useEffect(() => {
if (activeEditIndex !== undefined && editDivRef.current) { if (activeEditIndex !== undefined && editDivRef.current) {
editDivRef.current.focus(); editDivRef.current.focus();
editDivRef.current.textContent = editDivRef.current.textContent = escapeNewlines(
content.value[activeEditIndex][parserKeyMap[content.key]]; content.value[activeEditIndex][parserKeyMap[content.key]],
);
} }
}, [activeEditIndex, content]); }, [activeEditIndex, content]);
@ -119,7 +96,7 @@ export const ArrayContainer = (props: IProps) => {
: '' : ''
} }
> >
{isChunck && ( {isChunck && !isReadonly && (
<Checkbox <Checkbox
onCheckedChange={(e) => { onCheckedChange={(e) => {
handleCheck(e, index); handleCheck(e, index);
@ -132,16 +109,8 @@ export const ArrayContainer = (props: IProps) => {
{activeEditIndex === index && ( {activeEditIndex === index && (
<div <div
ref={editDivRef} ref={editDivRef}
contentEditable={true} contentEditable={!isReadonly}
onBlur={handleSave} onBlur={handleSave}
// onKeyUp={handleChange}
// dangerouslySetInnerHTML={{
// __html: DOMPurify.sanitize(
// escapeNewlines(
// content.value[index][parserKeyMap[content.key]],
// ),
// ),
// }}
className={cn( className={cn(
'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0', 'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0',
className, className,
@ -159,7 +128,10 @@ export const ArrayContainer = (props: IProps) => {
)} )}
key={index} key={index}
onClick={(e) => { onClick={(e) => {
handleEdit(e, index); clickChunk(item);
if (!isReadonly) {
handleEdit(e, index);
}
}} }}
> >
{escapeNewlines(item[parserKeyMap[content.key]])} {escapeNewlines(item[parserKeyMap[content.key]])}

View File

@ -1,24 +1,10 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { CheckedState } from '@radix-ui/react-checkbox'; import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { useParserInit } from './hook';
type IProps = { import { IObjContainerProps } from './interface';
initialValue: { export const ObjectContainer = (props: IObjContainerProps) => {
key: string;
type: string;
value: string;
};
isChunck?: boolean;
handleCheck: (e: CheckedState, index: number) => void;
unescapeNewlines: (text: string) => string;
escapeNewlines: (text: string) => string;
onSave: (data: { value: string; key: string; type: string }) => void;
className?: string;
textMode?: ChunkTextMode;
};
export const ObjectContainer = (props: IProps) => {
const { const {
initialValue, initialValue,
isChunck, isChunck,
@ -27,36 +13,34 @@ export const ObjectContainer = (props: IProps) => {
onSave, onSave,
className, className,
textMode, textMode,
clickChunk,
isReadonly,
} = props; } = props;
const [content, setContent] = useState(initialValue); const {
content,
setContent,
activeEditIndex,
setActiveEditIndex,
editDivRef,
} = useParserInit({ initialValue });
useEffect(() => {
setContent(initialValue);
console.log('initialValue object parse', initialValue);
}, [initialValue]);
const [activeEditIndex, setActiveEditIndex] = useState<number | undefined>(
undefined,
);
const editDivRef = useRef<HTMLDivElement>(null);
const handleEdit = useCallback( const handleEdit = useCallback(
(e?: any) => { (e?: any) => {
console.log(e, e.target.innerText);
setContent((pre) => ({ setContent((pre) => ({
...pre, ...pre,
value: e.target.innerText, value: escapeNewlines(e.target.innerText),
})); }));
setActiveEditIndex(1); setActiveEditIndex(1);
}, },
[setContent, setActiveEditIndex], [setContent, setActiveEditIndex],
); );
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
console.log(e, e.target.innerText);
const saveData = { const saveData = {
...content, ...content,
value: unescapeNewlines(e.target.innerText), value: e.target.innerText,
}; };
onSave(saveData); onSave(saveData);
setActiveEditIndex(undefined); setActiveEditIndex(undefined);
@ -83,7 +67,7 @@ export const ObjectContainer = (props: IProps) => {
{activeEditIndex && ( {activeEditIndex && (
<div <div
ref={editDivRef} ref={editDivRef}
contentEditable={true} contentEditable={!isReadonly}
onBlur={handleSave} onBlur={handleSave}
className={cn( className={cn(
'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0', 'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0',
@ -100,7 +84,10 @@ export const ObjectContainer = (props: IProps) => {
}, },
)} )}
onClick={(e) => { onClick={(e) => {
handleEdit(e); clickChunk(content);
if (!isReadonly) {
handleEdit(e);
}
}} }}
> >
{escapeNewlines(content.value)} {escapeNewlines(content.value)}

View File

@ -83,8 +83,8 @@ const TimelineDataFlow = ({
nodeSize={24} nodeSize={24}
activeStyle={{ activeStyle={{
nodeSize: 30, nodeSize: 30,
iconColor: 'var(--accent-primary)', iconColor: 'rgb(var(--accent-primary))',
textColor: 'var(--accent-primary)', textColor: 'rgb(var(--accent-primary))',
}} }}
/> />
</div> </div>

View File

@ -9,6 +9,15 @@ export enum TimelineNodeType {
contextGenerator = 'extractor', contextGenerator = 'extractor',
titleSplitter = 'hierarchicalMerger', titleSplitter = 'hierarchicalMerger',
characterSplitter = 'splitter', characterSplitter = 'splitter',
tokenizer = 'indexer', tokenizer = 'tokenizer',
end = 'end', end = 'end',
} }
export enum PipelineResultSearchParams {
DocumentId = 'doc_id',
KnowledgeId = 'knowledgeId',
Type = 'type',
IsReadOnly = 'is_read_only',
AgentId = 'agent_id',
AgentTitle = 'agent_title',
}

View File

@ -1,10 +1,6 @@
import { TimelineNode } from '@/components/originui/timeline'; import { TimelineNode } from '@/components/originui/timeline';
import message from '@/components/ui/message'; import message from '@/components/ui/message';
import { import { useCreateChunk, useDeleteChunk } from '@/hooks/chunk-hooks';
useCreateChunk,
useDeleteChunk,
useSelectChunkList,
} 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 { IChunk } from '@/interfaces/database/knowledge'; import { IChunk } from '@/interfaces/database/knowledge';
@ -18,7 +14,11 @@ import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter'; import { IHighlight } from 'react-pdf-highlighter';
import { useParams, useSearchParams } from 'umi'; import { useParams, useSearchParams } from 'umi';
import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line'; import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line';
import { ChunkTextMode, TimelineNodeType } from './constant'; import {
ChunkTextMode,
PipelineResultSearchParams,
TimelineNodeType,
} from './constant';
import { IDslComponent, IPipelineFileLogDetail } from './interface'; import { IDslComponent, IPipelineFileLogDetail } from './interface';
export const useFetchPipelineFileLogDetail = (props?: { export const useFetchPipelineFileLogDetail = (props?: {
@ -55,28 +55,21 @@ export const useFetchPipelineFileLogDetail = (props?: {
}; };
export const useHandleChunkCardClick = () => { export const useHandleChunkCardClick = () => {
const [selectedChunkId, setSelectedChunkId] = useState<string>(''); const [selectedChunk, setSelectedChunk] = useState<IChunk>();
const handleChunkCardClick = useCallback((chunkId: string) => { const handleChunkCardClick = useCallback((chunk: IChunk) => {
setSelectedChunkId(chunkId); console.log('click-chunk-->', chunk);
setSelectedChunk(chunk);
}, []); }, []);
return { handleChunkCardClick, selectedChunkId }; return { handleChunkCardClick, selectedChunk };
}; };
export const useGetSelectedChunk = (selectedChunkId: string) => { export const useGetChunkHighlights = (selectedChunk?: IChunk) => {
const data = useSelectChunkList();
return (
data?.data?.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk)
);
};
export const useGetChunkHighlights = (selectedChunkId: string) => {
const [size, setSize] = useState({ width: 849, height: 1200 }); const [size, setSize] = useState({ width: 849, height: 1200 });
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
const highlights: IHighlight[] = useMemo(() => { const highlights: IHighlight[] = useMemo(() => {
return buildChunkHighlights(selectedChunk, size); return selectedChunk ? buildChunkHighlights(selectedChunk, size) : [];
}, [selectedChunk, size]); }, [selectedChunk, size]);
const setWidthAndHeight = useCallback((width: number, height: number) => { const setWidthAndHeight = useCallback((width: number, height: number) => {
@ -276,3 +269,23 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
timelineNodes, timelineNodes,
}; };
}; };
export const useGetPipelineResultSearchParams = () => {
const [currentQueryParameters] = useSearchParams();
const is_read_only = currentQueryParameters.get(
PipelineResultSearchParams.IsReadOnly,
) as 'true' | 'false';
console.log('is_read_only', is_read_only);
return {
type: currentQueryParameters.get(PipelineResultSearchParams.Type) || '',
documentId:
currentQueryParameters.get(PipelineResultSearchParams.DocumentId) || '',
knowledgeId:
currentQueryParameters.get(PipelineResultSearchParams.KnowledgeId) || '',
isReadOnly: is_read_only === 'true',
agentId:
currentQueryParameters.get(PipelineResultSearchParams.AgentId) || '',
agentTitle:
currentQueryParameters.get(PipelineResultSearchParams.AgentTitle) || '',
};
};

View File

@ -5,6 +5,7 @@ import DocumentPreview from './components/document-preview';
import { import {
useFetchPipelineFileLogDetail, useFetchPipelineFileLogDetail,
useGetChunkHighlights, useGetChunkHighlights,
useGetPipelineResultSearchParams,
useHandleChunkCardClick, useHandleChunkCardClick,
useRerunDataflow, useRerunDataflow,
useTimelineDataFlow, useTimelineDataFlow,
@ -25,10 +26,7 @@ import {
} from '@/components/ui/breadcrumb'; } from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import { import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
QueryStringMap,
useNavigatePage,
} from '@/hooks/logic-hooks/navigate-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useGetDocumentUrl } from './components/document-preview/hooks'; import { useGetDocumentUrl } from './components/document-preview/hooks';
import TimelineDataFlow from './components/time-line'; import TimelineDataFlow from './components/time-line';
@ -38,22 +36,29 @@ import { IDslComponent } from './interface';
import ParserContainer from './parser'; import ParserContainer from './parser';
const Chunk = () => { const Chunk = () => {
const { isReadOnly, knowledgeId, agentId, agentTitle } =
useGetPipelineResultSearchParams();
const { const {
data: { documentInfo }, data: { documentInfo },
} = useFetchNextChunkList(); } = useFetchNextChunkList();
const { selectedChunkId } = useHandleChunkCardClick(); const { selectedChunk, handleChunkCardClick } = useHandleChunkCardClick();
const [activeStepId, setActiveStepId] = useState<number | string>(2); const [activeStepId, setActiveStepId] = useState<number | string>(2);
const { data: dataset } = useFetchPipelineFileLogDetail(); const { data: dataset } = useFetchPipelineFileLogDetail();
const { t } = useTranslation(); const { t } = useTranslation();
const { timelineNodes } = useTimelineDataFlow(dataset); const { timelineNodes } = useTimelineDataFlow(dataset);
const { navigateToDataset, getQueryString, navigateToDatasetList } = const {
useNavigatePage(); navigateToDataset,
navigateToDatasetList,
navigateToAgents,
navigateToDataflow,
} = useNavigatePage();
const fileUrl = useGetDocumentUrl(); const fileUrl = useGetDocumentUrl();
const { highlights, setWidthAndHeight } = const { highlights, setWidthAndHeight } =
useGetChunkHighlights(selectedChunkId); useGetChunkHighlights(selectedChunk);
const fileType = useMemo(() => { const fileType = useMemo(() => {
switch (documentInfo?.type) { switch (documentInfo?.type) {
@ -77,6 +82,7 @@ const Chunk = () => {
} = useRerunDataflow({ } = useRerunDataflow({
data: dataset, data: dataset,
}); });
const handleStepChange = (id: number | string, step: TimelineNode) => { const handleStepChange = (id: number | string, step: TimelineNode) => {
if (isChange) { if (isChange) {
Modal.show({ Modal.show({
@ -135,18 +141,32 @@ const Chunk = () => {
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbLink onClick={navigateToDatasetList}> <BreadcrumbLink
{t('knowledgeDetails.dataset')} onClick={() => {
if (knowledgeId) {
navigateToDatasetList();
}
if (agentId) {
navigateToAgents();
}
}}
>
{knowledgeId ? t('knowledgeDetails.dataset') : t('header.flow')}
</BreadcrumbLink> </BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />
<BreadcrumbItem> <BreadcrumbItem>
<BreadcrumbLink <BreadcrumbLink
onClick={navigateToDataset( onClick={() => {
getQueryString(QueryStringMap.KnowledgeId) as string, if (knowledgeId) {
)} navigateToDataset(knowledgeId)();
}
if (agentId) {
navigateToDataflow(agentId)();
}
}}
> >
{t('knowledgeDetails.overview')} {knowledgeId ? t('knowledgeDetails.overview') : agentTitle}
</BreadcrumbLink> </BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
<BreadcrumbSeparator /> <BreadcrumbSeparator />
@ -194,8 +214,10 @@ const Chunk = () => {
{/* {currentTimeNode?.type === TimelineNodeType.parser && ( */} {/* {currentTimeNode?.type === TimelineNodeType.parser && ( */}
{(currentTimeNode?.type === TimelineNodeType.parser || {(currentTimeNode?.type === TimelineNodeType.parser ||
currentTimeNode?.type === TimelineNodeType.characterSplitter || currentTimeNode?.type === TimelineNodeType.characterSplitter ||
currentTimeNode?.type === TimelineNodeType.titleSplitter) && ( currentTimeNode?.type === TimelineNodeType.titleSplitter ||
currentTimeNode?.type === TimelineNodeType.contextGenerator) && (
<ParserContainer <ParserContainer
isReadonly={isReadOnly}
isChange={isChange} isChange={isChange}
reRunLoading={reRunLoading} reRunLoading={reRunLoading}
setIsChange={setIsChange} setIsChange={setIsChange}
@ -206,6 +228,7 @@ const Chunk = () => {
key: string; key: string;
} }
} }
clickChunk={handleChunkCardClick}
reRunFunc={handleReRunFunc} reRunFunc={handleReRunFunc}
/> />
)} )}

View File

@ -1,3 +1,5 @@
import { PipelineResultSearchParams } from './constant';
interface ComponentParams { interface ComponentParams {
debug_inputs: Record<string, any>; debug_inputs: Record<string, any>;
delay_after_error: number; delay_after_error: number;
@ -60,3 +62,19 @@ export interface IPipelineFileLogDetail {
update_date: string; update_date: string;
update_time: number; update_time: number;
} }
export interface IChunk {
positions: number[][];
image_id: string;
text: string;
}
export interface NavigateToDataflowResultProps {
id: string;
[PipelineResultSearchParams.KnowledgeId]?: string;
[PipelineResultSearchParams.DocumentId]: string;
[PipelineResultSearchParams.AgentId]?: string;
[PipelineResultSearchParams.AgentTitle]?: string;
[PipelineResultSearchParams.IsReadOnly]?: string;
[PipelineResultSearchParams.Type]: string;
}

View File

@ -10,17 +10,28 @@ import FormatPreserEditor from './components/parse-editer';
import RerunButton from './components/rerun-button'; import RerunButton from './components/rerun-button';
import { TimelineNodeType } from './constant'; import { TimelineNodeType } from './constant';
import { useChangeChunkTextMode } from './hooks'; import { useChangeChunkTextMode } from './hooks';
import { IDslComponent } from './interface'; import { IChunk, IDslComponent } from './interface';
interface IProps { interface IProps {
isReadonly: boolean;
isChange: boolean; isChange: boolean;
setIsChange: (isChange: boolean) => void; setIsChange: (isChange: boolean) => void;
step?: TimelineNode; step?: TimelineNode;
data: { value: IDslComponent; key: string }; data: { value: IDslComponent; key: string };
reRunLoading: boolean; reRunLoading: boolean;
clickChunk: (chunk: IChunk) => void;
reRunFunc: (data: { value: IDslComponent; key: string }) => void; reRunFunc: (data: { value: IDslComponent; key: string }) => void;
} }
const ParserContainer = (props: IProps) => { const ParserContainer = (props: IProps) => {
const { isChange, setIsChange, step, data, reRunFunc, reRunLoading } = props; const {
isChange,
setIsChange,
step,
data,
reRunFunc,
reRunLoading,
clickChunk,
isReadonly,
} = props;
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]); const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
const { changeChunkTextMode, textMode } = useChangeChunkTextMode(); const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
@ -83,13 +94,13 @@ const ParserContainer = (props: IProps) => {
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);
setSelectedChunkIds([]); setSelectedChunkIds([]);
} }
}, [selectedChunkIds, initialText]); }, [selectedChunkIds, initialText, setIsChange]);
const handleCheckboxClick = useCallback( const handleCheckboxClick = useCallback(
(id: string | number, checked: boolean) => { (id: string | number, checked: boolean) => {
console.log('handleCheckboxClick', id, checked, selectedChunkIds);
setSelectedChunkIds((prev) => { setSelectedChunkIds((prev) => {
if (checked) { if (checked) {
return [...prev, id.toString()]; return [...prev, id.toString()];
@ -116,20 +127,18 @@ const ParserContainer = (props: IProps) => {
const handleCreateChunk = useCallback( const handleCreateChunk = useCallback(
(text: string) => { (text: string) => {
console.log('handleCreateChunk', text);
const newText = [...initialText.value, { text: text || ' ' }]; const newText = [...initialText.value, { text: text || ' ' }];
setInitialText({ setInitialText({
...initialText, ...initialText,
value: newText, value: newText,
}); });
console.log('newText', newText, initialText);
}, },
[initialText], [initialText],
); );
return ( return (
<> <>
{isChange && ( {isChange && !isReadonly && (
<div className=" absolute top-2 right-6"> <div className=" absolute top-2 right-6">
<RerunButton <RerunButton
step={step} step={step}
@ -163,13 +172,16 @@ const ParserContainer = (props: IProps) => {
{isChunck && ( {isChunck && (
<div className="pt-[5px] pb-[5px] flex justify-between items-center"> <div className="pt-[5px] pb-[5px] flex justify-between items-center">
<CheckboxSets {!isReadonly && (
selectAllChunk={selectAllChunk} <CheckboxSets
removeChunk={handleRemoveChunk} selectAllChunk={selectAllChunk}
checked={selectedChunkIds.length === initialText.value.length} removeChunk={handleRemoveChunk}
selectedChunkIds={selectedChunkIds} checked={selectedChunkIds.length === initialText.value.length}
/> selectedChunkIds={selectedChunkIds}
/>
)}
<ChunkResultBar <ChunkResultBar
isReadonly={isReadonly}
changeChunkTextMode={changeChunkTextMode} changeChunkTextMode={changeChunkTextMode}
createChunk={handleCreateChunk} createChunk={handleCreateChunk}
/> />
@ -189,15 +201,14 @@ const ParserContainer = (props: IProps) => {
<FormatPreserEditor <FormatPreserEditor
initialValue={initialText} initialValue={initialText}
onSave={handleSave} onSave={handleSave}
// className={ isReadonly={isReadonly}
// initialText.key !== 'json' ? '!h-[calc(100vh-220px)]' : ''
// }
isChunck={isChunck} isChunck={isChunck}
textMode={textMode} textMode={textMode}
isDelete={ isDelete={
step?.type === TimelineNodeType.characterSplitter || step?.type === TimelineNodeType.characterSplitter ||
step?.type === TimelineNodeType.titleSplitter step?.type === TimelineNodeType.titleSplitter
} }
clickChunk={clickChunk}
handleCheckboxClick={handleCheckboxClick} handleCheckboxClick={handleCheckboxClick}
selectedChunkIds={selectedChunkIds} selectedChunkIds={selectedChunkIds}
/> />

View File

@ -1,3 +1,4 @@
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
import { import {
useGetPaginationWithRouter, useGetPaginationWithRouter,
useHandleSearchChange, useHandleSearchChange,
@ -32,6 +33,7 @@ const useFetchFileLogList = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange(); const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter(); const { pagination, setPagination } = useGetPaginationWithRouter();
const { filterValue, handleFilterSubmit } = useHandleFilterSubmit();
const { id } = useParams(); const { id } = useParams();
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>( const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
LogTabs.FILE_LOGS, LogTabs.FILE_LOGS,
@ -48,6 +50,7 @@ const useFetchFileLogList = () => {
pagination, pagination,
searchString, searchString,
active, active,
filterValue,
], ],
placeholderData: (previousData) => { placeholderData: (previousData) => {
if (previousData === undefined) { if (previousData === undefined) {
@ -57,13 +60,16 @@ const useFetchFileLogList = () => {
}, },
enabled: true, enabled: true,
queryFn: async () => { queryFn: async () => {
const { data: res = {} } = await fetchFunc({ const { data: res = {} } = await fetchFunc(
kb_id: knowledgeBaseId, {
page: pagination.current, kb_id: knowledgeBaseId,
page_size: pagination.pageSize, page: pagination.current,
keywords: searchString, page_size: pagination.pageSize,
// order_by: '', keywords: searchString,
}); // order_by: '',
},
{ ...filterValue },
);
return res.data || []; return res.data || [];
}, },
}); });
@ -82,6 +88,8 @@ const useFetchFileLogList = () => {
setPagination, setPagination,
active, active,
setActive, setActive,
filterValue,
handleFilterSubmit,
}; };
}; };

View File

@ -1,10 +1,12 @@
import { FilterCollection } from '@/components/list-filter-bar/interface';
import SvgIcon from '@/components/svg-icon'; import SvgIcon from '@/components/svg-icon';
import { useIsDarkTheme } from '@/components/theme-provider'; import { useIsDarkTheme } from '@/components/theme-provider';
import { useFetchDocumentList } from '@/hooks/use-document-request'; import { useFetchDocumentList } from '@/hooks/use-document-request';
import { parseColorToRGBA } from '@/utils/common-util'; import { t } from 'i18next';
import { CircleQuestionMark } from 'lucide-react'; import { CircleQuestionMark } from 'lucide-react';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RunningStatus, RunningStatusMap } 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';
@ -16,6 +18,10 @@ interface StatCardProps {
icon: JSX.Element; icon: JSX.Element;
children?: JSX.Element; children?: JSX.Element;
} }
interface CardFooterProcessProps {
success: number;
failed: number;
}
const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => { const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
return ( return (
@ -35,10 +41,6 @@ const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
); );
}; };
interface CardFooterProcessProps {
success: number;
failed: number;
}
const CardFooterProcess: FC<CardFooterProcessProps> = ({ const CardFooterProcess: FC<CardFooterProcessProps> = ({
success = 0, success = 0,
failed = 0, failed = 0,
@ -47,12 +49,7 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
return ( return (
<div className="flex items-center flex-col gap-2"> <div className="flex items-center flex-col gap-2">
<div className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary"> <div className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary">
<div <div className="flex items-center justify-between rounded-md w-1/2 p-2 bg-state-success-5">
className="flex items-center justify-between rounded-md w-1/2 p-2"
style={{
backgroundColor: `${parseColorToRGBA('var(--state-success)', 0.05)}`,
}}
>
<div className="flex items-center rounded-lg gap-1"> <div className="flex items-center rounded-lg gap-1">
<div className="w-2 h-2 rounded-full bg-state-success"></div> <div className="w-2 h-2 rounded-full bg-state-success"></div>
<div>{t('knowledgeDetails.success')}</div> <div>{t('knowledgeDetails.success')}</div>
@ -70,6 +67,35 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
</div> </div>
); );
}; };
const filters = [
{
field: 'operation_status',
label: t('knowledgeDetails.status'),
list: Object.values(RunningStatus).map((value) => {
// const value = key as RunningStatus;
console.log(value);
return {
id: value,
label: RunningStatusMap[value].label,
};
}),
},
{
field: 'types',
label: t('knowledgeDetails.task'),
list: [
{
id: 'Parse',
label: 'Parse',
},
{
id: 'Download',
label: 'Download',
},
],
},
];
const FileLogsPage: FC = () => { const FileLogsPage: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -89,12 +115,11 @@ const FileLogsPage: FC = () => {
failed: 0, failed: 0,
}, },
}); });
const { data: topData } = useFetchOverviewTital(); const { data: topData } = useFetchOverviewTital();
const { const {
pagination: { total: fileTotal }, pagination: { total: fileTotal },
} = useFetchDocumentList(); } = useFetchDocumentList();
console.log('topData --> ', topData);
useEffect(() => { useEffect(() => {
setTopAllData((prev) => { setTopAllData((prev) => {
return { return {
@ -127,6 +152,8 @@ const FileLogsPage: FC = () => {
pagination, pagination,
setPagination, setPagination,
active, active,
filterValue,
handleFilterSubmit,
setActive, setActive,
} = useFetchFileLogList(); } = useFetchFileLogList();
@ -156,6 +183,7 @@ const FileLogsPage: FC = () => {
}; };
const isDark = useIsDarkTheme(); const isDark = useIsDarkTheme();
return ( return (
<div className="p-5 min-w-[880px] border-border border rounded-lg mr-5"> <div className="p-5 min-w-[880px] border-border border rounded-lg mr-5">
{/* Stats Cards */} {/* Stats Cards */}
@ -215,10 +243,13 @@ const FileLogsPage: FC = () => {
{/* Tabs & Search */} {/* Tabs & Search */}
<DatasetFilter <DatasetFilter
filters={filters as FilterCollection[]}
value={filterValue}
active={active} active={active}
setActive={changeActiveLogs} setActive={changeActiveLogs}
searchString={searchString} searchString={searchString}
onSearchChange={handleInputChange} onSearchChange={handleInputChange}
onChange={handleFilterSubmit}
/> />
{/* Table */} {/* Table */}

View File

@ -14,6 +14,8 @@ import {
import { RunningStatusMap } from '@/constants/knowledge'; import { RunningStatusMap } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
import { formatDate, formatSecondsToHumanReadable } from '@/utils/date'; import { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
import { import {
ColumnDef, ColumnDef,
@ -41,9 +43,7 @@ export const getFileLogsTableColumns = (
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void, showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
kowledgeId: string, kowledgeId: string,
navigateToDataflowResult: ( navigateToDataflowResult: (
id: string, props: NavigateToDataflowResultProps,
knowledgeId: string,
doc_id?: string,
) => () => void, ) => () => void,
) => { ) => {
// const { t } = useTranslate('knowledgeDetails'); // const { t } = useTranslate('knowledgeDetails');
@ -165,18 +165,23 @@ export const getFileLogsTableColumns = (
> >
<Eye /> <Eye />
</Button> </Button>
<Button {row.original.pipeline_id && (
variant="ghost" <Button
size="sm" variant="ghost"
className="p-1" size="sm"
onClick={navigateToDataflowResult( className="p-1"
row.original.id, onClick={navigateToDataflowResult({
kowledgeId, id: row.original.id,
row.original.document_id, [PipelineResultSearchParams.KnowledgeId]: kowledgeId,
)} [PipelineResultSearchParams.DocumentId]:
> row.original.document_id,
<ClipboardList /> [PipelineResultSearchParams.IsReadOnly]: 'false',
</Button> [PipelineResultSearchParams.Type]: 'dataflow',
})}
>
<ClipboardList />
</Button>
)}
</div> </div>
), ),
}, },
@ -371,6 +376,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
? Math.ceil(pagination.total / pagination.pageSize) ? Math.ceil(pagination.total / pagination.pageSize)
: 0, : 0,
}); });
return ( return (
<div className="w-full h-[calc(100vh-360px)]"> <div className="w-full h-[calc(100vh-360px)]">
<Table rootClassName="max-h-[calc(100vh-380px)]"> <Table rootClassName="max-h-[calc(100vh-380px)]">

View File

@ -3,14 +3,20 @@ import { RunningStatus } from '@/constants/knowledge';
export const RunningStatusMap = { export const RunningStatusMap = {
[RunningStatus.UNSTART]: { [RunningStatus.UNSTART]: {
label: 'UNSTART', label: 'UNSTART',
color: 'var(--accent-primary)', color: 'rgba(var(--accent-primary))',
}, },
[RunningStatus.RUNNING]: { [RunningStatus.RUNNING]: {
label: 'Parsing', label: 'Parsing',
color: 'var(--team-member)', color: 'var(--team-member)',
}, },
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'var(--state-warning)' }, [RunningStatus.CANCEL]: {
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'var(--state-success)' }, label: 'CANCEL',
color: 'rgba(var(--state-warning))',
},
[RunningStatus.DONE]: {
label: 'SUCCESS',
color: 'rgba(var(--state-success))',
},
[RunningStatus.FAIL]: { label: 'FAIL', color: 'rgba(var(--state-error))' }, [RunningStatus.FAIL]: { label: 'FAIL', color: 'rgba(var(--state-error))' },
}; };

View File

@ -21,7 +21,7 @@ import { UseSaveMetaShowType } from './use-save-meta';
import { isParserRunning } from './utils'; import { isParserRunning } from './utils';
const IconMap = { const IconMap = {
[RunningStatus.UNSTART]: ( [RunningStatus.UNSTART]: (
<div className="w-0 h-0 border-l-[10px] border-l-accent-primary border-t-8 border-r-4 border-b-8 border-transparent"></div> <IconFontFill name="play" className="text-accent-primary" />
), ),
[RunningStatus.RUNNING]: ( [RunningStatus.RUNNING]: (
<CircleX size={14} color="rgba(var(--state-error))" /> <CircleX size={14} color="rgba(var(--state-error))" />

View File

@ -210,6 +210,10 @@ const methods = {
url: traceRaptor, url: traceRaptor,
method: 'get', method: 'get',
}, },
pipelineRerun: {
url: api.pipelineRerun,
method: 'post',
},
}; };
const kbService = registerServer<keyof typeof methods>(methods, request); const kbService = registerServer<keyof typeof methods>(methods, request);

View File

@ -174,6 +174,32 @@ export function parseColorToRGB(color: string): [number, number, number] {
colorStr = getCSSVariableValue(varName); colorStr = getCSSVariableValue(varName);
} }
// Handle rgb(var(--accent-primary)) format
if (colorStr.startsWith('rgb(var(')) {
const varMatch = colorStr.match(/rgb\(var\(([^)]+)\)\)/);
if (!varMatch) {
console.error(`Invalid nested CSS variable: ${color}`);
return [0, 0, 0];
}
const varName = varMatch[1];
if (!varName) {
console.error(`Invalid nested CSS variable: ${colorStr}`);
return [0, 0, 0];
}
// Get the CSS variable value which should be in format "r, g, b"
const rgbValues = getCSSVariableValue(varName);
const rgbMatch = rgbValues.match(/^(\d+),?\s*(\d+),?\s*(\d+)$/);
if (rgbMatch) {
return [
parseInt(rgbMatch[1]),
parseInt(rgbMatch[2]),
parseInt(rgbMatch[3]),
];
}
console.error(`Unsupported RGB CSS variable format: ${rgbValues}`);
return [0, 0, 0];
}
// Handles hexadecimal colors (e.g. #FF5733) // Handles hexadecimal colors (e.g. #FF5733)
if (colorStr.startsWith('#')) { if (colorStr.startsWith('#')) {
const cleanedHex = colorStr.replace(/^#/, ''); const cleanedHex = colorStr.replace(/^#/, '');

View File

@ -81,8 +81,14 @@ module.exports = {
5: 'rgba(var(--accent-primary) / 0.05)', // 5% 5: 'rgba(var(--accent-primary) / 0.05)', // 5%
}, },
'bg-accent': 'var(--bg-accent)', 'bg-accent': 'var(--bg-accent)',
'state-success': 'var(--state-success)', 'state-success': {
'state-warning': 'var(--state-warning)', DEFAULT: 'rgb(var(--state-success) / <alpha-value>)',
5: 'rgba(var(--state-success) / 0.05)', // 5%
},
'state-warning': {
DEFAULT: 'rgb(var(--state-warning) / <alpha-value>)',
5: 'rgba(var(--state-warning) / 0.05)', // 5%
},
'state-error': { 'state-error': {
DEFAULT: 'rgb(var(--state-error) / <alpha-value>)', DEFAULT: 'rgb(var(--state-error) / <alpha-value>)',
5: 'rgba(var(--state-error) / 0.05)', // 5% 5: 'rgba(var(--state-error) / 0.05)', // 5%

View File

@ -116,8 +116,10 @@
/* Output Variables Box */ /* Output Variables Box */
--bg-accent: rgba(76, 164, 231, 0.05); --bg-accent: rgba(76, 164, 231, 0.05);
--state-success: #3ba05c; /* --state-success: #3ba05c; */
--state-warning: #faad14; --state-success: 59 160 92;
/* --state-warning: #767573; */
--state-warning: 118 117 115;
--state-error: 216 73 75; --state-error: 216 73 75;
--team-group: #5ab77e; --team-group: #5ab77e;