diff --git a/web/.oxlintrc.json b/web/.oxlintrc.json index ebea8f787e..6391d69dea 100644 --- a/web/.oxlintrc.json +++ b/web/.oxlintrc.json @@ -4,24 +4,24 @@ "es2021": true, "node": true }, - "plugins": ["@typescript-eslint", "react", "react-hooks"], + "plugins": ["typescript", "react"], "settings": { "react": { "version": "18.2.0" } }, "rules": { - "@typescript-eslint/no-use-before-define": [ + "typescript/no-use-before-define": [ "warn", { "functions": false, "variables": true } ], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-non-null-assertion": "off", + "typescript/no-explicit-any": "off", + "typescript/ban-ts-comment": "off", + "typescript/no-empty-function": "off", + "typescript/no-non-null-assertion": "off", "react/react-in-jsx-scope": "off", "react/no-unescaped-entities": "warn", "no-console": ["warn", { "allow": ["warn", "error"] }] diff --git a/web/CLAUDE.md b/web/CLAUDE.md index fed624cae5..e3f92eec69 100644 --- a/web/CLAUDE.md +++ b/web/CLAUDE.md @@ -18,7 +18,8 @@ RAGFlow frontend is a React/TypeScript application built with UmiJS: npm install npm run dev # Development server npm run build # Production build -npm run lint # ESLint +npm run lint # oxlint +npm run format # oxfmt npm run test # Jest tests ``` diff --git a/web/src/components/data-pipeline-select/index.tsx b/web/src/components/data-pipeline-select/index.tsx index 110427be4a..ee6322829b 100644 --- a/web/src/components/data-pipeline-select/index.tsx +++ b/web/src/components/data-pipeline-select/index.tsx @@ -3,6 +3,7 @@ import { FormLayout } from '@/constants/form'; import { useTranslate } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useFetchAgentList } from '@/hooks/use-agent-request'; +import { AgentListItem, AgentListItemType } from '@/interfaces/database/agent'; import { buildSelectOptions } from '@/utils/component-util'; import { ArrowUpRight } from 'lucide-react'; import { useEffect, useMemo } from 'react'; @@ -48,19 +49,25 @@ export function DataFlowSelect(props: IProps) { const { data: dataPipelineOptions } = useFetchAgentList({ canvas_category: AgentCategory.DataflowCanvas, }); + + const canvasList = useMemo( + () => + (dataPipelineOptions?.canvas ?? []).filter( + (item): item is AgentListItem & { title: string; avatar?: string } => + item.type !== AgentListItemType.CompilationTemplateGroup, + ), + [dataPipelineOptions?.canvas], + ); + const options = useMemo(() => { - const option = buildSelectOptions( - dataPipelineOptions?.canvas, - 'id', - 'title', - ); + const option = buildSelectOptions(canvasList, 'id', 'title'); return option || []; - }, [dataPipelineOptions]); + }, [canvasList]); const nodes = useMemo(() => { return ( - dataPipelineOptions?.canvas?.map((item) => { + canvasList.map((item) => { return { id: item?.id, name: item?.title, @@ -68,7 +75,7 @@ export function DataFlowSelect(props: IProps) { }; }) || [] ); - }, [dataPipelineOptions]); + }, [canvasList]); useEffect(() => { setDataList?.(nodes); diff --git a/web/src/components/document-preview/hooks.ts b/web/src/components/document-preview/hooks.ts index 21266afc26..af05cdd53c 100644 --- a/web/src/components/document-preview/hooks.ts +++ b/web/src/components/document-preview/hooks.ts @@ -132,7 +132,7 @@ export const useFetchExcel = (filePath: string) => { setStatus(true); }) .catch((e) => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line no-console console.warn('failed', e); myExcelPreviewer.destroy(); setStatus(false); diff --git a/web/src/components/file-upload-dialog/index.tsx b/web/src/components/file-upload-dialog/index.tsx index ced2a011ea..4f8beb1c82 100644 --- a/web/src/components/file-upload-dialog/index.tsx +++ b/web/src/components/file-upload-dialog/index.tsx @@ -144,7 +144,7 @@ function UploadForm({ setColumnRoles(roles); form.setValue('tableColumnRoles', roles); } - }, [extractedColumns, columnMode]); // eslint-disable-line react-hooks/exhaustive-deps + }, [extractedColumns, columnMode]); // oxlint-disable-line react/exhaustive-deps const showColumnConfig = isTableParser && extractedColumns.length > 0; diff --git a/web/src/components/file-uploader.tsx b/web/src/components/file-uploader.tsx index 2cf3bcfc6f..769ea94213 100644 --- a/web/src/components/file-uploader.tsx +++ b/web/src/components/file-uploader.tsx @@ -304,7 +304,7 @@ export function FileUploader(props: FileUploaderProps) { } }); }; - // eslint-disable-next-line react-hooks/exhaustive-deps + // oxlint-disable-next-line react/exhaustive-deps }, []); const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount; diff --git a/web/src/components/floating-chat-widget-markdown.tsx b/web/src/components/floating-chat-widget-markdown.tsx index f0ec228129..b02332da2f 100644 --- a/web/src/components/floating-chat-widget-markdown.tsx +++ b/web/src/components/floating-chat-widget-markdown.tsx @@ -307,7 +307,7 @@ const FloatingChatWidgetMarkdown = ({ 'custom-typography': ({ children }: { children: string }) => renderReference(children), code(props: any) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // oxlint-disable-next-line typescript/no-unused-vars const { children, className, node, ...rest } = props; const match = /language-(\w+)/.exec(className || ''); return match ? ( diff --git a/web/src/components/reui/stepper.tsx b/web/src/components/reui/stepper.tsx index bb20901049..f6c753277c 100644 --- a/web/src/components/reui/stepper.tsx +++ b/web/src/components/reui/stepper.tsx @@ -1,4 +1,4 @@ -/* eslint-disable react-hooks/exhaustive-deps */ +/* oxlint-disable react/exhaustive-deps */ 'use client'; diff --git a/web/src/hooks/logic-hooks.ts b/web/src/hooks/logic-hooks.ts index da0b9272a1..52876e7900 100644 --- a/web/src/hooks/logic-hooks.ts +++ b/web/src/hooks/logic-hooks.ts @@ -266,7 +266,7 @@ export const useSendMessageWithSse = () => { .pipeThrough(new EventSourceParserStream()) .getReader(); - // eslint-disable-next-line no-constant-condition + // oxlint-disable-next-line no-constant-condition while (true) { try { const x = await reader?.read(); diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 7c40c4274e..435c1dc65e 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -4,6 +4,7 @@ import message from '@/components/ui/message'; import { AgentCategory, AgentGlobals } from '@/constants/agent'; import { useFetchTenantInfo } from '@/hooks/use-user-setting-request'; import { + AgentListItem, IAgentLogResponse, IAgentLogsRequest, IAgentLogsResponse, @@ -154,7 +155,7 @@ export const useFetchAgentListByPage = () => { }); const { data, isFetching: loading } = useQuery<{ - canvas: IFlow[]; + canvas: AgentListItem[]; total: number; }>({ queryKey: [ @@ -205,7 +206,7 @@ export const useFetchAgentListByPage = () => { }; export function useFetchAllAgentList() { - const { data, isFetching: loading } = useQuery({ + const { data, isFetching: loading } = useQuery({ queryKey: [AgentApiAction.FetchAllAgentList], queryFn: async () => { const { data } = await agentService.listAgents( @@ -853,7 +854,7 @@ export const useFetchAgentList = ({ canvas_category, }: IPipeLineListRequest) => { const { data, isFetching: loading } = useQuery<{ - canvas: IFlow[]; + canvas: AgentListItem[]; total: number; }>({ queryKey: [AgentApiAction.FetchAgentList], diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index 49d1e31b85..9fc99c5ffe 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -193,7 +193,7 @@ export const useSendMessageBySSE = (url: string) => { let lastEventData: ResponseType | undefined; try { - // eslint-disable-next-line no-constant-condition + // oxlint-disable-next-line no-constant-condition while (true) { const x = await reader?.read(); if (!x) { diff --git a/web/src/lib/editor/lexical-editor.tsx b/web/src/lib/editor/lexical-editor.tsx index 85bdaaa539..1914f6793e 100644 --- a/web/src/lib/editor/lexical-editor.tsx +++ b/web/src/lib/editor/lexical-editor.tsx @@ -67,7 +67,7 @@ function InitialContentPlugin({ root.append($createParagraphNode()); } }); - // eslint-disable-next-line react-hooks/exhaustive-deps + // oxlint-disable-next-line react/exhaustive-deps }, [editor]); return null; diff --git a/web/src/lib/editor/markdown/horizontal-rule-node.ts b/web/src/lib/editor/markdown/horizontal-rule-node.ts index 1975ccd74e..e1e95a959b 100644 --- a/web/src/lib/editor/markdown/horizontal-rule-node.ts +++ b/web/src/lib/editor/markdown/horizontal-rule-node.ts @@ -32,13 +32,13 @@ export class HorizontalRuleNode extends ElementNode { return new HorizontalRuleNode(node.__key); } - /* eslint-disable @typescript-eslint/no-unused-vars */ + /* oxlint-disable typescript/no-unused-vars */ static importJSON( _serializedNode: SerializedHorizontalRuleNode, ): HorizontalRuleNode { return $createHorizontalRuleNode(); } - /* eslint-enable @typescript-eslint/no-unused-vars */ + /* eslint-enable typescript/no-unused-vars */ exportJSON(): SerializedHorizontalRuleNode { return { @@ -51,7 +51,7 @@ export class HorizontalRuleNode extends ElementNode { }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // oxlint-disable-next-line typescript/no-unused-vars createDOM(_config: EditorConfig, _editor: any): HTMLElement { const element = document.createElement('hr'); return element; diff --git a/web/src/lib/editor/markdown/markdown-transformers.ts b/web/src/lib/editor/markdown/markdown-transformers.ts index 81d0d05ff0..34bfbab1a8 100644 --- a/web/src/lib/editor/markdown/markdown-transformers.ts +++ b/web/src/lib/editor/markdown/markdown-transformers.ts @@ -7,7 +7,7 @@ * - LINK, HR */ -/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type, no-useless-escape */ +/* oxlint-disable typescript/no-unused-vars, typescript/no-empty-object-type, no-useless-escape */ import { $createCodeNode, $isCodeNode, CodeNode } from '@lexical/code'; import { diff --git a/web/src/lib/editor/mermaid-node.tsx b/web/src/lib/editor/mermaid-node.tsx index d554723fbc..b77c77a4e1 100644 --- a/web/src/lib/editor/mermaid-node.tsx +++ b/web/src/lib/editor/mermaid-node.tsx @@ -3,7 +3,7 @@ * Ported from Nimbalyst — supports click-to-edit source code. */ -/* eslint-disable @typescript-eslint/no-unused-vars */ +/* oxlint-disable typescript/no-unused-vars */ import { addClassNamesToElement } from '@lexical/utils'; import type { diff --git a/web/src/lib/editor/mermaid-transformer.ts b/web/src/lib/editor/mermaid-transformer.ts index 4f97dae5c4..8c257315da 100644 --- a/web/src/lib/editor/mermaid-transformer.ts +++ b/web/src/lib/editor/mermaid-transformer.ts @@ -3,7 +3,7 @@ * Ported from Nimbalyst. Creates MermaidNode instead of CodeNode. */ -/* eslint-disable no-console */ +/* oxlint-disable no-console */ import type { MultilineElementTransformer } from '@lexical/markdown'; import { diff --git a/web/src/lib/editor/plugins/table-transformer.ts b/web/src/lib/editor/plugins/table-transformer.ts index 44bed60dce..672601fbf1 100644 --- a/web/src/lib/editor/plugins/table-transformer.ts +++ b/web/src/lib/editor/plugins/table-transformer.ts @@ -2,7 +2,7 @@ * Table transformer for markdown import/export */ -/* eslint-disable @typescript-eslint/no-unused-vars, no-console, @typescript-eslint/no-use-before-define */ +/* oxlint-disable typescript/no-unused-vars, no-console, typescript/no-use-before-define */ import { ElementTransformer } from '@lexical/markdown'; import { diff --git a/web/src/pages/admin/forms/import-excel-form.tsx b/web/src/pages/admin/forms/import-excel-form.tsx index 64f136f87e..357d214ae6 100644 --- a/web/src/pages/admin/forms/import-excel-form.tsx +++ b/web/src/pages/admin/forms/import-excel-form.tsx @@ -43,7 +43,7 @@ export const ImportExcelForm = ({ ( diff --git a/web/src/pages/agent/context.ts b/web/src/pages/agent/context.ts index cbfa1999ec..2e899194c4 100644 --- a/web/src/pages/agent/context.ts +++ b/web/src/pages/agent/context.ts @@ -15,7 +15,7 @@ export const OwnerTenantIdContext = createContext( undefined, ); -// eslint-disable-next-line react-refresh/only-export-components +// oxlint-disable-next-line react-refresh/only-export-components export const useOwnerTenantId = () => useContext(OwnerTenantIdContext); type AgentInstanceContextType = Pick< diff --git a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx index 9541bb8429..400bba78f5 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx +++ b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx @@ -108,7 +108,7 @@ function useHandleSelectChange({ onChange, value }: ToolCommandProps) { }; } -// eslint-disable-next-line +// oxlint-disable-next-line export function ToolCommand({ value, onChange }: ToolCommandProps) { const { t } = useTranslation(); diff --git a/web/src/pages/agent/store.ts b/web/src/pages/agent/store.ts index 1030a00652..65a551e1ea 100644 --- a/web/src/pages/agent/store.ts +++ b/web/src/pages/agent/store.ts @@ -672,7 +672,7 @@ const useGraphStore = create()( id: string, nodeOrNodeId?: RAGFlowNodeType | string, ) => { - // eslint-disable-next-line eqeqeq + // oxlint-disable-next-line eqeqeq const tools = nodeOrNodeId != null ? getAgentNodeTools( diff --git a/web/src/pages/agent/utils/tests/dsl-bridge.test.ts b/web/src/pages/agent/utils/tests/dsl-bridge.test.ts index 8d480cff44..2d2d32a797 100644 --- a/web/src/pages/agent/utils/tests/dsl-bridge.test.ts +++ b/web/src/pages/agent/utils/tests/dsl-bridge.test.ts @@ -334,7 +334,7 @@ describe('dsl-bridge round-trip stability', () => { // state. const diff = diffDsl(v2BrowserLike.graph, exported.graph, 'graph'); if (diff.warnings.length > 0) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line no-console console.warn( '[v2 round-trip] React-Flow-internal mismatches (warnings, not failures):', diff.warnings, diff --git a/web/src/pages/agents/agent-card.tsx b/web/src/pages/agents/agent-card.tsx index fc27b79bba..82731d5cfb 100644 --- a/web/src/pages/agents/agent-card.tsx +++ b/web/src/pages/agents/agent-card.tsx @@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'; import { AgentCategory } from '@/constants/agent'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { AgentListItemType, IFlow } from '@/interfaces/database/agent'; -import { LucideIcon, Network, Route, Shapes } from 'lucide-react'; +import { CanvasCategoryToFlowType, FlowType, FlowTypeConfig } from './constant'; import { AgentDropdown } from './agent-dropdown'; import { useRenameAgent } from './use-rename-agent'; @@ -14,25 +14,24 @@ export type DatasetCardProps = { data: IFlow & { type?: AgentListItemType }; } & Pick, 'showAgentRenameModal'>; -const CanvasCategoryIconMap: Record = { - [AgentCategory.AgentCanvas]: Network, - [AgentCategory.DataflowCanvas]: Route, -}; - function AgentTypeIcon({ data, }: { data: IFlow & { type?: AgentListItemType }; }) { - const Icon = + const flowType = data.type === AgentListItemType.CompilationTemplateGroup - ? Shapes - : CanvasCategoryIconMap[data.canvas_category]; + ? FlowType.Compiler + : CanvasCategoryToFlowType[data.canvas_category ?? '']; - if (!Icon) { + const icon = flowType ? FlowTypeConfig[flowType].icon : null; + + if (!icon) { return null; } + const Icon = icon; + return ( @@ -73,6 +66,11 @@ export function CompilationTemplateCard({ ))} + +
+ {t('flow.lastSavedAt')}: +

{formatDate(data.update_time)}

+
diff --git a/web/src/pages/agents/constant.ts b/web/src/pages/agents/constant.ts index cb0c2bb108..710e662fdc 100644 --- a/web/src/pages/agents/constant.ts +++ b/web/src/pages/agents/constant.ts @@ -1,5 +1,28 @@ +import { AgentCategory } from '@/constants/agent'; +import { LucideIcon, Network, Route, Shapes } from 'lucide-react'; + export enum FlowType { Agent = 'agent', Compiler = 'compiler', Flow = 'flow', } + +export const FlowTypeConfig: Record< + FlowType, + { icon: LucideIcon; labelKey: string } +> = { + [FlowType.Agent]: { icon: Network, labelKey: 'tabList.workflow' }, + [FlowType.Compiler]: { + icon: Shapes, + labelKey: 'tabList.compilationOperator', + }, + [FlowType.Flow]: { icon: Route, labelKey: 'tabList.ingestionPipeline' }, +}; + +/** + * Map canvas_category → FlowType for resolving icons in agent cards. + */ +export const CanvasCategoryToFlowType: Record = { + [AgentCategory.AgentCanvas]: FlowType.Agent, + [AgentCategory.DataflowCanvas]: FlowType.Flow, +}; diff --git a/web/src/pages/agents/create-agent-form.tsx b/web/src/pages/agents/create-agent-form.tsx index 731bc6cda1..11db63cfdc 100644 --- a/web/src/pages/agents/create-agent-form.tsx +++ b/web/src/pages/agents/create-agent-form.tsx @@ -13,11 +13,11 @@ import { TagRenameId } from '@/constants/knowledge'; import { IModalProps } from '@/interfaces/common'; import { cn } from '@/lib/utils'; import { Routes } from '@/routes'; -import { BrainCircuit, Check, Route, Shapes } from 'lucide-react'; +import { Check } from 'lucide-react'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; -import { FlowType } from './constant'; +import { FlowType, FlowTypeConfig } from './constant'; import { NameFormField, NameFormSchema } from './name-form-field'; export type CreateAgentFormProps = IModalProps & { @@ -30,18 +30,6 @@ type FlowTypeCardProps = { onChange?: (value: FlowType) => void; }; -const FLOW_TYPE_CONFIG: Record< - FlowType, - { icon: typeof BrainCircuit; labelKey: string } -> = { - [FlowType.Flow]: { icon: Route, labelKey: 'tabList.ingestionPipeline' }, - [FlowType.Compiler]: { - icon: Shapes, - labelKey: 'tabList.compilationOperator', - }, - [FlowType.Agent]: { icon: BrainCircuit, labelKey: 'tabList.workflow' }, -}; - function FlowTypeCards({ value, onChange }: FlowTypeCardProps) { const { t } = useTranslation(); const handleChange = useCallback( @@ -55,7 +43,7 @@ function FlowTypeCards({ value, onChange }: FlowTypeCardProps) {
{[FlowType.Flow, FlowType.Compiler, FlowType.Agent].map((val) => { const isActive = value === val; - const config = FLOW_TYPE_CONFIG[val]; + const config = FlowTypeConfig[val]; const Icon = config.icon; return ( - Array.isArray(filterValue.canvasCategory) - ? (filterValue.canvasCategory[0] as string | undefined) - : undefined, - [filterValue.canvasCategory], - ); - const isCompilation = canvasCategory === CompilationGroupCategory; - const { navigateToAgentTemplates } = useNavigatePage(); const navigate = useNavigate(); @@ -92,10 +83,6 @@ export default function Agents() { [setPagination], ); - const handleAddCompilation = useCallback(() => { - navigate(`${Routes.CompilationTemplatesEditNext}?source=agents`); - }, [navigate]); - const handleEditCompilation = useCallback( (id: string) => () => { navigate(`${Routes.CompilationTemplatesEditNext}/${id}?source=agents`); @@ -137,68 +124,59 @@ export default function Agents() { onChange={handleFilterSubmit} value={filterValue} > - {isCompilation ? ( - - ) : ( - - - - - - - - {t('flow.createFromBlank')} - - navigateToAgentTemplates()} - > - - {t('flow.createFromTemplate')} - - - - {t('flow.importJsonFile')} - - - - )} + + + + + + + + {t('flow.createFromBlank')} + + navigateToAgentTemplates()} + > + + {t('flow.createFromTemplate')} + + + + {t('flow.importJsonFile')} + + + {data.length ? ( <> - {isCompilation - ? (data as any[]).map((item) => ( - - )) - : data.map((x) => ( - - ))} + {data.map((x) => + x.type === CompilationGroupCategory ? ( + + ) : ( + + ), + )}