mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-30 04:29:24 +08:00
Feat: Optimize dataset-level wiki generation log display. (#17492)
This commit is contained in:
@@ -73,7 +73,7 @@ export function NextMessageInput({
|
||||
null,
|
||||
);
|
||||
|
||||
const [enableThinking, setEnableThinking] = useState('0');
|
||||
const [enableThinking, setEnableThinking] = useState('1');
|
||||
const [enableInternet, setEnableInternet] = useState(false);
|
||||
|
||||
const thinkingOptions = useMemo(
|
||||
|
||||
@@ -469,6 +469,8 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
updateTooltip:
|
||||
'{{newlyUploaded}} new, {{removed}} removed documents found. Click to compile and merge into current Wiki.',
|
||||
updateSheetTitle: 'Update Wiki',
|
||||
viewUpdateLogs: 'View update logs',
|
||||
log: 'Log',
|
||||
noSkills: 'No skills yet',
|
||||
generate: 'Generate',
|
||||
raptor: 'RAPTOR',
|
||||
@@ -526,7 +528,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
graphPlaceholder: 'Graph view placeholder',
|
||||
skills: 'To Skills',
|
||||
llmWiki: 'Wiki',
|
||||
navTree: 'Tree',
|
||||
navTree: 'Tree/Page index',
|
||||
structureGraph: 'Graph',
|
||||
structureMindmap: 'Mind map',
|
||||
structureTimeline: 'Timeline',
|
||||
@@ -1147,7 +1149,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
thinkingDefault: 'System default',
|
||||
thinkingEnabled: 'Enabled',
|
||||
thinkingDisabled: 'Disabled',
|
||||
thinkingLevelNone: 'None',
|
||||
thinkingLevelNone: 'Naive',
|
||||
thinkingLevelLow: 'Low',
|
||||
thinkingLevelLowDescription: 'Instant responses',
|
||||
thinkingLevelMedium: 'Medium',
|
||||
|
||||
@@ -419,6 +419,8 @@ export default {
|
||||
updateTooltip:
|
||||
'发现 {{newlyUploaded}} 个新文档,{{removed}} 个已移除文档。点击编译并合并到当前 Wiki。',
|
||||
updateSheetTitle: '更新 Wiki',
|
||||
viewUpdateLogs: '查看更新日志',
|
||||
log: '日志',
|
||||
noSkills: '暂无技能',
|
||||
processingType: '处理类型',
|
||||
dataPipeline: '切换或配置 ingestion pipeline。',
|
||||
@@ -471,7 +473,7 @@ export default {
|
||||
graphPlaceholder: '图谱视图占位',
|
||||
skills: '技能',
|
||||
llmWiki: 'Wiki',
|
||||
navTree: '目录树',
|
||||
navTree: '目录树/页面索引',
|
||||
structureGraph: '图谱',
|
||||
structureMindmap: '思维导图',
|
||||
structureTimeline: '时间线',
|
||||
@@ -1041,7 +1043,7 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
thinkingDefault: '系统默认',
|
||||
thinkingEnabled: '开启',
|
||||
thinkingDisabled: '关闭',
|
||||
thinkingLevelNone: '无',
|
||||
thinkingLevelNone: '简单',
|
||||
thinkingLevelLow: '轻度',
|
||||
thinkingLevelLowDescription: '即时响应',
|
||||
thinkingLevelMedium: '中',
|
||||
|
||||
@@ -26,10 +26,15 @@ export const StructureKinds = [
|
||||
|
||||
export type StructureKind = (typeof StructureKinds)[number];
|
||||
|
||||
export const StructureKindLabelKeyMap: Record<StructureKind, string> = {
|
||||
export const ViewModeLabelKeyMap: Record<ViewMode, string> = {
|
||||
[ViewMode.LlmWiki]: 'knowledgeDetails.llmWiki',
|
||||
[ViewMode.Skills]: 'knowledgeDetails.skills',
|
||||
[ViewMode.Tree]: 'knowledgeDetails.navTree',
|
||||
[ViewMode.Graph]: 'knowledgeDetails.structureGraph',
|
||||
[ViewMode.MindMap]: 'knowledgeDetails.structureMindmap',
|
||||
[ViewMode.Timeline]: 'knowledgeDetails.structureTimeline',
|
||||
// [ViewMode.SessionEssence]: 'knowledgeDetails.structureSessionEssence',
|
||||
// [ViewMode.SessionGraph]: 'knowledgeDetails.structureSessionGraph',
|
||||
};
|
||||
|
||||
export type GenerableViewMode = Exclude<ViewMode, ViewMode.Tree>;
|
||||
|
||||
@@ -24,9 +24,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
StructureKind,
|
||||
StructureKindLabelKeyMap,
|
||||
ViewMode,
|
||||
ViewModeGenerateTypeMap,
|
||||
ViewModeLabelKeyMap,
|
||||
} from './constants';
|
||||
import CompilationEmptyState from './empty-state';
|
||||
import { CompilationLoadingCard } from './loading-card';
|
||||
@@ -126,7 +126,7 @@ export function DatasetStructureView({ kind }: DatasetStructureViewProps) {
|
||||
<div className="flex justify-between gap-4 px-4 pt-4">
|
||||
<ConfirmDeleteDialog
|
||||
title={t('knowledgeDetails.deleteStructureConfirm', {
|
||||
name: t(StructureKindLabelKeyMap[kind]),
|
||||
name: t(ViewModeLabelKeyMap[kind]),
|
||||
})}
|
||||
onOk={handleDeleteStructure}
|
||||
>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { CirclePause, Logs, WandSparkles } from 'lucide-react';
|
||||
import { CircleX, WandSparkles } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
ITraceInfo,
|
||||
useDatasetGenerate,
|
||||
} from '@/pages/dataset/dataset/generate-button/hook';
|
||||
import { useGenerateStatus } from '@/pages/dataset/dataset/generate-button/use-generate-status';
|
||||
import { replaceText } from '@/pages/dataset/process-log-modal';
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
|
||||
import {
|
||||
GenerableViewMode,
|
||||
ViewMode,
|
||||
ViewModeGenerateTypeMap,
|
||||
ViewModeLabelKeyMap,
|
||||
} from './constants';
|
||||
import { ProgressLogPanel } from './progress-log-panel';
|
||||
import { ProgressRing } from './progress-ring';
|
||||
|
||||
type EmptyStateType = GenerableViewMode;
|
||||
|
||||
@@ -37,16 +37,6 @@ const TitleKeyMap: Record<EmptyStateType, string> = {
|
||||
// [ViewMode.SessionGraph]: 'knowledgeDetails.noStructureSessionGraph',
|
||||
};
|
||||
|
||||
const LabelKeyMap: Record<EmptyStateType, string> = {
|
||||
[ViewMode.LlmWiki]: 'knowledgeDetails.artifact',
|
||||
[ViewMode.Skills]: 'knowledgeDetails.toSkills',
|
||||
[ViewMode.Graph]: 'knowledgeDetails.structureGraph',
|
||||
[ViewMode.MindMap]: 'knowledgeDetails.structureMindmap',
|
||||
[ViewMode.Timeline]: 'knowledgeDetails.structureTimeline',
|
||||
// [ViewMode.SessionEssence]: 'knowledgeDetails.structureSessionEssence',
|
||||
// [ViewMode.SessionGraph]: 'knowledgeDetails.structureSessionGraph',
|
||||
};
|
||||
|
||||
export function CompilationEmptyState({
|
||||
type,
|
||||
disabled,
|
||||
@@ -84,48 +74,32 @@ export function CompilationEmptyState({
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full max-w-md p-6 flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<Logs className="size-5" />
|
||||
<span>{t(LabelKeyMap[type])}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={cn('bg-border-button h-1 rounded-full', {
|
||||
'w-[calc(100%-100px)]': status === 'running',
|
||||
'w-[calc(100%-50px)]': status === 'failed',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn('h-1 rounded-full', {
|
||||
'bg-state-error': status === 'failed',
|
||||
'bg-accent-primary': status === 'running',
|
||||
})}
|
||||
style={{ width: `${toFixed(percent)}%` }}
|
||||
/>
|
||||
<div className="grid h-full w-full grid-cols-[1fr_auto_1fr] items-center gap-8 p-6">
|
||||
<div />
|
||||
<div className="flex flex-col items-center gap-5">
|
||||
<ProgressRing percent={percent} failed={status === 'failed'} />
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<span>{t(ViewModeLabelKeyMap[type])}</span>
|
||||
{status === 'failed' && (
|
||||
<span className="cursor-pointer" onClick={handleGenerate}>
|
||||
<IconFontFill
|
||||
name="reparse"
|
||||
className="text-accent-primary"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{status !== 'failed' && (
|
||||
<span
|
||||
className="text-state-error cursor-pointer"
|
||||
onClick={handlePause}
|
||||
>
|
||||
<CircleX className="size-5 text-state-error" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{status === 'running' && (
|
||||
<span>{(toFixed(percent) as string) + '%'}</span>
|
||||
)}
|
||||
{status === 'failed' && (
|
||||
<span
|
||||
className="text-state-error cursor-pointer"
|
||||
onClick={handleGenerate}
|
||||
>
|
||||
<IconFontFill name="reparse" className="text-accent-primary" />
|
||||
</span>
|
||||
)}
|
||||
{status !== 'failed' && (
|
||||
<span
|
||||
className="text-state-error cursor-pointer"
|
||||
onClick={handlePause}
|
||||
>
|
||||
<CirclePause />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="whitespace-pre-line text-wrap rounded-lg max-h-[350px] overflow-y-auto scrollbar-auto p-2 bg-bg-base text-sm text-text-secondary">
|
||||
{replaceText(data?.progress_msg || '')}
|
||||
<div className="flex min-h-0 h-full justify-end">
|
||||
<ProgressLogPanel progressMsg={data?.progress_msg} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { StructureKinds, ViewMode } from './constants';
|
||||
import { StructureKinds, ViewMode, ViewModeLabelKeyMap } from './constants';
|
||||
import { DatasetStructureView } from './dataset-structure-view';
|
||||
import { LlmWikiView } from './llm-wiki-view';
|
||||
import { NavTreeView } from './nav-tree-view';
|
||||
@@ -24,40 +24,10 @@ export default function Compilation() {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(ViewMode.LlmWiki);
|
||||
|
||||
const viewOptions = useMemo<SelectWithSearchFlagOptionType[]>(() => {
|
||||
return [
|
||||
{
|
||||
value: ViewMode.LlmWiki,
|
||||
label: t('knowledgeDetails.llmWiki'),
|
||||
},
|
||||
{
|
||||
value: ViewMode.Skills,
|
||||
label: t('knowledgeDetails.skills', 'To Skills'),
|
||||
},
|
||||
{
|
||||
value: ViewMode.Tree,
|
||||
label: t('knowledgeDetails.navTree'),
|
||||
},
|
||||
{
|
||||
value: ViewMode.Graph,
|
||||
label: t('knowledgeDetails.structureGraph'),
|
||||
},
|
||||
{
|
||||
value: ViewMode.MindMap,
|
||||
label: t('knowledgeDetails.structureMindmap'),
|
||||
},
|
||||
{
|
||||
value: ViewMode.Timeline,
|
||||
label: t('knowledgeDetails.structureTimeline'),
|
||||
},
|
||||
// {
|
||||
// value: ViewMode.SessionEssence,
|
||||
// label: t('knowledgeDetails.structureSessionEssence'),
|
||||
// },
|
||||
// {
|
||||
// value: ViewMode.SessionGraph,
|
||||
// label: t('knowledgeDetails.structureSessionGraph'),
|
||||
// },
|
||||
];
|
||||
return Object.values(ViewMode).map((mode) => ({
|
||||
value: mode,
|
||||
label: t(ViewModeLabelKeyMap[mode]),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const handleViewModeChange = useCallback((value: string) => {
|
||||
|
||||
60
web/src/pages/dataset/compilation/progress-log-panel.tsx
Normal file
60
web/src/pages/dataset/compilation/progress-log-panel.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Logs } from 'lucide-react';
|
||||
import { useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import { parseProgressMsg } from './utils/parse-progress-msg';
|
||||
|
||||
interface IProgressLogPanelProps {
|
||||
progressMsg?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ProgressLogPanel({
|
||||
progressMsg,
|
||||
className,
|
||||
}: IProgressLogPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
const entries = useMemo(() => parseProgressMsg(progressMsg), [progressMsg]);
|
||||
|
||||
// Depend on progressMsg itself (not entries.length): backend head-trimming
|
||||
// can keep the line count unchanged while the content has changed.
|
||||
useLayoutEffect(() => {
|
||||
const el = listRef.current;
|
||||
if (el) {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, [progressMsg]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex min-h-0 flex-col gap-2', className)}>
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<Logs className="size-5" />
|
||||
<span>{t('knowledgeDetails.log')}</span>
|
||||
</div>
|
||||
<div
|
||||
ref={listRef}
|
||||
className="flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto scrollbar-auto"
|
||||
>
|
||||
{entries.map((entry) => (
|
||||
<div
|
||||
key={entry.key}
|
||||
className="flex items-center gap-2 rounded-lg border border-border-button bg-bg-card px-3 py-2"
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'size-2 shrink-0 rounded-full',
|
||||
entry.isError ? 'bg-state-error' : 'bg-state-success',
|
||||
)}
|
||||
/>
|
||||
<span className="break-all text-sm text-text-primary">
|
||||
{entry.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
web/src/pages/dataset/compilation/progress-ring.tsx
Normal file
65
web/src/pages/dataset/compilation/progress-ring.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
|
||||
interface IProgressRingProps {
|
||||
percent: number;
|
||||
failed?: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const DefaultRingSize = 240;
|
||||
const RingStrokeWidth = 16;
|
||||
|
||||
export function ProgressRing({
|
||||
percent,
|
||||
failed = false,
|
||||
size = DefaultRingSize,
|
||||
}: IProgressRingProps) {
|
||||
const radius = (size - RingStrokeWidth * 2) / 2;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const strokeDashoffset = circumference - (percent / 100) * circumference;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={Math.round(percent)}
|
||||
className="relative inline-flex items-center justify-center"
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
<svg
|
||||
className="rotate-[-90deg] transform"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<circle
|
||||
className="text-border-button"
|
||||
strokeWidth={RingStrokeWidth}
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
/>
|
||||
<circle
|
||||
className={cn(
|
||||
'transition-[stroke-dashoffset] duration-300 ease-linear',
|
||||
failed ? 'text-state-error' : 'text-accent-primary',
|
||||
)}
|
||||
strokeWidth={RingStrokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
/>
|
||||
</svg>
|
||||
<span className="absolute text-5xl font-medium text-text-primary">
|
||||
{`${toFixed(percent, 0)}%`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface IProgressLogEntry {
|
||||
key: number;
|
||||
text: string;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export const parseProgressMsg = (progressMsg?: string): IProgressLogEntry[] =>
|
||||
(progressMsg ?? '')
|
||||
.split('\n')
|
||||
.filter((line) => line.trim().length > 0)
|
||||
.map((line, index) => ({
|
||||
key: index,
|
||||
text: line,
|
||||
isError: line.includes('[ERROR]') || line.startsWith('ERROR:'),
|
||||
}));
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { IArtifact } from '@/interfaces/database/dataset';
|
||||
import { GenerateStatus } from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import { ITraceInfo } from '@/pages/dataset/dataset/generate-button/hook';
|
||||
import { useGenerateStatus } from '@/pages/dataset/dataset/generate-button/use-generate-status';
|
||||
import { Trash2, WandSparkles } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -19,6 +21,7 @@ import { useWikiClear } from './hooks/use-wiki-clear';
|
||||
import { useWikiUpdate } from './hooks/use-wiki-update';
|
||||
import { WikiGraphPanel } from './wiki-graph-panel';
|
||||
import { WikiNavBar } from './wiki-nav-bar';
|
||||
import { WikiUpdateProgress } from './wiki-update-progress';
|
||||
import { WikiUpdateSheet } from './wiki-update-sheet';
|
||||
|
||||
type WikiLeftPanelProps = {
|
||||
@@ -58,15 +61,22 @@ export function WikiLeftPanel({
|
||||
loading: updateLoading,
|
||||
} = useWikiUpdate();
|
||||
|
||||
const { status } = useGenerateStatus(traceData);
|
||||
const isGenerating =
|
||||
status === GenerateStatus.running || status === GenerateStatus.failed;
|
||||
|
||||
const handleUpdateClick = useCallback(async () => {
|
||||
onUpdateSheetOpenChange(true);
|
||||
if (status === GenerateStatus.running) {
|
||||
return;
|
||||
}
|
||||
await handleUpdate();
|
||||
}, [handleUpdate, onUpdateSheetOpenChange]);
|
||||
}, [status, handleUpdate, onUpdateSheetOpenChange]);
|
||||
|
||||
return (
|
||||
<aside className="size-full flex flex-col p-5">
|
||||
<div className="flex items-center justify-between pb-5">
|
||||
{hasChanges && (
|
||||
{(hasChanges || isGenerating) && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -75,27 +85,37 @@ export function WikiLeftPanel({
|
||||
onClick={handleUpdateClick}
|
||||
disabled={updateLoading}
|
||||
>
|
||||
{t('knowledgeDetails.update', { defaultValue: 'Update' })}
|
||||
{newlyUploaded > 0 && (
|
||||
<Badge variant="success" className="ml-1">
|
||||
{newlyUploaded}
|
||||
</Badge>
|
||||
{isGenerating ? (
|
||||
<WikiUpdateProgress data={traceData} />
|
||||
) : (
|
||||
<>
|
||||
{t('knowledgeDetails.update', { defaultValue: 'Update' })}
|
||||
{newlyUploaded > 0 && (
|
||||
<Badge variant="success" className="ml-1">
|
||||
{newlyUploaded}
|
||||
</Badge>
|
||||
)}
|
||||
{removed > 0 && (
|
||||
<Badge variant="destructive" className="ml-1">
|
||||
{removed}
|
||||
</Badge>
|
||||
)}
|
||||
<WandSparkles />
|
||||
</>
|
||||
)}
|
||||
{removed > 0 && (
|
||||
<Badge variant="destructive" className="ml-1">
|
||||
{removed}
|
||||
</Badge>
|
||||
)}
|
||||
<WandSparkles />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t('knowledgeDetails.updateTooltip', {
|
||||
newlyUploaded,
|
||||
removed,
|
||||
defaultValue:
|
||||
'{{newlyUploaded}} new, {{removed}} removed documents found. Click to compile and merge into current Wiki.',
|
||||
})}
|
||||
{isGenerating
|
||||
? t('knowledgeDetails.viewUpdateLogs', {
|
||||
defaultValue: 'View update logs',
|
||||
})
|
||||
: t('knowledgeDetails.updateTooltip', {
|
||||
newlyUploaded,
|
||||
removed,
|
||||
defaultValue:
|
||||
'{{newlyUploaded}} new, {{removed}} removed documents found. Click to compile and merge into current Wiki.',
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { CircleX } from 'lucide-react';
|
||||
import { useCallback, type MouseEvent } from 'react';
|
||||
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { GenerateType } from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import {
|
||||
ITraceInfo,
|
||||
useDatasetGenerate,
|
||||
} from '@/pages/dataset/dataset/generate-button/hook';
|
||||
import { useGenerateStatus } from '@/pages/dataset/dataset/generate-button/use-generate-status';
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
|
||||
type WikiUpdateProgressProps = {
|
||||
data?: ITraceInfo;
|
||||
};
|
||||
|
||||
export function WikiUpdateProgress({ data }: WikiUpdateProgressProps) {
|
||||
const { pauseGenerate } = useDatasetGenerate();
|
||||
const { status, percent } = useGenerateStatus(data);
|
||||
|
||||
const handlePause = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (data?.id) {
|
||||
pauseGenerate({ task_id: data.id, type: GenerateType.Artifact }).catch(
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
},
|
||||
[pauseGenerate, data?.id],
|
||||
);
|
||||
|
||||
return (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="bg-border-button h-1 w-16 rounded-full">
|
||||
<span
|
||||
className={cn('block h-1 rounded-full', {
|
||||
'bg-state-error': status === 'failed',
|
||||
'bg-accent-primary': status === 'running',
|
||||
})}
|
||||
style={{ width: `${toFixed(percent)}%` }}
|
||||
/>
|
||||
</span>
|
||||
{status === 'running' && (
|
||||
<>
|
||||
<span>{(toFixed(percent) as string) + '%'}</span>
|
||||
<span
|
||||
className="text-state-error cursor-pointer"
|
||||
onClick={handlePause}
|
||||
>
|
||||
<CircleX className="size-4" />
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{status === 'failed' && (
|
||||
<IconFontFill name="reparse" className="text-accent-primary" />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CirclePause, Logs } from 'lucide-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
@@ -8,15 +7,11 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { GenerateType } from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import {
|
||||
ITraceInfo,
|
||||
useDatasetGenerate,
|
||||
} from '@/pages/dataset/dataset/generate-button/hook';
|
||||
import { GenerateStatus } from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import { ITraceInfo } from '@/pages/dataset/dataset/generate-button/hook';
|
||||
import { useGenerateStatus } from '@/pages/dataset/dataset/generate-button/use-generate-status';
|
||||
import { replaceText } from '@/pages/dataset/process-log-modal';
|
||||
import { toFixed } from '@/utils/common-util';
|
||||
|
||||
import { ProgressLogPanel } from '../progress-log-panel';
|
||||
|
||||
type WikiUpdateSheetProps = {
|
||||
open: boolean;
|
||||
@@ -30,23 +25,14 @@ export function WikiUpdateSheet({
|
||||
data,
|
||||
}: WikiUpdateSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const { pauseGenerate } = useDatasetGenerate();
|
||||
const { status, percent } = useGenerateStatus(data);
|
||||
const { status } = useGenerateStatus(data);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'completed') {
|
||||
if (status === GenerateStatus.completed) {
|
||||
onOpenChange(false);
|
||||
}
|
||||
}, [status, onOpenChange]);
|
||||
|
||||
const handlePause = useCallback(() => {
|
||||
if (data?.id) {
|
||||
pauseGenerate({ task_id: data.id, type: GenerateType.Artifact }).catch(
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
}, [pauseGenerate, data?.id]);
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange} modal={false}>
|
||||
<SheetContent className="flex flex-col">
|
||||
@@ -57,43 +43,11 @@ export function WikiUpdateSheet({
|
||||
})}
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 min-h-0 flex flex-col gap-4 pt-4">
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<Logs className="size-5" />
|
||||
<span>
|
||||
{t('knowledgeDetails.artifact', { defaultValue: 'Artifact' })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={cn('bg-border-button h-1 rounded-full', {
|
||||
'w-[calc(100%-100px)]': status === 'running',
|
||||
'w-[calc(100%-50px)]': status === 'failed',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn('h-1 rounded-full', {
|
||||
'bg-state-error': status === 'failed',
|
||||
'bg-accent-primary': status === 'running',
|
||||
})}
|
||||
style={{ width: `${toFixed(percent)}%` }}
|
||||
/>
|
||||
</div>
|
||||
{status === 'running' && (
|
||||
<span>{(toFixed(percent) as string) + '%'}</span>
|
||||
)}
|
||||
{status !== 'failed' && (
|
||||
<span
|
||||
className="text-state-error cursor-pointer"
|
||||
onClick={handlePause}
|
||||
>
|
||||
<CirclePause />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 whitespace-pre-line text-wrap rounded-lg overflow-y-auto scrollbar-auto p-2 bg-bg-base text-sm text-text-secondary">
|
||||
{replaceText(data?.progress_msg || '')}
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 flex flex-col pt-4">
|
||||
<ProgressLogPanel
|
||||
progressMsg={data?.progress_msg}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
Reference in New Issue
Block a user