diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index e2747d2744..52b4029130 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -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( diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index ddf0bf42e6..43647640ef 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -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', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index c047177394..d5c82ba222 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -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: '中', diff --git a/web/src/pages/dataset/compilation/constants.ts b/web/src/pages/dataset/compilation/constants.ts index 7e8dba9d51..c40a3f5ff9 100644 --- a/web/src/pages/dataset/compilation/constants.ts +++ b/web/src/pages/dataset/compilation/constants.ts @@ -26,10 +26,15 @@ export const StructureKinds = [ export type StructureKind = (typeof StructureKinds)[number]; -export const StructureKindLabelKeyMap: Record = { +export const ViewModeLabelKeyMap: Record = { + [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; diff --git a/web/src/pages/dataset/compilation/dataset-structure-view.tsx b/web/src/pages/dataset/compilation/dataset-structure-view.tsx index 0a1300b440..bf8bc5e703 100644 --- a/web/src/pages/dataset/compilation/dataset-structure-view.tsx +++ b/web/src/pages/dataset/compilation/dataset-structure-view.tsx @@ -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) {
diff --git a/web/src/pages/dataset/compilation/empty-state.tsx b/web/src/pages/dataset/compilation/empty-state.tsx index dc4a2b97f7..46a96a6be5 100644 --- a/web/src/pages/dataset/compilation/empty-state.tsx +++ b/web/src/pages/dataset/compilation/empty-state.tsx @@ -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 = { // [ViewMode.SessionGraph]: 'knowledgeDetails.noStructureSessionGraph', }; -const LabelKeyMap: Record = { - [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({
) : ( -
-
- - {t(LabelKeyMap[type])} -
-
-
-
+
+
+
+ +
+ {t(ViewModeLabelKeyMap[type])} + {status === 'failed' && ( + + + + )} + {status !== 'failed' && ( + + + + )}
- {status === 'running' && ( - {(toFixed(percent) as string) + '%'} - )} - {status === 'failed' && ( - - - - )} - {status !== 'failed' && ( - - - - )}
-
- {replaceText(data?.progress_msg || '')} +
+
)} diff --git a/web/src/pages/dataset/compilation/index.tsx b/web/src/pages/dataset/compilation/index.tsx index edb919e2d9..2fb52bd515 100644 --- a/web/src/pages/dataset/compilation/index.tsx +++ b/web/src/pages/dataset/compilation/index.tsx @@ -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.LlmWiki); const viewOptions = useMemo(() => { - 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) => { diff --git a/web/src/pages/dataset/compilation/progress-log-panel.tsx b/web/src/pages/dataset/compilation/progress-log-panel.tsx new file mode 100644 index 0000000000..9def77b2c5 --- /dev/null +++ b/web/src/pages/dataset/compilation/progress-log-panel.tsx @@ -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(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 ( +
+
+ + {t('knowledgeDetails.log')} +
+
+ {entries.map((entry) => ( +
+ + + {entry.text} + +
+ ))} +
+
+ ); +} diff --git a/web/src/pages/dataset/compilation/progress-ring.tsx b/web/src/pages/dataset/compilation/progress-ring.tsx new file mode 100644 index 0000000000..11efaa8900 --- /dev/null +++ b/web/src/pages/dataset/compilation/progress-ring.tsx @@ -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 ( +
+ + + + + + {`${toFixed(percent, 0)}%`} + +
+ ); +} diff --git a/web/src/pages/dataset/compilation/utils/parse-progress-msg.ts b/web/src/pages/dataset/compilation/utils/parse-progress-msg.ts new file mode 100644 index 0000000000..8420bbe469 --- /dev/null +++ b/web/src/pages/dataset/compilation/utils/parse-progress-msg.ts @@ -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:'), + })); diff --git a/web/src/pages/dataset/compilation/wiki-left-panel/index.tsx b/web/src/pages/dataset/compilation/wiki-left-panel/index.tsx index 6639cc2862..55bc8ec468 100644 --- a/web/src/pages/dataset/compilation/wiki-left-panel/index.tsx +++ b/web/src/pages/dataset/compilation/wiki-left-panel/index.tsx @@ -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 (