diff --git a/web/package-lock.json b/web/package-lock.json index 1579cf240..e1b4f4581 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -66,7 +66,7 @@ "jsencrypt": "^3.3.2", "lexical": "^0.23.1", "lodash": "^4.17.21", - "lucide-react": "^0.508.0", + "lucide-react": "^0.542.0", "mammoth": "^1.7.2", "next-themes": "^0.4.6", "openai-speech-stream-player": "^1.0.8", @@ -25113,9 +25113,9 @@ } }, "node_modules/lucide-react": { - "version": "0.508.0", - "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.508.0.tgz", - "integrity": "sha512-gcP16PnexqtOFrTtv98kVsGzTfnbPekzZiQfByi2S89xfk7E/4uKE1USZqccIp58v42LqkO7MuwpCqshwSrJCg==", + "version": "0.542.0", + "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.542.0.tgz", + "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/web/package.json b/web/package.json index 2e7a9c0f7..783ffff66 100644 --- a/web/package.json +++ b/web/package.json @@ -79,7 +79,7 @@ "jsencrypt": "^3.3.2", "lexical": "^0.23.1", "lodash": "^4.17.21", - "lucide-react": "^0.508.0", + "lucide-react": "^0.542.0", "mammoth": "^1.7.2", "next-themes": "^0.4.6", "openai-speech-stream-player": "^1.0.8", diff --git a/web/src/assets/svg/data-flow/knowledgegraph.svg b/web/src/assets/svg/data-flow/knowledgegraph.svg new file mode 100644 index 000000000..b0feec924 --- /dev/null +++ b/web/src/assets/svg/data-flow/knowledgegraph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/data-flow/raptor.svg b/web/src/assets/svg/data-flow/raptor.svg new file mode 100644 index 000000000..8f83bffc2 --- /dev/null +++ b/web/src/assets/svg/data-flow/raptor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/assets/svg/rerun.svg b/web/src/assets/svg/rerun.svg new file mode 100644 index 000000000..cd972f4e6 --- /dev/null +++ b/web/src/assets/svg/rerun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/components/file-status-badge.tsx b/web/src/components/file-status-badge.tsx new file mode 100644 index 000000000..ad681eb34 --- /dev/null +++ b/web/src/components/file-status-badge.tsx @@ -0,0 +1,57 @@ +// src/pages/dataset/file-logs/file-status-badge.tsx +import { FC } from 'react'; + +interface StatusBadgeProps { + status: 'Success' | 'Failed' | 'Running' | 'Pending'; +} + +const FileStatusBadge: FC = ({ status }) => { + const getStatusColor = () => { + // #3ba05c → rgb(59, 160, 92) // state-success + // #d8494b → rgb(216, 73, 75) // state-error + // #00beb4 → rgb(0, 190, 180) // accent-primary + // #faad14 → rgb(250, 173, 20) // state-warning + switch (status) { + case 'Success': + return `bg-[rgba(59,160,92,0.1)] text-state-success`; + case 'Failed': + return `bg-[rgba(216,73,75,0.1)] text-state-error`; + case 'Running': + return `bg-[rgba(0,190,180,0.1)] text-accent-primary`; + case 'Pending': + return `bg-[rgba(250,173,20,0.1)] text-state-warning`; + default: + return 'bg-gray-500/10 text-white'; + } + }; + + const getBgStatusColor = () => { + // #3ba05c → rgb(59, 160, 92) // state-success + // #d8494b → rgb(216, 73, 75) // state-error + // #00beb4 → rgb(0, 190, 180) // accent-primary + // #faad14 → rgb(250, 173, 20) // state-warning + switch (status) { + case 'Success': + return `bg-[rgba(59,160,92,1)] text-state-success`; + case 'Failed': + return `bg-[rgba(216,73,75,1)] text-state-error`; + case 'Running': + return `bg-[rgba(0,190,180,1)] text-accent-primary`; + case 'Pending': + return `bg-[rgba(250,173,20,1)] text-state-warning`; + default: + return 'bg-gray-500/10 text-white'; + } + }; + + return ( + +
+ {status} +
+ ); +}; + +export default FileStatusBadge; diff --git a/web/src/components/originui/timeline.tsx b/web/src/components/originui/timeline.tsx index befe5272e..57694ee3b 100644 --- a/web/src/components/originui/timeline.tsx +++ b/web/src/components/originui/timeline.tsx @@ -1,6 +1,7 @@ 'use client'; import { cn } from '@/lib/utils'; +import { parseColorToRGBA } from '@/utils/common-util'; import { Slot } from '@radix-ui/react-slot'; import * as React from 'react'; @@ -197,7 +198,208 @@ function TimelineTitle({ ); } +interface TimelineIndicatorNodeProps { + nodeSize?: string | number; + iconColor?: string; + lineColor?: string; + textColor?: string; + indicatorBgColor?: string; + indicatorBorderColor?: string; +} +interface TimelineNode + extends Omit< + React.HTMLAttributes, + 'id' | 'title' | 'content' + >, + TimelineIndicatorNodeProps { + id: string | number; + title?: React.ReactNode; + content?: React.ReactNode; + date?: React.ReactNode; + icon?: React.ReactNode; + completed?: boolean; + clickable?: boolean; + activeStyle?: TimelineIndicatorNodeProps; +} + +interface CustomTimelineProps extends React.HTMLAttributes { + nodes: TimelineNode[]; + activeStep?: number; + nodeSize?: string | number; + onStepChange?: (step: number, id: string | number) => void; + orientation?: 'horizontal' | 'vertical'; + lineStyle?: 'solid' | 'dashed'; + lineColor?: string; + indicatorColor?: string; + defaultValue?: number; + activeStyle?: TimelineIndicatorNodeProps; +} + +const CustomTimeline = ({ + nodes, + activeStep, + nodeSize = 12, + onStepChange, + orientation = 'horizontal', + lineStyle = 'solid', + lineColor = 'var(--text-secondary)', + indicatorColor = 'var(--accent-primary)', + defaultValue = 1, + className, + activeStyle, + ...props +}: CustomTimelineProps) => { + const [internalActiveStep, setInternalActiveStep] = + React.useState(defaultValue); + const _lineColor = `rgb(${parseColorToRGBA(lineColor)})`; + console.log(lineColor, _lineColor); + const currentActiveStep = activeStep ?? internalActiveStep; + + const handleStepChange = (step: number, id: string | number) => { + if (activeStep === undefined) { + setInternalActiveStep(step); + } + onStepChange?.(step, id); + }; + const [r, g, b] = parseColorToRGBA(indicatorColor); + return ( + handleStepChange(step, nodes[step - 1]?.id)} + orientation={orientation} + className={className} + {...props} + > + {nodes.map((node, index) => { + const step = index + 1; + const isCompleted = node.completed ?? step <= currentActiveStep; + const isActive = step === currentActiveStep; + const isClickable = node.clickable ?? true; + const _activeStyle = node.activeStyle ?? (activeStyle || {}); + const _nodeSizeTemp = + isActive && _activeStyle?.nodeSize + ? _activeStyle?.nodeSize + : node.nodeSize ?? nodeSize; + const _nodeSize = + typeof _nodeSizeTemp === 'number' + ? `${_nodeSizeTemp}px` + : _nodeSizeTemp; + console.log('icon-size', nodeSize, node.nodeSize, _nodeSize); + // const activeStyle = _activeStyle || {}; + + return ( + isClickable && handleStepChange(step, node.id)} + > + + + + {node.icon && ( +
+ {node.icon} +
+ )} +
+ + + {node.date && {node.date}} + + {node.title} + + + {node.content && {node.content}} +
+ ); + })} +
+ ); +}; + +CustomTimeline.displayName = 'CustomTimeline'; + export { + CustomTimeline, Timeline, TimelineContent, TimelineDate, @@ -206,4 +408,5 @@ export { TimelineItem, TimelineSeparator, TimelineTitle, + type TimelineNode, }; diff --git a/web/src/pages/next-search/spotlight.tsx b/web/src/components/spotlight.tsx similarity index 56% rename from web/src/pages/next-search/spotlight.tsx rename to web/src/components/spotlight.tsx index 3d9b31f4a..f5eacbcee 100644 --- a/web/src/pages/next-search/spotlight.tsx +++ b/web/src/components/spotlight.tsx @@ -3,10 +3,22 @@ import React from 'react'; interface SpotlightProps { className?: string; + opcity?: number; + coverage?: number; } - -const Spotlight: React.FC = ({ className }) => { +/** + * + * @param opcity 0~1 default 0.5 + * @param coverage 0~100 default 60 + * @returns + */ +const Spotlight: React.FC = ({ + className, + opcity = 0.5, + coverage = 60, +}) => { const isDark = useIsDarkTheme(); + const rgb = isDark ? '255, 255, 255' : '194, 221, 243'; return (
= ({ className }) => {
diff --git a/web/src/hooks/logic-hooks/navigate-hooks.ts b/web/src/hooks/logic-hooks/navigate-hooks.ts index b31e9fb5f..b403b37ec 100644 --- a/web/src/hooks/logic-hooks/navigate-hooks.ts +++ b/web/src/hooks/logic-hooks/navigate-hooks.ts @@ -125,6 +125,16 @@ export const useNavigatePage = () => { [navigate], ); + const navigateToDataflowResult = useCallback( + (id: string, knowledgeId?: string) => () => { + navigate( + // `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`, + `${Routes.DataflowResult}/${id}`, + ); + }, + [navigate], + ); + return { navigateToDatasetList, navigateToDataset, @@ -144,5 +154,6 @@ export const useNavigatePage = () => { navigateToFiles, navigateToAgentList, navigateToOldProfile, + navigateToDataflowResult, }; }; diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index c42e2b693..997eb1b76 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -102,6 +102,28 @@ export default { noMoreData: `That's all. Nothing more.`, }, knowledgeDetails: { + generateKnowledgeGraph: + 'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.', + generateRaptor: + 'This will extract entities and relationships from all your documents in this dataset. The process may take a while to complete.', + generate: 'Generate', + raptor: 'Raptor', + knowledgeGraph: 'Knowledge Graph', + processingType: 'Processing Type', + dataPipeline: 'Data Pipeline', + operations: 'Operations', + status: 'Status', + task: 'Task', + startDate: 'Start Date', + source: 'Source', + fileName: 'File Name', + datasetLogs: 'Dataset Logs', + fileLogs: 'File Logs', + overview: 'Overview', + success: 'Success', + failed: 'Failed', + completed: 'Completed', + processLog: 'Process Log', created: 'Created', learnMore: 'Learn More', general: 'General', @@ -195,6 +217,7 @@ export default { chunk: 'Chunk', bulk: 'Bulk', cancel: 'Cancel', + close: 'Close', rerankModel: 'Rerank model', rerankPlaceholder: 'Please select', rerankTip: `Optional. If left empty, RAGFlow will use a combination of weighted keyword similarity and weighted vector cosine similarity; if a rerank model is selected, a weighted reranking score will replace the weighted vector cosine similarity. Please be aware that using a rerank model will significantly increase the system's response time. If you wish to use a rerank model, ensure you use a SaaS reranker; if you prefer a locally deployed rerank model, ensure you start RAGFlow with docker-compose-gpu.yml.`, @@ -238,6 +261,16 @@ export default { reRankModelWaring: 'Re-rank model is very time consuming.', }, knowledgeConfiguration: { + enableAutoGenerate: 'Enable Auto Generate', + teamPlaceholder: 'Please select a team.', + dataFlowPlaceholder: 'Please select a data flow.', + buildItFromScratch: 'Build it from scratch', + useRAPTORToEnhanceRetrieval: 'Use RAPTOR to Enhance Retrieval', + extractKnowledgeGraph: 'Extract Knowledge Graph', + dataFlow: 'Data Flow', + parseType: 'Parse Type', + manualSetup: 'Manual Setup', + builtIn: 'Built-in', titleDescription: 'Update your knowledge base configuration here, particularly the chunking method.', name: 'Knowledge base name', @@ -1589,5 +1622,11 @@ This delimiter is used to split the input text into several text pieces echo of total: 'Total {{total}}', page: '{{page}} /Page', }, + dataflowParser: { + parseSummary: 'Parse Summary', + parseSummaryTip: 'Parser:deepdoc', + rerunFromCurrentStep: 'Rerun From Current Step', + rerunFromCurrentStepTip: 'Changes detected. Click to re-run.', + }, }, }; diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 1f8d9d9ee..403ff6b45 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -94,6 +94,24 @@ export default { noMoreData: '没有更多数据了', }, knowledgeDetails: { + generate: '生成', + raptor: 'Raptor', + knowledgeGraph: '知识图谱', + processingType: '处理类型', + dataPipeline: '数据管道', + operations: '操作', + status: '状态', + task: '任务', + startDate: '开始时间', + source: '来源', + fileName: '文件名', + datasetLogs: '数据集日志', + fileLogs: '文件日志', + overview: '概览', + success: '成功', + failed: '失败', + completed: '已完成', + processLog: '处理进度日志', created: '创建于', learnMore: '了解更多', general: '通用', @@ -183,6 +201,7 @@ export default { chunk: '解析块', bulk: '批量', cancel: '取消', + close: '关闭', rerankModel: 'Rerank模型', rerankPlaceholder: '请选择', rerankTip: `非必选项:若不选择 rerank 模型,系统将默认采用关键词相似度与向量余弦相似度相结合的混合查询方式;如果设置了 rerank 模型,则混合查询中的向量相似度部分将被 rerank 打分替代。请注意:采用 rerank 模型会非常耗时。如需选用 rerank 模型,建议使用 SaaS 的 rerank 模型服务;如果你倾向使用本地部署的 rerank 模型,请务必确保你使用 docker-compose-gpu.yml 启动 RAGFlow。`, @@ -227,6 +246,16 @@ export default { theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除', }, knowledgeConfiguration: { + enableAutoGenerate: '是否启用自动生成', + teamPlaceholder: '请选择团队', + dataFlowPlaceholder: '请选择数据流', + buildItFromScratch: '去Scratch构建', + useRAPTORToEnhanceRetrieval: '使用 RAPTOR 提升检索效果', + extractKnowledgeGraph: '知识图谱提取', + dataFlow: '数据流', + parseType: '切片方法', + manualSetup: '手动设置', + builtIn: '内置', titleDescription: '在这里更新您的知识库详细信息,尤其是切片方法。', name: '知识库名称', photo: '知识库图片', @@ -1501,5 +1530,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 total: '总共 {{total}} 条', page: '{{page}}条/页', }, + dataflowParser: { + parseSummary: '解析摘要', + parseSummaryTip: '解析器: deepdoc', + rerunFromCurrentStep: '从当前步骤重新运行', + rerunFromCurrentStepTip: '已修改,点击重新运行。', + }, }, }; diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index b7c929a52..74e6d146a 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -40,6 +40,7 @@ import { useCacheChatLog } from '../hooks/use-cache-chat-log'; import { useMoveNote } from '../hooks/use-move-note'; import { useDropdownManager } from './context'; +import Spotlight from '@/components/spotlight'; import { useHideFormSheetOnNodeDeletion, useShowDrawer, @@ -309,6 +310,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { onBeforeDelete={handleBeforeDelete} > + diff --git a/web/src/pages/dataflow-result/chunker.tsx b/web/src/pages/dataflow-result/chunker.tsx new file mode 100644 index 000000000..ff869809d --- /dev/null +++ b/web/src/pages/dataflow-result/chunker.tsx @@ -0,0 +1,234 @@ +import message from '@/components/ui/message'; +import { + RAGFlowPagination, + RAGFlowPaginationType, +} from '@/components/ui/ragflow-pagination'; +import { Spin } from '@/components/ui/spin'; +import { + useFetchNextChunkList, + useSwitchChunk, +} from '@/hooks/use-chunk-request'; +import classNames from 'classnames'; +import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import ChunkCard from './components/chunk-card'; +import CreatingModal from './components/chunk-creating-modal'; +import ChunkResultBar from './components/chunk-result-bar'; +import CheckboxSets from './components/chunk-result-bar/checkbox-sets'; +import RerunButton from './components/rerun-button'; +import { + useChangeChunkTextMode, + useDeleteChunkByIds, + useHandleChunkCardClick, + useUpdateChunk, +} from './hooks'; +import styles from './index.less'; +const ChunkerContainer = () => { + const [selectedChunkIds, setSelectedChunkIds] = useState([]); + const [isChange, setIsChange] = useState(false); + const { t } = useTranslation(); + const { + data: { documentInfo, data = [], total }, + pagination, + loading, + searchString, + handleInputChange, + available, + handleSetAvailable, + } = useFetchNextChunkList(); + const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); + const isPdf = documentInfo?.type === 'pdf'; + const { + chunkUpdatingLoading, + onChunkUpdatingOk, + showChunkUpdatingModal, + hideChunkUpdatingModal, + chunkId, + chunkUpdatingVisible, + documentId, + } = useUpdateChunk(); + const { removeChunk } = useDeleteChunkByIds(); + const { changeChunkTextMode, textMode } = useChangeChunkTextMode(); + const selectAllChunk = useCallback( + (checked: boolean) => { + setSelectedChunkIds(checked ? data.map((x) => x.chunk_id) : []); + }, + [data], + ); + const showSelectedChunkWarning = useCallback(() => { + message.warning(t('message.pleaseSelectChunk')); + }, [t]); + const { switchChunk } = useSwitchChunk(); + + const [chunkList, setChunkList] = useState(data); + useEffect(() => { + setChunkList(data); + }, [data]); + const onPaginationChange: RAGFlowPaginationType['onChange'] = ( + page, + size, + ) => { + setSelectedChunkIds([]); + pagination.onChange?.(page, size); + }; + + const handleSwitchChunk = useCallback( + async (available?: number, chunkIds?: string[]) => { + let ids = chunkIds; + if (!chunkIds) { + ids = selectedChunkIds; + if (selectedChunkIds.length === 0) { + showSelectedChunkWarning(); + return; + } + } + + const resCode: number = await switchChunk({ + chunk_ids: ids, + available_int: available, + doc_id: documentId, + }); + if (ids?.length && resCode === 0) { + chunkList.forEach((x: any) => { + if (ids.indexOf(x['chunk_id']) > -1) { + x['available_int'] = available; + } + }); + setChunkList(chunkList); + } + }, + [ + switchChunk, + documentId, + selectedChunkIds, + showSelectedChunkWarning, + chunkList, + ], + ); + const handleSingleCheckboxClick = useCallback( + (chunkId: string, checked: boolean) => { + setSelectedChunkIds((previousIds) => { + const idx = previousIds.findIndex((x) => x === chunkId); + const nextIds = [...previousIds]; + if (checked && idx === -1) { + nextIds.push(chunkId); + } else if (!checked && idx !== -1) { + nextIds.splice(idx, 1); + } + return nextIds; + }); + }, + [], + ); + const handleRemoveChunk = useCallback(async () => { + if (selectedChunkIds.length > 0) { + const resCode: number = await removeChunk(selectedChunkIds, documentId); + if (resCode === 0) { + setSelectedChunkIds([]); + } + } else { + showSelectedChunkWarning(); + } + }, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]); + + const handleChunkEditSave = (e: any) => { + setIsChange(true); + onChunkUpdatingOk(e); + }; + return ( + <> + {isChange && ( +
+ +
+ )} +
+ +
+
+

{t('chunk.chunkResult')}

+
+ {t('chunk.chunkResultTip')} +
+
+ +
+
+
+ +
+
+
+ {chunkList.map((item) => ( + x === item.chunk_id)} + handleCheckboxClick={handleSingleCheckboxClick} + switchChunk={handleSwitchChunk} + clickChunkCard={handleChunkCardClick} + selected={item.chunk_id === selectedChunkId} + textMode={textMode} + > + ))} +
+
+
+ { + onPaginationChange(page, pageSize); + }} + > +
+
+
+
+ {chunkUpdatingVisible && ( + { + handleChunkEditSave(e); + }} + parserId={documentInfo.parser_id} + /> + )} + + ); +}; + +export { ChunkerContainer }; diff --git a/web/src/pages/dataflow-result/components/chunk-card/index.less b/web/src/pages/dataflow-result/components/chunk-card/index.less new file mode 100644 index 000000000..aac7724af --- /dev/null +++ b/web/src/pages/dataflow-result/components/chunk-card/index.less @@ -0,0 +1,36 @@ +.image { + width: 100px !important; + object-fit: contain; +} + +.imagePreview { + max-width: 50vw; + max-height: 50vh; + object-fit: contain; +} + +.content { + flex: 1; + .chunkText; +} + +.contentEllipsis { + .multipleLineEllipsis(3); +} + +.contentText { + word-break: break-all !important; +} + +.chunkCard { + width: 100%; + padding: 18px 10px; +} + +.cardSelected { + background-color: @selectedBackgroundColor; +} + +.cardSelectedDark { + background-color: #ffffff2f; +} diff --git a/web/src/pages/dataflow-result/components/chunk-card/index.tsx b/web/src/pages/dataflow-result/components/chunk-card/index.tsx new file mode 100644 index 000000000..198746aef --- /dev/null +++ b/web/src/pages/dataflow-result/components/chunk-card/index.tsx @@ -0,0 +1,127 @@ +import Image from '@/components/image'; +import { useTheme } from '@/components/theme-provider'; +import { Card } from '@/components/ui/card'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Switch } from '@/components/ui/switch'; +import { IChunk } from '@/interfaces/database/knowledge'; +import { CheckedState } from '@radix-ui/react-checkbox'; +import classNames from 'classnames'; +import DOMPurify from 'dompurify'; +import { useEffect, useState } from 'react'; +import { ChunkTextMode } from '../../constant'; +import styles from './index.less'; + +interface IProps { + item: IChunk; + checked: boolean; + switchChunk: (available?: number, chunkIds?: string[]) => void; + editChunk: (chunkId: string) => void; + handleCheckboxClick: (chunkId: string, checked: boolean) => void; + selected: boolean; + clickChunkCard: (chunkId: string) => void; + textMode: ChunkTextMode; +} + +const ChunkCard = ({ + item, + checked, + handleCheckboxClick, + editChunk, + switchChunk, + selected, + clickChunkCard, + textMode, +}: IProps) => { + const available = Number(item.available_int); + const [enabled, setEnabled] = useState(false); + const { theme } = useTheme(); + + const onChange = (checked: boolean) => { + setEnabled(checked); + switchChunk(available === 0 ? 1 : 0, [item.chunk_id]); + }; + + const handleCheck = (e: CheckedState) => { + handleCheckboxClick(item.chunk_id, e === 'indeterminate' ? false : e); + }; + + const handleContentDoubleClick = () => { + editChunk(item.chunk_id); + }; + + const handleContentClick = () => { + clickChunkCard(item.chunk_id); + }; + + useEffect(() => { + setEnabled(available === 1); + }, [available]); + const [open, setOpen] = useState(false); + return ( + +
+ + {item.image_id && ( + + setOpen(true)} + onMouseLeave={() => setOpen(false)} + > +
+ +
+
+ +
+ +
+
+
+ )} +
+
+
+
+ +
+
+
+ ); +}; + +export default ChunkCard; diff --git a/web/src/pages/dataflow-result/components/chunk-creating-modal/index.tsx b/web/src/pages/dataflow-result/components/chunk-creating-modal/index.tsx new file mode 100644 index 000000000..66cf4d619 --- /dev/null +++ b/web/src/pages/dataflow-result/components/chunk-creating-modal/index.tsx @@ -0,0 +1,206 @@ +import EditTag from '@/components/edit-tag'; +import Divider from '@/components/ui/divider'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from '@/components/ui/hover-card'; +import { Modal } from '@/components/ui/modal/modal'; +import Space from '@/components/ui/space'; +import { Switch } from '@/components/ui/switch'; +import { Textarea } from '@/components/ui/textarea'; +import { useFetchChunk } from '@/hooks/chunk-hooks'; +import { IModalProps } from '@/interfaces/common'; +import { Trash2 } from 'lucide-react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { FieldValues, FormProvider, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { useDeleteChunkByIds } from '../../hooks'; +import { + transformTagFeaturesArrayToObject, + transformTagFeaturesObjectToArray, +} from '../../utils'; +import { TagFeatureItem } from './tag-feature-item'; + +interface kFProps { + doc_id: string; + chunkId: string | undefined; + parserId: string; +} + +const ChunkCreatingModal: React.FC & kFProps> = ({ + doc_id, + chunkId, + hideModal, + onOk, + loading, + parserId, +}) => { + // const [form] = Form.useForm(); + // const form = useFormContext(); + const form = useForm({ + defaultValues: { + content_with_weight: '', + tag_kwd: [], + question_kwd: [], + important_kwd: [], + tag_feas: [], + }, + }); + const [checked, setChecked] = useState(false); + const { removeChunk } = useDeleteChunkByIds(); + const { data } = useFetchChunk(chunkId); + const { t } = useTranslation(); + + const isTagParser = parserId === 'tag'; + const onSubmit = useCallback( + (values: FieldValues) => { + onOk?.({ + ...values, + tag_feas: transformTagFeaturesArrayToObject(values.tag_feas), + available_int: checked ? 1 : 0, + }); + }, + [checked, onOk], + ); + + const handleOk = form.handleSubmit(onSubmit); + + const handleRemove = useCallback(() => { + if (chunkId) { + return removeChunk([chunkId], doc_id); + } + }, [chunkId, doc_id, removeChunk]); + + const handleCheck = useCallback(() => { + setChecked(!checked); + }, [checked]); + + useEffect(() => { + if (data?.code === 0) { + const { available_int, tag_feas } = data.data; + form.reset({ + ...data.data, + tag_feas: transformTagFeaturesObjectToArray(tag_feas), + }); + + setChecked(available_int !== 0); + } + }, [data, form, chunkId]); + + return ( + +
+
+ ( + + {t('chunk.chunk')} + +