From 95a2b3d08837b018317c7e62de1ec0c31d01ccb3 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Fri, 19 Dec 2025 13:00:34 +0900 Subject: [PATCH 01/71] refactor: split changes for api/libs/helper.py (#29875) --- api/libs/helper.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/libs/helper.py b/api/libs/helper.py index 4a7afe0bda..74e1808e49 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -11,6 +11,7 @@ from collections.abc import Generator, Mapping from datetime import datetime from hashlib import sha256 from typing import TYPE_CHECKING, Annotated, Any, Optional, Union, cast +from uuid import UUID from zoneinfo import available_timezones from flask import Response, stream_with_context @@ -119,6 +120,19 @@ def uuid_value(value: Any) -> str: raise ValueError(error) +def normalize_uuid(value: str | UUID) -> str: + if not value: + return "" + + try: + return uuid_value(value) + except ValueError as exc: + raise ValueError("must be a valid UUID") from exc + + +UUIDStrOrEmpty = Annotated[str, AfterValidator(normalize_uuid)] + + def alphanumeric(value: str): # check if the value is alphanumeric and underlined if re.match(r"^[a-zA-Z0-9_]+$", value): From 80f11471aeef1885246cf1ef157b495b7c9c9c9b Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:00:46 +0800 Subject: [PATCH 02/71] perf: improve Jest caching and configuration in web tests (#29881) --- .github/workflows/web-tests.yml | 10 +- .../edit-item/index.spec.tsx | 73 ++++++- .../edit-annotation-modal/edit-item/index.tsx | 23 ++- .../edit-annotation-modal/index.spec.tsx | 192 ++++++++++++++---- .../edit-annotation-modal/index.tsx | 52 +++-- .../app/annotation/header-opts/index.spec.tsx | 116 +++++++++++ web/app/components/app/annotation/type.ts | 6 + .../params-config/index.spec.tsx | 103 +++++++--- .../annotation-ctrl-button.tsx | 4 +- web/i18n/ar-TN/common.ts | 1 + web/i18n/de-DE/common.ts | 1 + web/i18n/en-US/common.ts | 1 + web/i18n/es-ES/common.ts | 1 + web/i18n/fa-IR/common.ts | 1 + web/i18n/fr-FR/common.ts | 1 + web/i18n/hi-IN/common.ts | 1 + web/i18n/id-ID/common.ts | 1 + web/i18n/it-IT/common.ts | 1 + web/i18n/ja-JP/common.ts | 1 + web/i18n/ko-KR/common.ts | 1 + web/i18n/pl-PL/common.ts | 1 + web/i18n/pt-BR/common.ts | 1 + web/i18n/ro-RO/common.ts | 1 + web/i18n/ru-RU/common.ts | 1 + web/i18n/sl-SI/common.ts | 1 + web/i18n/th-TH/common.ts | 1 + web/i18n/tr-TR/common.ts | 1 + web/i18n/uk-UA/common.ts | 1 + web/i18n/vi-VN/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + web/i18n/zh-Hant/common.ts | 1 + web/jest.config.ts | 2 +- web/service/annotation.ts | 4 +- 33 files changed, 496 insertions(+), 111 deletions(-) diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 8b871403cc..b1f32f96c2 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -35,6 +35,14 @@ jobs: cache: pnpm cache-dependency-path: ./web/pnpm-lock.yaml + - name: Restore Jest cache + uses: actions/cache@v4 + with: + path: web/.cache/jest + key: ${{ runner.os }}-jest-${{ hashFiles('web/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-jest- + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -45,7 +53,7 @@ jobs: run: | pnpm exec jest \ --ci \ - --runInBand \ + --maxWorkers=100% \ --coverage \ --passWithNoTests diff --git a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.spec.tsx b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.spec.tsx index 1f32e55928..95a5586292 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.spec.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.spec.tsx @@ -245,7 +245,7 @@ describe('EditItem', () => { expect(mockSave).toHaveBeenCalledWith('Test save content') }) - it('should show delete option when content changes', async () => { + it('should show delete option and restore original content when delete is clicked', async () => { // Arrange const mockSave = jest.fn().mockResolvedValue(undefined) const props = { @@ -267,7 +267,13 @@ describe('EditItem', () => { await user.click(screen.getByRole('button', { name: 'common.operation.save' })) // Assert - expect(mockSave).toHaveBeenCalledWith('Modified content') + expect(mockSave).toHaveBeenNthCalledWith(1, 'Modified content') + expect(await screen.findByText('common.operation.delete')).toBeInTheDocument() + + await user.click(screen.getByText('common.operation.delete')) + + expect(mockSave).toHaveBeenNthCalledWith(2, 'Test content') + expect(screen.queryByText('common.operation.delete')).not.toBeInTheDocument() }) it('should handle keyboard interactions in edit mode', async () => { @@ -393,5 +399,68 @@ describe('EditItem', () => { expect(screen.queryByRole('textbox')).not.toBeInTheDocument() expect(screen.getByText('Test content')).toBeInTheDocument() }) + + it('should handle save failure gracefully in edit mode', async () => { + // Arrange + const mockSave = jest.fn().mockRejectedValueOnce(new Error('Save failed')) + const props = { + ...defaultProps, + onSave: mockSave, + } + const user = userEvent.setup() + + // Act + render() + + // Enter edit mode and save (should fail) + await user.click(screen.getByText('common.operation.edit')) + const textarea = screen.getByRole('textbox') + await user.type(textarea, 'New content') + + // Save should fail but not throw + await user.click(screen.getByRole('button', { name: 'common.operation.save' })) + + // Assert - Should remain in edit mode when save fails + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() + expect(mockSave).toHaveBeenCalledWith('New content') + }) + + it('should handle delete action failure gracefully', async () => { + // Arrange + const mockSave = jest.fn() + .mockResolvedValueOnce(undefined) // First save succeeds + .mockRejectedValueOnce(new Error('Delete failed')) // Delete fails + const props = { + ...defaultProps, + onSave: mockSave, + } + const user = userEvent.setup() + + // Act + render() + + // Edit content to show delete button + await user.click(screen.getByText('common.operation.edit')) + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'Modified content') + + // Save to create new content + await user.click(screen.getByRole('button', { name: 'common.operation.save' })) + await screen.findByText('common.operation.delete') + + // Click delete (should fail but not throw) + await user.click(screen.getByText('common.operation.delete')) + + // Assert - Delete action should handle error gracefully + expect(mockSave).toHaveBeenCalledTimes(2) + expect(mockSave).toHaveBeenNthCalledWith(1, 'Modified content') + expect(mockSave).toHaveBeenNthCalledWith(2, 'Test content') + + // When delete fails, the delete button should still be visible (state not changed) + expect(screen.getByText('common.operation.delete')).toBeInTheDocument() + expect(screen.getByText('Modified content')).toBeInTheDocument() + }) }) }) diff --git a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx index e808d0b48a..37b5ab0686 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx @@ -52,8 +52,14 @@ const EditItem: FC = ({ }, [content]) const handleSave = async () => { - await onSave(newContent) - setIsEdit(false) + try { + await onSave(newContent) + setIsEdit(false) + } + catch { + // Keep edit mode open when save fails + // Error notification is handled by the parent component + } } const handleCancel = () => { @@ -96,9 +102,16 @@ const EditItem: FC = ({
·
{ - setNewContent(content) - onSave(content) + onClick={async () => { + try { + await onSave(content) + // Only update UI state after successful delete + setNewContent(content) + } + catch { + // Delete action failed - error is already handled by parent + // UI state remains unchanged, user can retry + } }} >
diff --git a/web/app/components/app/annotation/edit-annotation-modal/index.spec.tsx b/web/app/components/app/annotation/edit-annotation-modal/index.spec.tsx index b48f8a2a4a..bdc991116c 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/index.spec.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/index.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import Toast, { type IToastProps, type ToastHandle } from '@/app/components/base/toast' import EditAnnotationModal from './index' @@ -408,7 +408,7 @@ describe('EditAnnotationModal', () => { // Error Handling (CRITICAL for coverage) describe('Error Handling', () => { - it('should handle addAnnotation API failure gracefully', async () => { + it('should show error toast and skip callbacks when addAnnotation fails', async () => { // Arrange const mockOnAdded = jest.fn() const props = { @@ -420,29 +420,75 @@ describe('EditAnnotationModal', () => { // Mock API failure mockAddAnnotation.mockRejectedValueOnce(new Error('API Error')) - // Act & Assert - Should handle API error without crashing - expect(async () => { - render() + // Act + render() - // Find and click edit link for query - const editLinks = screen.getAllByText(/common\.operation\.edit/i) - await user.click(editLinks[0]) + // Find and click edit link for query + const editLinks = screen.getAllByText(/common\.operation\.edit/i) + await user.click(editLinks[0]) - // Find textarea and enter new content - const textarea = screen.getByRole('textbox') - await user.clear(textarea) - await user.type(textarea, 'New query content') + // Find textarea and enter new content + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'New query content') - // Click save button - const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) - await user.click(saveButton) + // Click save button + const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) + await user.click(saveButton) - // Should not call onAdded on error - expect(mockOnAdded).not.toHaveBeenCalled() - }).not.toThrow() + // Assert + await waitFor(() => { + expect(toastNotifySpy).toHaveBeenCalledWith({ + message: 'API Error', + type: 'error', + }) + }) + expect(mockOnAdded).not.toHaveBeenCalled() + + // Verify edit mode remains open (textarea should still be visible) + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() }) - it('should handle editAnnotation API failure gracefully', async () => { + it('should show fallback error message when addAnnotation error has no message', async () => { + // Arrange + const mockOnAdded = jest.fn() + const props = { + ...defaultProps, + onAdded: mockOnAdded, + } + const user = userEvent.setup() + + mockAddAnnotation.mockRejectedValueOnce({}) + + // Act + render() + + const editLinks = screen.getAllByText(/common\.operation\.edit/i) + await user.click(editLinks[0]) + + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'New query content') + + const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) + await user.click(saveButton) + + // Assert + await waitFor(() => { + expect(toastNotifySpy).toHaveBeenCalledWith({ + message: 'common.api.actionFailed', + type: 'error', + }) + }) + expect(mockOnAdded).not.toHaveBeenCalled() + + // Verify edit mode remains open (textarea should still be visible) + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() + }) + + it('should show error toast and skip callbacks when editAnnotation fails', async () => { // Arrange const mockOnEdited = jest.fn() const props = { @@ -456,24 +502,72 @@ describe('EditAnnotationModal', () => { // Mock API failure mockEditAnnotation.mockRejectedValueOnce(new Error('API Error')) - // Act & Assert - Should handle API error without crashing - expect(async () => { - render() + // Act + render() - // Edit query content - const editLinks = screen.getAllByText(/common\.operation\.edit/i) - await user.click(editLinks[0]) + // Edit query content + const editLinks = screen.getAllByText(/common\.operation\.edit/i) + await user.click(editLinks[0]) - const textarea = screen.getByRole('textbox') - await user.clear(textarea) - await user.type(textarea, 'Modified query') + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'Modified query') - const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) - await user.click(saveButton) + const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) + await user.click(saveButton) - // Should not call onEdited on error - expect(mockOnEdited).not.toHaveBeenCalled() - }).not.toThrow() + // Assert + await waitFor(() => { + expect(toastNotifySpy).toHaveBeenCalledWith({ + message: 'API Error', + type: 'error', + }) + }) + expect(mockOnEdited).not.toHaveBeenCalled() + + // Verify edit mode remains open (textarea should still be visible) + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() + }) + + it('should show fallback error message when editAnnotation error is not an Error instance', async () => { + // Arrange + const mockOnEdited = jest.fn() + const props = { + ...defaultProps, + annotationId: 'test-annotation-id', + messageId: 'test-message-id', + onEdited: mockOnEdited, + } + const user = userEvent.setup() + + mockEditAnnotation.mockRejectedValueOnce('oops') + + // Act + render() + + const editLinks = screen.getAllByText(/common\.operation\.edit/i) + await user.click(editLinks[0]) + + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'Modified query') + + const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) + await user.click(saveButton) + + // Assert + await waitFor(() => { + expect(toastNotifySpy).toHaveBeenCalledWith({ + message: 'common.api.actionFailed', + type: 'error', + }) + }) + expect(mockOnEdited).not.toHaveBeenCalled() + + // Verify edit mode remains open (textarea should still be visible) + expect(screen.getByRole('textbox')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'common.operation.save' })).toBeInTheDocument() }) }) @@ -526,25 +620,33 @@ describe('EditAnnotationModal', () => { }) }) - // Toast Notifications (Simplified) + // Toast Notifications (Success) describe('Toast Notifications', () => { - it('should trigger success notification when save operation completes', async () => { + it('should show success notification when save operation completes', async () => { // Arrange - const mockOnAdded = jest.fn() - const props = { - ...defaultProps, - onAdded: mockOnAdded, - } + const props = { ...defaultProps } + const user = userEvent.setup() // Act render() - // Simulate successful save by calling handleSave indirectly - const mockSave = jest.fn() - expect(mockSave).not.toHaveBeenCalled() + const editLinks = screen.getAllByText(/common\.operation\.edit/i) + await user.click(editLinks[0]) - // Assert - Toast spy is available and will be called during real save operations - expect(toastNotifySpy).toBeDefined() + const textarea = screen.getByRole('textbox') + await user.clear(textarea) + await user.type(textarea, 'Updated query') + + const saveButton = screen.getByRole('button', { name: 'common.operation.save' }) + await user.click(saveButton) + + // Assert + await waitFor(() => { + expect(toastNotifySpy).toHaveBeenCalledWith({ + message: 'common.api.actionSuccess', + type: 'success', + }) + }) }) }) diff --git a/web/app/components/app/annotation/edit-annotation-modal/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/index.tsx index 2961ce393c..6172a215e4 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/index.tsx @@ -53,27 +53,39 @@ const EditAnnotationModal: FC = ({ postQuery = editedContent else postAnswer = editedContent - if (!isAdd) { - await editAnnotation(appId, annotationId, { - message_id: messageId, - question: postQuery, - answer: postAnswer, - }) - onEdited(postQuery, postAnswer) - } - else { - const res: any = await addAnnotation(appId, { - question: postQuery, - answer: postAnswer, - message_id: messageId, - }) - onAdded(res.id, res.account?.name, postQuery, postAnswer) - } + try { + if (!isAdd) { + await editAnnotation(appId, annotationId, { + message_id: messageId, + question: postQuery, + answer: postAnswer, + }) + onEdited(postQuery, postAnswer) + } + else { + const res = await addAnnotation(appId, { + question: postQuery, + answer: postAnswer, + message_id: messageId, + }) + onAdded(res.id, res.account?.name ?? '', postQuery, postAnswer) + } - Toast.notify({ - message: t('common.api.actionSuccess') as string, - type: 'success', - }) + Toast.notify({ + message: t('common.api.actionSuccess') as string, + type: 'success', + }) + } + catch (error) { + const fallbackMessage = t('common.api.actionFailed') as string + const message = error instanceof Error && error.message ? error.message : fallbackMessage + Toast.notify({ + message, + type: 'error', + }) + // Re-throw to preserve edit mode behavior for UI components + throw error + } } const [showModal, setShowModal] = useState(false) diff --git a/web/app/components/app/annotation/header-opts/index.spec.tsx b/web/app/components/app/annotation/header-opts/index.spec.tsx index 8c640c2790..3d8a1fd4ef 100644 --- a/web/app/components/app/annotation/header-opts/index.spec.tsx +++ b/web/app/components/app/annotation/header-opts/index.spec.tsx @@ -1,3 +1,4 @@ +import * as React from 'react' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import type { ComponentProps } from 'react' @@ -7,6 +8,120 @@ import { LanguagesSupported } from '@/i18n-config/language' import type { AnnotationItemBasic } from '../type' import { clearAllAnnotations, fetchExportAnnotationList } from '@/service/annotation' +jest.mock('@headlessui/react', () => { + type PopoverContextValue = { open: boolean; setOpen: (open: boolean) => void } + type MenuContextValue = { open: boolean; setOpen: (open: boolean) => void } + const PopoverContext = React.createContext(null) + const MenuContext = React.createContext(null) + + const Popover = ({ children }: { children: React.ReactNode | ((props: { open: boolean }) => React.ReactNode) }) => { + const [open, setOpen] = React.useState(false) + const value = React.useMemo(() => ({ open, setOpen }), [open]) + return ( + + {typeof children === 'function' ? children({ open }) : children} + + ) + } + + const PopoverButton = React.forwardRef(({ onClick, children, ...props }: { onClick?: () => void; children?: React.ReactNode }, ref: React.Ref) => { + const context = React.useContext(PopoverContext) + const handleClick = () => { + context?.setOpen(!context.open) + onClick?.() + } + return ( + + ) + }) + + const PopoverPanel = React.forwardRef(({ children, ...props }: { children: React.ReactNode | ((props: { close: () => void }) => React.ReactNode) }, ref: React.Ref) => { + const context = React.useContext(PopoverContext) + if (!context?.open) return null + const content = typeof children === 'function' ? children({ close: () => context.setOpen(false) }) : children + return ( +
+ {content} +
+ ) + }) + + const Menu = ({ children }: { children: React.ReactNode }) => { + const [open, setOpen] = React.useState(false) + const value = React.useMemo(() => ({ open, setOpen }), [open]) + return ( + + {children} + + ) + } + + const MenuButton = ({ onClick, children, ...props }: { onClick?: () => void; children?: React.ReactNode }) => { + const context = React.useContext(MenuContext) + const handleClick = () => { + context?.setOpen(!context.open) + onClick?.() + } + return ( + + ) + } + + const MenuItems = ({ children, ...props }: { children: React.ReactNode }) => { + const context = React.useContext(MenuContext) + if (!context?.open) return null + return ( +
+ {children} +
+ ) + } + + return { + Dialog: ({ open, children, className }: { open?: boolean; children: React.ReactNode; className?: string }) => { + if (open === false) return null + return ( +
+ {children} +
+ ) + }, + DialogBackdrop: ({ children, className, onClick }: { children?: React.ReactNode; className?: string; onClick?: () => void }) => ( +
+ {children} +
+ ), + DialogPanel: ({ children, className, ...props }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), + DialogTitle: ({ children, className, ...props }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), + Popover, + PopoverButton, + PopoverPanel, + Menu, + MenuButton, + MenuItems, + Transition: ({ show = true, children }: { show?: boolean; children: React.ReactNode }) => (show ? <>{children} : null), + TransitionChild: ({ children }: { children: React.ReactNode }) => <>{children}, + } +}) + let lastCSVDownloaderProps: Record | undefined const mockCSVDownloader = jest.fn(({ children, ...props }) => { lastCSVDownloaderProps = props @@ -121,6 +236,7 @@ const mockedClearAllAnnotations = jest.mocked(clearAllAnnotations) describe('HeaderOptions', () => { beforeEach(() => { jest.clearAllMocks() + jest.useRealTimers() mockCSVDownloader.mockClear() lastCSVDownloaderProps = undefined mockedFetchAnnotations.mockResolvedValue({ data: [] }) diff --git a/web/app/components/app/annotation/type.ts b/web/app/components/app/annotation/type.ts index 5df6f51ace..e2f2264f07 100644 --- a/web/app/components/app/annotation/type.ts +++ b/web/app/components/app/annotation/type.ts @@ -12,6 +12,12 @@ export type AnnotationItem = { hit_count: number } +export type AnnotationCreateResponse = AnnotationItem & { + account?: { + name?: string + } +} + export type HitHistoryItem = { id: string question: string diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx index b666a6cb5b..c432ca68e2 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.spec.tsx @@ -1,5 +1,6 @@ import * as React from 'react' -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import ParamsConfig from './index' import ConfigContext from '@/context/debug-configuration' import type { DatasetConfigs } from '@/models/debug' @@ -11,6 +12,37 @@ import { useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' +jest.mock('@headlessui/react', () => ({ + Dialog: ({ children, className }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), + DialogPanel: ({ children, className, ...props }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), + DialogTitle: ({ children, className, ...props }: { children: React.ReactNode; className?: string }) => ( +
+ {children} +
+ ), + Transition: ({ show, children }: { show: boolean; children: React.ReactNode }) => (show ? <>{children} : null), + TransitionChild: ({ children }: { children: React.ReactNode }) => <>{children}, + Switch: ({ checked, onChange, children, ...props }: { checked: boolean; onChange?: (value: boolean) => void; children?: React.ReactNode }) => ( + + ), +})) + jest.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({ useModelListAndDefaultModelAndCurrentProviderAndModel: jest.fn(), useCurrentProviderAndModel: jest.fn(), @@ -74,9 +106,6 @@ const renderParamsConfig = ({ initialModalOpen?: boolean disabled?: boolean } = {}) => { - const setDatasetConfigsSpy = jest.fn() - const setModalOpenSpy = jest.fn() - const Wrapper = ({ children }: { children: React.ReactNode }) => { const [datasetConfigsState, setDatasetConfigsState] = React.useState(datasetConfigs) const [modalOpen, setModalOpen] = React.useState(initialModalOpen) @@ -84,12 +113,10 @@ const renderParamsConfig = ({ const contextValue = { datasetConfigs: datasetConfigsState, setDatasetConfigs: (next: DatasetConfigs) => { - setDatasetConfigsSpy(next) setDatasetConfigsState(next) }, rerankSettingModalOpen: modalOpen, setRerankSettingModalOpen: (open: boolean) => { - setModalOpenSpy(open) setModalOpen(open) }, } as unknown as React.ComponentProps['value'] @@ -101,18 +128,13 @@ const renderParamsConfig = ({ ) } - render( + return render( , { wrapper: Wrapper }, ) - - return { - setDatasetConfigsSpy, - setModalOpenSpy, - } } describe('dataset-config/params-config', () => { @@ -151,77 +173,92 @@ describe('dataset-config/params-config', () => { describe('User Interactions', () => { it('should open modal and persist changes when save is clicked', async () => { // Arrange - const { setDatasetConfigsSpy } = renderParamsConfig() + renderParamsConfig() + const user = userEvent.setup() // Act - fireEvent.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) + await user.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) const dialog = await screen.findByRole('dialog', {}, { timeout: 3000 }) const dialogScope = within(dialog) - // Change top_k via the first number input increment control. const incrementButtons = dialogScope.getAllByRole('button', { name: 'increment' }) - fireEvent.click(incrementButtons[0]) + await user.click(incrementButtons[0]) - const saveButton = await dialogScope.findByRole('button', { name: 'common.operation.save' }) - fireEvent.click(saveButton) + await waitFor(() => { + const [topKInput] = dialogScope.getAllByRole('spinbutton') + expect(topKInput).toHaveValue(5) + }) + + await user.click(dialogScope.getByRole('button', { name: 'common.operation.save' })) - // Assert - expect(setDatasetConfigsSpy).toHaveBeenCalledWith(expect.objectContaining({ top_k: 5 })) await waitFor(() => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument() }) + + await user.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) + const reopenedDialog = await screen.findByRole('dialog', {}, { timeout: 3000 }) + const reopenedScope = within(reopenedDialog) + const [reopenedTopKInput] = reopenedScope.getAllByRole('spinbutton') + + // Assert + expect(reopenedTopKInput).toHaveValue(5) }) it('should discard changes when cancel is clicked', async () => { // Arrange - const { setDatasetConfigsSpy } = renderParamsConfig() + renderParamsConfig() + const user = userEvent.setup() // Act - fireEvent.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) + await user.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) const dialog = await screen.findByRole('dialog', {}, { timeout: 3000 }) const dialogScope = within(dialog) const incrementButtons = dialogScope.getAllByRole('button', { name: 'increment' }) - fireEvent.click(incrementButtons[0]) + await user.click(incrementButtons[0]) + + await waitFor(() => { + const [topKInput] = dialogScope.getAllByRole('spinbutton') + expect(topKInput).toHaveValue(5) + }) const cancelButton = await dialogScope.findByRole('button', { name: 'common.operation.cancel' }) - fireEvent.click(cancelButton) + await user.click(cancelButton) await waitFor(() => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument() }) - // Re-open and save without changes. - fireEvent.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) + // Re-open and verify the original value remains. + await user.click(screen.getByRole('button', { name: 'dataset.retrievalSettings' })) const reopenedDialog = await screen.findByRole('dialog', {}, { timeout: 3000 }) const reopenedScope = within(reopenedDialog) - const reopenedSave = await reopenedScope.findByRole('button', { name: 'common.operation.save' }) - fireEvent.click(reopenedSave) + const [reopenedTopKInput] = reopenedScope.getAllByRole('spinbutton') - // Assert - should save original top_k rather than the canceled change. - expect(setDatasetConfigsSpy).toHaveBeenCalledWith(expect.objectContaining({ top_k: 4 })) + // Assert + expect(reopenedTopKInput).toHaveValue(4) }) it('should prevent saving when rerank model is required but invalid', async () => { // Arrange - const { setDatasetConfigsSpy } = renderParamsConfig({ + renderParamsConfig({ datasetConfigs: createDatasetConfigs({ reranking_enable: true, reranking_mode: RerankingModeEnum.RerankingModel, }), initialModalOpen: true, }) + const user = userEvent.setup() // Act const dialog = await screen.findByRole('dialog', {}, { timeout: 3000 }) const dialogScope = within(dialog) - fireEvent.click(dialogScope.getByRole('button', { name: 'common.operation.save' })) + await user.click(dialogScope.getByRole('button', { name: 'common.operation.save' })) // Assert expect(toastNotifySpy).toHaveBeenCalledWith({ type: 'error', message: 'appDebug.datasetConfig.rerankModelRequired', }) - expect(setDatasetConfigsSpy).not.toHaveBeenCalled() expect(screen.getByRole('dialog')).toBeInTheDocument() }) }) diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx index 3050249bb6..ef1fb183c8 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx @@ -41,7 +41,7 @@ const AnnotationCtrlButton: FC = ({ setShowAnnotationFullModal() return } - const res: any = await addAnnotation(appId, { + const res = await addAnnotation(appId, { message_id: messageId, question: query, answer, @@ -50,7 +50,7 @@ const AnnotationCtrlButton: FC = ({ message: t('common.api.actionSuccess') as string, type: 'success', }) - onAdded(res.id, res.account?.name) + onAdded(res.id, res.account?.name ?? '') } return ( diff --git a/web/i18n/ar-TN/common.ts b/web/i18n/ar-TN/common.ts index 8437c0643f..b1f4f46f22 100644 --- a/web/i18n/ar-TN/common.ts +++ b/web/i18n/ar-TN/common.ts @@ -11,6 +11,7 @@ const translation = { saved: 'تم الحفظ', create: 'تم الإنشاء', remove: 'تمت الإزالة', + actionFailed: 'فشل الإجراء', }, operation: { create: 'إنشاء', diff --git a/web/i18n/de-DE/common.ts b/web/i18n/de-DE/common.ts index 479348ef43..d9ebfd60e0 100644 --- a/web/i18n/de-DE/common.ts +++ b/web/i18n/de-DE/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Gespeichert', create: 'Erstellt', remove: 'Entfernt', + actionFailed: 'Aktion fehlgeschlagen', }, operation: { create: 'Erstellen', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index d78520cf1f..92d24b1351 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -8,6 +8,7 @@ const translation = { api: { success: 'Success', actionSuccess: 'Action succeeded', + actionFailed: 'Action failed', saved: 'Saved', create: 'Created', remove: 'Removed', diff --git a/web/i18n/es-ES/common.ts b/web/i18n/es-ES/common.ts index 38d4402fd2..8f56c7e668 100644 --- a/web/i18n/es-ES/common.ts +++ b/web/i18n/es-ES/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Guardado', create: 'Creado', remove: 'Eliminado', + actionFailed: 'Acción fallida', }, operation: { create: 'Crear', diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 62a2e2cec8..afd6f760aa 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'ذخیره شد', create: 'ایجاد شد', remove: 'حذف شد', + actionFailed: 'عمل شکست خورد', }, operation: { create: 'ایجاد', diff --git a/web/i18n/fr-FR/common.ts b/web/i18n/fr-FR/common.ts index da72b0497c..4b0deb4a8c 100644 --- a/web/i18n/fr-FR/common.ts +++ b/web/i18n/fr-FR/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Sauvegardé', create: 'Créé', remove: 'Supprimé', + actionFailed: 'Action échouée', }, operation: { create: 'Créer', diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index fa25074b9c..88f8f814e6 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'सहेजा गया', create: 'बनाया गया', remove: 'हटाया गया', + actionFailed: 'क्रिया विफल', }, operation: { create: 'बनाएं', diff --git a/web/i18n/id-ID/common.ts b/web/i18n/id-ID/common.ts index 0c70b0341e..4cce24e76a 100644 --- a/web/i18n/id-ID/common.ts +++ b/web/i18n/id-ID/common.ts @@ -11,6 +11,7 @@ const translation = { remove: 'Dihapus', actionSuccess: 'Aksi berhasil', create: 'Dibuat', + actionFailed: 'Tindakan gagal', }, operation: { setup: 'Setup', diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts index d5793bb902..b52b93b1a5 100644 --- a/web/i18n/it-IT/common.ts +++ b/web/i18n/it-IT/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Salvato', create: 'Creato', remove: 'Rimosso', + actionFailed: 'Azione non riuscita', }, operation: { create: 'Crea', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index d4647fbc12..bde00cb66b 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -11,6 +11,7 @@ const translation = { saved: '保存済み', create: '作成済み', remove: '削除済み', + actionFailed: 'アクションに失敗しました', }, operation: { create: '作成', diff --git a/web/i18n/ko-KR/common.ts b/web/i18n/ko-KR/common.ts index 805b9f9840..531aa29054 100644 --- a/web/i18n/ko-KR/common.ts +++ b/web/i18n/ko-KR/common.ts @@ -5,6 +5,7 @@ const translation = { saved: '저장됨', create: '생성됨', remove: '삭제됨', + actionFailed: '작업 실패', }, operation: { create: '생성', diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts index 2ecf18c7e6..10f566258a 100644 --- a/web/i18n/pl-PL/common.ts +++ b/web/i18n/pl-PL/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Zapisane', create: 'Utworzono', remove: 'Usunięto', + actionFailed: 'Akcja nie powiodła się', }, operation: { create: 'Utwórz', diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts index d0838b4f09..b739561ca4 100644 --- a/web/i18n/pt-BR/common.ts +++ b/web/i18n/pt-BR/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Salvo', create: 'Criado', remove: 'Removido', + actionFailed: 'Ação falhou', }, operation: { create: 'Criar', diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index 8b8ab9ac26..df3cf01b6c 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Salvat', create: 'Creat', remove: 'Eliminat', + actionFailed: 'Acțiunea a eșuat', }, operation: { create: 'Creează', diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts index afc9368e9e..ae8b2e558f 100644 --- a/web/i18n/ru-RU/common.ts +++ b/web/i18n/ru-RU/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Сохранено', create: 'Создано', remove: 'Удалено', + actionFailed: 'Действие не удалось', }, operation: { create: 'Создать', diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts index b024ace3be..697d06eb8b 100644 --- a/web/i18n/sl-SI/common.ts +++ b/web/i18n/sl-SI/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Shranjeno', create: 'Ustvarjeno', remove: 'Odstranjeno', + actionFailed: 'Dejanje ni uspelo', }, operation: { create: 'Ustvari', diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index 9c325e3781..dc82c71c78 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'บันทึก', create: 'สร้าง', remove: 'ถูก เอา ออก', + actionFailed: 'การดำเนินการล้มเหลว', }, operation: { create: 'สร้าง', diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts index 8b0a7cba69..5e7f2182c7 100644 --- a/web/i18n/tr-TR/common.ts +++ b/web/i18n/tr-TR/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Kaydedildi', create: 'Oluşturuldu', remove: 'Kaldırıldı', + actionFailed: 'İşlem başarısız', }, operation: { create: 'Oluştur', diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts index bd0f55c2f5..70b8aaa862 100644 --- a/web/i18n/uk-UA/common.ts +++ b/web/i18n/uk-UA/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Збережено', create: 'Створено', remove: 'Видалено', + actionFailed: 'Не вдалося виконати дію', }, operation: { create: 'Створити', diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts index 8b1b69163e..666dc7a133 100644 --- a/web/i18n/vi-VN/common.ts +++ b/web/i18n/vi-VN/common.ts @@ -5,6 +5,7 @@ const translation = { saved: 'Đã lưu', create: 'Tạo', remove: 'Xóa', + actionFailed: 'Thao tác thất bại', }, operation: { create: 'Tạo mới', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 8e7103564f..bd0e0e3ba4 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -11,6 +11,7 @@ const translation = { saved: '已保存', create: '已创建', remove: '已移除', + actionFailed: '操作失败', }, operation: { create: '创建', diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 1a72a083d8..8ed1e336ef 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -5,6 +5,7 @@ const translation = { saved: '已儲存', create: '已建立', remove: '已移除', + actionFailed: '操作失敗', }, operation: { create: '建立', diff --git a/web/jest.config.ts b/web/jest.config.ts index e86ec5af74..434b19270f 100644 --- a/web/jest.config.ts +++ b/web/jest.config.ts @@ -20,7 +20,7 @@ const config: Config = { // bail: 0, // The directory where Jest should store its cached dependency information - // cacheDirectory: "/private/var/folders/9c/7gly5yl90qxdjljqsvkk758h0000gn/T/jest_dx", + cacheDirectory: '/.cache/jest', // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, diff --git a/web/service/annotation.ts b/web/service/annotation.ts index 58efb7b976..af708fe174 100644 --- a/web/service/annotation.ts +++ b/web/service/annotation.ts @@ -1,6 +1,6 @@ import type { Fetcher } from 'swr' import { del, get, post } from './base' -import type { AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type' +import type { AnnotationCreateResponse, AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type' import { ANNOTATION_DEFAULT } from '@/config' export const fetchAnnotationConfig = (appId: string) => { @@ -41,7 +41,7 @@ export const fetchExportAnnotationList = (appId: string) => { } export const addAnnotation = (appId: string, body: AnnotationItemBasic) => { - return post(`apps/${appId}/annotations`, { body }) + return post(`apps/${appId}/annotations`, { body }) } export const annotationBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => { From a26881cb246ca503e8a58654b0cb9d27738351e0 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:08:34 +0800 Subject: [PATCH 03/71] refactor: unified cn utils (#29916) Co-authored-by: yyh Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com> --- .../(appDetailLayout)/[appId]/layout-main.tsx | 2 +- .../time-range-picker/date-picker.tsx | 2 +- .../time-range-picker/range-selector.tsx | 2 +- .../overview/tracing/config-button.tsx | 2 +- .../[appId]/overview/tracing/config-popup.tsx | 2 +- .../[appId]/overview/tracing/field.tsx | 2 +- .../[appId]/overview/tracing/panel.tsx | 2 +- .../overview/tracing/provider-panel.tsx | 2 +- .../[appId]/overview/tracing/tracing-icon.tsx | 2 +- .../[datasetId]/layout-main.tsx | 2 +- .../webapp-reset-password/layout.tsx | 2 +- .../set-password/page.tsx | 2 +- .../(shareLayout)/webapp-signin/layout.tsx | 2 +- .../webapp-signin/normalForm.tsx | 2 +- web/app/account/oauth/authorize/layout.tsx | 2 +- web/app/activate/activateForm.tsx | 2 +- web/app/activate/page.tsx | 2 +- web/app/components/app-sidebar/app-info.tsx | 2 +- .../app-sidebar/app-sidebar-dropdown.tsx | 2 +- .../app-sidebar/dataset-info/dropdown.tsx | 2 +- .../app-sidebar/dataset-info/index.tsx | 2 +- .../app-sidebar/dataset-sidebar-dropdown.tsx | 2 +- web/app/components/app-sidebar/index.tsx | 2 +- web/app/components/app-sidebar/navLink.tsx | 32 +++++-------- .../components/app-sidebar/toggle-button.tsx | 2 +- .../app/annotation/batch-action.tsx | 4 +- .../csv-uploader.tsx | 2 +- .../edit-annotation-modal/edit-item/index.tsx | 2 +- .../app/annotation/header-opts/index.tsx | 2 +- web/app/components/app/annotation/index.tsx | 2 +- web/app/components/app/annotation/list.tsx | 2 +- .../view-annotation-modal/index.tsx | 2 +- .../access-control-dialog.tsx | 2 +- .../add-member-or-group-pop.tsx | 6 +-- .../app/app-publisher/suggested-action.tsx | 8 ++-- .../base/feature-panel/index.tsx | 2 +- .../base/operation-btn/index.tsx | 2 +- .../config-prompt/advanced-prompt-input.tsx | 2 +- .../config-prompt/message-type-selector.tsx | 2 +- .../prompt-editor-height-resize-wrap.tsx | 2 +- .../config-prompt/simple-prompt-input.tsx | 2 +- .../config-var/config-modal/field.tsx | 2 +- .../config-var/config-modal/type-select.tsx | 7 ++- .../config-var/config-select/index.tsx | 2 +- .../app/configuration/config-var/index.tsx | 2 +- .../config-var/select-type-item/index.tsx | 2 +- .../app/configuration/config-var/var-item.tsx | 2 +- .../config-vision/param-config.tsx | 2 +- .../config/agent/agent-setting/item-panel.tsx | 2 +- .../config/agent/agent-tools/index.tsx | 2 +- .../agent-tools/setting-built-in-tool.tsx | 2 +- .../config/agent/prompt-editor.tsx | 2 +- .../config/assistant-type-picker/index.tsx | 2 +- .../config/automatic/idea-output.tsx | 2 +- .../config/automatic/instruction-editor.tsx | 2 +- .../config/automatic/prompt-toast.tsx | 2 +- .../config/automatic/version-selector.tsx | 2 +- .../dataset-config/card-item/index.tsx | 2 +- .../dataset-config/context-var/index.tsx | 2 +- .../dataset-config/context-var/var-picker.tsx | 2 +- .../params-config/config-content.tsx | 2 +- .../dataset-config/params-config/index.tsx | 2 +- .../params-config/weighted-score.tsx | 2 +- .../dataset-config/select-dataset/index.tsx | 2 +- .../dataset-config/settings-modal/index.tsx | 2 +- .../settings-modal/retrieval-section.tsx | 2 +- .../configuration/debug/chat-user-input.tsx | 2 +- .../prompt-value-panel/index.tsx | 2 +- .../app/create-app-dialog/app-card/index.tsx | 2 +- .../app/create-app-dialog/app-list/index.tsx | 2 +- .../create-app-dialog/app-list/sidebar.tsx | 6 +-- .../components/app/create-app-modal/index.tsx | 2 +- .../app/create-from-dsl-modal/index.tsx | 2 +- .../app/create-from-dsl-modal/uploader.tsx | 2 +- .../components/app/duplicate-modal/index.tsx | 2 +- .../components/app/log-annotation/index.tsx | 2 +- web/app/components/app/log/list.tsx | 2 +- web/app/components/app/log/model-info.tsx | 2 +- web/app/components/app/log/var-panel.tsx | 2 +- .../app/overview/apikey-info-panel/index.tsx | 2 +- .../app/overview/embedded/index.tsx | 2 +- .../app/overview/settings/index.tsx | 2 +- .../components/app/switch-app-modal/index.tsx | 2 +- .../app/text-generate/item/index.tsx | 2 +- .../app/text-generate/saved-items/index.tsx | 2 +- .../components/app/type-selector/index.tsx | 2 +- web/app/components/app/workflow-log/list.tsx | 2 +- web/app/components/apps/app-card.tsx | 2 +- web/app/components/apps/new-app-card.tsx | 2 +- .../components/base/action-button/index.tsx | 8 ++-- .../base/agent-log-modal/detail.tsx | 2 +- .../components/base/agent-log-modal/index.tsx | 2 +- .../base/agent-log-modal/iteration.tsx | 2 +- .../base/agent-log-modal/tool-call.tsx | 2 +- web/app/components/base/answer-icon/index.tsx | 8 ++-- .../base/app-icon-picker/ImageInput.tsx | 7 ++- .../components/base/app-icon-picker/index.tsx | 2 +- web/app/components/base/app-icon/index.tsx | 4 +- web/app/components/base/app-unavailable.tsx | 4 +- .../base/audio-gallery/AudioPlayer.tsx | 2 +- .../base/auto-height-textarea/index.tsx | 2 +- web/app/components/base/avatar/index.tsx | 2 +- web/app/components/base/badge.tsx | 2 +- web/app/components/base/badge/index.tsx | 8 ++-- web/app/components/base/block-input/index.tsx | 12 ++--- web/app/components/base/button/add-button.tsx | 2 +- web/app/components/base/button/index.tsx | 10 ++-- .../components/base/button/sync-button.tsx | 2 +- .../chat/chat-with-history/chat-wrapper.tsx | 2 +- .../chat/chat-with-history/header/index.tsx | 2 +- .../chat-with-history/header/operation.tsx | 2 +- .../base/chat/chat-with-history/index.tsx | 2 +- .../chat-with-history/inputs-form/index.tsx | 2 +- .../chat/chat-with-history/sidebar/index.tsx | 2 +- .../chat/chat-with-history/sidebar/item.tsx | 2 +- .../chat-with-history/sidebar/operation.tsx | 2 +- .../base/chat/chat/answer/basic-content.tsx | 2 +- .../base/chat/chat/answer/index.tsx | 2 +- .../base/chat/chat/answer/operation.tsx | 2 +- .../base/chat/chat/answer/tool-detail.tsx | 2 +- .../chat/chat/answer/workflow-process.tsx | 2 +- .../base/chat/chat/chat-input-area/index.tsx | 2 +- .../chat/chat/chat-input-area/operation.tsx | 2 +- web/app/components/base/chat/chat/index.tsx | 2 +- .../base/chat/chat/loading-anim/index.tsx | 2 +- .../components/base/chat/chat/question.tsx | 2 +- .../chat/embedded-chatbot/chat-wrapper.tsx | 2 +- .../chat/embedded-chatbot/header/index.tsx | 2 +- .../base/chat/embedded-chatbot/index.tsx | 2 +- .../embedded-chatbot/inputs-form/index.tsx | 2 +- .../inputs-form/view-form-dropdown.tsx | 2 +- .../components/base/checkbox-list/index.tsx | 2 +- web/app/components/base/checkbox/index.tsx | 2 +- web/app/components/base/chip/index.tsx | 2 +- .../components/base/content-dialog/index.tsx | 14 ++---- .../components/base/corner-label/index.tsx | 2 +- .../date-and-time-picker/calendar/item.tsx | 2 +- .../common/option-list-item.tsx | 2 +- .../date-picker/footer.tsx | 2 +- .../date-picker/index.tsx | 2 +- .../time-picker/index.tsx | 2 +- web/app/components/base/dialog/index.tsx | 20 ++++---- web/app/components/base/divider/index.tsx | 4 +- web/app/components/base/drawer-plus/index.tsx | 2 +- web/app/components/base/drawer/index.tsx | 2 +- web/app/components/base/dropdown/index.tsx | 2 +- web/app/components/base/effect/index.tsx | 2 +- .../components/base/emoji-picker/Inner.tsx | 2 +- .../components/base/emoji-picker/index.tsx | 2 +- .../base/encrypted-bottom/index.tsx | 2 +- .../components/base/error-boundary/index.tsx | 2 +- .../score-slider/base-slider/index.tsx | 2 +- .../conversation-opener/modal.tsx | 2 +- .../new-feature-panel/dialog-wrapper.tsx | 2 +- .../new-feature-panel/feature-bar.tsx | 2 +- .../moderation/moderation-setting-modal.tsx | 2 +- .../text-to-speech/param-config-content.tsx | 18 +++----- web/app/components/base/file-thumb/index.tsx | 2 +- .../file-from-link-or-local/index.tsx | 2 +- .../base/file-uploader/file-image-render.tsx | 2 +- .../base/file-uploader/file-list-in-log.tsx | 2 +- .../base/file-uploader/file-type-icon.tsx | 2 +- .../file-uploader-in-attachment/file-item.tsx | 2 +- .../file-uploader-in-attachment/index.tsx | 2 +- .../file-uploader-in-chat-input/file-item.tsx | 2 +- .../file-uploader-in-chat-input/file-list.tsx | 2 +- .../file-uploader-in-chat-input/index.tsx | 2 +- .../base/form/components/base/base-field.tsx | 2 +- .../base/form/components/base/base-form.tsx | 2 +- .../base/form/components/field/checkbox.tsx | 2 +- .../form/components/field/custom-select.tsx | 2 +- .../base/form/components/field/file-types.tsx | 2 +- .../form/components/field/file-uploader.tsx | 2 +- .../field/input-type-select/index.tsx | 2 +- .../field/input-type-select/trigger.tsx | 2 +- .../field/mixed-variable-text-input/index.tsx | 2 +- .../form/components/field/number-input.tsx | 2 +- .../form/components/field/number-slider.tsx | 2 +- .../base/form/components/field/options.tsx | 2 +- .../base/form/components/field/select.tsx | 2 +- .../base/form/components/field/text-area.tsx | 2 +- .../base/form/components/field/text.tsx | 2 +- .../form/components/field/upload-method.tsx | 2 +- .../field/variable-or-constant-input.tsx | 2 +- .../components/field/variable-selector.tsx | 2 +- .../components/base/form/components/label.tsx | 2 +- .../base/fullscreen-modal/index.tsx | 16 +++---- web/app/components/base/grid-mask/index.tsx | 8 ++-- web/app/components/base/icons/script.mjs | 2 +- .../icons/src/image/llm/BaichuanTextCn.tsx | 2 +- .../base/icons/src/image/llm/Minimax.tsx | 2 +- .../base/icons/src/image/llm/MinimaxText.tsx | 2 +- .../base/icons/src/image/llm/Tongyi.tsx | 2 +- .../base/icons/src/image/llm/TongyiText.tsx | 2 +- .../base/icons/src/image/llm/TongyiTextCn.tsx | 2 +- .../base/icons/src/image/llm/Wxyy.tsx | 2 +- .../base/icons/src/image/llm/WxyyText.tsx | 2 +- .../base/icons/src/image/llm/WxyyTextCn.tsx | 2 +- .../components/base/image-gallery/index.tsx | 2 +- .../image-uploader/chat-image-uploader.tsx | 2 +- .../base/image-uploader/image-list.tsx | 2 +- .../base/inline-delete-confirm/index.tsx | 2 +- .../components/base/input-number/index.tsx | 21 ++++----- .../components/base/input-with-copy/index.tsx | 2 +- web/app/components/base/input/index.tsx | 2 +- .../base/linked-apps-panel/index.tsx | 2 +- web/app/components/base/logo/dify-logo.tsx | 4 +- .../base/logo/logo-embedded-chat-header.tsx | 4 +- web/app/components/base/logo/logo-site.tsx | 4 +- .../base/markdown-blocks/button.tsx | 2 +- .../base/markdown-blocks/think-block.tsx | 2 +- web/app/components/base/markdown/index.tsx | 2 +- web/app/components/base/mermaid/index.tsx | 2 +- .../base/message-log-modal/index.tsx | 2 +- .../components/base/modal-like-wrap/index.tsx | 2 +- web/app/components/base/modal/index.tsx | 18 +++----- web/app/components/base/modal/modal.tsx | 2 +- web/app/components/base/node-status/index.tsx | 12 ++--- web/app/components/base/notion-icon/index.tsx | 2 +- .../page-selector/index.tsx | 2 +- .../search-input/index.tsx | 2 +- web/app/components/base/pagination/index.tsx | 2 +- .../components/base/pagination/pagination.tsx | 2 +- web/app/components/base/popover/index.tsx | 2 +- .../base/portal-to-follow-elem/index.tsx | 2 +- .../components/base/premium-badge/index.tsx | 14 ++---- .../base/progress-bar/progress-circle.tsx | 2 +- .../components/base/prompt-editor/index.tsx | 2 +- .../plugins/current-block/component.tsx | 2 +- .../plugins/error-message-block/component.tsx | 2 +- .../plugins/last-run-block/component.tsx | 2 +- .../prompt-editor/plugins/placeholder.tsx | 2 +- web/app/components/base/radio-card/index.tsx | 2 +- .../base/radio-card/simple/index.tsx | 2 +- .../base/radio/component/group/index.tsx | 2 +- .../base/radio/component/radio/index.tsx | 2 +- web/app/components/base/radio/ui.tsx | 2 +- .../components/base/search-input/index.tsx | 2 +- .../base/segmented-control/index.tsx | 2 +- web/app/components/base/select/custom.tsx | 2 +- web/app/components/base/select/index.tsx | 46 ++++++++----------- web/app/components/base/select/pure.tsx | 2 +- .../base/simple-pie-chart/index.tsx | 4 +- web/app/components/base/skeleton/index.tsx | 10 ++-- web/app/components/base/slider/index.tsx | 2 +- web/app/components/base/sort/index.tsx | 2 +- web/app/components/base/svg/index.tsx | 2 +- web/app/components/base/switch/index.tsx | 14 ++---- web/app/components/base/tab-header/index.tsx | 2 +- .../components/base/tab-slider-new/index.tsx | 2 +- .../base/tab-slider-plain/index.tsx | 2 +- web/app/components/base/tab-slider/index.tsx | 2 +- web/app/components/base/tag-input/index.tsx | 2 +- .../components/base/tag-management/filter.tsx | 2 +- .../base/tag-management/selector.tsx | 2 +- .../base/tag-management/tag-item-editor.tsx | 2 +- .../base/tag-management/tag-remove-modal.tsx | 2 +- web/app/components/base/tag/index.tsx | 4 +- web/app/components/base/textarea/index.tsx | 2 +- web/app/components/base/theme-switcher.tsx | 2 +- .../components/base/timezone-label/index.tsx | 2 +- web/app/components/base/toast/index.tsx | 2 +- web/app/components/base/tooltip/index.tsx | 2 +- web/app/components/base/voice-input/index.tsx | 2 +- .../billing/annotation-full/index.tsx | 2 +- .../billing/annotation-full/modal.tsx | 2 +- .../billing/apps-full-in-dialog/index.tsx | 2 +- .../billing/header-billing-btn/index.tsx | 2 +- web/app/components/billing/pricing/footer.tsx | 2 +- .../billing/pricing/plan-switcher/tab.tsx | 2 +- .../pricing/plans/cloud-plan-item/button.tsx | 2 +- .../plans/self-hosted-plan-item/button.tsx | 2 +- .../plans/self-hosted-plan-item/index.tsx | 2 +- .../billing/priority-label/index.tsx | 2 +- .../components/billing/progress-bar/index.tsx | 2 +- .../components/billing/usage-info/index.tsx | 2 +- .../billing/vector-space-full/index.tsx | 2 +- .../custom/custom-web-app-brand/index.tsx | 2 +- .../datasets/common/credential-icon.tsx | 2 +- .../common/document-picker/document-list.tsx | 2 +- .../datasets/common/document-picker/index.tsx | 2 +- .../preview-document-picker.tsx | 2 +- .../status-with-action.tsx | 2 +- .../datasets/common/image-list/index.tsx | 2 +- .../image-uploader-in-chunk/image-input.tsx | 2 +- .../image-uploader-in-chunk/index.tsx | 2 +- .../index.tsx | 2 +- .../common/retrieval-param-config/index.tsx | 2 +- .../create-from-dsl-modal/tab/item.tsx | 2 +- .../create-from-dsl-modal/uploader.tsx | 2 +- .../details/chunk-structure-card.tsx | 2 +- .../create/embedding-process/index.tsx | 2 +- .../empty-dataset-creation-modal/index.tsx | 2 +- .../datasets/create/file-preview/index.tsx | 2 +- .../datasets/create/file-uploader/index.tsx | 2 +- .../create/notion-page-preview/index.tsx | 2 +- .../datasets/create/step-one/index.tsx | 7 ++- .../datasets/create/step-two/index.tsx | 2 +- .../create/step-two/language-select/index.tsx | 2 +- .../datasets/create/step-two/option-card.tsx | 16 +++---- .../datasets/create/stepper/step.tsx | 19 +++----- .../create/stop-embedding-modal/index.tsx | 2 +- .../datasets/create/top-bar/index.tsx | 4 +- .../website/base/checkbox-with-label.tsx | 2 +- .../website/base/crawled-result-item.tsx | 2 +- .../create/website/base/crawled-result.tsx | 2 +- .../create/website/base/error-message.tsx | 2 +- .../datasets/create/website/base/field.tsx | 2 +- .../datasets/create/website/base/header.tsx | 2 +- .../create/website/base/options-wrap.tsx | 2 +- .../create/website/firecrawl/options.tsx | 2 +- .../datasets/create/website/index.tsx | 2 +- .../create/website/jina-reader/options.tsx | 2 +- .../datasets/create/website/preview.tsx | 2 +- .../create/website/watercrawl/options.tsx | 2 +- .../data-source-options/datasource-icon.tsx | 2 +- .../data-source-options/option-card.tsx | 2 +- .../base/credential-selector/trigger.tsx | 2 +- .../data-source/local-file/index.tsx | 2 +- .../online-documents/page-selector/item.tsx | 2 +- .../file-list/header/breadcrumbs/bucket.tsx | 2 +- .../file-list/header/breadcrumbs/drive.tsx | 2 +- .../header/breadcrumbs/dropdown/index.tsx | 2 +- .../file-list/header/breadcrumbs/item.tsx | 2 +- .../online-drive/file-list/list/file-icon.tsx | 2 +- .../online-drive/file-list/list/item.tsx | 2 +- .../base/checkbox-with-label.tsx | 2 +- .../base/crawled-result-item.tsx | 2 +- .../website-crawl/base/crawled-result.tsx | 2 +- .../website-crawl/base/crawling.tsx | 2 +- .../website-crawl/base/error-message.tsx | 2 +- .../website-crawl/base/options/index.tsx | 2 +- .../processing/embedding-process/index.tsx | 2 +- .../create-from-pipeline/step-indicator.tsx | 2 +- .../detail/batch-modal/csv-uploader.tsx | 2 +- .../detail/completed/child-segment-detail.tsx | 8 ++-- .../detail/completed/child-segment-list.tsx | 2 +- .../detail/completed/common/add-another.tsx | 4 +- .../detail/completed/common/batch-action.tsx | 2 +- .../detail/completed/common/chunk-content.tsx | 14 ++---- .../detail/completed/common/drawer.tsx | 2 +- .../completed/common/full-screen-drawer.tsx | 2 +- .../detail/completed/common/keywords.tsx | 4 +- .../completed/common/segment-index-tag.tsx | 2 +- .../documents/detail/completed/common/tag.tsx | 2 +- .../documents/detail/completed/index.tsx | 2 +- .../detail/completed/new-child-segment.tsx | 8 ++-- .../completed/segment-card/chunk-content.tsx | 2 +- .../detail/completed/segment-card/index.tsx | 2 +- .../detail/completed/segment-detail.tsx | 2 +- .../documents/detail/document-title.tsx | 2 +- .../documents/detail/embedding/index.tsx | 2 +- .../datasets/documents/detail/index.tsx | 2 +- .../documents/detail/metadata/index.tsx | 2 +- .../datasets/documents/detail/new-segment.tsx | 14 +++--- .../documents/detail/segment-add/index.tsx | 2 +- .../components/datasets/documents/index.tsx | 2 +- .../components/datasets/documents/list.tsx | 2 +- .../datasets/documents/operations.tsx | 2 +- .../datasets/documents/status-item/index.tsx | 2 +- .../external-api/external-api-modal/Form.tsx | 2 +- .../external-api/external-api-panel/index.tsx | 2 +- .../create/RetrievalSettings.tsx | 2 +- .../datasets/extra-info/service-api/card.tsx | 2 +- .../datasets/extra-info/service-api/index.tsx | 2 +- .../formatted-text/flavours/edit-slice.tsx | 20 +++----- .../formatted-text/flavours/shared.tsx | 28 ++++------- .../datasets/formatted-text/formatted.tsx | 4 +- .../components/chunk-detail-modal.tsx | 2 +- .../datasets/hit-testing/components/mask.tsx | 2 +- .../components/query-input/index.tsx | 2 +- .../components/query-input/textarea.tsx | 2 +- .../hit-testing/components/records.tsx | 2 +- .../components/result-item-external.tsx | 2 +- .../components/result-item-meta.tsx | 2 +- .../hit-testing/components/result-item.tsx | 2 +- .../datasets/hit-testing/components/score.tsx | 2 +- .../components/datasets/hit-testing/index.tsx | 2 +- .../datasets/list/dataset-card/index.tsx | 2 +- .../datasets/metadata/add-metadata-button.tsx | 2 +- .../datasets/metadata/base/date-picker.tsx | 2 +- .../metadata/edit-metadata-batch/add-row.tsx | 2 +- .../metadata/edit-metadata-batch/edit-row.tsx | 2 +- .../edit-metadata-batch/input-combined.tsx | 2 +- .../input-has-set-multiple-value.tsx | 2 +- .../metadata/edit-metadata-batch/label.tsx | 2 +- .../dataset-metadata-drawer.tsx | 2 +- .../metadata/metadata-document/index.tsx | 2 +- .../metadata/metadata-document/info-group.tsx | 2 +- .../components/datasets/preview/container.tsx | 8 ++-- .../components/datasets/preview/header.tsx | 6 +-- .../datasets/rename-modal/index.tsx | 2 +- .../datasets/settings/index-method/index.tsx | 4 +- .../datasets/settings/option-card.tsx | 2 +- .../settings/permission-selector/index.tsx | 2 +- .../permission-selector/member-item.tsx | 2 +- web/app/components/develop/code.tsx | 26 ++++------- web/app/components/develop/doc.tsx | 2 +- web/app/components/develop/md.tsx | 8 ++-- web/app/components/develop/tag.tsx | 8 ++-- web/app/components/explore/app-card/index.tsx | 2 +- web/app/components/explore/app-list/index.tsx | 2 +- web/app/components/explore/category.tsx | 2 +- .../explore/item-operation/index.tsx | 2 +- .../explore/sidebar/app-nav-item/index.tsx | 2 +- web/app/components/explore/sidebar/index.tsx | 2 +- .../goto-anything/actions/knowledge.tsx | 2 +- .../header/account-dropdown/compliance.tsx | 2 +- .../header/account-dropdown/index.tsx | 2 +- .../header/account-dropdown/support.tsx | 2 +- .../workplace-selector/index.tsx | 2 +- .../Integrations-page/index.tsx | 4 +- .../header/account-setting/collapse/index.tsx | 4 +- .../install-from-marketplace.tsx | 2 +- .../data-source-notion/operate/index.tsx | 2 +- .../data-source-website/index.tsx | 2 +- .../data-source-page/panel/config-item.tsx | 2 +- .../data-source-page/panel/index.tsx | 2 +- .../header/account-setting/index.tsx | 2 +- .../edit-workspace-modal/index.tsx | 2 +- .../account-setting/members-page/index.tsx | 2 +- .../members-page/invite-modal/index.tsx | 2 +- .../invite-modal/role-selector.tsx | 2 +- .../members-page/operation/index.tsx | 2 +- .../operation/transfer-ownership.tsx | 2 +- .../member-selector.tsx | 2 +- .../header/account-setting/menu-dialog.tsx | 2 +- .../model-provider-page/index.tsx | 2 +- .../install-from-marketplace.tsx | 2 +- .../add-credential-in-load-balancing.tsx | 2 +- .../model-auth/add-custom-model.tsx | 2 +- .../model-auth/authorized/credential-item.tsx | 2 +- .../model-auth/authorized/index.tsx | 2 +- .../model-auth/config-model.tsx | 2 +- .../manage-custom-model-credentials.tsx | 2 +- .../switch-credential-in-load-balancing.tsx | 2 +- .../model-provider-page/model-badge/index.tsx | 8 ++-- .../model-provider-page/model-icon/index.tsx | 2 +- .../model-provider-page/model-modal/Form.tsx | 2 +- .../model-provider-page/model-name/index.tsx | 2 +- .../agent-model-trigger.tsx | 2 +- .../model-parameter-modal/index.tsx | 2 +- .../model-parameter-modal/parameter-item.tsx | 2 +- .../presets-parameter.tsx | 2 +- .../model-parameter-modal/trigger.tsx | 2 +- .../deprecated-model-trigger.tsx | 2 +- .../model-selector/empty-trigger.tsx | 2 +- .../model-selector/feature-icon.tsx | 2 +- .../model-selector/index.tsx | 4 +- .../model-selector/model-trigger.tsx | 2 +- .../model-selector/popup-item.tsx | 2 +- .../provider-added-card/add-model-button.tsx | 2 +- .../provider-added-card/credential-panel.tsx | 2 +- .../provider-added-card/index.tsx | 2 +- .../provider-added-card/model-list-item.tsx | 8 ++-- .../model-load-balancing-configs.tsx | 10 ++-- .../model-load-balancing-modal.tsx | 8 ++-- .../provider-added-card/priority-selector.tsx | 2 +- .../provider-icon/index.tsx | 2 +- web/app/components/header/app-back/index.tsx | 4 +- .../components/header/explore-nav/index.tsx | 8 ++-- web/app/components/header/header-wrapper.tsx | 8 ++-- web/app/components/header/indicator/index.tsx | 8 ++-- web/app/components/header/nav/index.tsx | 8 ++-- .../header/nav/nav-selector/index.tsx | 2 +- .../components/header/plugins-nav/index.tsx | 12 ++--- web/app/components/header/tools-nav/index.tsx | 8 ++-- .../plugins/base/badges/icon-with-tooltip.tsx | 2 +- .../plugins/base/deprecation-notice.tsx | 2 +- .../plugins/base/key-value-item.tsx | 2 +- .../plugins/card/base/card-icon.tsx | 2 +- .../plugins/card/base/description.tsx | 2 +- .../components/plugins/card/base/org-info.tsx | 2 +- .../plugins/card/base/placeholder.tsx | 2 +- web/app/components/plugins/card/index.tsx | 2 +- .../install-plugin/install-bundle/index.tsx | 2 +- .../install-from-github/index.tsx | 2 +- .../install-from-local-package/index.tsx | 2 +- .../install-from-marketplace/index.tsx | 2 +- .../plugins/marketplace/empty/index.tsx | 2 +- .../plugins/marketplace/list/index.tsx | 2 +- .../marketplace/list/list-with-collection.tsx | 2 +- .../marketplace/plugin-type-switch.tsx | 2 +- .../plugins/marketplace/search-box/index.tsx | 2 +- .../search-box/trigger/marketplace.tsx | 2 +- .../search-box/trigger/tool-selector.tsx | 2 +- .../sticky-search-and-switch-wrapper.tsx | 2 +- .../authorize/add-oauth-button.tsx | 2 +- .../plugins/plugin-auth/authorize/index.tsx | 2 +- .../authorized-in-data-source-node.tsx | 2 +- .../plugin-auth/authorized-in-node.tsx | 2 +- .../plugins/plugin-auth/authorized/index.tsx | 2 +- .../plugins/plugin-auth/authorized/item.tsx | 2 +- .../plugin-auth/plugin-auth-in-agent.tsx | 2 +- .../plugins/plugin-auth/plugin-auth.tsx | 2 +- .../app-selector/app-inputs-panel.tsx | 2 +- .../app-selector/app-trigger.tsx | 2 +- .../plugin-detail-panel/detail-header.tsx | 2 +- .../plugin-detail-panel/endpoint-list.tsx | 2 +- .../plugin-detail-panel/endpoint-modal.tsx | 2 +- .../plugins/plugin-detail-panel/index.tsx | 2 +- .../model-selector/index.tsx | 2 +- .../model-selector/llm-params-panel.tsx | 2 +- .../model-selector/tts-params-panel.tsx | 2 +- .../multiple-tool-selector/index.tsx | 2 +- .../operation-dropdown.tsx | 2 +- .../plugin-detail-panel/strategy-detail.tsx | 2 +- .../plugin-detail-panel/strategy-item.tsx | 2 +- .../subscription-list/create/index.tsx | 2 +- .../subscription-list/list-view.tsx | 2 +- .../subscription-list/log-viewer.tsx | 2 +- .../subscription-list/selector-entry.tsx | 2 +- .../subscription-list/selector-view.tsx | 2 +- .../subscription-list/subscription-card.tsx | 2 +- .../tool-selector/index.tsx | 2 +- .../tool-selector/reasoning-config-form.tsx | 2 +- .../tool-selector/tool-credentials-form.tsx | 2 +- .../tool-selector/tool-item.tsx | 2 +- .../tool-selector/tool-trigger.tsx | 2 +- .../trigger/event-detail-drawer.tsx | 2 +- .../trigger/event-list.tsx | 2 +- .../components/plugins/plugin-item/index.tsx | 2 +- .../filter-management/category-filter.tsx | 2 +- .../filter-management/tag-filter.tsx | 2 +- .../components/plugins/plugin-page/index.tsx | 2 +- .../plugin-page/install-plugin-dropdown.tsx | 2 +- .../plugin-page/plugin-tasks/index.tsx | 2 +- web/app/components/plugins/provider-card.tsx | 2 +- .../plugins/readme-panel/entrance.tsx | 2 +- .../components/plugins/readme-panel/index.tsx | 2 +- .../auto-update-setting/index.tsx | 2 +- .../no-data-placeholder.tsx | 2 +- .../auto-update-setting/plugins-selected.tsx | 2 +- .../auto-update-setting/tool-picker.tsx | 2 +- .../plugins/reference-setting-modal/label.tsx | 2 +- .../update-plugin/from-market-place.tsx | 2 +- .../update-plugin/plugin-version-picker.tsx | 2 +- .../components/chunk-card-list/index.tsx | 2 +- .../panel/input-field/editor/index.tsx | 2 +- .../input-field/field-list/field-item.tsx | 2 +- .../field-list/field-list-container.tsx | 2 +- .../panel/input-field/field-list/index.tsx | 2 +- .../components/panel/input-field/index.tsx | 2 +- .../panel/input-field/preview/index.tsx | 2 +- .../data-source-options/option-card.tsx | 2 +- .../test-run/preparation/step-indicator.tsx | 2 +- .../panel/test-run/result/tabs/tab.tsx | 2 +- .../rag-pipeline-header/publisher/popup.tsx | 8 ++-- .../rag-pipeline-header/run-mode.tsx | 2 +- .../share/text-generation/index.tsx | 2 +- .../share/text-generation/info-modal.tsx | 2 +- .../share/text-generation/menu-dropdown.tsx | 2 +- .../run-batch/csv-reader/index.tsx | 2 +- .../share/text-generation/run-batch/index.tsx | 2 +- .../run-batch/res-download/index.tsx | 2 +- .../share/text-generation/run-once/index.tsx | 2 +- .../config-credentials.tsx | 2 +- .../edit-custom-collection-modal/index.tsx | 2 +- web/app/components/tools/labels/filter.tsx | 2 +- web/app/components/tools/labels/selector.tsx | 2 +- .../components/tools/mcp/detail/content.tsx | 2 +- .../tools/mcp/detail/list-loading.tsx | 2 +- .../tools/mcp/detail/operation-dropdown.tsx | 2 +- .../tools/mcp/detail/provider-detail.tsx | 2 +- .../components/tools/mcp/detail/tool-item.tsx | 2 +- .../components/tools/mcp/headers-input.tsx | 2 +- web/app/components/tools/mcp/index.tsx | 2 +- .../components/tools/mcp/mcp-server-modal.tsx | 2 +- .../components/tools/mcp/mcp-service-card.tsx | 2 +- web/app/components/tools/mcp/modal.tsx | 2 +- .../components/tools/mcp/provider-card.tsx | 2 +- web/app/components/tools/provider-list.tsx | 2 +- web/app/components/tools/provider/detail.tsx | 2 +- web/app/components/tools/provider/empty.tsx | 2 +- .../components/tools/provider/tool-item.tsx | 2 +- .../setting/build-in/config-credentials.tsx | 2 +- .../tools/workflow-tool/configure-button.tsx | 2 +- .../workflow-tool/confirm-modal/index.tsx | 2 +- .../components/tools/workflow-tool/index.tsx | 2 +- .../tools/workflow-tool/method-selector.tsx | 2 +- .../workflow-header/features-trigger.tsx | 2 +- .../start-node-option.tsx | 2 +- web/app/components/workflow/block-icon.tsx | 2 +- .../block-selector/all-start-blocks.tsx | 2 +- .../workflow/block-selector/all-tools.tsx | 2 +- .../workflow/block-selector/data-sources.tsx | 2 +- .../workflow/block-selector/index-bar.tsx | 6 +-- .../market-place-plugin/action.tsx | 2 +- .../market-place-plugin/item.tsx | 2 +- .../market-place-plugin/list.tsx | 2 +- .../rag-tool-recommendations/list.tsx | 2 +- .../workflow/block-selector/tabs.tsx | 2 +- .../workflow/block-selector/tool-picker.tsx | 2 +- .../block-selector/tool/action-item.tsx | 2 +- .../workflow/block-selector/tool/tool.tsx | 2 +- .../workflow/block-selector/tools.tsx | 4 +- .../trigger-plugin/action-item.tsx | 2 +- .../block-selector/trigger-plugin/item.tsx | 2 +- .../block-selector/view-type-select.tsx | 2 +- web/app/components/workflow/custom-edge.tsx | 2 +- .../workflow/dsl-export-confirm-modal.tsx | 2 +- .../workflow/header/chat-variable-button.tsx | 2 +- .../components/workflow/header/checklist.tsx | 2 +- .../components/workflow/header/env-button.tsx | 2 +- .../header/global-variable-button.tsx | 2 +- .../workflow/header/header-in-restoring.tsx | 2 +- .../workflow/header/run-and-history.tsx | 2 +- .../components/workflow/header/run-mode.tsx | 2 +- .../header/scroll-to-selected-node-button.tsx | 2 +- .../components/workflow/header/undo-redo.tsx | 9 ++-- .../header/version-history-button.tsx | 2 +- .../workflow/header/view-history.tsx | 2 +- .../workflow/header/view-workflow-history.tsx | 8 ++-- web/app/components/workflow/index.tsx | 2 +- .../nodes/_base/components/add-button.tsx | 2 +- .../components/agent-strategy-selector.tsx | 4 +- .../components/before-run-form/form-item.tsx | 2 +- .../_base/components/before-run-form/form.tsx | 2 +- .../components/before-run-form/index.tsx | 2 +- .../components/code-generator-button.tsx | 2 +- .../nodes/_base/components/collapse/index.tsx | 2 +- .../nodes/_base/components/editor/base.tsx | 2 +- .../code-editor/editor-support-vars.tsx | 2 +- .../components/editor/code-editor/index.tsx | 2 +- .../error-handle/error-handle-on-node.tsx | 2 +- .../workflow/nodes/_base/components/field.tsx | 2 +- .../nodes/_base/components/file-type-item.tsx | 2 +- .../_base/components/form-input-boolean.tsx | 2 +- .../_base/components/form-input-item.tsx | 2 +- .../components/form-input-type-switch.tsx | 2 +- .../workflow/nodes/_base/components/group.tsx | 6 +-- .../components/input-support-select-var.tsx | 2 +- .../components/install-plugin-button.tsx | 4 +- .../nodes/_base/components/layout/box.tsx | 2 +- .../_base/components/layout/field-title.tsx | 2 +- .../nodes/_base/components/layout/group.tsx | 2 +- .../nodes/_base/components/memory-config.tsx | 2 +- .../mixed-variable-text-input/index.tsx | 2 +- .../_base/components/next-step/container.tsx | 2 +- .../nodes/_base/components/next-step/item.tsx | 2 +- .../nodes/_base/components/node-handle.tsx | 2 +- .../nodes/_base/components/node-resizer.tsx | 2 +- .../_base/components/node-status-icon.tsx | 2 +- .../nodes/_base/components/option-card.tsx | 2 +- .../nodes/_base/components/output-vars.tsx | 2 +- .../nodes/_base/components/prompt/editor.tsx | 2 +- .../readonly-input-with-select-var.tsx | 2 +- .../_base/components/retry/retry-on-node.tsx | 2 +- .../nodes/_base/components/selector.tsx | 2 +- .../nodes/_base/components/setting-item.tsx | 4 +- .../workflow/nodes/_base/components/split.tsx | 2 +- .../components/support-var-input/index.tsx | 2 +- .../components/switch-plugin-version.tsx | 2 +- .../object-child-tree-panel/picker/field.tsx | 2 +- .../object-child-tree-panel/picker/index.tsx | 2 +- .../object-child-tree-panel/show/field.tsx | 2 +- .../tree-indent-line.tsx | 2 +- .../_base/components/variable/var-list.tsx | 2 +- .../variable/var-reference-picker.tsx | 2 +- .../variable/var-reference-vars.tsx | 2 +- .../components/variable/var-type-picker.tsx | 2 +- .../variable-label/base/variable-icon.tsx | 2 +- .../variable-label/base/variable-label.tsx | 2 +- .../variable-label/base/variable-name.tsx | 2 +- .../variable-icon-with-color.tsx | 2 +- .../variable-label-in-editor.tsx | 2 +- .../variable-label/variable-label-in-node.tsx | 2 +- .../variable-label/variable-label-in-text.tsx | 2 +- .../_base/components/workflow-panel/index.tsx | 2 +- .../workflow-panel/trigger-subscription.tsx | 2 +- .../components/workflow/nodes/_base/node.tsx | 2 +- .../nodes/agent/components/tool-icon.tsx | 18 +++----- .../components/operation-selector.tsx | 14 ++---- .../nodes/data-source-empty/index.tsx | 2 +- .../nodes/http/components/api-input.tsx | 2 +- .../http/components/authorization/index.tsx | 2 +- .../components/authorization/radio-group.tsx | 2 +- .../nodes/http/components/edit-body/index.tsx | 2 +- .../key-value/key-value-edit/index.tsx | 2 +- .../key-value/key-value-edit/input-item.tsx | 2 +- .../key-value/key-value-edit/item.tsx | 2 +- .../components/workflow/nodes/http/panel.tsx | 2 +- .../condition-list/condition-item.tsx | 2 +- .../condition-list/condition-operator.tsx | 2 +- .../components/condition-list/index.tsx | 2 +- .../components/condition-number-input.tsx | 2 +- .../if-else/components/condition-wrap.tsx | 2 +- .../workflow/nodes/iteration/add-block.tsx | 2 +- .../workflow/nodes/iteration/node.tsx | 2 +- .../components/chunk-structure/hooks.tsx | 2 +- .../chunk-structure/instruction/index.tsx | 2 +- .../components/index-method.tsx | 2 +- .../knowledge-base/components/option-card.tsx | 2 +- .../search-method-option.tsx | 2 +- .../condition-list/condition-date.tsx | 2 +- .../condition-list/condition-item.tsx | 2 +- .../condition-list/condition-operator.tsx | 2 +- .../condition-list/condition-value-method.tsx | 2 +- .../metadata/condition-list/index.tsx | 2 +- .../components/metadata/metadata-icon.tsx | 2 +- .../components/retrieval-config.tsx | 2 +- .../components/extract-input.tsx | 2 +- .../components/filter-condition.tsx | 2 +- .../list-operator/components/limit-config.tsx | 2 +- .../components/sub-variable-picker.tsx | 2 +- .../nodes/llm/components/config-prompt.tsx | 2 +- .../json-schema-config-modal/code-editor.tsx | 6 +-- .../error-message.tsx | 8 ++-- .../json-importer.tsx | 2 +- .../json-schema-generator/index.tsx | 2 +- .../schema-editor.tsx | 2 +- .../edit-card/auto-width-input.tsx | 2 +- .../visual-editor/edit-card/index.tsx | 4 +- .../visual-editor/edit-card/type-selector.tsx | 2 +- .../visual-editor/index.tsx | 2 +- .../visual-editor/schema-node.tsx | 18 +++----- .../llm/components/prompt-generator-btn.tsx | 2 +- .../nodes/llm/components/structure-output.tsx | 2 +- .../workflow/nodes/loop/add-block.tsx | 2 +- .../condition-list/condition-item.tsx | 2 +- .../condition-list/condition-operator.tsx | 2 +- .../loop/components/condition-list/index.tsx | 2 +- .../components/condition-number-input.tsx | 2 +- .../nodes/loop/components/condition-wrap.tsx | 2 +- .../workflow/nodes/loop/insert-block.tsx | 2 +- .../components/workflow/nodes/loop/node.tsx | 2 +- .../extract-parameter/import-from-tool.tsx | 2 +- .../components/class-list.tsx | 2 +- .../nodes/start/components/var-item.tsx | 2 +- .../nodes/start/components/var-list.tsx | 2 +- .../nodes/tool/components/input-var-list.tsx | 2 +- .../mixed-variable-text-input/index.tsx | 2 +- .../components/generic-table.tsx | 2 +- .../components/paragraph-input.tsx | 2 +- .../components/add-variable/index.tsx | 2 +- .../components/node-group-item.tsx | 2 +- .../components/node-variable-item.tsx | 2 +- .../nodes/variable-assigner/panel.tsx | 2 +- .../components/workflow/note-node/index.tsx | 2 +- .../plugins/link-editor-plugin/component.tsx | 2 +- .../note-editor/toolbar/color-picker.tsx | 2 +- .../note-node/note-editor/toolbar/command.tsx | 2 +- .../toolbar/font-size-selector.tsx | 2 +- .../note-editor/toolbar/operator.tsx | 2 +- .../workflow/operator/add-block.tsx | 2 +- .../components/workflow/operator/control.tsx | 2 +- .../workflow/operator/more-actions.tsx | 2 +- .../workflow/operator/zoom-in-out.tsx | 2 +- .../components/workflow/panel-contextmenu.tsx | 2 +- .../components/array-bool-list.tsx | 2 +- .../components/variable-item.tsx | 2 +- .../components/variable-modal.tsx | 2 +- .../components/variable-type-select.tsx | 2 +- .../panel/chat-variable-panel/index.tsx | 2 +- .../conversation-variable-modal.tsx | 2 +- .../panel/debug-and-preview/index.tsx | 2 +- .../panel/debug-and-preview/user-input.tsx | 2 +- .../workflow/panel/env-panel/env-item.tsx | 2 +- .../workflow/panel/env-panel/index.tsx | 2 +- .../panel/env-panel/variable-modal.tsx | 2 +- .../panel/global-variable-panel/index.tsx | 2 +- .../panel/global-variable-panel/item.tsx | 2 +- web/app/components/workflow/panel/index.tsx | 2 +- .../context-menu/menu-item.tsx | 2 +- .../version-history-panel/filter/index.tsx | 2 +- .../version-history-panel/loading/item.tsx | 2 +- .../version-history-item.tsx | 2 +- .../workflow/panel/workflow-preview.tsx | 2 +- .../workflow/run/agent-log/agent-log-item.tsx | 2 +- web/app/components/workflow/run/index.tsx | 2 +- .../iteration-log/iteration-result-panel.tsx | 2 +- .../run/loop-log/loop-result-panel.tsx | 2 +- .../workflow/run/loop-result-panel.tsx | 2 +- web/app/components/workflow/run/node.tsx | 2 +- .../workflow/run/status-container.tsx | 2 +- web/app/components/workflow/run/status.tsx | 2 +- .../components/workflow/run/tracing-panel.tsx | 2 +- .../components/workflow/shortcuts-name.tsx | 2 +- .../components/workflow/simple-node/index.tsx | 2 +- .../variable-inspect/display-content.tsx | 2 +- .../workflow/variable-inspect/group.tsx | 2 +- .../workflow/variable-inspect/index.tsx | 2 +- .../variable-inspect/large-data-alert.tsx | 2 +- .../workflow/variable-inspect/left.tsx | 2 +- .../workflow/variable-inspect/panel.tsx | 2 +- .../workflow/variable-inspect/right.tsx | 2 +- .../workflow/variable-inspect/trigger.tsx | 2 +- .../variable-inspect/value-content.tsx | 2 +- .../components/error-handle-on-node.tsx | 2 +- .../components/node-handle.tsx | 2 +- .../components/nodes/base.tsx | 2 +- .../components/nodes/iteration/node.tsx | 2 +- .../components/nodes/loop/node.tsx | 2 +- .../components/note-node/index.tsx | 2 +- .../components/zoom-in-out.tsx | 2 +- .../workflow/workflow-preview/index.tsx | 2 +- web/app/education-apply/role-selector.tsx | 2 +- .../forgot-password/ChangePasswordForm.tsx | 2 +- web/app/forgot-password/page.tsx | 2 +- web/app/init/page.tsx | 2 +- web/app/install/installForm.tsx | 4 +- web/app/install/page.tsx | 2 +- web/app/layout.tsx | 2 +- web/app/reset-password/layout.tsx | 2 +- web/app/reset-password/set-password/page.tsx | 2 +- web/app/signin/components/social-auth.tsx | 14 ++---- web/app/signin/layout.tsx | 2 +- web/app/signin/normal-form.tsx | 2 +- web/app/signin/split.tsx | 2 +- web/app/signup/layout.tsx | 2 +- web/app/signup/set-password/page.tsx | 2 +- web/package.json | 2 +- web/pnpm-lock.yaml | 11 ++--- web/utils/classnames.spec.ts | 2 +- web/utils/classnames.ts | 8 ++-- 815 files changed, 1064 insertions(+), 1227 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 1f836de6e6..d5e3c61932 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -16,7 +16,7 @@ import { import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import s from './style.module.css' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useStore } from '@/app/components/app/store' import AppSideBar from '@/app/components/app-sidebar' import type { NavIcon } from '@/app/components/app-sidebar/navLink' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx index 2bfdece433..dda5dff2b9 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx @@ -3,7 +3,7 @@ import { RiCalendarLine } from '@remixicon/react' import type { Dayjs } from 'dayjs' import type { FC } from 'react' import React, { useCallback } from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { formatToLocalTime } from '@/utils/format' import { useI18N } from '@/context/i18n' import Picker from '@/app/components/base/date-and-time-picker/date-picker' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx index f99ea52492..0a80bf670d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx @@ -6,7 +6,7 @@ import { SimpleSelect } from '@/app/components/base/select' import type { Item } from '@/app/components/base/select' import dayjs from 'dayjs' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useTranslation } from 'react-i18next' const today = dayjs() diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx index 246a1eb6a3..17c919bf22 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useRef, useState } from 'react' import type { PopupProps } from './config-popup' import ConfigPopup from './config-popup' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 628eb13071..767ccb8c59 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -12,7 +12,7 @@ import Indicator from '@/app/components/header/indicator' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' import Divider from '@/app/components/base/divider' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' const I18N_PREFIX = 'app.tracing' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx index eecd356e08..e170159e35 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Input from '@/app/components/base/input' type Props = { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 2c17931b83..319ff3f423 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -12,7 +12,7 @@ import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangS import { TracingProvider } from './type' import TracingIcon from './tracing-icon' import ConfigButton from './config-button' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { AliyunIcon, ArizeIcon, DatabricksIcon, LangfuseIcon, LangsmithIcon, MlflowIcon, OpikIcon, PhoenixIcon, TencentIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' import Indicator from '@/app/components/header/indicator' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx index ac1704d60d..0779689c76 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx @@ -6,7 +6,7 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { TracingProvider } from './type' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { AliyunIconBig, ArizeIconBig, DatabricksIconBig, LangfuseIconBig, LangsmithIconBig, MlflowIconBig, OpikIconBig, PhoenixIconBig, TencentIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing' import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx index ec9117dd38..aeca1cd3ab 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { TracingIcon as Icon } from '@/app/components/base/icons/src/public/tracing' type Props = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 3effb79f20..3581587b54 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -23,7 +23,7 @@ import { useDatasetDetail, useDatasetRelatedApps } from '@/service/knowledge/use import useDocumentTitle from '@/hooks/use-document-title' import ExtraInfo from '@/app/components/datasets/extra-info' import { useEventEmitterContextContext } from '@/context/event-emitter' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' export type IAppDetailLayoutProps = { children: React.ReactNode diff --git a/web/app/(shareLayout)/webapp-reset-password/layout.tsx b/web/app/(shareLayout)/webapp-reset-password/layout.tsx index e0ac6b9ad6..13073b0e6a 100644 --- a/web/app/(shareLayout)/webapp-reset-password/layout.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/layout.tsx @@ -1,7 +1,7 @@ 'use client' import Header from '@/app/signin/_header' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' export default function SignInLayout({ children }: any) { diff --git a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx index 5e3f6fff1d..843f10e039 100644 --- a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRouter, useSearchParams } from 'next/navigation' -import cn from 'classnames' +import { cn } from '@/utils/classnames' import { RiCheckboxCircleFill } from '@remixicon/react' import { useCountDown } from 'ahooks' import Button from '@/app/components/base/button' diff --git a/web/app/(shareLayout)/webapp-signin/layout.tsx b/web/app/(shareLayout)/webapp-signin/layout.tsx index 7649982072..c75f925d40 100644 --- a/web/app/(shareLayout)/webapp-signin/layout.tsx +++ b/web/app/(shareLayout)/webapp-signin/layout.tsx @@ -1,6 +1,6 @@ 'use client' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' import type { PropsWithChildren } from 'react' diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx index 219722eef3..a14bfcd737 100644 --- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -7,7 +7,7 @@ import Loading from '@/app/components/base/loading' import MailAndCodeAuth from './components/mail-and-code-auth' import MailAndPasswordAuth from './components/mail-and-password-auth' import SSOAuth from './components/sso-auth' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { LicenseStatus } from '@/types/feature' import { IS_CE_EDITION } from '@/config' import { useGlobalPublicStore } from '@/context/global-public-context' diff --git a/web/app/account/oauth/authorize/layout.tsx b/web/app/account/oauth/authorize/layout.tsx index 2ab676d6b6..b70ab210d0 100644 --- a/web/app/account/oauth/authorize/layout.tsx +++ b/web/app/account/oauth/authorize/layout.tsx @@ -1,7 +1,7 @@ 'use client' import Header from '@/app/signin/_header' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' import { AppContextProvider } from '@/context/app-context' diff --git a/web/app/activate/activateForm.tsx b/web/app/activate/activateForm.tsx index d9d07cbfa1..4789a579a7 100644 --- a/web/app/activate/activateForm.tsx +++ b/web/app/activate/activateForm.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useRouter, useSearchParams } from 'next/navigation' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Button from '@/app/components/base/button' import { invitationCheck } from '@/service/common' diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index cfb1e6b149..9ae03f3711 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -2,7 +2,7 @@ import React from 'react' import Header from '../signin/_header' import ActivateForm from './activateForm' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' const Activate = () => { diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index f143c2fcef..1b4377c10a 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -29,7 +29,7 @@ import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overvie import type { Operation } from './app-operations' import AppOperations from './app-operations' import dynamic from 'next/dynamic' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { AppModeEnum } from '@/types/app' const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), { diff --git a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx index 3c5d38dd82..04634906af 100644 --- a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx @@ -16,7 +16,7 @@ import AppInfo from './app-info' import NavLink from './navLink' import { useStore as useAppStore } from '@/app/components/app/store' import type { NavIcon } from './navLink' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { AppModeEnum } from '@/types/app' type Props = { diff --git a/web/app/components/app-sidebar/dataset-info/dropdown.tsx b/web/app/components/app-sidebar/dataset-info/dropdown.tsx index ff110f70bd..dc46af2d02 100644 --- a/web/app/components/app-sidebar/dataset-info/dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-info/dropdown.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem' import ActionButton from '../../base/action-button' import { RiMoreFill } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Menu from './menu' import { useSelector as useAppContextWithSelector } from '@/context/app-context' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' diff --git a/web/app/components/app-sidebar/dataset-info/index.tsx b/web/app/components/app-sidebar/dataset-info/index.tsx index 44b0baa72b..bace656d54 100644 --- a/web/app/components/app-sidebar/dataset-info/index.tsx +++ b/web/app/components/app-sidebar/dataset-info/index.tsx @@ -8,7 +8,7 @@ import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import type { DataSet } from '@/models/datasets' import { DOC_FORM_TEXT } from '@/models/datasets' import { useKnowledge } from '@/hooks/use-knowledge' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Dropdown from './dropdown' type DatasetInfoProps = { diff --git a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx index ac07333712..cf380d00d2 100644 --- a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx @@ -11,7 +11,7 @@ import AppIcon from '../base/app-icon' import Divider from '../base/divider' import NavLink from './navLink' import type { NavIcon } from './navLink' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import Effect from '../base/effect' import Dropdown from './dataset-info/dropdown' diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 86de2e2034..fe52c4cfa2 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -9,7 +9,7 @@ import AppSidebarDropdown from './app-sidebar-dropdown' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useStore as useAppStore } from '@/app/components/app/store' import { useEventEmitterContextContext } from '@/context/event-emitter' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Divider from '../base/divider' import { useHover, useKeyPress } from 'ahooks' import ToggleButton from './toggle-button' diff --git a/web/app/components/app-sidebar/navLink.tsx b/web/app/components/app-sidebar/navLink.tsx index ad90b91250..f6d8e57682 100644 --- a/web/app/components/app-sidebar/navLink.tsx +++ b/web/app/components/app-sidebar/navLink.tsx @@ -2,7 +2,7 @@ import React from 'react' import { useSelectedLayoutSegment } from 'next/navigation' import Link from 'next/link' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { RemixiconComponentType } from '@remixicon/react' export type NavIcon = React.ComponentType< @@ -42,7 +42,7 @@ const NavLink = ({ const NavIcon = isActive ? iconMap.selected : iconMap.normal const renderIcon = () => ( -
+
) @@ -53,21 +53,17 @@ const NavLink = ({ key={name} type='button' disabled - className={classNames( - 'system-sm-medium flex h-8 cursor-not-allowed items-center rounded-lg text-components-menu-item-text opacity-30 hover:bg-components-menu-item-bg-hover', - 'pl-3 pr-1', - )} + className={cn('system-sm-medium flex h-8 cursor-not-allowed items-center rounded-lg text-components-menu-item-text opacity-30 hover:bg-components-menu-item-bg-hover', + 'pl-3 pr-1')} title={mode === 'collapse' ? name : ''} aria-disabled > {renderIcon()} {name} @@ -79,22 +75,18 @@ const NavLink = ({ {renderIcon()} {name} diff --git a/web/app/components/app-sidebar/toggle-button.tsx b/web/app/components/app-sidebar/toggle-button.tsx index 8de6f887f6..4f69adfc34 100644 --- a/web/app/components/app-sidebar/toggle-button.tsx +++ b/web/app/components/app-sidebar/toggle-button.tsx @@ -1,7 +1,7 @@ import React from 'react' import Button from '../base/button' import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Tooltip from '../base/tooltip' import { useTranslation } from 'react-i18next' import { getKeyboardKeyNameBySystem } from '../workflow/utils' diff --git a/web/app/components/app/annotation/batch-action.tsx b/web/app/components/app/annotation/batch-action.tsx index 6e80d0c4c8..6ff392d17e 100644 --- a/web/app/components/app/annotation/batch-action.tsx +++ b/web/app/components/app/annotation/batch-action.tsx @@ -3,7 +3,7 @@ import { RiDeleteBinLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import Divider from '@/app/components/base/divider' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' const i18nPrefix = 'appAnnotation.batchAction' @@ -38,7 +38,7 @@ const BatchAction: FC = ({ setIsNotDeleting() } return ( -
+
diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index ccad46b860..c9766135df 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { RiDeleteBinLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx index 37b5ab0686..6ba830967d 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx @@ -6,7 +6,7 @@ import { RiDeleteBinLine, RiEditFill, RiEditLine } from '@remixicon/react' import { Robot, User } from '@/app/components/base/icons/src/public/avatar' import Textarea from '@/app/components/base/textarea' import Button from '@/app/components/base/button' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' export enum EditItemType { Query = 'query', diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index 024f75867c..5f8ef658e7 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -17,7 +17,7 @@ import Button from '../../../base/button' import AddAnnotationModal from '../add-annotation-modal' import type { AnnotationItemBasic } from '../type' import BatchAddModal from '../batch-add-annotation-modal' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import CustomPopover from '@/app/components/base/popover' import { FileDownload02, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index 32d0c799fc..2d639c91e4 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -25,7 +25,7 @@ import { sleep } from '@/utils' import { useProviderContext } from '@/context/provider-context' import AnnotationFullModal from '@/app/components/billing/annotation-full/modal' import { type App, AppModeEnum } from '@/types/app' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { delAnnotations } from '@/service/annotation' type Props = { diff --git a/web/app/components/app/annotation/list.tsx b/web/app/components/app/annotation/list.tsx index 4135b4362e..62a0c50e60 100644 --- a/web/app/components/app/annotation/list.tsx +++ b/web/app/components/app/annotation/list.tsx @@ -7,7 +7,7 @@ import type { AnnotationItem } from './type' import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal' import ActionButton from '@/app/components/base/action-button' import useTimestamp from '@/hooks/use-timestamp' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Checkbox from '@/app/components/base/checkbox' import BatchAction from './batch-action' diff --git a/web/app/components/app/annotation/view-annotation-modal/index.tsx b/web/app/components/app/annotation/view-annotation-modal/index.tsx index 8426ab0005..d21b177098 100644 --- a/web/app/components/app/annotation/view-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/view-annotation-modal/index.tsx @@ -14,7 +14,7 @@ import TabSlider from '@/app/components/base/tab-slider-plain' import { fetchHitHistoryList } from '@/service/annotation' import { APP_PAGE_LIMIT } from '@/config' import useTimestamp from '@/hooks/use-timestamp' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' type Props = { appId: string diff --git a/web/app/components/app/app-access-control/access-control-dialog.tsx b/web/app/components/app/app-access-control/access-control-dialog.tsx index ee3fa9650b..99cf6d7074 100644 --- a/web/app/components/app/app-access-control/access-control-dialog.tsx +++ b/web/app/components/app/app-access-control/access-control-dialog.tsx @@ -2,7 +2,7 @@ import { Fragment, useCallback } from 'react' import type { ReactNode } from 'react' import { Dialog, Transition } from '@headlessui/react' import { RiCloseLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' type DialogProps = { className?: string diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx index bb8dabbae6..17263fdd46 100644 --- a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx +++ b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx @@ -11,7 +11,7 @@ import Input from '../../base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem' import Loading from '../../base/loading' import useAccessControlStore from '../../../../context/access-control-store' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useSearchForWhiteListCandidates } from '@/service/access-control' import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control' import { SubjectType } from '@/models/access-control' @@ -106,7 +106,7 @@ function SelectedGroupsBreadCrumb() { setSelectedGroupsForBreadcrumb([]) }, [setSelectedGroupsForBreadcrumb]) return
- 0 && 'cursor-pointer text-text-accent')} onClick={handleReset}>{t('app.accessControlDialog.operateGroupAndMember.allMembers')} + 0 && 'cursor-pointer text-text-accent')} onClick={handleReset}>{t('app.accessControlDialog.operateGroupAndMember.allMembers')} {selectedGroupsForBreadcrumb.map((group, index) => { return
/ @@ -198,7 +198,7 @@ type BaseItemProps = { children: React.ReactNode } function BaseItem({ children, className }: BaseItemProps) { - return
+ return
{children}
} diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx index 2535de6654..154bacc361 100644 --- a/web/app/components/app/app-publisher/suggested-action.tsx +++ b/web/app/components/app/app-publisher/suggested-action.tsx @@ -1,6 +1,6 @@ import type { HTMLProps, PropsWithChildren } from 'react' import { RiArrowRightUpLine } from '@remixicon/react' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' export type SuggestedActionProps = PropsWithChildren & { icon?: React.ReactNode @@ -19,11 +19,9 @@ const SuggestedAction = ({ icon, link, disabled, children, className, onClick, . href={disabled ? undefined : link} target='_blank' rel='noreferrer' - className={classNames( - 'flex items-center justify-start gap-2 rounded-lg bg-background-section-burn px-2.5 py-2 text-text-secondary transition-colors [&:not(:first-child)]:mt-1', + className={cn('flex items-center justify-start gap-2 rounded-lg bg-background-section-burn px-2.5 py-2 text-text-secondary transition-colors [&:not(:first-child)]:mt-1', disabled ? 'cursor-not-allowed opacity-30 shadow-xs' : 'cursor-pointer text-text-secondary hover:bg-state-accent-hover hover:text-text-accent', - className, - )} + className)} onClick={handleClick} {...props} > diff --git a/web/app/components/app/configuration/base/feature-panel/index.tsx b/web/app/components/app/configuration/base/feature-panel/index.tsx index ec5ab96d76..c9ebfefbe5 100644 --- a/web/app/components/app/configuration/base/feature-panel/index.tsx +++ b/web/app/components/app/configuration/base/feature-panel/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC, ReactNode } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' export type IFeaturePanelProps = { className?: string diff --git a/web/app/components/app/configuration/base/operation-btn/index.tsx b/web/app/components/app/configuration/base/operation-btn/index.tsx index aba35cded2..db19d2976e 100644 --- a/web/app/components/app/configuration/base/operation-btn/index.tsx +++ b/web/app/components/app/configuration/base/operation-btn/index.tsx @@ -6,7 +6,7 @@ import { RiAddLine, RiEditLine, } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { noop } from 'lodash-es' export type IOperationBtnProps = { diff --git a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx index 5bf2f177ff..6492864ce2 100644 --- a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx @@ -14,7 +14,7 @@ import s from './style.module.css' import MessageTypeSelector from './message-type-selector' import ConfirmAddVar from './confirm-add-var' import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { PromptRole, PromptVariable } from '@/models/debug' import { Copy, diff --git a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx index 17b3ecb2f1..71f3e6ee5f 100644 --- a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx +++ b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useBoolean, useClickAway } from 'ahooks' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { PromptRole } from '@/models/debug' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' type Props = { diff --git a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx index 9e10db93ae..90a19c883a 100644 --- a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx +++ b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react' import type { FC } from 'react' import { useDebounceFn } from 'ahooks' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index 68bf6dd7c2..e4c21b0cbc 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -7,7 +7,7 @@ import { produce } from 'immer' import { useContext } from 'use-context-selector' import ConfirmAddVar from './confirm-add-var' import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { PromptVariable } from '@/models/debug' import Tooltip from '@/app/components/base/tooltip' import { AppModeEnum } from '@/types/app' diff --git a/web/app/components/app/configuration/config-var/config-modal/field.tsx b/web/app/components/app/configuration/config-var/config-modal/field.tsx index b24e0be6ce..76d228358a 100644 --- a/web/app/components/app/configuration/config-var/config-modal/field.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/field.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useTranslation } from 'react-i18next' type Props = { diff --git a/web/app/components/app/configuration/config-var/config-modal/type-select.tsx b/web/app/components/app/configuration/config-var/config-modal/type-select.tsx index 2b52991d4a..53d59eb24b 100644 --- a/web/app/components/app/configuration/config-var/config-modal/type-select.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/type-select.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useState } from 'react' import { ChevronDownIcon } from '@heroicons/react/20/solid' -import classNames from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, @@ -10,7 +9,7 @@ import { } from '@/app/components/base/portal-to-follow-elem' import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon' import type { InputVarType } from '@/app/components/workflow/types' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Badge from '@/app/components/base/badge' import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils' @@ -47,7 +46,7 @@ const TypeSelector: FC = ({ > !readonly && setOpen(v => !v)} className='w-full'>
@@ -69,7 +68,7 @@ const TypeSelector: FC = ({
{items.map((item: Item) => (
{ const { t } = useTranslation() diff --git a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx index 6512e11545..6193392026 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Tooltip from '@/app/components/base/tooltip' type Props = { className?: string diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 4793b5fe49..8dfa2f194b 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -25,7 +25,7 @@ import { MAX_TOOLS_NUM } from '@/config' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import Tooltip from '@/app/components/base/tooltip' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' import { canFindTool } from '@/utils' diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index c5947495db..0627666b4c 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -22,7 +22,7 @@ import { CollectionType } from '@/app/components/tools/types' import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList, fetchWorkflowToolList } from '@/service/tools' import I18n from '@/context/i18n' import { getLanguage } from '@/i18n-config/language' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { ToolWithProvider } from '@/app/components/workflow/types' import { AuthCategory, diff --git a/web/app/components/app/configuration/config/agent/prompt-editor.tsx b/web/app/components/app/configuration/config/agent/prompt-editor.tsx index 71a9304d0c..78d7eef029 100644 --- a/web/app/components/app/configuration/config/agent/prompt-editor.tsx +++ b/web/app/components/app/configuration/config/agent/prompt-editor.tsx @@ -4,7 +4,7 @@ import React from 'react' import copy from 'copy-to-clipboard' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { Copy, CopyCheck, diff --git a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx index 3597a6e292..50f16f957a 100644 --- a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx +++ b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { RiArrowDownSLine } from '@remixicon/react' import AgentSetting from '../agent/agent-setting' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/app/configuration/config/automatic/idea-output.tsx b/web/app/components/app/configuration/config/automatic/idea-output.tsx index df4f76c92b..895f74baa3 100644 --- a/web/app/components/app/configuration/config/automatic/idea-output.tsx +++ b/web/app/components/app/configuration/config/automatic/idea-output.tsx @@ -3,7 +3,7 @@ import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid import { useBoolean } from 'ahooks' import type { FC } from 'react' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Textarea from '@/app/components/base/textarea' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/config/automatic/instruction-editor.tsx b/web/app/components/app/configuration/config/automatic/instruction-editor.tsx index b14ee93313..409f335232 100644 --- a/web/app/components/app/configuration/config/automatic/instruction-editor.tsx +++ b/web/app/components/app/configuration/config/automatic/instruction-editor.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React from 'react' import PromptEditor from '@/app/components/base/prompt-editor' import type { GeneratorType } from './types' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/config/automatic/prompt-toast.tsx b/web/app/components/app/configuration/config/automatic/prompt-toast.tsx index 2826cc97c8..c9169f0ad7 100644 --- a/web/app/components/app/configuration/config/automatic/prompt-toast.tsx +++ b/web/app/components/app/configuration/config/automatic/prompt-toast.tsx @@ -1,7 +1,7 @@ import { RiArrowDownSLine, RiSparklingFill } from '@remixicon/react' import { useBoolean } from 'ahooks' import React from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { Markdown } from '@/app/components/base/markdown' import { useTranslation } from 'react-i18next' import s from './style.module.css' diff --git a/web/app/components/app/configuration/config/automatic/version-selector.tsx b/web/app/components/app/configuration/config/automatic/version-selector.tsx index c3d3e1d91c..715c1f3c80 100644 --- a/web/app/components/app/configuration/config/automatic/version-selector.tsx +++ b/web/app/components/app/configuration/config/automatic/version-selector.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { useBoolean } from 'ahooks' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/dataset-config/card-item/index.tsx b/web/app/components/app/configuration/dataset-config/card-item/index.tsx index 85d46122a3..7fd7011a56 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/index.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/index.tsx @@ -13,7 +13,7 @@ import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Badge from '@/app/components/base/badge' import { useKnowledge } from '@/hooks/use-knowledge' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import AppIcon from '@/app/components/base/app-icon' type ItemProps = { diff --git a/web/app/components/app/configuration/dataset-config/context-var/index.tsx b/web/app/components/app/configuration/dataset-config/context-var/index.tsx index ebba9c51cb..80cc50acdf 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/index.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/index.tsx @@ -4,7 +4,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import type { Props } from './var-picker' import VarPicker from './var-picker' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { BracketsX } from '@/app/components/base/icons/src/vender/line/development' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx index c443ea0b1f..f5ea2eaa27 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { ChevronDownIcon } from '@heroicons/react/24/outline' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx index 8e06d6c901..c7a43fbfbd 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx @@ -20,7 +20,7 @@ import type { DataSet, } from '@/models/datasets' import { RerankingModeEnum } from '@/models/datasets' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowledge-retrieval/hooks' import Switch from '@/app/components/base/switch' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.tsx index df2b4293c4..24da958217 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { RiEqualizer2Line } from '@remixicon/react' import ConfigContent from './config-content' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import ConfigContext from '@/context/debug-configuration' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx index ebfa3b1e12..459623104d 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx @@ -2,7 +2,7 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' import './weighted-score.css' import Slider from '@/app/components/base/slider' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { noop } from 'lodash-es' const formatNumber = (value: number) => { diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 6857c38e1e..f02fdcb5d7 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -10,7 +10,7 @@ import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' import Badge from '@/app/components/base/badge' import { useKnowledge } from '@/hooks/use-knowledge' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import AppIcon from '@/app/components/base/app-icon' import { useInfiniteDatasets } from '@/service/knowledge/use-dataset' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 37d9ddd372..8c3e753b22 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -4,7 +4,7 @@ import { useMount } from 'ahooks' import { useTranslation } from 'react-i18next' import { isEqual } from 'lodash-es' import { RiCloseLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import IndexMethod from '@/app/components/datasets/settings/index-method' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/retrieval-section.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/retrieval-section.tsx index 5ea799d092..99d042f681 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/retrieval-section.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/retrieval-section.tsx @@ -1,6 +1,6 @@ import { RiCloseLine } from '@remixicon/react' import type { FC } from 'react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Divider from '@/app/components/base/divider' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' diff --git a/web/app/components/app/configuration/debug/chat-user-input.tsx b/web/app/components/app/configuration/debug/chat-user-input.tsx index 16666d514e..c25bed548c 100644 --- a/web/app/components/app/configuration/debug/chat-user-input.tsx +++ b/web/app/components/app/configuration/debug/chat-user-input.tsx @@ -7,7 +7,7 @@ import Select from '@/app/components/base/select' import Textarea from '@/app/components/base/textarea' import { DEFAULT_VALUE_MAX_LEN } from '@/config' import type { Inputs } from '@/models/debug' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input' type Props = { diff --git a/web/app/components/app/configuration/prompt-value-panel/index.tsx b/web/app/components/app/configuration/prompt-value-panel/index.tsx index 005f7f938f..9874664443 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -21,7 +21,7 @@ import FeatureBar from '@/app/components/base/features/new-feature-panel/feature import type { VisionFile, VisionSettings } from '@/types/app' import { DEFAULT_VALUE_MAX_LEN } from '@/config' import { useStore as useAppStore } from '@/app/components/app/store' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input' export type IPromptValuePanelProps = { diff --git a/web/app/components/app/create-app-dialog/app-card/index.tsx b/web/app/components/app/create-app-dialog/app-card/index.tsx index a3bf91cb5d..df35a74ec7 100644 --- a/web/app/components/app/create-app-dialog/app-card/index.tsx +++ b/web/app/components/app/create-app-dialog/app-card/index.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { PlusIcon } from '@heroicons/react/20/solid' import { AppTypeIcon, AppTypeLabel } from '../../type-selector' import Button from '@/app/components/base/button' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { App } from '@/models/explore' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 51b6874d52..4655d7a676 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -11,7 +11,7 @@ import AppCard from '../app-card' import Sidebar, { AppCategories, AppCategoryLabel } from './sidebar' import Toast from '@/app/components/base/toast' import Divider from '@/app/components/base/divider' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import ExploreContext from '@/context/explore-context' import type { App } from '@/models/explore' import { fetchAppDetail, fetchAppList } from '@/service/explore' diff --git a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx index 85c55c5385..89062cdcf9 100644 --- a/web/app/components/app/create-app-dialog/app-list/sidebar.tsx +++ b/web/app/components/app/create-app-dialog/app-list/sidebar.tsx @@ -1,7 +1,7 @@ 'use client' import { RiStickyNoteAddLine, RiThumbUpLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Divider from '@/app/components/base/divider' export enum AppCategories { @@ -40,13 +40,13 @@ type CategoryItemProps = { } function CategoryItem({ category, active, onClick }: CategoryItemProps) { return
  • { onClick?.(category) }}> {category === AppCategories.RECOMMENDED &&
    } + className={cn('system-sm-medium text-components-menu-item-text group-hover:text-components-menu-item-text-hover group-[.active]:text-components-menu-item-text-active', active && 'system-sm-semibold')} />
  • } diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index a449ec8ef2..d74715187f 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -13,7 +13,7 @@ import AppIconPicker from '../../base/app-icon-picker' import type { AppIconSelection } from '../../base/app-icon-picker' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index 3564738dfd..0d30a2abac 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -25,7 +25,7 @@ import { useProviderContext } from '@/context/provider-context' import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { getRedirection } from '@/utils/app-redirection' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { noop } from 'lodash-es' import { trackEvent } from '@/app/components/base/amplitude' diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx index b6644da5a4..2745ca84c6 100644 --- a/web/app/components/app/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx @@ -8,7 +8,7 @@ import { import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { formatFileSize } from '@/utils/format' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/app/duplicate-modal/index.tsx b/web/app/components/app/duplicate-modal/index.tsx index f98fb831ed..f25eb5373d 100644 --- a/web/app/components/app/duplicate-modal/index.tsx +++ b/web/app/components/app/duplicate-modal/index.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' import AppIconPicker from '../../base/app-icon-picker' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/app/log-annotation/index.tsx b/web/app/components/app/log-annotation/index.tsx index c0b0854b29..e7c2be3eed 100644 --- a/web/app/components/app/log-annotation/index.tsx +++ b/web/app/components/app/log-annotation/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Log from '@/app/components/app/log' import WorkflowLog from '@/app/components/app/workflow-log' import Annotation from '@/app/components/app/annotation' diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 0ff375d815..e479cbe881 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -39,7 +39,7 @@ import Tooltip from '@/app/components/base/tooltip' import CopyIcon from '@/app/components/base/copy-icon' import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { noop } from 'lodash-es' import PromptLogModal from '../../base/prompt-log-modal' import { WorkflowContextProvider } from '@/app/components/workflow/context' diff --git a/web/app/components/app/log/model-info.tsx b/web/app/components/app/log/model-info.tsx index 626ef093e9..b3c4f11be5 100644 --- a/web/app/components/app/log/model-info.tsx +++ b/web/app/components/app/log/model-info.tsx @@ -13,7 +13,7 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' const PARAM_MAP = { temperature: 'Temperature', diff --git a/web/app/components/app/log/var-panel.tsx b/web/app/components/app/log/var-panel.tsx index dd8c231a56..8915b3438a 100644 --- a/web/app/components/app/log/var-panel.tsx +++ b/web/app/components/app/log/var-panel.tsx @@ -9,7 +9,7 @@ import { } from '@remixicon/react' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import ImagePreview from '@/app/components/base/image-uploader/image-preview' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' type Props = { varList: { label: string; value: string }[] diff --git a/web/app/components/app/overview/apikey-info-panel/index.tsx b/web/app/components/app/overview/apikey-info-panel/index.tsx index b50b0077cb..47fe7af972 100644 --- a/web/app/components/app/overview/apikey-info-panel/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' import { IS_CE_EDITION } from '@/config' diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index 6eba993e1d..d4be58b1b2 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -14,7 +14,7 @@ import type { SiteInfo } from '@/models/share' import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context' import ActionButton from '@/app/components/base/action-button' import { basePath } from '@/utils/var' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' type Props = { siteInfo?: SiteInfo diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 3b71b8f75c..d079631cf7 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -25,7 +25,7 @@ import { useModalContext } from '@/context/modal-context' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import AppIconPicker from '@/app/components/base/app-icon-picker' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { useDocLink } from '@/context/i18n' export type ISettingsModalProps = { diff --git a/web/app/components/app/switch-app-modal/index.tsx b/web/app/components/app/switch-app-modal/index.tsx index a7e1cea429..742212a44d 100644 --- a/web/app/components/app/switch-app-modal/index.tsx +++ b/web/app/components/app/switch-app-modal/index.tsx @@ -6,7 +6,7 @@ import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' import AppIconPicker from '../../base/app-icon-picker' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import Checkbox from '@/app/components/base/checkbox' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index 92d86351e0..d284ecd46e 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -30,7 +30,7 @@ import type { SiteInfo } from '@/models/share' import { useChatContext } from '@/app/components/base/chat/chat/context' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import NewAudioButton from '@/app/components/base/new-audio-button' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' const MAX_DEPTH = 3 diff --git a/web/app/components/app/text-generate/saved-items/index.tsx b/web/app/components/app/text-generate/saved-items/index.tsx index c22a4ca6c2..e6cf264cf2 100644 --- a/web/app/components/app/text-generate/saved-items/index.tsx +++ b/web/app/components/app/text-generate/saved-items/index.tsx @@ -8,7 +8,7 @@ import { import { useTranslation } from 'react-i18next' import copy from 'copy-to-clipboard' import NoData from './no-data' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { SavedMessage } from '@/models/debug' import { Markdown } from '@/app/components/base/markdown' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx index 7be2351119..f213a89a94 100644 --- a/web/app/components/app/type-selector/index.tsx +++ b/web/app/components/app/type-selector/index.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' import React, { useState } from 'react' import { RiArrowDownSLine, RiCloseCircleFill, RiExchange2Fill, RiFilter3Line } from '@remixicon/react' import Checkbox from '../../base/checkbox' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/app/workflow-log/list.tsx b/web/app/components/app/workflow-log/list.tsx index 0e9b5dd67f..cef8a98f44 100644 --- a/web/app/components/app/workflow-log/list.tsx +++ b/web/app/components/app/workflow-log/list.tsx @@ -12,7 +12,7 @@ import Drawer from '@/app/components/base/drawer' import Indicator from '@/app/components/header/indicator' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useTimestamp from '@/hooks/use-timestamp' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import type { WorkflowRunTriggeredFrom } from '@/models/log' type ILogs = { diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index b8da0264e4..8140422c0f 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -5,7 +5,7 @@ import { useContext } from 'use-context-selector' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import { type App, AppModeEnum } from '@/types/app' import Toast, { ToastContext } from '@/app/components/base/toast' import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps' diff --git a/web/app/components/apps/new-app-card.tsx b/web/app/components/apps/new-app-card.tsx index 7a10bc8527..51e4bae8fe 100644 --- a/web/app/components/apps/new-app-card.tsx +++ b/web/app/components/apps/new-app-card.tsx @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next' import { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal' import { useProviderContext } from '@/context/provider-context' import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' -import cn from '@/utils/classnames' +import { cn } from '@/utils/classnames' import dynamic from 'next/dynamic' const CreateAppModal = dynamic(() => import('@/app/components/app/create-app-modal'), { diff --git a/web/app/components/base/action-button/index.tsx b/web/app/components/base/action-button/index.tsx index f70bfb4448..eff6a43d22 100644 --- a/web/app/components/base/action-button/index.tsx +++ b/web/app/components/base/action-button/index.tsx @@ -1,7 +1,7 @@ import type { CSSProperties } from 'react' import React from 'react' import { type VariantProps, cva } from 'class-variance-authority' -import classNames from '@/utils/classnames' +import { cn } from '@/utils/classnames' enum ActionButtonState { Destructive = 'destructive', @@ -54,10 +54,8 @@ const ActionButton = ({ className, size, state = ActionButtonState.Default, styl return ( +
    +
    diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx index a48d1b92b0..3d99cc5bad 100644 --- a/web/app/components/tools/mcp/mcp-server-param-item.tsx +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -17,19 +17,20 @@ const MCPServerParamItem = ({ const { t } = useTranslation() return ( -
    -
    -
    {data.label}
    -
    ·
    -
    {data.variable}
    -
    {data.type}
    +
    +
    +
    {data.label}
    +
    ·
    +
    {data.variable}
    +
    {data.type}
    + > +
    ) } diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 006ef44ad3..521e93222b 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -1,32 +1,33 @@ 'use client' +import type { AppDetailResponse } from '@/models/app' +import type { AppSSO } from '@/types/app' +import { RiEditLine, RiLoopLeftLine } from '@remixicon/react' import React, { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiEditLine, RiLoopLeftLine } from '@remixicon/react' +import Button from '@/app/components/base/button' +import Confirm from '@/app/components/base/confirm' +import CopyFeedback from '@/app/components/base/copy-feedback' +import Divider from '@/app/components/base/divider' import { Mcp, } from '@/app/components/base/icons/src/vender/other' -import Button from '@/app/components/base/button' -import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' -import Divider from '@/app/components/base/divider' -import CopyFeedback from '@/app/components/base/copy-feedback' -import Confirm from '@/app/components/base/confirm' -import type { AppDetailResponse } from '@/models/app' -import { useAppContext } from '@/context/app-context' -import { AppModeEnum, type AppSSO } from '@/types/app' +import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal' -import { useAppWorkflow } from '@/service/use-workflow' +import { BlockEnum } from '@/app/components/workflow/types' +import { useAppContext } from '@/context/app-context' +import { useDocLink } from '@/context/i18n' +import { fetchAppDetail } from '@/service/apps' import { useInvalidateMCPServerDetail, useMCPServerDetail, useRefreshMCPServerCode, useUpdateMCPServer, } from '@/service/use-tools' -import { BlockEnum } from '@/app/components/workflow/types' +import { useAppWorkflow } from '@/service/use-workflow' +import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' -import { fetchAppDetail } from '@/service/apps' -import { useDocLink } from '@/context/i18n' export type IAppCardProps = { appInfo: AppDetailResponse & Partial @@ -54,7 +55,7 @@ function MCPServiceCard({ const { data: currentWorkflow } = useAppWorkflow(isAdvancedApp ? appId : '') const [basicAppConfig, setBasicAppConfig] = useState({}) const basicAppInputForm = useMemo(() => { - if(!isBasicApp || !basicAppConfig?.user_input_form) + if (!isBasicApp || !basicAppConfig?.user_input_form) return [] return basicAppConfig.user_input_form.map((item: any) => { const type = Object.keys(item)[0] @@ -65,7 +66,7 @@ function MCPServiceCard({ }) }, [basicAppConfig.user_input_form, isBasicApp]) useEffect(() => { - if(isBasicApp && appId) { + if (isBasicApp && appId) { (async () => { const res = await fetchAppDetail({ url: '/apps', id: appId }) setBasicAppConfig(res?.model_config || {}) @@ -89,7 +90,7 @@ function MCPServiceCard({ const [activated, setActivated] = useState(serverActivated) const latestParams = useMemo(() => { - if(isAdvancedApp) { + if (isAdvancedApp) { if (!currentWorkflow?.graph) return [] const startNode = currentWorkflow?.graph.nodes.find(node => node.data.type === BlockEnum.Start) as any @@ -150,21 +151,23 @@ function MCPServiceCard({
    {triggerModeDisabled && ( - triggerModeMessage ? ( - - - - ) : + triggerModeMessage + ? ( + + + + ) + : )}
    -
    -
    -
    - +
    +
    +
    +
    @@ -172,7 +175,7 @@ function MCPServiceCard({
    -
    +
    {serverActivated @@ -182,23 +185,29 @@ function MCPServiceCard({
    -
    - {t('appOverview.overview.appInfo.enableTooltip.description')} -
    -
    window.open(docLink('/guides/workflow/node/user-input'), '_blank')} - > - {t('appOverview.overview.appInfo.enableTooltip.learnMore')} -
    - - ) : triggerModeMessage || '' - ) : '' + toggleDisabled + ? ( + appUnpublished + ? ( + t('tools.mcp.server.publishTip') + ) + : missingStartNode + ? ( + <> +
    + {t('appOverview.overview.appInfo.enableTooltip.description')} +
    +
    window.open(docLink('/guides/workflow/node/user-input'), '_blank')} + > + {t('appOverview.overview.appInfo.enableTooltip.learnMore')} +
    + + ) + : triggerModeMessage || '' + ) + : '' } position="right" popupClassName="w-58 max-w-60 rounded-xl bg-components-panel-bg px-3.5 py-3 shadow-lg" @@ -210,7 +219,7 @@ function MCPServiceCard({
    {!isMinimalState && ( -
    +
    {t('tools.mcp.server.url')}
    @@ -224,7 +233,7 @@ function MCPServiceCard({ <> {isCurrentWorkspaceManager && ( @@ -235,7 +244,7 @@ function MCPServiceCard({ className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover" onClick={() => setShowConfirmDelete(true)} > - +
    )} @@ -246,11 +255,11 @@ function MCPServiceCard({ )}
    {!isMinimalState && ( -
    +
    +
    +
    - {showAppIconPicker && { - setAppIcon(payload) - setShowAppIconPicker(false) - }} - onClose={() => { - setAppIcon(getIcon(data)) - setShowAppIconPicker(false) - }} - />} + {showAppIconPicker && ( + { + setAppIcon(payload) + setShowAppIconPicker(false) + }} + onClose={() => { + setAppIcon(getIcon(data)) + setShowAppIconPicker(false) + }} + /> + )} ) diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 831a1122ed..a7b092e0c0 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -1,18 +1,18 @@ 'use client' -import { useCallback, useState } from 'react' -import { useBoolean } from 'ahooks' -import { useTranslation } from 'react-i18next' -import { useAppContext } from '@/context/app-context' +import type { ToolWithProvider } from '../../workflow/types' import { RiHammerFill } from '@remixicon/react' +import { useBoolean } from 'ahooks' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Confirm from '@/app/components/base/confirm' import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' +import { useAppContext } from '@/context/app-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' -import type { ToolWithProvider } from '../../workflow/types' -import Confirm from '@/app/components/base/confirm' -import MCPModal from './modal' -import OperationDropdown from './detail/operation-dropdown' import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools' import { cn } from '@/utils/classnames' +import OperationDropdown from './detail/operation-dropdown' +import MCPModal from './modal' type Props = { currentProvider?: ToolWithProvider @@ -82,34 +82,34 @@ const MCPCard = ({ currentProvider?.id === data.id && 'border-components-option-card-option-selected-border bg-components-card-bg-alt', )} > -
    -
    +
    +
    -
    -
    {data.name}
    -
    {data.server_identifier}
    +
    +
    {data.name}
    +
    {data.server_identifier}
    -
    -
    -
    - +
    +
    +
    + {data.tools.length > 0 && ( -
    {t('tools.mcp.toolsCount', { count: data.tools.length })}
    +
    {t('tools.mcp.toolsCount', { count: data.tools.length })}
    )} {!data.tools.length && ( -
    {t('tools.mcp.noTools')}
    +
    {t('tools.mcp.noTools')}
    )}
    /
    {`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}
    - {data.is_team_authorization && data.tools.length > 0 && } + {data.is_team_authorization && data.tools.length > 0 && } {(!data.is_team_authorization || !data.tools.length) && ( -
    +
    {t('tools.mcp.noConfigured')} - +
    )}
    @@ -135,11 +135,11 @@ const MCPCard = ({ {t('tools.mcp.deleteConfirmTitle', { mcp: data.name })}
    - } + )} onCancel={hideDeleteConfirm} onConfirm={handleDelete} isLoading={deleting} diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 567cc94450..648ecb9802 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -1,27 +1,27 @@ 'use client' +import type { Collection } from './types' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import type { Collection } from './types' -import Marketplace from './marketplace' -import { cn } from '@/utils/classnames' -import { useTabSearchParams } from '@/hooks/use-tab-searchparams' -import TabSliderNew from '@/app/components/base/tab-slider-new' -import LabelFilter from '@/app/components/tools/labels/filter' import Input from '@/app/components/base/input' -import ProviderDetail from '@/app/components/tools/provider/detail' -import Empty from '@/app/components/plugins/marketplace/empty' -import CustomCreateCard from '@/app/components/tools/provider/custom-create-card' -import WorkflowToolEmpty from '@/app/components/tools/provider/empty' +import TabSliderNew from '@/app/components/base/tab-slider-new' import Card from '@/app/components/plugins/card' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' -import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' -import MCPList from './mcp' -import { useAllToolProviders } from '@/service/use-tools' -import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { ToolTypeEnum } from '../workflow/block-selector/types' -import { useMarketplace } from './marketplace/hooks' import { useTags } from '@/app/components/plugins/hooks' +import Empty from '@/app/components/plugins/marketplace/empty' +import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' +import LabelFilter from '@/app/components/tools/labels/filter' +import CustomCreateCard from '@/app/components/tools/provider/custom-create-card' +import ProviderDetail from '@/app/components/tools/provider/detail' +import WorkflowToolEmpty from '@/app/components/tools/provider/empty' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' +import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins' +import { useAllToolProviders } from '@/service/use-tools' +import { cn } from '@/utils/classnames' +import { ToolTypeEnum } from '../workflow/block-selector/types' +import Marketplace from './marketplace' +import { useMarketplace } from './marketplace/hooks' +import MCPList from './mcp' const getToolType = (type: string) => { switch (type) { @@ -125,15 +125,16 @@ const ProviderList = () => { return ( <> -
    +
    + )} + > { @@ -143,14 +144,14 @@ const ProviderList = () => { }} options={options} /> -
    +
    {activeTab !== 'mcp' && ( )} handleKeywordsChange(e.target.value)} onClear={() => handleKeywordsChange('')} @@ -161,7 +162,8 @@ const ProviderList = () => {
    + )} + > {activeTab === 'api' && } {filteredCollectionList.map(collection => (
    { org: collection.plugin_id ? collection.plugin_id.split('/')[0] : '', name: collection.plugin_id ? collection.plugin_id.split('/')[1] : collection.name, } as any} - footer={ + footer={( getTagLabel(label)) || []} /> - } + )} />
    ))} - {!filteredCollectionList.length && activeTab === 'workflow' &&
    } + {!filteredCollectionList.length && activeTab === 'workflow' &&
    }
    )} {!filteredCollectionList.length && activeTab === 'builtin' && ( - + )}
    {enable_marketplace && activeTab === 'builtin' && ( diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx index dbb7026aba..ba0c9e6449 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -1,20 +1,19 @@ 'use client' -import { useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' +import type { CustomCollectionBackend } from '../types' import { RiAddCircleFill, RiArrowRightUpLine, RiBookOpenLine, } from '@remixicon/react' -import type { CustomCollectionBackend } from '../types' -import I18n from '@/context/i18n' -import { getLanguage } from '@/i18n-config/language' -import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' -import { createCustomCollection } from '@/service/tools' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' import Toast from '@/app/components/base/toast' +import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' import { useAppContext } from '@/context/app-context' -import { useDocLink } from '@/context/i18n' +import I18n, { useDocLink } from '@/context/i18n' +import { getLanguage } from '@/i18n-config/language' +import { createCustomCollection } from '@/service/tools' type Props = { onRefreshData: () => void @@ -47,20 +46,20 @@ const Contribute = ({ onRefreshData }: Props) => { return ( <> {isCurrentWorkspaceManager && ( -
    -
    setIsShowEditCustomCollectionModal(true)}> -
    -
    - +
    +
    setIsShowEditCustomCollectionModal(true)}> +
    +
    +
    -
    {t('tools.createCustomTool')}
    +
    {t('tools.createCustomTool')}
    - diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx index d310dde41b..e881dd3997 100644 --- a/web/app/components/tools/provider/detail.tsx +++ b/web/app/components/tools/provider/detail.tsx @@ -1,32 +1,33 @@ 'use client' -import React, { useCallback, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' +import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types' import { RiCloseLine, } from '@remixicon/react' -import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' -import { basePath } from '@/utils/var' -import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types' -import ToolItem from './tool-item' -import { cn } from '@/utils/classnames' -import I18n from '@/context/i18n' -import { getLanguage } from '@/i18n-config/language' -import Confirm from '@/app/components/base/confirm' -import Button from '@/app/components/base/button' -import Indicator from '@/app/components/header/indicator' -import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import Icon from '@/app/components/plugins/card/base/card-icon' -import Title from '@/app/components/plugins/card/base/title' -import OrgInfo from '@/app/components/plugins/card/base/org-info' -import Description from '@/app/components/plugins/card/base/description' -import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' -import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' -import WorkflowToolModal from '@/app/components/tools/workflow-tool' -import Toast from '@/app/components/base/toast' -import Drawer from '@/app/components/base/drawer' +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' +import Button from '@/app/components/base/button' +import Confirm from '@/app/components/base/confirm' +import Drawer from '@/app/components/base/drawer' +import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' +import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' +import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import Indicator from '@/app/components/header/indicator' +import Icon from '@/app/components/plugins/card/base/card-icon' +import Description from '@/app/components/plugins/card/base/description' +import OrgInfo from '@/app/components/plugins/card/base/org-info' +import Title from '@/app/components/plugins/card/base/title' +import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' +import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' +import WorkflowToolModal from '@/app/components/tools/workflow-tool' +import { useAppContext } from '@/context/app-context' +import I18n from '@/context/i18n' +import { useModalContext } from '@/context/modal-context' +import { useProviderContext } from '@/context/provider-context' +import { getLanguage } from '@/i18n-config/language' import { deleteWorkflowTool, fetchBuiltInToolList, @@ -40,12 +41,11 @@ import { updateBuiltInToolCredential, updateCustomCollection, } from '@/service/tools' -import { useModalContext } from '@/context/modal-context' -import { useProviderContext } from '@/context/provider-context' -import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import Loading from '@/app/components/base/loading' -import { useAppContext } from '@/context/app-context' import { useInvalidateAllWorkflowTools } from '@/service/use-tools' +import { cn } from '@/utils/classnames' +import { basePath } from '@/utils/var' +import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' +import ToolItem from './tool-item' type Props = { collection: Collection @@ -236,25 +236,25 @@ const ProviderDetail = ({ positionCenter={false} panelClassName={cn('mb-2 mr-2 mt-[64px] !w-[420px] !max-w-[420px] justify-start rounded-2xl border-[0.5px] border-components-panel-border !bg-components-panel-bg !p-0 shadow-xl')} > -
    +
    -
    +
    </div> - <div className='mb-1 mt-0.5 flex h-4 items-center justify-between'> + <div className="mb-1 mt-0.5 flex h-4 items-center justify-between"> <OrgInfo - packageNameClassName='w-auto' + packageNameClassName="w-auto" orgName={collection.author} packageName={collection.name} /> </div> </div> - <div className='flex gap-1'> + <div className="flex gap-1"> <ActionButton onClick={onHide}> - <RiCloseLine className='h-4 w-4' /> + <RiCloseLine className="h-4 w-4" /> </ActionButton> </div> </div> @@ -262,25 +262,25 @@ const ProviderDetail = ({ {!!collection.description[language] && ( <Description text={collection.description[language]} descriptionLineRows={2}></Description> )} - <div className='flex gap-1 border-b-[0.5px] border-divider-subtle'> + <div className="flex gap-1 border-b-[0.5px] border-divider-subtle"> {collection.type === CollectionType.custom && !isDetailLoading && ( <Button className={cn('my-3 w-full shrink-0')} onClick={() => setIsShowEditCustomCollectionModal(true)} > - <Settings01 className='mr-1 h-4 w-4 text-text-tertiary' /> - <div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div> + <Settings01 className="mr-1 h-4 w-4 text-text-tertiary" /> + <div className="system-sm-medium text-text-secondary">{t('tools.createTool.editAction')}</div> </Button> )} {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( <> <Button - variant='primary' + variant="primary" className={cn('my-3 w-[183px] shrink-0')} > - <a className='flex items-center' href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'> - <div className='system-sm-medium'>{t('tools.openInStudio')}</div> - <LinkExternal02 className='ml-1 h-4 w-4' /> + <a className="flex items-center" href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel="noreferrer" target="_blank"> + <div className="system-sm-medium">{t('tools.openInStudio')}</div> + <LinkExternal02 className="ml-1 h-4 w-4" /> </a> </Button> <Button @@ -288,30 +288,30 @@ const ProviderDetail = ({ onClick={() => setIsShowEditWorkflowToolModal(true)} disabled={!isCurrentWorkspaceManager} > - <div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div> + <div className="system-sm-medium text-text-secondary">{t('tools.createTool.editAction')}</div> </Button> </> )} </div> - <div className='flex min-h-0 flex-1 flex-col pt-3'> - {isDetailLoading && <div className='flex h-[200px]'><Loading type='app' /></div>} + <div className="flex min-h-0 flex-1 flex-col pt-3"> + {isDetailLoading && <div className="flex h-[200px]"><Loading type="app" /></div>} {!isDetailLoading && ( <> <div className="shrink-0"> {(collection.type === CollectionType.builtIn || collection.type === CollectionType.model) && isAuthed && ( - <div className='system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary'> + <div className="system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary"> {t('plugin.detailPanel.actionNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' })} {needAuth && ( <Button - variant='secondary' - size='small' + variant="secondary" + size="small" onClick={() => { if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) showSettingAuthModal() }} disabled={!isCurrentWorkspaceManager} > - <Indicator className='mr-2' color={'green'} /> + <Indicator className="mr-2" color="green" /> {t('tools.auth.authorized')} </Button> )} @@ -319,13 +319,13 @@ const ProviderDetail = ({ )} {(collection.type === CollectionType.builtIn || collection.type === CollectionType.model) && needAuth && !isAuthed && ( <> - <div className='system-sm-semibold-uppercase text-text-secondary'> - <span className=''>{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> - <span className='px-1'>·</span> - <span className='text-util-colors-orange-orange-600'>{t('tools.auth.setup').toLocaleUpperCase()}</span> + <div className="system-sm-semibold-uppercase text-text-secondary"> + <span className="">{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> + <span className="px-1">·</span> + <span className="text-util-colors-orange-orange-600">{t('tools.auth.setup').toLocaleUpperCase()}</span> </div> <Button - variant='primary' + variant="primary" className={cn('my-3 w-full shrink-0')} onClick={() => { if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) @@ -338,17 +338,17 @@ const ProviderDetail = ({ </> )} {(collection.type === CollectionType.custom) && ( - <div className='system-sm-semibold-uppercase text-text-secondary'> - <span className=''>{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> + <div className="system-sm-semibold-uppercase text-text-secondary"> + <span className="">{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> </div> )} {(collection.type === CollectionType.workflow) && ( - <div className='system-sm-semibold-uppercase text-text-secondary'> - <span className=''>{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span> + <div className="system-sm-semibold-uppercase text-text-secondary"> + <span className="">{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span> </div> )} </div> - <div className='mt-1 flex-1 overflow-y-auto py-2'> + <div className="mt-1 flex-1 overflow-y-auto py-2"> {collection.type !== CollectionType.workflow && toolList.map(tool => ( <ToolItem key={tool.name} @@ -360,13 +360,13 @@ const ProviderDetail = ({ /> ))} {collection.type === CollectionType.workflow && (customCollection as WorkflowToolProviderResponse)?.tool?.parameters.map(item => ( - <div key={item.name} className='mb-1 py-1'> - <div className='mb-1 flex items-center gap-2'> - <span className='code-sm-semibold text-text-secondary'>{item.name}</span> - <span className='system-xs-regular text-text-tertiary'>{item.type}</span> - <span className='system-xs-medium text-text-warning-secondary'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span> + <div key={item.name} className="mb-1 py-1"> + <div className="mb-1 flex items-center gap-2"> + <span className="code-sm-semibold text-text-secondary">{item.name}</span> + <span className="system-xs-regular text-text-tertiary">{item.type}</span> + <span className="system-xs-medium text-text-warning-secondary">{item.required ? t('tools.createTool.toolInput.required') : ''}</span> </div> - <div className='system-xs-regular text-text-tertiary'>{item.llm_description}</div> + <div className="system-xs-regular text-text-tertiary">{item.llm_description}</div> </div> ))} </div> diff --git a/web/app/components/tools/provider/empty.tsx b/web/app/components/tools/provider/empty.tsx index bbd0f6fec1..7e916ba62f 100644 --- a/web/app/components/tools/provider/empty.tsx +++ b/web/app/components/tools/provider/empty.tsx @@ -1,11 +1,12 @@ 'use client' -import { useTranslation } from 'react-i18next' -import { ToolTypeEnum } from '../../workflow/block-selector/types' import { RiArrowRightUpLine } from '@remixicon/react' import Link from 'next/link' +import { useTranslation } from 'react-i18next' +import useTheme from '@/hooks/use-theme' import { cn } from '@/utils/classnames' import { NoToolPlaceholder } from '../../base/icons/src/vender/other' -import useTheme from '@/hooks/use-theme' +import { ToolTypeEnum } from '../../workflow/block-selector/types' + type Props = { type?: ToolTypeEnum isAgent?: boolean @@ -35,14 +36,16 @@ const Empty = ({ const hasTitle = t(`tools.addToolModal.${renderType}.title`) !== `tools.addToolModal.${renderType}.title` return ( - <div className='flex flex-col items-center justify-center'> + <div className="flex flex-col items-center justify-center"> <NoToolPlaceholder className={theme === 'dark' ? 'invert' : ''} /> - <div className='mb-1 mt-2 text-[13px] font-medium leading-[18px] text-text-primary'> + <div className="mb-1 mt-2 text-[13px] font-medium leading-[18px] text-text-primary"> {hasTitle ? t(`tools.addToolModal.${renderType}.title`) : 'No tools available'} </div> {(!isAgent && hasTitle) && ( <Comp className={cn('flex items-center text-[13px] leading-[18px] text-text-tertiary', hasLink && 'cursor-pointer hover:text-text-accent')} {...linkProps}> - {t(`tools.addToolModal.${renderType}.tip`)} {hasLink && <RiArrowRightUpLine className='ml-0.5 h-3 w-3' />} + {t(`tools.addToolModal.${renderType}.tip`)} + {' '} + {hasLink && <RiArrowRightUpLine className="ml-0.5 h-3 w-3" />} </Comp> )} </div> diff --git a/web/app/components/tools/provider/tool-item.tsx b/web/app/components/tools/provider/tool-item.tsx index 7edf1c61f1..3248e3c024 100644 --- a/web/app/components/tools/provider/tool-item.tsx +++ b/web/app/components/tools/provider/tool-item.tsx @@ -1,11 +1,11 @@ 'use client' +import type { Collection, Tool } from '../types' import React, { useState } from 'react' import { useContext } from 'use-context-selector' -import type { Collection, Tool } from '../types' -import { cn } from '@/utils/classnames' +import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool' import I18n from '@/context/i18n' import { getLanguage } from '@/i18n-config/language' -import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool' +import { cn } from '@/utils/classnames' type Props = { disabled?: boolean @@ -32,8 +32,8 @@ const ToolItem = ({ className={cn('bg-components-panel-item-bg cursor-pointer rounded-xl border-[0.5px] border-components-panel-border-subtle px-4 py-3 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover', disabled && '!cursor-not-allowed opacity-50')} onClick={() => !disabled && setShowDetail(true)} > - <div className='system-md-semibold pb-0.5 text-text-secondary'>{tool.label[language]}</div> - <div className='system-xs-regular line-clamp-2 text-text-tertiary' title={tool.description[language]}>{tool.description[language]}</div> + <div className="system-md-semibold pb-0.5 text-text-secondary">{tool.label[language]}</div> + <div className="system-xs-regular line-clamp-2 text-text-tertiary" title={tool.description[language]}>{tool.description[language]}</div> </div> {showDetail && ( <SettingBuiltInTool diff --git a/web/app/components/tools/setting/build-in/config-credentials.tsx b/web/app/components/tools/setting/build-in/config-credentials.tsx index 5effeaa47d..783d4a6476 100644 --- a/web/app/components/tools/setting/build-in/config-credentials.tsx +++ b/web/app/components/tools/setting/build-in/config-credentials.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' +import type { Collection } from '../../types' +import { noop } from 'lodash-es' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { addDefaultValue, toolCredentialToFormSchemas } from '../../utils/to-form-schema' -import type { Collection } from '../../types' -import { cn } from '@/utils/classnames' -import Drawer from '@/app/components/base/drawer-plus' import Button from '@/app/components/base/button' -import Toast from '@/app/components/base/toast' -import { fetchBuiltInToolCredential, fetchBuiltInToolCredentialSchema } from '@/service/tools' -import Loading from '@/app/components/base/loading' -import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' +import Drawer from '@/app/components/base/drawer-plus' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' +import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { noop } from 'lodash-es' +import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' +import { fetchBuiltInToolCredential, fetchBuiltInToolCredentialSchema } from '@/service/tools' +import { cn } from '@/utils/classnames' +import { addDefaultValue, toolCredentialToFormSchemas } from '../../utils/to-form-schema' type Props = { collection: Collection @@ -72,56 +72,57 @@ const ConfigCredential: FC<Props> = ({ onHide={onCancel} title={t('tools.auth.setupModalTitle') as string} titleDescription={t('tools.auth.setupModalTitleDescription') as string} - panelClassName='mt-[64px] mb-2 !w-[420px] border-components-panel-border' - maxWidthClassName='!max-w-[420px]' - height='calc(100vh - 64px)' - contentClassName='!bg-components-panel-bg' - headerClassName='!border-b-divider-subtle' - body={ - - <div className='h-full px-6 py-3'> + panelClassName="mt-[64px] mb-2 !w-[420px] border-components-panel-border" + maxWidthClassName="!max-w-[420px]" + height="calc(100vh - 64px)" + contentClassName="!bg-components-panel-bg" + headerClassName="!border-b-divider-subtle" + body={( + <div className="h-full px-6 py-3"> {!credentialSchema - ? <Loading type='app' /> + ? <Loading type="app" /> : ( - <> - <Form - value={tempCredential} - onChange={(v) => { - setTempCredential(v) - }} - formSchemas={credentialSchema} - isEditMode={true} - showOnVariableMap={{}} - validating={false} - inputClassName='!bg-components-input-bg-normal' - fieldMoreInfo={item => item.url - ? (<a - href={item.url} - target='_blank' rel='noopener noreferrer' - className='inline-flex items-center text-xs text-text-accent' - > - {t('tools.howToGet')} - <LinkExternal02 className='ml-1 h-3 w-3' /> - </a>) - : null} - /> - <div className={cn((collection.is_team_authorization && !isHideRemoveBtn) ? 'justify-between' : 'justify-end', 'mt-2 flex ')} > - { - (collection.is_team_authorization && !isHideRemoveBtn) && ( - <Button onClick={onRemove}>{t('common.operation.remove')}</Button> - ) - } - <div className='flex space-x-2'> - <Button onClick={onCancel}>{t('common.operation.cancel')}</Button> - <Button loading={isLoading || isSaving} disabled={isLoading || isSaving} variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button> + <> + <Form + value={tempCredential} + onChange={(v) => { + setTempCredential(v) + }} + formSchemas={credentialSchema} + isEditMode={true} + showOnVariableMap={{}} + validating={false} + inputClassName="!bg-components-input-bg-normal" + fieldMoreInfo={item => item.url + ? ( + <a + href={item.url} + target="_blank" + rel="noopener noreferrer" + className="inline-flex items-center text-xs text-text-accent" + > + {t('tools.howToGet')} + <LinkExternal02 className="ml-1 h-3 w-3" /> + </a> + ) + : null} + /> + <div className={cn((collection.is_team_authorization && !isHideRemoveBtn) ? 'justify-between' : 'justify-end', 'mt-2 flex ')}> + { + (collection.is_team_authorization && !isHideRemoveBtn) && ( + <Button onClick={onRemove}>{t('common.operation.remove')}</Button> + ) + } + <div className="flex space-x-2"> + <Button onClick={onCancel}>{t('common.operation.cancel')}</Button> + <Button loading={isLoading || isSaving} disabled={isLoading || isSaving} variant="primary" onClick={handleSave}>{t('common.operation.save')}</Button> + </div> </div> - </div> - </> - ) - } + </> + )} - </div > - } + </div> + )} isShowMask={true} clickOutsideNotOpen={false} /> diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 69f5dd5f2f..e3d1f660fd 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -1,7 +1,7 @@ -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import type { TriggerEventParameter } from '../../plugins/types' import type { ToolCredential, ToolParameter } from '../types' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' export const toType = (type: string) => { switch (type) { @@ -76,7 +76,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => { return formSchemas } -export const addDefaultValue = (value: Record<string, any>, formSchemas: { variable: string; type: string; default?: any }[]) => { +export const addDefaultValue = (value: Record<string, any>, formSchemas: { variable: string, type: string, default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] @@ -122,7 +122,7 @@ const correctInitialData = (type: string, target: any, defaultValue: any) => { return target } -export const generateFormValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { +export const generateFormValue = (value: Record<string, any>, formSchemas: { variable: string, default?: any, type: string }[], isReasoning = false) => { const newValues = {} as any formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] @@ -162,7 +162,7 @@ export const getStructureValue = (value: Record<string, any>) => { return newValue } -export const getConfiguredValue = (value: Record<string, any>, formSchemas: { variable: string; type: string; default?: any }[]) => { +export const getConfiguredValue = (value: Record<string, any>, formSchemas: { variable: string, type: string, default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] @@ -187,7 +187,7 @@ const getVarKindType = (type: FormTypeEnum) => { return VarKindType.mixed } -export const generateAgentToolValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { +export const generateAgentToolValue = (value: Record<string, any>, formSchemas: { variable: string, default?: any, type: string }[], isReasoning = false) => { const newValues = {} as any if (!isReasoning) { formSchemas.forEach((formSchema) => { diff --git a/web/app/components/tools/workflow-tool/configure-button.tsx b/web/app/components/tools/workflow-tool/configure-button.tsx index 0feee28abf..0d28576de5 100644 --- a/web/app/components/tools/workflow-tool/configure-button.tsx +++ b/web/app/components/tools/workflow-tool/configure-button.tsx @@ -1,21 +1,21 @@ 'use client' -import React, { useCallback, useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useRouter } from 'next/navigation' -import { RiArrowRightUpLine, RiHammerLine } from '@remixicon/react' -import Divider from '../../base/divider' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' -import Indicator from '@/app/components/header/indicator' -import WorkflowToolModal from '@/app/components/tools/workflow-tool' -import Loading from '@/app/components/base/loading' -import Toast from '@/app/components/base/toast' -import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types' import type { InputVar, Variable } from '@/app/components/workflow/types' import type { PublishWorkflowParams } from '@/types/workflow' +import { RiArrowRightUpLine, RiHammerLine } from '@remixicon/react' +import { useRouter } from 'next/navigation' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' +import Indicator from '@/app/components/header/indicator' +import WorkflowToolModal from '@/app/components/tools/workflow-tool' import { useAppContext } from '@/context/app-context' +import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' import { useInvalidateAllWorkflowTools } from '@/service/use-tools' +import { cn } from '@/utils/classnames' +import Divider from '../../base/divider' type Props = { disabled: boolean @@ -132,11 +132,11 @@ const WorkflowToolConfigureButton = ({ privacy_policy: detail?.privacy_policy || '', ...(published ? { - workflow_tool_id: detail?.workflow_tool_id, - } + workflow_tool_id: detail?.workflow_tool_id, + } : { - workflow_app_id: workflowAppId, - }), + workflow_app_id: workflowAppId, + }), } }, [detail, published, workflowAppId, icon, name, description, inputs]) @@ -197,75 +197,76 @@ const WorkflowToolConfigureButton = ({ return ( <> - <Divider type='horizontal' className='h-px bg-divider-subtle' /> + <Divider type="horizontal" className="h-px bg-divider-subtle" /> {(!published || !isLoading) && ( <div className={cn( 'group rounded-lg bg-background-section-burn transition-colors', disabled || !isCurrentWorkspaceManager ? 'cursor-not-allowed opacity-60 shadow-xs' : 'cursor-pointer', !disabled && !published && isCurrentWorkspaceManager && 'hover:bg-state-accent-hover', - )}> + )} + > {isCurrentWorkspaceManager ? ( - <div - className='flex items-center justify-start gap-2 p-2 pl-2.5' - onClick={() => !disabled && !published && setShowModal(true)} - > - <RiHammerLine className={cn('relative h-4 w-4 text-text-secondary', !disabled && !published && 'group-hover:text-text-accent')} /> <div - title={t('workflow.common.workflowAsTool') || ''} - className={cn('system-sm-medium shrink grow basis-0 truncate text-text-secondary', !disabled && !published && 'group-hover:text-text-accent')} + className="flex items-center justify-start gap-2 p-2 pl-2.5" + onClick={() => !disabled && !published && setShowModal(true)} > - {t('workflow.common.workflowAsTool')} + <RiHammerLine className={cn('relative h-4 w-4 text-text-secondary', !disabled && !published && 'group-hover:text-text-accent')} /> + <div + title={t('workflow.common.workflowAsTool') || ''} + className={cn('system-sm-medium shrink grow basis-0 truncate text-text-secondary', !disabled && !published && 'group-hover:text-text-accent')} + > + {t('workflow.common.workflowAsTool')} + </div> + {!published && ( + <span className="system-2xs-medium-uppercase shrink-0 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 text-text-tertiary"> + {t('workflow.common.configureRequired')} + </span> + )} </div> - {!published && ( - <span className='system-2xs-medium-uppercase shrink-0 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 text-text-tertiary'> - {t('workflow.common.configureRequired')} - </span> - )} - </div> - ) + ) : ( - <div - className='flex items-center justify-start gap-2 p-2 pl-2.5' - > - <RiHammerLine className='h-4 w-4 text-text-tertiary' /> <div - title={t('workflow.common.workflowAsTool') || ''} - className='system-sm-medium shrink grow basis-0 truncate text-text-tertiary' + className="flex items-center justify-start gap-2 p-2 pl-2.5" > - {t('workflow.common.workflowAsTool')} + <RiHammerLine className="h-4 w-4 text-text-tertiary" /> + <div + title={t('workflow.common.workflowAsTool') || ''} + className="system-sm-medium shrink grow basis-0 truncate text-text-tertiary" + > + {t('workflow.common.workflowAsTool')} + </div> </div> - </div> - )} + )} {disabledReason && ( - <div className='mt-1 px-2.5 pb-2 text-xs leading-[18px] text-text-tertiary'> + <div className="mt-1 px-2.5 pb-2 text-xs leading-[18px] text-text-tertiary"> {disabledReason} </div> )} {published && ( - <div className='border-t-[0.5px] border-divider-regular px-2.5 py-2'> - <div className='flex justify-between gap-x-2'> + <div className="border-t-[0.5px] border-divider-regular px-2.5 py-2"> + <div className="flex justify-between gap-x-2"> <Button - size='small' - className='w-[140px]' + size="small" + className="w-[140px]" onClick={() => setShowModal(true)} disabled={!isCurrentWorkspaceManager || disabled} > {t('workflow.common.configure')} - {outdated && <Indicator className='ml-1' color={'yellow'} />} + {outdated && <Indicator className="ml-1" color="yellow" />} </Button> <Button - size='small' - className='w-[140px]' + size="small" + className="w-[140px]" onClick={() => router.push('/tools?category=workflow')} disabled={disabled} > {t('workflow.common.manageInTools')} - <RiArrowRightUpLine className='ml-1 h-4 w-4' /> + <RiArrowRightUpLine className="ml-1 h-4 w-4" /> </Button> </div> {outdated && ( - <div className='mt-1 text-xs leading-[18px] text-text-warning'> + <div className="mt-1 text-xs leading-[18px] text-text-warning"> {t('workflow.common.workflowAsToolTip')} </div> )} @@ -273,7 +274,7 @@ const WorkflowToolConfigureButton = ({ )} </div> )} - {published && isLoading && <div className='pt-2'><Loading type='app' /></div>} + {published && isLoading && <div className="pt-2"><Loading type="app" /></div>} {showModal && ( <WorkflowToolModal isAdd={!published} diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx index 064a4d3cda..972ac8c882 100644 --- a/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx +++ b/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx @@ -1,6 +1,6 @@ -import React from 'react' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import React from 'react' import ConfirmModal from './index' // Test utilities diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx index e76ad4add4..f90774cb32 100644 --- a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx +++ b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx @@ -1,12 +1,12 @@ 'use client' -import { useTranslation } from 'react-i18next' import { RiCloseLine } from '@remixicon/react' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { noop } from 'lodash-es' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' +import Modal from '@/app/components/base/modal' +import { cn } from '@/utils/classnames' type ConfirmModalProps = { show: boolean @@ -23,19 +23,19 @@ const ConfirmModal = ({ show, onConfirm, onClose }: ConfirmModalProps) => { isShow={show} onClose={noop} > - <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onClose}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="absolute right-4 top-4 cursor-pointer p-2" onClick={onClose}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> - <div className='h-12 w-12 rounded-xl border-[0.5px] border-divider-regular bg-background-section p-3 shadow-xl'> - <AlertTriangle className='h-6 w-6 text-[rgb(247,144,9)]' /> + <div className="h-12 w-12 rounded-xl border-[0.5px] border-divider-regular bg-background-section p-3 shadow-xl"> + <AlertTriangle className="h-6 w-6 text-[rgb(247,144,9)]" /> </div> - <div className='relative mt-3 text-xl font-semibold leading-[30px] text-text-primary'>{t('tools.createTool.confirmTitle')}</div> - <div className='my-1 text-sm leading-5 text-text-tertiary'> + <div className="relative mt-3 text-xl font-semibold leading-[30px] text-text-primary">{t('tools.createTool.confirmTitle')}</div> + <div className="my-1 text-sm leading-5 text-text-tertiary"> {t('tools.createTool.confirmTip')} </div> - <div className='flex items-center justify-end pt-6'> - <div className='flex items-center'> - <Button className='mr-2' onClick={onClose}>{t('common.operation.cancel')}</Button> + <div className="flex items-center justify-end pt-6"> + <div className="flex items-center"> + <Button className="mr-2" onClick={onClose}>{t('common.operation.cancel')}</Button> <Button variant="warning" onClick={onConfirm}>{t('common.operation.confirm')}</Button> </div> </div> diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx index 3d70f1f424..af226e7071 100644 --- a/web/app/components/tools/workflow-tool/index.tsx +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -1,24 +1,24 @@ 'use client' import type { FC } from 'react' +import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types' +import { RiErrorWarningLine } from '@remixicon/react' +import { produce } from 'immer' import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types' -import { buildWorkflowOutputParameters } from './utils' -import { cn } from '@/utils/classnames' +import AppIcon from '@/app/components/base/app-icon' +import Button from '@/app/components/base/button' import Drawer from '@/app/components/base/drawer-plus' +import EmojiPicker from '@/app/components/base/emoji-picker' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' -import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' -import EmojiPicker from '@/app/components/base/emoji-picker' -import AppIcon from '@/app/components/base/app-icon' -import MethodSelector from '@/app/components/tools/workflow-tool/method-selector' +import Tooltip from '@/app/components/base/tooltip' import LabelSelector from '@/app/components/tools/labels/selector' import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal' -import Tooltip from '@/app/components/base/tooltip' +import MethodSelector from '@/app/components/tools/workflow-tool/method-selector' import { VarType } from '@/app/components/workflow/types' -import { RiErrorWarningLine } from '@remixicon/react' +import { cn } from '@/utils/classnames' +import { buildWorkflowOutputParameters } from './utils' type Props = { isAdd?: boolean @@ -152,20 +152,24 @@ const WorkflowToolAsModal: FC<Props> = ({ isShow onHide={onHide} title={t('workflow.common.workflowAsTool')!} - panelClassName='mt-2 !w-[640px]' - maxWidthClassName='!max-w-[640px]' - height='calc(100vh - 16px)' - headerClassName='!border-b-divider' - body={ - <div className='flex h-full flex-col'> - <div className='h-0 grow space-y-4 overflow-y-auto px-6 py-3'> + panelClassName="mt-2 !w-[640px]" + maxWidthClassName="!max-w-[640px]" + height="calc(100vh - 16px)" + headerClassName="!border-b-divider" + body={( + <div className="flex h-full flex-col"> + <div className="h-0 grow space-y-4 overflow-y-auto px-6 py-3"> {/* name & icon */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div> - <div className='flex items-center justify-between gap-3'> - <AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' iconType='emoji' icon={emoji.content} background={emoji.background} /> + <div className="system-sm-medium py-2 text-text-primary"> + {t('tools.createTool.name')} + {' '} + <span className="ml-1 text-red-500">*</span> + </div> + <div className="flex items-center justify-between gap-3"> + <AppIcon size="large" onClick={() => { setShowEmojiPicker(true) }} className="cursor-pointer" iconType="emoji" icon={emoji.content} background={emoji.background} /> <Input - className='h-10 grow' + className="h-10 grow" placeholder={t('tools.createTool.toolNamePlaceHolder')!} value={label} onChange={e => setLabel(e.target.value)} @@ -174,29 +178,31 @@ const WorkflowToolAsModal: FC<Props> = ({ </div> {/* name for tool call */} <div> - <div className='system-sm-medium flex items-center py-2 text-text-primary'> - {t('tools.createTool.nameForToolCall')} <span className='ml-1 text-red-500'>*</span> + <div className="system-sm-medium flex items-center py-2 text-text-primary"> + {t('tools.createTool.nameForToolCall')} + {' '} + <span className="ml-1 text-red-500">*</span> <Tooltip - popupContent={ - <div className='w-[180px]'> + popupContent={( + <div className="w-[180px]"> {t('tools.createTool.nameForToolCallPlaceHolder')} </div> - } + )} /> </div> <Input - className='h-10' + className="h-10" placeholder={t('tools.createTool.nameForToolCallPlaceHolder')!} value={name} onChange={e => setName(e.target.value)} /> {!isNameValid(name) && ( - <div className='text-xs leading-[18px] text-red-500'>{t('tools.createTool.nameForToolCallTip')}</div> + <div className="text-xs leading-[18px] text-red-500">{t('tools.createTool.nameForToolCallTip')}</div> )} </div> {/* description */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.description')}</div> + <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.description')}</div> <Textarea placeholder={t('tools.createTool.descriptionPlaceholder') || ''} value={description} @@ -205,11 +211,11 @@ const WorkflowToolAsModal: FC<Props> = ({ </div> {/* Tool Input */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.toolInput.title')}</div> - <div className='w-full overflow-x-auto rounded-lg border border-divider-regular'> - <table className='w-full text-xs font-normal leading-[18px] text-text-secondary'> - <thead className='uppercase text-text-tertiary'> - <tr className='border-b border-divider-regular'> + <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.toolInput.title')}</div> + <div className="w-full overflow-x-auto rounded-lg border border-divider-regular"> + <table className="w-full text-xs font-normal leading-[18px] text-text-secondary"> + <thead className="uppercase text-text-tertiary"> + <tr className="border-b border-divider-regular"> <th className="w-[156px] p-2 pl-3 font-medium">{t('tools.createTool.toolInput.name')}</th> <th className="w-[102px] p-2 pl-3 font-medium">{t('tools.createTool.toolInput.method')}</th> <th className="p-2 pl-3 font-medium">{t('tools.createTool.toolInput.description')}</th> @@ -217,21 +223,22 @@ const WorkflowToolAsModal: FC<Props> = ({ </thead> <tbody> {parameters.map((item, index) => ( - <tr key={index} className='border-b border-divider-regular last:border-0'> + <tr key={index} className="border-b border-divider-regular last:border-0"> <td className="max-w-[156px] p-2 pl-3"> - <div className='text-[13px] leading-[18px]'> - <div title={item.name} className='flex'> - <span className='truncate font-medium text-text-primary'>{item.name}</span> - <span className='shrink-0 pl-1 text-xs leading-[18px] text-[#ec4a0a]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span> + <div className="text-[13px] leading-[18px]"> + <div title={item.name} className="flex"> + <span className="truncate font-medium text-text-primary">{item.name}</span> + <span className="shrink-0 pl-1 text-xs leading-[18px] text-[#ec4a0a]">{item.required ? t('tools.createTool.toolInput.required') : ''}</span> </div> - <div className='text-text-tertiary'>{item.type}</div> + <div className="text-text-tertiary">{item.type}</div> </div> </td> <td> {item.name === '__image' && ( <div className={cn( 'flex h-9 min-h-[56px] cursor-default items-center gap-1 bg-transparent px-3 py-2', - )}> + )} + > <div className={cn('grow truncate text-[13px] leading-[18px] text-text-secondary')}> {t('tools.createTool.toolInput.methodParameter')} </div> @@ -243,8 +250,8 @@ const WorkflowToolAsModal: FC<Props> = ({ </td> <td className="w-[236px] p-2 pl-3 text-text-tertiary"> <input - type='text' - className='w-full appearance-none bg-transparent text-[13px] font-normal leading-[18px] text-text-secondary caret-primary-600 outline-none placeholder:text-text-quaternary' + type="text" + className="w-full appearance-none bg-transparent text-[13px] font-normal leading-[18px] text-text-secondary caret-primary-600 outline-none placeholder:text-text-quaternary" placeholder={t('tools.createTool.toolInput.descriptionPlaceholder')!} value={item.description} onChange={e => handleParameterChange('description', e.target.value, index)} @@ -258,42 +265,44 @@ const WorkflowToolAsModal: FC<Props> = ({ </div> {/* Tool Output */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.toolOutput.title')}</div> - <div className='w-full overflow-x-auto rounded-lg border border-divider-regular'> - <table className='w-full text-xs font-normal leading-[18px] text-text-secondary'> - <thead className='uppercase text-text-tertiary'> - <tr className='border-b border-divider-regular'> + <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.toolOutput.title')}</div> + <div className="w-full overflow-x-auto rounded-lg border border-divider-regular"> + <table className="w-full text-xs font-normal leading-[18px] text-text-secondary"> + <thead className="uppercase text-text-tertiary"> + <tr className="border-b border-divider-regular"> <th className="w-[156px] p-2 pl-3 font-medium">{t('tools.createTool.name')}</th> <th className="p-2 pl-3 font-medium">{t('tools.createTool.toolOutput.description')}</th> </tr> </thead> <tbody> {[...reservedOutputParameters, ...outputParameters].map((item, index) => ( - <tr key={index} className='border-b border-divider-regular last:border-0'> + <tr key={index} className="border-b border-divider-regular last:border-0"> <td className="max-w-[156px] p-2 pl-3"> - <div className='text-[13px] leading-[18px]'> - <div title={item.name} className='flex items-center'> - <span className='truncate font-medium text-text-primary'>{item.name}</span> - <span className='shrink-0 pl-1 text-xs leading-[18px] text-[#ec4a0a]'>{item.reserved ? t('tools.createTool.toolOutput.reserved') : ''}</span> + <div className="text-[13px] leading-[18px]"> + <div title={item.name} className="flex items-center"> + <span className="truncate font-medium text-text-primary">{item.name}</span> + <span className="shrink-0 pl-1 text-xs leading-[18px] text-[#ec4a0a]">{item.reserved ? t('tools.createTool.toolOutput.reserved') : ''}</span> { - !item.reserved && isOutputParameterReserved(item.name) ? ( - <Tooltip - popupContent={ - <div className='w-[180px]'> - {t('tools.createTool.toolOutput.reservedParameterDuplicateTip')} - </div> - } - > - <RiErrorWarningLine className='h-3 w-3 text-text-warning-secondary' /> - </Tooltip> - ) : null + !item.reserved && isOutputParameterReserved(item.name) + ? ( + <Tooltip + popupContent={( + <div className="w-[180px]"> + {t('tools.createTool.toolOutput.reservedParameterDuplicateTip')} + </div> + )} + > + <RiErrorWarningLine className="h-3 w-3 text-text-warning-secondary" /> + </Tooltip> + ) + : null } </div> - <div className='text-text-tertiary'>{item.type}</div> + <div className="text-text-tertiary">{item.type}</div> </div> </td> <td className="w-[236px] p-2 pl-3 text-text-tertiary"> - <span className='text-[13px] font-normal leading-[18px] text-text-secondary'>{item.description}</span> + <span className="text-[13px] font-normal leading-[18px] text-text-secondary">{item.description}</span> </td> </tr> ))} @@ -303,47 +312,55 @@ const WorkflowToolAsModal: FC<Props> = ({ </div> {/* Tags */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.toolInput.label')}</div> + <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.toolInput.label')}</div> <LabelSelector value={labels} onChange={handleLabelSelect} /> </div> {/* Privacy Policy */} <div> - <div className='system-sm-medium py-2 text-text-primary'>{t('tools.createTool.privacyPolicy')}</div> + <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.privacyPolicy')}</div> <Input - className='h-10' + className="h-10" value={privacyPolicy} onChange={e => setPrivacyPolicy(e.target.value)} - placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} /> + placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} + /> </div> </div> - <div className={cn((!isAdd && onRemove) ? 'justify-between' : 'justify-end', 'mt-2 flex shrink-0 rounded-b-[10px] border-t border-divider-regular bg-background-section-burn px-6 py-4')} > + <div className={cn((!isAdd && onRemove) ? 'justify-between' : 'justify-end', 'mt-2 flex shrink-0 rounded-b-[10px] border-t border-divider-regular bg-background-section-burn px-6 py-4')}> {!isAdd && onRemove && ( - <Button variant='warning' onClick={onRemove}>{t('common.operation.delete')}</Button> + <Button variant="warning" onClick={onRemove}>{t('common.operation.delete')}</Button> )} - <div className='flex space-x-2 '> + <div className="flex space-x-2 "> <Button onClick={onHide}>{t('common.operation.cancel')}</Button> - <Button variant='primary' onClick={() => { - if (isAdd) - onConfirm() - else - setShowModal(true) - }}>{t('common.operation.save')}</Button> + <Button + variant="primary" + onClick={() => { + if (isAdd) + onConfirm() + else + setShowModal(true) + }} + > + {t('common.operation.save')} + </Button> </div> </div> </div> - } + )} isShowMask={true} clickOutsideNotOpen={true} /> - {showEmojiPicker && <EmojiPicker - onSelect={(icon, icon_background) => { - setEmoji({ content: icon, background: icon_background }) - setShowEmojiPicker(false) - }} - onClose={() => { - setShowEmojiPicker(false) - }} - />} + {showEmojiPicker && ( + <EmojiPicker + onSelect={(icon, icon_background) => { + setEmoji({ content: icon, background: icon_background }) + setShowEmojiPicker(false) + }} + onClose={() => { + setShowEmojiPicker(false) + }} + /> + )} {showModal && ( <ConfirmModal show={showModal} diff --git a/web/app/components/tools/workflow-tool/method-selector.tsx b/web/app/components/tools/workflow-tool/method-selector.tsx index 03eb651ba3..c5a259ef43 100644 --- a/web/app/components/tools/workflow-tool/method-selector.tsx +++ b/web/app/components/tools/workflow-tool/method-selector.tsx @@ -1,14 +1,14 @@ import type { FC } from 'react' +import { RiArrowDownSLine } from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import { Check } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { Check } from '@/app/components/base/icons/src/vender/line/general' +import { cn } from '@/utils/classnames' type MethodSelectorProps = { value?: string @@ -25,46 +25,47 @@ const MethodSelector: FC<MethodSelectorProps> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={4} > - <div className='relative'> + <div className="relative"> <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} - className='block' + className="block" > <div className={cn( 'flex h-9 min-h-[56px] cursor-pointer items-center gap-1 bg-transparent px-3 py-2 hover:bg-background-section-burn', open && '!bg-background-section-burn hover:bg-background-section-burn', - )}> + )} + > <div className={cn('grow truncate text-[13px] leading-[18px] text-text-secondary')}> {value === 'llm' ? t('tools.createTool.toolInput.methodParameter') : t('tools.createTool.toolInput.methodSetting')} </div> - <div className='ml-1 shrink-0 text-text-secondary opacity-60'> - <RiArrowDownSLine className='h-4 w-4' /> + <div className="ml-1 shrink-0 text-text-secondary opacity-60"> + <RiArrowDownSLine className="h-4 w-4" /> </div> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1040]'> - <div className='relative w-[320px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'> - <div className='p-1'> - <div className='cursor-pointer rounded-lg py-2.5 pl-3 pr-2 hover:bg-components-panel-on-panel-item-bg-hover' onClick={() => onChange('llm')}> - <div className='item-center flex gap-1'> - <div className='h-4 w-4 shrink-0'> - {value === 'llm' && <Check className='h-4 w-4 shrink-0 text-text-accent' />} + <PortalToFollowElemContent className="z-[1040]"> + <div className="relative w-[320px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm"> + <div className="p-1"> + <div className="cursor-pointer rounded-lg py-2.5 pl-3 pr-2 hover:bg-components-panel-on-panel-item-bg-hover" onClick={() => onChange('llm')}> + <div className="item-center flex gap-1"> + <div className="h-4 w-4 shrink-0"> + {value === 'llm' && <Check className="h-4 w-4 shrink-0 text-text-accent" />} </div> - <div className='text-[13px] font-medium leading-[18px] text-text-secondary'>{t('tools.createTool.toolInput.methodParameter')}</div> + <div className="text-[13px] font-medium leading-[18px] text-text-secondary">{t('tools.createTool.toolInput.methodParameter')}</div> </div> - <div className='pl-5 text-[13px] leading-[18px] text-text-tertiary'>{t('tools.createTool.toolInput.methodParameterTip')}</div> + <div className="pl-5 text-[13px] leading-[18px] text-text-tertiary">{t('tools.createTool.toolInput.methodParameterTip')}</div> </div> - <div className='cursor-pointer rounded-lg py-2.5 pl-3 pr-2 hover:bg-components-panel-on-panel-item-bg-hover' onClick={() => onChange('form')}> - <div className='item-center flex gap-1'> - <div className='h-4 w-4 shrink-0'> - {value === 'form' && <Check className='h-4 w-4 shrink-0 text-text-accent' />} + <div className="cursor-pointer rounded-lg py-2.5 pl-3 pr-2 hover:bg-components-panel-on-panel-item-bg-hover" onClick={() => onChange('form')}> + <div className="item-center flex gap-1"> + <div className="h-4 w-4 shrink-0"> + {value === 'form' && <Check className="h-4 w-4 shrink-0 text-text-accent" />} </div> - <div className='text-[13px] font-medium leading-[18px] text-text-secondary'>{t('tools.createTool.toolInput.methodSetting')}</div> + <div className="text-[13px] font-medium leading-[18px] text-text-secondary">{t('tools.createTool.toolInput.methodSetting')}</div> </div> - <div className='pl-5 text-[13px] leading-[18px] text-text-tertiary'>{t('tools.createTool.toolInput.methodSettingTip')}</div> + <div className="pl-5 text-[13px] leading-[18px] text-text-tertiary">{t('tools.createTool.toolInput.methodSettingTip')}</div> </div> </div> </div> diff --git a/web/app/components/tools/workflow-tool/utils.test.ts b/web/app/components/tools/workflow-tool/utils.test.ts index fef8c05489..bc2dc98c19 100644 --- a/web/app/components/tools/workflow-tool/utils.test.ts +++ b/web/app/components/tools/workflow-tool/utils.test.ts @@ -1,5 +1,5 @@ -import { VarType } from '@/app/components/workflow/types' import type { WorkflowToolProviderOutputParameter, WorkflowToolProviderOutputSchema } from '../types' +import { VarType } from '@/app/components/workflow/types' import { buildWorkflowOutputParameters } from './utils' describe('buildWorkflowOutputParameters', () => { diff --git a/web/app/components/workflow-app/components/workflow-children.tsx b/web/app/components/workflow-app/components/workflow-children.tsx index 1c8ed0cdf9..2634e8da2a 100644 --- a/web/app/components/workflow-app/components/workflow-children.tsx +++ b/web/app/components/workflow-app/components/workflow-children.tsx @@ -1,32 +1,31 @@ +import type { + PluginDefaultValue, + TriggerDefaultValue, +} from '@/app/components/workflow/block-selector/types' +import type { EnvironmentVariable } from '@/app/components/workflow/types' +import dynamic from 'next/dynamic' import { memo, useCallback, useState, } from 'react' -import type { EnvironmentVariable } from '@/app/components/workflow/types' -import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants' -import { START_INITIAL_POSITION } from '@/app/components/workflow/constants' -import { generateNewNode } from '@/app/components/workflow/utils' -import { useStore } from '@/app/components/workflow/store' import { useStoreApi } from 'reactflow' -import PluginDependency from '../../workflow/plugin-dependency' +import { DSL_EXPORT_CHECK, START_INITIAL_POSITION } from '@/app/components/workflow/constants' import { useAutoGenerateWebhookUrl, useDSL, usePanelInteractions, } from '@/app/components/workflow/hooks' import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft' +import { useStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' +import { generateNewNode } from '@/app/components/workflow/utils' import { useEventEmitterContextContext } from '@/context/event-emitter' +import PluginDependency from '../../workflow/plugin-dependency' +import { useAvailableNodesMetaData } from '../hooks' +import { useAutoOnboarding } from '../hooks/use-auto-onboarding' import WorkflowHeader from './workflow-header' import WorkflowPanel from './workflow-panel' -import dynamic from 'next/dynamic' -import { BlockEnum } from '@/app/components/workflow/types' -import type { - PluginDefaultValue, - TriggerDefaultValue, -} from '@/app/components/workflow/block-selector/types' -import { useAutoOnboarding } from '../hooks/use-auto-onboarding' -import { useAvailableNodesMetaData } from '../hooks' const Features = dynamic(() => import('@/app/components/workflow/features'), { ssr: false, diff --git a/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.spec.tsx b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.spec.tsx index 33115a2577..c73a4fb1da 100644 --- a/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/chat-variable-trigger.spec.tsx @@ -17,7 +17,7 @@ vi.mock('../../hooks', () => ({ vi.mock('@/app/components/workflow/header/chat-variable-button', () => ({ __esModule: true, default: ({ disabled }: { disabled: boolean }) => ( - <button data-testid='chat-variable-button' type='button' disabled={disabled}> + <button data-testid="chat-variable-button" type="button" disabled={disabled}> ChatVariableButton </button> ), diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.spec.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.spec.tsx index 9f7b5c9129..5f17b0885c 100644 --- a/web/app/components/workflow-app/components/workflow-header/features-trigger.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.spec.tsx @@ -1,9 +1,9 @@ import type { ReactElement } from 'react' +import type { AppPublisherProps } from '@/app/components/app/app-publisher' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { Plan } from '@/app/components/billing/type' -import type { AppPublisherProps } from '@/app/components/app/app-publisher' import { ToastContext } from '@/app/components/base/toast' +import { Plan } from '@/app/components/billing/type' import { BlockEnum, InputVarType } from '@/app/components/workflow/types' import FeaturesTrigger from './features-trigger' @@ -96,7 +96,7 @@ vi.mock('@/app/components/app/app-publisher', () => ({ const inputs = props.inputs ?? [] return ( <div - data-testid='app-publisher' + data-testid="app-publisher" data-disabled={String(Boolean(props.disabled))} data-publish-disabled={String(Boolean(props.publishDisabled))} data-start-node-limit-exceeded={String(Boolean(props.startNodeLimitExceeded))} @@ -147,7 +147,7 @@ vi.mock('@/hooks/use-theme', () => ({ vi.mock('@/app/components/app/store', () => ({ __esModule: true, - useStore: (selector: (state: { appDetail?: { id: string }; setAppDetail: typeof mockSetAppDetail }) => unknown) => mockUseAppStoreSelector(selector), + useStore: (selector: (state: { appDetail?: { id: string }, setAppDetail: typeof mockSetAppDetail }) => unknown) => mockUseAppStoreSelector(selector), })) const createProviderContext = ({ diff --git a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx index cba66996e8..1df6f10195 100644 --- a/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx +++ b/web/app/components/workflow-app/components/workflow-header/features-trigger.tsx @@ -1,48 +1,48 @@ +import type { EndNodeType } from '@/app/components/workflow/nodes/end/types' +import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' +import type { + CommonEdgeType, + Node, +} from '@/app/components/workflow/types' +import type { PublishWorkflowParams } from '@/types/workflow' +import { RiApps2AddLine } from '@remixicon/react' import { memo, useCallback, useMemo, } from 'react' -import { useEdges } from 'reactflow' -import { RiApps2AddLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { - useStore, - useWorkflowStore, -} from '@/app/components/workflow/store' +import { useEdges } from 'reactflow' +import AppPublisher from '@/app/components/app/app-publisher' +import { useStore as useAppStore } from '@/app/components/app/store' +import Button from '@/app/components/base/button' +import { useFeatures } from '@/app/components/base/features/hooks' +import { useToastContext } from '@/app/components/base/toast' +import { Plan } from '@/app/components/billing/type' import { useChecklist, useChecklistBeforePublish, + useIsChatMode, useNodesReadOnly, useNodesSyncDraft, // useWorkflowRunValidation, } from '@/app/components/workflow/hooks' -import Button from '@/app/components/base/button' -import AppPublisher from '@/app/components/app/app-publisher' -import { useFeatures } from '@/app/components/base/features/hooks' -import type { - CommonEdgeType, - Node, -} from '@/app/components/workflow/types' +import { + useStore, + useWorkflowStore, +} from '@/app/components/workflow/store' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { BlockEnum, InputVarType, isTriggerNode, } from '@/app/components/workflow/types' -import { useToastContext } from '@/app/components/base/toast' -import { useInvalidateAppWorkflow, usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow' -import { useInvalidateAppTriggers } from '@/service/use-tools' -import type { PublishWorkflowParams } from '@/types/workflow' -import { fetchAppDetail } from '@/service/apps' -import { useStore as useAppStore } from '@/app/components/app/store' -import useTheme from '@/hooks/use-theme' -import { cn } from '@/utils/classnames' -import { useIsChatMode } from '@/app/components/workflow/hooks' -import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' -import type { EndNodeType } from '@/app/components/workflow/nodes/end/types' import { useProviderContext } from '@/context/provider-context' -import { Plan } from '@/app/components/billing/type' -import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import useTheme from '@/hooks/use-theme' +import { fetchAppDetail } from '@/service/apps' +import { useInvalidateAppTriggers } from '@/service/use-tools' +import { useInvalidateAppWorkflow, usePublishWorkflow, useResetWorkflowVersionHistory } from '@/service/use-workflow' +import { cn } from '@/utils/classnames' const FeaturesTrigger = () => { const { t } = useTranslation() @@ -193,7 +193,7 @@ const FeaturesTrigger = () => { )} onClick={handleShowFeatures} > - <RiApps2AddLine className='mr-1 h-4 w-4 text-components-button-secondary-text' /> + <RiApps2AddLine className="mr-1 h-4 w-4 text-components-button-secondary-text" /> {t('workflow.common.features')} </Button> )} diff --git a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx index fce8e0d724..8eee878cd9 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx @@ -1,7 +1,7 @@ -import { render, screen } from '@testing-library/react' -import type { App } from '@/types/app' -import { AppModeEnum } from '@/types/app' import type { HeaderProps } from '@/app/components/workflow/header' +import type { App } from '@/types/app' +import { render, screen } from '@testing-library/react' +import { AppModeEnum } from '@/types/app' import WorkflowHeader from './index' const mockUseAppStoreSelector = vi.fn() @@ -13,7 +13,7 @@ let appDetail: App vi.mock('@/app/components/app/store', () => ({ __esModule: true, - useStore: (selector: (state: { appDetail?: App; setCurrentLogItem: typeof mockSetCurrentLogItem; setShowMessageLogModal: typeof mockSetShowMessageLogModal }) => unknown) => mockUseAppStoreSelector(selector), + useStore: (selector: (state: { appDetail?: App, setCurrentLogItem: typeof mockSetCurrentLogItem, setShowMessageLogModal: typeof mockSetShowMessageLogModal }) => unknown) => mockUseAppStoreSelector(selector), })) vi.mock('@/app/components/workflow/header', () => ({ @@ -24,7 +24,7 @@ vi.mock('@/app/components/workflow/header', () => ({ return ( <div - data-testid='workflow-header' + data-testid="workflow-header" data-show-run={String(Boolean(props.normal?.runAndHistoryProps?.showRunButton))} data-show-preview={String(Boolean(props.normal?.runAndHistoryProps?.showPreviewButton))} data-history-url={props.normal?.runAndHistoryProps?.viewHistoryProps?.historyUrl ?? ''} diff --git a/web/app/components/workflow-app/components/workflow-header/index.tsx b/web/app/components/workflow-app/components/workflow-header/index.tsx index c0b8a37b87..4acb721487 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.tsx @@ -1,19 +1,19 @@ +import type { HeaderProps } from '@/app/components/workflow/header' import { memo, useCallback, useMemo, } from 'react' import { useShallow } from 'zustand/react/shallow' -import type { HeaderProps } from '@/app/components/workflow/header' -import Header from '@/app/components/workflow/header' import { useStore as useAppStore } from '@/app/components/app/store' +import Header from '@/app/components/workflow/header' +import { useResetWorkflowVersionHistory } from '@/service/use-workflow' import { fetchWorkflowRunHistory, } from '@/service/workflow' +import { useIsChatMode } from '../../hooks' import ChatVariableTrigger from './chat-variable-trigger' import FeaturesTrigger from './features-trigger' -import { useResetWorkflowVersionHistory } from '@/service/use-workflow' -import { useIsChatMode } from '../../hooks' const WorkflowHeader = () => { const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({ diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index e90b2904c9..38a044f088 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -1,11 +1,11 @@ +import type { WorkflowProps } from '@/app/components/workflow' import { useCallback, useMemo, } from 'react' import { useFeaturesStore } from '@/app/components/base/features/hooks' import { WorkflowWithInnerContext } from '@/app/components/workflow' -import type { WorkflowProps } from '@/app/components/workflow' -import WorkflowChildren from './workflow-children' +import { useWorkflowStore } from '@/app/components/workflow/store' import { useAvailableNodesMetaData, useConfigsMap, @@ -18,7 +18,7 @@ import { useWorkflowRun, useWorkflowStartRun, } from '../hooks' -import { useWorkflowStore } from '@/app/components/workflow/store' +import WorkflowChildren from './workflow-children' type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'> const WorkflowMain = ({ diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx index b37451fa07..b7a2cefd1c 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx @@ -1,8 +1,8 @@ -import React from 'react' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import WorkflowOnboardingModal from './index' +import React from 'react' import { BlockEnum } from '@/app/components/workflow/types' +import WorkflowOnboardingModal from './index' // Mock Modal component vi.mock('@/app/components/base/modal', () => ({ diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx index 747a232ca7..633bb59ef1 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx @@ -1,15 +1,15 @@ 'use client' import type { FC } from 'react' +import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types' import { useCallback, useEffect, } from 'react' import { useTranslation } from 'react-i18next' -import { BlockEnum } from '@/app/components/workflow/types' -import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types' import Modal from '@/app/components/base/modal' -import StartNodeSelectionPanel from './start-node-selection-panel' +import { BlockEnum } from '@/app/components/workflow/types' import { useDocLink } from '@/context/i18n' +import StartNodeSelectionPanel from './start-node-selection-panel' type WorkflowOnboardingModalProps = { isShow: boolean @@ -61,7 +61,8 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({ {t('workflow.onboarding.title')} </h3> <div className="body-xs-regular leading-4 text-text-tertiary"> - {t('workflow.onboarding.description')}{' '} + {t('workflow.onboarding.description')} + {' '} <a href={docLink('/guides/workflow/node/start')} target="_blank" @@ -69,7 +70,8 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({ className="hover:text-text-accent-hover cursor-pointer text-text-accent underline" > {t('workflow.onboarding.learnMore')} - </a>{' '} + </a> + {' '} {t('workflow.onboarding.aboutStartNode')} </div> </div> diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx index e089e96a59..0fa5c9f13d 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx @@ -1,6 +1,6 @@ -import React from 'react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import React from 'react' import StartNodeOption from './start-node-option' describe('StartNodeOption', () => { diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx index 2cc54b39ca..77f2a842c9 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.tsx @@ -35,7 +35,10 @@ const StartNodeOption: FC<StartNodeOptionProps> = ({ <h3 className="system-md-semi-bold text-text-primary"> {title} {subtitle && ( - <span className="system-md-regular text-text-quaternary"> {subtitle}</span> + <span className="system-md-regular text-text-quaternary"> + {' '} + {subtitle} + </span> )} </h3> </div> diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx index a7e748deeb..8c59ec3b82 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx @@ -1,8 +1,8 @@ -import React from 'react' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import StartNodeSelectionPanel from './start-node-selection-panel' +import React from 'react' import { BlockEnum } from '@/app/components/workflow/types' +import StartNodeSelectionPanel from './start-node-selection-panel' // Mock NodeSelector component vi.mock('@/app/components/workflow/block-selector', () => ({ diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.tsx index de934a13b2..e46fac86fc 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.tsx @@ -1,14 +1,13 @@ 'use client' import type { FC } from 'react' +import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import StartNodeOption from './start-node-option' +import { Home, TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import NodeSelector from '@/app/components/workflow/block-selector' -import { Home } from '@/app/components/base/icons/src/vender/workflow' -import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' -import { BlockEnum } from '@/app/components/workflow/types' -import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types' import { TabsEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import StartNodeOption from './start-node-option' type StartNodeSelectionPanelProps = { onSelectUserInput: () => void @@ -34,11 +33,11 @@ const StartNodeSelectionPanel: FC<StartNodeSelectionPanelProps> = ({ return ( <div className="grid grid-cols-2 gap-4"> <StartNodeOption - icon={ + icon={( <div className="flex h-9 w-9 items-center justify-center rounded-[10px] border-[0.5px] border-transparent bg-util-colors-blue-brand-blue-brand-500 p-2"> <Home className="h-5 w-5 text-white" /> </div> - } + )} title={t('workflow.onboarding.userInputFull')} description={t('workflow.onboarding.userInputDescription')} onClick={onSelectUserInput} @@ -61,11 +60,11 @@ const StartNodeSelectionPanel: FC<StartNodeSelectionPanelProps> = ({ ]} trigger={() => ( <StartNodeOption - icon={ + icon={( <div className="flex h-9 w-9 items-center justify-center rounded-[10px] border-[0.5px] border-transparent bg-util-colors-blue-brand-blue-brand-500 p-2"> <TriggerAll className="h-5 w-5 text-white" /> </div> - } + )} title={t('workflow.onboarding.trigger')} description={t('workflow.onboarding.triggerDescription')} onClick={handleTriggerClick} diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx index 6e0504710e..a1ed289f94 100644 --- a/web/app/components/workflow-app/components/workflow-panel.tsx +++ b/web/app/components/workflow-app/components/workflow-panel.tsx @@ -1,16 +1,16 @@ +import type { PanelProps } from '@/app/components/workflow/panel' +import dynamic from 'next/dynamic' import { memo, useMemo, } from 'react' import { useShallow } from 'zustand/react/shallow' +import { useStore as useAppStore } from '@/app/components/app/store' +import Panel from '@/app/components/workflow/panel' import { useStore } from '@/app/components/workflow/store' import { useIsChatMode, } from '../hooks' -import { useStore as useAppStore } from '@/app/components/app/store' -import type { PanelProps } from '@/app/components/workflow/panel' -import Panel from '@/app/components/workflow/panel' -import dynamic from 'next/dynamic' const MessageLogModal = dynamic(() => import('@/app/components/base/message-log-modal'), { ssr: false, diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index a9eb0f9b7b..2a8c806859 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -1,13 +1,13 @@ -export * from './use-workflow-init' -export * from './use-workflow-template' +export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars' +export * from './use-available-nodes-meta-data' +export * from './use-configs-map' +export * from './use-DSL' +export * from './use-get-run-and-trace-url' +export * from './use-inspect-vars-crud' +export * from './use-is-chat-mode' export * from './use-nodes-sync-draft' +export * from './use-workflow-init' +export * from './use-workflow-refresh-draft' export * from './use-workflow-run' export * from './use-workflow-start-run' -export * from './use-is-chat-mode' -export * from './use-available-nodes-meta-data' -export * from './use-workflow-refresh-draft' -export * from './use-get-run-and-trace-url' -export * from './use-DSL' -export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars' -export * from './use-inspect-vars-crud' -export * from './use-configs-map' +export * from './use-workflow-template' diff --git a/web/app/components/workflow-app/hooks/use-DSL.ts b/web/app/components/workflow-app/hooks/use-DSL.ts index 6787c06198..afa55f38c8 100644 --- a/web/app/components/workflow-app/hooks/use-DSL.ts +++ b/web/app/components/workflow-app/hooks/use-DSL.ts @@ -3,15 +3,15 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' +import { useStore as useAppStore } from '@/app/components/app/store' +import { useToastContext } from '@/app/components/base/toast' import { DSL_EXPORT_CHECK, } from '@/app/components/workflow/constants' -import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useEventEmitterContextContext } from '@/context/event-emitter' -import { fetchWorkflowDraft } from '@/service/workflow' import { exportAppConfig } from '@/service/apps' -import { useToastContext } from '@/app/components/base/toast' -import { useStore as useAppStore } from '@/app/components/app/store' +import { fetchWorkflowDraft } from '@/service/workflow' +import { useNodesSyncDraft } from './use-nodes-sync-draft' export const useDSL = () => { const { t } = useTranslation() diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts index b95d4d47f7..24d862321d 100644 --- a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -1,16 +1,16 @@ +import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useDocLink } from '@/context/i18n' -import StartDefault from '@/app/components/workflow/nodes/start/default' -import TriggerWebhookDefault from '@/app/components/workflow/nodes/trigger-webhook/default' -import TriggerScheduleDefault from '@/app/components/workflow/nodes/trigger-schedule/default' -import TriggerPluginDefault from '@/app/components/workflow/nodes/trigger-plugin/default' -import EndDefault from '@/app/components/workflow/nodes/end/default' -import AnswerDefault from '@/app/components/workflow/nodes/answer/default' import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' -import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' -import { useIsChatMode } from './use-is-chat-mode' +import AnswerDefault from '@/app/components/workflow/nodes/answer/default' +import EndDefault from '@/app/components/workflow/nodes/end/default' +import StartDefault from '@/app/components/workflow/nodes/start/default' +import TriggerPluginDefault from '@/app/components/workflow/nodes/trigger-plugin/default' +import TriggerScheduleDefault from '@/app/components/workflow/nodes/trigger-schedule/default' +import TriggerWebhookDefault from '@/app/components/workflow/nodes/trigger-webhook/default' import { BlockEnum } from '@/app/components/workflow/types' +import { useDocLink } from '@/context/i18n' +import { useIsChatMode } from './use-is-chat-mode' export const useAvailableNodesMetaData = () => { const { t } = useTranslation() @@ -32,11 +32,11 @@ export const useAvailableNodesMetaData = () => { isChatMode ? [AnswerDefault] : [ - EndDefault, - TriggerWebhookDefault, - TriggerScheduleDefault, - TriggerPluginDefault, - ] + EndDefault, + TriggerWebhookDefault, + TriggerScheduleDefault, + TriggerPluginDefault, + ] ), ], [isChatMode, startNodeMetaData]) diff --git a/web/app/components/workflow-app/hooks/use-configs-map.ts b/web/app/components/workflow-app/hooks/use-configs-map.ts index 5d41901924..b16a16fdc9 100644 --- a/web/app/components/workflow-app/hooks/use-configs-map.ts +++ b/web/app/components/workflow-app/hooks/use-configs-map.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react' +import { useFeatures } from '@/app/components/base/features/hooks' import { useStore } from '@/app/components/workflow/store' import { FlowType } from '@/types/common' -import { useFeatures } from '@/app/components/base/features/hooks' export const useConfigsMap = () => { const appId = useStore(s => s.appId) diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 56d9021feb..5d394bab1e 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -1,12 +1,12 @@ -import { useCallback } from 'react' import { produce } from 'immer' +import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { useWorkflowStore } from '@/app/components/workflow/store' -import { useNodesReadOnly } from '@/app/components/workflow/hooks/use-workflow' -import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-serial-async-callback' -import { syncWorkflowDraft } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' +import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-serial-async-callback' +import { useNodesReadOnly } from '@/app/components/workflow/hooks/use-workflow' +import { useWorkflowStore } from '@/app/components/workflow/store' import { API_PREFIX } from '@/config' +import { syncWorkflowDraft } from '@/service/workflow' import { useWorkflowRefreshDraft } from '.' export const useNodesSyncDraft = () => { diff --git a/web/app/components/workflow-app/hooks/use-workflow-init.ts b/web/app/components/workflow-app/hooks/use-workflow-init.ts index a0a6cc22a1..8e976937b5 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-init.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-init.ts @@ -1,26 +1,26 @@ +import type { Edge, Node } from '@/app/components/workflow/types' +import type { FileUploadConfigResponse } from '@/models/common' +import type { FetchWorkflowDraftResponse } from '@/types/workflow' import { useCallback, useEffect, useState, } from 'react' +import { useStore as useAppStore } from '@/app/components/app/store' import { useStore, useWorkflowStore, } from '@/app/components/workflow/store' -import { useWorkflowTemplate } from './use-workflow-template' -import { useStore as useAppStore } from '@/app/components/app/store' +import { BlockEnum } from '@/app/components/workflow/types' +import { useWorkflowConfig } from '@/service/use-workflow' import { fetchNodesDefaultConfigs, fetchPublishedWorkflow, fetchWorkflowDraft, syncWorkflowDraft, } from '@/service/workflow' -import type { FetchWorkflowDraftResponse } from '@/types/workflow' -import { useWorkflowConfig } from '@/service/use-workflow' -import type { FileUploadConfigResponse } from '@/models/common' -import type { Edge, Node } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' import { AppModeEnum } from '@/types/app' +import { useWorkflowTemplate } from './use-workflow-template' const hasConnectedUserInput = (nodes: Node[] = [], edges: Edge[] = []): boolean => { const startNodeIds = nodes diff --git a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts index 910ddd4a8d..fa4a44d894 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts @@ -1,8 +1,8 @@ +import type { WorkflowDataUpdater } from '@/app/components/workflow/types' import { useCallback } from 'react' +import { useWorkflowUpdate } from '@/app/components/workflow/hooks' import { useWorkflowStore } from '@/app/components/workflow/store' import { fetchWorkflowDraft } from '@/service/workflow' -import type { WorkflowDataUpdater } from '@/app/components/workflow/types' -import { useWorkflowUpdate } from '@/app/components/workflow/hooks' export const useWorkflowRefreshDraft = () => { const workflowStore = useWorkflowStore() diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index f051f73489..16c7bab6d4 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -1,35 +1,34 @@ +import type AudioPlayer from '@/app/components/base/audio-btn/audio' +import type { Node } from '@/app/components/workflow/types' +import type { IOtherOptions } from '@/service/base' +import type { VersionHistory } from '@/types/workflow' +import { produce } from 'immer' +import { noop } from 'lodash-es' +import { usePathname } from 'next/navigation' import { useCallback, useRef } from 'react' import { useReactFlow, useStoreApi, } from 'reactflow' -import { produce } from 'immer' import { v4 as uuidV4 } from 'uuid' -import { usePathname } from 'next/navigation' -import { useWorkflowStore } from '@/app/components/workflow/store' -import type { Node } from '@/app/components/workflow/types' -import { WorkflowRunningStatus } from '@/app/components/workflow/types' +import { useStore as useAppStore } from '@/app/components/app/store' +import { trackEvent } from '@/app/components/base/amplitude' +import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' +import { useFeaturesStore } from '@/app/components/base/features/hooks' +import Toast from '@/app/components/base/toast' +import { TriggerType } from '@/app/components/workflow/header/test-run-menu' import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-interactions' import { useWorkflowRunEvent } from '@/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event' -import { useStore as useAppStore } from '@/app/components/app/store' -import type { IOtherOptions } from '@/service/base' -import Toast from '@/app/components/base/toast' -import { handleStream, ssePost } from '@/service/base' -import { stopWorkflowRun } from '@/service/workflow' -import { useFeaturesStore } from '@/app/components/base/features/hooks' -import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' -import type AudioPlayer from '@/app/components/base/audio-btn/audio' -import type { VersionHistory } from '@/types/workflow' -import { noop } from 'lodash-es' -import { useNodesSyncDraft } from './use-nodes-sync-draft' +import { useWorkflowStore } from '@/app/components/workflow/store' +import { WorkflowRunningStatus } from '@/app/components/workflow/types' +import { handleStream, post, ssePost } from '@/service/base' +import { ContentType } from '@/service/fetch' import { useInvalidAllLastRun } from '@/service/use-workflow' +import { stopWorkflowRun } from '@/service/workflow' +import { AppModeEnum } from '@/types/app' import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars' import { useConfigsMap } from './use-configs-map' -import { post } from '@/service/base' -import { ContentType } from '@/service/fetch' -import { TriggerType } from '@/app/components/workflow/header/test-run-menu' -import { AppModeEnum } from '@/types/app' -import { trackEvent } from '@/app/components/base/amplitude' +import { useNodesSyncDraft } from './use-nodes-sync-draft' type HandleRunMode = TriggerType type HandleRunOptions = { @@ -668,8 +667,7 @@ export const useWorkflowRun = () => { }, }, ) - }, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace], - ) + }, [store, doSyncWorkflowDraft, workflowStore, pathname, handleWorkflowStarted, handleWorkflowFinished, fetchInspectVars, invalidAllLastRun, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeLoopStarted, handleWorkflowNodeLoopNext, handleWorkflowNodeLoopFinished, handleWorkflowNodeRetry, handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace]) const handleStopRun = useCallback((taskId: string) => { const setStoppedState = () => { diff --git a/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx index d2e3b3e3c9..9344a8d1ab 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx +++ b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx @@ -1,18 +1,18 @@ import { useCallback } from 'react' import { useStoreApi } from 'reactflow' +import { useFeaturesStore } from '@/app/components/base/features/hooks' +import { TriggerType } from '@/app/components/workflow/header/test-run-menu' +import { useWorkflowInteractions } from '@/app/components/workflow/hooks' import { useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum, WorkflowRunningStatus, } from '@/app/components/workflow/types' -import { useWorkflowInteractions } from '@/app/components/workflow/hooks' -import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useIsChatMode, useNodesSyncDraft, useWorkflowRun, } from '.' -import { TriggerType } from '@/app/components/workflow/header/test-run-menu' export const useWorkflowStartRun = () => { const store = useStoreApi() diff --git a/web/app/components/workflow-app/hooks/use-workflow-template.ts b/web/app/components/workflow-app/hooks/use-workflow-template.ts index 5f3f9762f0..b48a68e1eb 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-template.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-template.ts @@ -1,14 +1,14 @@ +import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' import { useTranslation } from 'react-i18next' -import { generateNewNode } from '@/app/components/workflow/utils' import { NODE_WIDTH_X_OFFSET, START_INITIAL_POSITION, } from '@/app/components/workflow/constants' -import { useIsChatMode } from './use-is-chat-mode' -import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' -import startDefault from '@/app/components/workflow/nodes/start/default' -import llmDefault from '@/app/components/workflow/nodes/llm/default' import answerDefault from '@/app/components/workflow/nodes/answer/default' +import llmDefault from '@/app/components/workflow/nodes/llm/default' +import startDefault from '@/app/components/workflow/nodes/start/default' +import { generateNewNode } from '@/app/components/workflow/utils' +import { useIsChatMode } from './use-is-chat-mode' export const useWorkflowTemplate = () => { const isChatMode = useIsChatMode() diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index fcd247ef22..6a778ab6b8 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -1,40 +1,40 @@ 'use client' +import type { Features as FeaturesData } from '@/app/components/base/features/types' +import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store' +import { useSearchParams } from 'next/navigation' import { useEffect, useMemo, } from 'react' -import { - SupportUploadFileTypes, -} from '@/app/components/workflow/types' -import { - useWorkflowInit, -} from './hooks/use-workflow-init' -import { useAppTriggers } from '@/service/use-tools' -import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status' import { useStore as useAppStore } from '@/app/components/app/store' -import { useWorkflowStore } from '@/app/components/workflow/store' -import { - initialEdges, - initialNodes, -} from '@/app/components/workflow/utils' -import Loading from '@/app/components/base/loading' import { FeaturesProvider } from '@/app/components/base/features' -import type { Features as FeaturesData } from '@/app/components/base/features/types' +import Loading from '@/app/components/base/loading' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import { useAppContext } from '@/context/app-context' import WorkflowWithDefaultContext from '@/app/components/workflow' import { WorkflowContextProvider, } from '@/app/components/workflow/context' -import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store' -import { createWorkflowSlice } from './store/workflow/workflow-slice' -import WorkflowAppMain from './components/workflow-main' -import { useSearchParams } from 'next/navigation' - +import { useWorkflowStore } from '@/app/components/workflow/store' +import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status' +import { + SupportUploadFileTypes, +} from '@/app/components/workflow/types' +import { + initialEdges, + initialNodes, +} from '@/app/components/workflow/utils' +import { useAppContext } from '@/context/app-context' import { fetchRunDetail } from '@/service/log' -import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url' +import { useAppTriggers } from '@/service/use-tools' import { AppModeEnum } from '@/types/app' +import WorkflowAppMain from './components/workflow-main' + +import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url' +import { + useWorkflowInit, +} from './hooks/use-workflow-init' +import { createWorkflowSlice } from './store/workflow/workflow-slice' const WorkflowAppWithAdditionalContext = () => { const { @@ -161,7 +161,7 @@ const WorkflowAppWithAdditionalContext = () => { if (!data || isLoading || isLoadingCurrentWorkspace || !currentWorkspace.id) { return ( - <div className='relative flex h-full w-full items-center justify-center'> + <div className="relative flex h-full w-full items-center justify-center"> <Loading /> </div> ) diff --git a/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx b/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx index 6295b3f5a9..6bf7e8f542 100644 --- a/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx +++ b/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx @@ -1,10 +1,10 @@ import type { MockedFunction } from 'vitest' -import React, { useCallback } from 'react' +import type { EntryNodeStatus } from '../store/trigger-status' +import type { BlockEnum } from '../types' import { act, render } from '@testing-library/react' +import React, { useCallback } from 'react' import { useTriggerStatusStore } from '../store/trigger-status' import { isTriggerNode } from '../types' -import type { BlockEnum } from '../types' -import type { EntryNodeStatus } from '../store/trigger-status' // Mock the isTriggerNode function while preserving BlockEnum vi.mock('../types', async importOriginal => ({ @@ -25,7 +25,9 @@ const TestTriggerNode: React.FC<{ return ( <div data-testid={`node-${nodeId}`} data-status={triggerStatus}> - Status: {triggerStatus} + Status: + {' '} + {triggerStatus} </div> ) } @@ -274,14 +276,14 @@ describe('Trigger Status Synchronization Integration', () => { nodeType: string }> = ({ nodeId, nodeType }) => { const triggerStatusSelector = useCallback((state: any) => - mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses[nodeId] || 'disabled') : 'enabled', - [nodeId, nodeType], - ) + mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses[nodeId] || 'disabled') : 'enabled', [nodeId, nodeType]) const triggerStatus = useTriggerStatusStore(triggerStatusSelector) return ( <div data-testid={`optimized-node-${nodeId}`} data-status={triggerStatus}> - Status: {triggerStatus} + Status: + {' '} + {triggerStatus} </div> ) } @@ -315,9 +317,10 @@ describe('Trigger Status Synchronization Integration', () => { mockIsTriggerNode.mockImplementation(nodeType => nodeType === 'trigger-webhook') const TestComponent: React.FC<{ nodeType: string }> = ({ nodeType }) => { - const triggerStatusSelector = useCallback((state: any) => - mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses['test-node'] || 'disabled') : 'enabled', - ['test-node', nodeType], // Dependencies should match implementation + const triggerStatusSelector = useCallback( + (state: any) => + mockIsTriggerNode(nodeType as BlockEnum) ? (state.triggerStatuses['test-node'] || 'disabled') : 'enabled', + ['test-node', nodeType], // Dependencies should match implementation ) const status = useTriggerStatusStore(triggerStatusSelector) return <div data-testid="test-component" data-status={status} /> diff --git a/web/app/components/workflow/block-icon.tsx b/web/app/components/workflow/block-icon.tsx index 3c66d07364..32b06d475f 100644 --- a/web/app/components/workflow/block-icon.tsx +++ b/web/app/components/workflow/block-icon.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { memo } from 'react' -import { BlockEnum } from './types' +import AppIcon from '@/app/components/base/app-icon' import { Agent, Answer, @@ -26,14 +26,14 @@ import { VariableX, WebhookLine, } from '@/app/components/base/icons/src/vender/workflow' -import AppIcon from '@/app/components/base/app-icon' import { cn } from '@/utils/classnames' +import { BlockEnum } from './types' type BlockIconProps = { type: BlockEnum size?: string className?: string - toolIcon?: string | { content: string; background: string } + toolIcon?: string | { content: string, background: string } } const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record<string, string> = { xs: 'w-4 h-4 rounded-[5px] shadow-xs', @@ -125,15 +125,14 @@ const BlockIcon: FC<BlockIconProps> = ({ showDefaultIcon && ICON_CONTAINER_BG_COLOR_MAP[type], toolIcon && '!shadow-none', className, - )} + ) + } > { showDefaultIcon && ( - getIcon(type, - (type === BlockEnum.TriggerSchedule || type === BlockEnum.TriggerWebhook) - ? (size === 'xs' ? 'w-4 h-4' : 'w-4.5 h-4.5') - : (size === 'xs' ? 'w-3 h-3' : 'w-3.5 h-3.5'), - ) + getIcon(type, (type === BlockEnum.TriggerSchedule || type === BlockEnum.TriggerWebhook) + ? (size === 'xs' ? 'w-4 h-4' : 'w-4.5 h-4.5') + : (size === 'xs' ? 'w-3 h-3' : 'w-3.5 h-3.5')) ) } { @@ -142,21 +141,22 @@ const BlockIcon: FC<BlockIconProps> = ({ { typeof toolIcon === 'string' ? ( - <div - className='h-full w-full shrink-0 rounded-md bg-cover bg-center' - style={{ - backgroundImage: `url(${toolIcon})`, - }} - ></div> - ) + <div + className="h-full w-full shrink-0 rounded-md bg-cover bg-center" + style={{ + backgroundImage: `url(${toolIcon})`, + }} + > + </div> + ) : ( - <AppIcon - className='!h-full !w-full shrink-0' - size='tiny' - icon={toolIcon?.content} - background={toolIcon?.background} - /> - ) + <AppIcon + className="!h-full !w-full shrink-0" + size="tiny" + icon={toolIcon?.content} + background={toolIcon?.background} + /> + ) } </> ) diff --git a/web/app/components/workflow/block-selector/all-start-blocks.tsx b/web/app/components/workflow/block-selector/all-start-blocks.tsx index e073113c05..44d06a0084 100644 --- a/web/app/components/workflow/block-selector/all-start-blocks.tsx +++ b/web/app/components/workflow/block-selector/all-start-blocks.tsx @@ -1,4 +1,12 @@ 'use client' +import type { + RefObject, +} from 'react' +import type { BlockEnum, OnSelectBlock } from '../types' +import type { ListRef } from './market-place-plugin/list' +import type { TriggerDefaultValue, TriggerWithProvider } from './types' +import { RiArrowRightUpLine } from '@remixicon/react' +import Link from 'next/link' import { useCallback, useEffect, @@ -6,30 +14,23 @@ import { useRef, useState, } from 'react' -import type { - RefObject, -} from 'react' import { useTranslation } from 'react-i18next' -import type { BlockEnum, OnSelectBlock } from '../types' -import type { TriggerDefaultValue, TriggerWithProvider } from './types' +import Button from '@/app/components/base/button' +import Divider from '@/app/components/base/divider' +import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' +import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' +import { cn } from '@/utils/classnames' +import { getMarketplaceUrl } from '@/utils/var' +import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' +import { PluginCategoryEnum } from '../../plugins/types' +import { BlockEnum as BlockEnumValue } from '../types' +import { ENTRY_NODE_TYPES } from './constants' +import FeaturedTriggers from './featured-triggers' +import PluginList from './market-place-plugin/list' import StartBlocks from './start-blocks' import TriggerPluginList from './trigger-plugin/list' -import { ENTRY_NODE_TYPES } from './constants' -import { cn } from '@/utils/classnames' -import Link from 'next/link' -import { RiArrowRightUpLine } from '@remixicon/react' -import { getMarketplaceUrl } from '@/utils/var' -import Button from '@/app/components/base/button' -import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' -import { BlockEnum as BlockEnumValue } from '../types' -import FeaturedTriggers from './featured-triggers' -import Divider from '@/app/components/base/divider' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' -import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' -import { PluginCategoryEnum } from '../../plugins/types' -import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' -import PluginList, { type ListRef } from './market-place-plugin/list' const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg' @@ -114,7 +115,8 @@ const AllStartBlocks = ({ }, [enableTriggerPlugin, hasPluginContent]) useEffect(() => { - if (!enableTriggerPlugin || !enable_marketplace) return + if (!enableTriggerPlugin || !enable_marketplace) + return if (hasFilter) { fetchPlugins({ query: searchText, @@ -126,10 +128,10 @@ const AllStartBlocks = ({ return ( <div className={cn('min-w-[400px] max-w-[500px]', className)}> - <div className='flex max-h-[640px] flex-col'> + <div className="flex max-h-[640px] flex-col"> <div ref={wrapElemRef} - className='flex-1 overflow-y-auto' + className="flex-1 overflow-y-auto" onScroll={() => pluginRef.current?.handleScroll()} > <div className={cn(shouldShowEmptyState && 'hidden')}> @@ -144,14 +146,14 @@ const AllStartBlocks = ({ invalidateTriggers() }} /> - <div className='px-3'> - <Divider className='!h-px' /> + <div className="px-3"> + <Divider className="!h-px" /> </div> </> )} {shouldShowTriggerListTitle && ( - <div className='px-3 pb-1 pt-2'> - <span className='system-xs-medium text-text-primary'>{t('workflow.tabs.allTriggers')}</span> + <div className="px-3 pb-1 pt-2"> + <span className="system-xs-medium text-text-primary">{t('workflow.tabs.allTriggers')}</span> </div> )} <StartBlocks @@ -184,19 +186,19 @@ const AllStartBlocks = ({ </div> {shouldShowEmptyState && ( - <div className='flex h-full flex-col items-center justify-center gap-3 py-12 text-center'> - <SearchMenu className='h-8 w-8 text-text-quaternary' /> - <div className='text-sm font-medium text-text-secondary'> + <div className="flex h-full flex-col items-center justify-center gap-3 py-12 text-center"> + <SearchMenu className="h-8 w-8 text-text-quaternary" /> + <div className="text-sm font-medium text-text-secondary"> {t('workflow.tabs.noPluginsFound')} </div> <Link - href='https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml' - target='_blank' + href="https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml" + target="_blank" > <Button - size='small' - variant='secondary-accent' - className='h-6 cursor-pointer px-3 text-xs' + size="small" + variant="secondary-accent" + className="h-6 cursor-pointer px-3 text-xs" > {t('workflow.tabs.requestToCommunity')} </Button> @@ -210,10 +212,10 @@ const AllStartBlocks = ({ <Link className={marketplaceFooterClassName} href={getMarketplaceUrl('', { category: PluginCategoryEnum.trigger })} - target='_blank' + target="_blank" > <span>{t('plugin.findMoreInMarketplace')}</span> - <RiArrowRightUpLine className='ml-0.5 h-3 w-3' /> + <RiArrowRightUpLine className="ml-0.5 h-3 w-3" /> </Link> )} </div> diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 8968a01557..060f8849d5 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -3,34 +3,34 @@ import type { RefObject, SetStateAction, } from 'react' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { Plugin } from '../../plugins/types' import type { BlockEnum, ToolWithProvider, } from '../types' import type { ToolDefaultValue, ToolValue } from './types' -import { ToolTypeEnum } from './types' -import Tools from './tools' -import { useToolTabs } from './hooks' -import ViewTypeSelect, { ViewType } from './view-type-select' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' -import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' -import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' -import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list' -import type { Plugin } from '../../plugins/types' -import { PluginCategoryEnum } from '../../plugins/types' -import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' -import { useGlobalPublicStore } from '@/context/global-public-context' -import RAGToolRecommendations from './rag-tool-recommendations' -import FeaturedTools from './featured-tools' -import Link from 'next/link' -import Divider from '@/app/components/base/divider' -import { RiArrowRightUpLine } from '@remixicon/react' -import { getMarketplaceUrl } from '@/utils/var' -import { useGetLanguage } from '@/context/i18n' +import type { ListProps, ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' import type { OnSelectBlock } from '@/app/components/workflow/types' +import { RiArrowRightUpLine } from '@remixicon/react' +import Link from 'next/link' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import Divider from '@/app/components/base/divider' +import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' +import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useGetLanguage } from '@/context/i18n' +import { cn } from '@/utils/classnames' +import { getMarketplaceUrl } from '@/utils/var' +import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' +import { PluginCategoryEnum } from '../../plugins/types' +import FeaturedTools from './featured-tools' +import { useToolTabs } from './hooks' +import RAGToolRecommendations from './rag-tool-recommendations' +import Tools from './tools' +import { ToolTypeEnum } from './types' +import ViewTypeSelect, { ViewType } from './view-type-select' const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg' @@ -172,7 +172,8 @@ const AllTools = ({ const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) useEffect(() => { - if (!enable_marketplace) return + if (!enable_marketplace) + return if (hasFilter) { fetchPlugins({ query: searchText, @@ -205,8 +206,8 @@ const AllTools = ({ return ( <div className={cn('max-w-[500px]', className)}> - <div className='flex items-center justify-between border-b border-divider-subtle px-3'> - <div className='flex h-8 items-center space-x-1'> + <div className="flex items-center justify-between border-b border-divider-subtle px-3"> + <div className="flex h-8 items-center space-x-1"> { tabs.map(tab => ( <div @@ -227,10 +228,10 @@ const AllTools = ({ <ViewTypeSelect viewType={activeView} onChange={setActiveView} /> )} </div> - <div className='flex max-h-[464px] flex-col'> + <div className="flex max-h-[464px] flex-col"> <div ref={wrapElemRef} - className='flex-1 overflow-y-auto' + className="flex-1 overflow-y-auto" onScroll={() => pluginRef.current?.handleScroll()} > <div className={cn(shouldShowEmptyState && 'hidden')}> @@ -254,15 +255,15 @@ const AllTools = ({ await onFeaturedInstallSuccess?.() }} /> - <div className='px-3'> - <Divider className='!h-px' /> + <div className="px-3"> + <Divider className="!h-px" /> </div> </> )} {hasToolsListContent && ( <> - <div className='px-3 pb-1 pt-2'> - <span className='system-xs-medium text-text-primary'>{t('tools.allTools')}</span> + <div className="px-3 pb-1 pt-2"> + <span className="system-xs-medium text-text-primary">{t('tools.allTools')}</span> </div> <Tools className={toolContentClassName} @@ -293,19 +294,19 @@ const AllTools = ({ </div> {shouldShowEmptyState && ( - <div className='flex h-full flex-col items-center justify-center gap-3 py-12 text-center'> - <SearchMenu className='h-8 w-8 text-text-quaternary' /> - <div className='text-sm font-medium text-text-secondary'> + <div className="flex h-full flex-col items-center justify-center gap-3 py-12 text-center"> + <SearchMenu className="h-8 w-8 text-text-quaternary" /> + <div className="text-sm font-medium text-text-secondary"> {t('workflow.tabs.noPluginsFound')} </div> <Link - href='https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml' - target='_blank' + href="https://github.com/langgenius/dify-plugins/issues/new?template=plugin_request.yaml" + target="_blank" > <Button - size='small' - variant='secondary-accent' - className='h-6 cursor-pointer px-3 text-xs' + size="small" + variant="secondary-accent" + className="h-6 cursor-pointer px-3 text-xs" > {t('workflow.tabs.requestToCommunity')} </Button> @@ -317,10 +318,10 @@ const AllTools = ({ <Link className={marketplaceFooterClassName} href={getMarketplaceUrl('', { category: PluginCategoryEnum.tool })} - target='_blank' + target="_blank" > <span>{t('plugin.findMoreInMarketplace')}</span> - <RiArrowRightUpLine className='ml-0.5 h-3 w-3' /> + <RiArrowRightUpLine className="ml-0.5 h-3 w-3" /> </Link> )} </div> diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 16256bf345..e626073a3a 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -1,18 +1,18 @@ +import type { NodeDefault } from '../types' +import { groupBy } from 'lodash-es' import { memo, useCallback, useMemo, } from 'react' -import { useStoreApi } from 'reactflow' import { useTranslation } from 'react-i18next' -import { groupBy } from 'lodash-es' +import { useStoreApi } from 'reactflow' +import Badge from '@/app/components/base/badge' +import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' -import type { NodeDefault } from '../types' import { BLOCK_CLASSIFICATIONS } from './constants' import { useBlocks } from './hooks' -import Tooltip from '@/app/components/base/tooltip' -import Badge from '@/app/components/base/badge' type BlocksProps = { searchText: string @@ -50,9 +50,10 @@ const Blocks = ({ const list = (grouped[classification] || []).filter((block) => { // Filter out trigger types from Blocks tab if (block.metaData.type === BlockEnum.TriggerWebhook - || block.metaData.type === BlockEnum.TriggerSchedule - || block.metaData.type === BlockEnum.TriggerPlugin) + || block.metaData.type === BlockEnum.TriggerSchedule + || block.metaData.type === BlockEnum.TriggerPlugin) { return false + } return block.metaData.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.metaData.type) }) @@ -79,11 +80,11 @@ const Blocks = ({ return ( <div key={classification} - className='mb-1 last-of-type:mb-0' + className="mb-1 last-of-type:mb-0" > { classification !== '-' && !!filteredList.length && ( - <div className='flex h-[22px] items-start px-3 text-xs font-medium text-text-tertiary'> + <div className="flex h-[22px] items-start px-3 text-xs font-medium text-text-tertiary"> {t(`workflow.tabs.${classification}`)} </div> ) @@ -92,36 +93,36 @@ const Blocks = ({ filteredList.map(block => ( <Tooltip key={block.metaData.type} - position='right' - popupClassName='w-[200px] rounded-xl' + position="right" + popupClassName="w-[200px] rounded-xl" needsDelay={false} popupContent={( <div> <BlockIcon - size='md' - className='mb-2' + size="md" + className="mb-2" type={block.metaData.type} /> - <div className='system-md-medium mb-1 text-text-primary'>{block.metaData.title}</div> - <div className='system-xs-regular text-text-tertiary'>{block.metaData.description}</div> + <div className="system-md-medium mb-1 text-text-primary">{block.metaData.title}</div> + <div className="system-xs-regular text-text-tertiary">{block.metaData.description}</div> </div> )} > <div key={block.metaData.type} - className='flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover' + className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover" onClick={() => onSelect(block.metaData.type)} > <BlockIcon - className='mr-2 shrink-0' + className="mr-2 shrink-0" type={block.metaData.type} /> - <div className='grow text-sm text-text-secondary'>{block.metaData.title}</div> + <div className="grow text-sm text-text-secondary">{block.metaData.title}</div> { block.metaData.type === BlockEnum.LoopEnd && ( <Badge text={t('workflow.nodes.loop.loopNode')} - className='ml-2 shrink-0' + className="ml-2 shrink-0" /> ) } @@ -134,10 +135,10 @@ const Blocks = ({ }, [groups, onSelect, t, store]) return ( - <div className='max-h-[480px] max-w-[500px] overflow-y-auto p-1'> + <div className="max-h-[480px] max-w-[500px] overflow-y-auto p-1"> { isEmpty && ( - <div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div> + <div className="flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary">{t('workflow.tabs.noResult')}</div> ) } { diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx index c354208dee..5204f4e86c 100644 --- a/web/app/components/workflow/block-selector/data-sources.tsx +++ b/web/app/components/workflow/block-selector/data-sources.tsx @@ -1,24 +1,25 @@ +import type { + OnSelectBlock, + ToolWithProvider, +} from '../types' +import type { DataSourceDefaultValue, ToolDefaultValue } from './types' +import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' import { useCallback, useEffect, useMemo, useRef, } from 'react' -import { BlockEnum } from '../types' -import type { - OnSelectBlock, - ToolWithProvider, -} from '../types' -import type { DataSourceDefaultValue, ToolDefaultValue } from './types' -import Tools from './tools' -import { ViewType } from './view-type-select' -import { cn } from '@/utils/classnames' -import PluginList, { type ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' +import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' import { useGlobalPublicStore } from '@/context/global-public-context' -import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants' +import { useGetLanguage } from '@/context/i18n' +import { cn } from '@/utils/classnames' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { PluginCategoryEnum } from '../../plugins/types' -import { useGetLanguage } from '@/context/i18n' +import { BlockEnum } from '../types' +import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants' +import Tools from './tools' +import { ViewType } from './view-type-select' type AllToolsProps = { className?: string @@ -83,7 +84,8 @@ const DataSources = ({ } = useMarketplacePlugins() useEffect(() => { - if (!enable_marketplace) return + if (!enable_marketplace) + return if (searchText) { fetchPlugins({ query: searchText, @@ -96,7 +98,7 @@ const DataSources = ({ <div className={cn('w-[400px] min-w-0 max-w-full', className)}> <div ref={wrapElemRef} - className='max-h-[464px] overflow-y-auto overflow-x-hidden' + className="max-h-[464px] overflow-y-auto overflow-x-hidden" onScroll={pluginRef.current?.handleScroll} > <Tools diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx index fe5c561362..1e6b976739 100644 --- a/web/app/components/workflow/block-selector/featured-tools.tsx +++ b/web/app/components/workflow/block-selector/featured-tools.tsx @@ -1,23 +1,24 @@ 'use client' -import { useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { BlockEnum, type ToolWithProvider } from '../types' +import type { ToolWithProvider } from '../types' import type { ToolDefaultValue, ToolValue } from './types' import type { Plugin } from '@/app/components/plugins/types' -import { useGetLanguage } from '@/context/i18n' -import BlockIcon from '../block-icon' -import Tooltip from '@/app/components/base/tooltip' import { RiMoreLine } from '@remixicon/react' -import Loading from '@/app/components/base/loading' import Link from 'next/link' +import { useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' +import Loading from '@/app/components/base/loading' +import Tooltip from '@/app/components/base/tooltip' +import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' +import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' +import { useGetLanguage } from '@/context/i18n' +import { formatNumber } from '@/utils/format' import { getMarketplaceUrl } from '@/utils/var' +import BlockIcon from '../block-icon' +import { BlockEnum } from '../types' +import Tools from './tools' import { ToolTypeEnum } from './types' import { ViewType } from './view-type-select' -import Tools from './tools' -import { formatNumber } from '@/utils/format' -import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' -import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' -import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' const MAX_RECOMMENDED_COUNT = 15 const INITIAL_VISIBLE_COUNT = 5 @@ -125,27 +126,27 @@ const FeaturedTools = ({ const showEmptyState = !isLoading && totalVisible === 0 return ( - <div className='px-3 pb-3 pt-2'> + <div className="px-3 pb-3 pt-2"> <button - type='button' - className='flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary' + type="button" + className="flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary" onClick={() => setIsCollapsed(prev => !prev)} > - <span className='system-xs-medium text-text-primary'>{t('workflow.tabs.featuredTools')}</span> + <span className="system-xs-medium text-text-primary">{t('workflow.tabs.featuredTools')}</span> <ArrowDownRoundFill className={`ml-0.5 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} /> </button> {!isCollapsed && ( <> {isLoading && ( - <div className='py-3'> - <Loading type='app' /> + <div className="py-3"> + <Loading type="app" /> </div> )} {showEmptyState && ( - <p className='system-xs-regular py-2 text-text-tertiary'> - <Link className='text-text-accent' href={getMarketplaceUrl('', { category: 'tool' })} target='_blank' rel='noopener noreferrer'> + <p className="system-xs-regular py-2 text-text-tertiary"> + <Link className="text-text-accent" href={getMarketplaceUrl('', { category: 'tool' })} target="_blank" rel="noopener noreferrer"> {t('workflow.tabs.noFeaturedPlugins')} </Link> </p> @@ -155,7 +156,7 @@ const FeaturedTools = ({ <> {visibleInstalledProviders.length > 0 && ( <Tools - className='p-0' + className="p-0" tools={visibleInstalledProviders} onSelect={onSelect} canNotSelectMultiple @@ -168,7 +169,7 @@ const FeaturedTools = ({ )} {visibleUninstalledPlugins.length > 0 && ( - <div className='mt-1 flex flex-col gap-1'> + <div className="mt-1 flex flex-col gap-1"> {visibleUninstalledPlugins.map(plugin => ( <FeaturedToolUninstalledItem key={plugin.plugin_id} @@ -187,7 +188,7 @@ const FeaturedTools = ({ {!isLoading && totalVisible > 0 && canToggleVisibility && ( <div - className='group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary' + className="group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary" onClick={() => { setVisibleCount((count) => { if (count >= maxAvailable) @@ -197,15 +198,17 @@ const FeaturedTools = ({ }) }} > - <div className='flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary'> - <RiMoreLine className='size-4 group-hover:hidden' /> - {isExpanded ? ( - <ArrowUpDoubleLine className='hidden size-4 group-hover:block' /> - ) : ( - <ArrowDownDoubleLine className='hidden size-4 group-hover:block' /> - )} + <div className="flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary"> + <RiMoreLine className="size-4 group-hover:hidden" /> + {isExpanded + ? ( + <ArrowUpDoubleLine className="hidden size-4 group-hover:block" /> + ) + : ( + <ArrowDownDoubleLine className="hidden size-4 group-hover:block" /> + )} </div> - <div className='system-xs-regular'> + <div className="system-xs-regular"> {t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')} </div> </div> @@ -255,28 +258,28 @@ function FeaturedToolUninstalledItem({ return ( <> <Tooltip - position='right' + position="right" needsDelay={false} - popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' + popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg" popupContent={( <div> - <BlockIcon size='md' className='mb-2' type={BlockEnum.Tool} toolIcon={plugin.icon} /> - <div className='mb-1 text-sm leading-5 text-text-primary'>{label}</div> - <div className='text-xs leading-[18px] text-text-secondary'>{description}</div> + <BlockIcon size="md" className="mb-2" type={BlockEnum.Tool} toolIcon={plugin.icon} /> + <div className="mb-1 text-sm leading-5 text-text-primary">{label}</div> + <div className="text-xs leading-[18px] text-text-secondary">{description}</div> </div> )} disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} > <div - className='group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover' + className="group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover" > - <div className='flex h-full min-w-0 items-center'> + <div className="flex h-full min-w-0 items-center"> <BlockIcon type={BlockEnum.Tool} toolIcon={plugin.icon} /> - <div className='ml-2 min-w-0'> - <div className='system-sm-medium truncate text-text-secondary'>{label}</div> + <div className="ml-2 min-w-0"> + <div className="system-sm-medium truncate text-text-secondary">{label}</div> </div> </div> - <div className='ml-auto flex h-full items-center gap-1 pl-1'> + <div className="ml-auto flex h-full items-center gap-1 pl-1"> <span className={`system-xs-regular text-text-tertiary ${actionOpen ? 'hidden' : 'group-hover:hidden'}`}>{installCountLabel}</span> <div className={`system-xs-medium flex h-full items-center gap-1 text-components-button-secondary-accent-text [&_.action-btn]:h-6 [&_.action-btn]:min-h-0 [&_.action-btn]:w-6 [&_.action-btn]:rounded-lg [&_.action-btn]:p-0 ${actionOpen ? 'flex' : 'hidden group-hover:flex'}`} @@ -287,8 +290,8 @@ function FeaturedToolUninstalledItem({ }} > <button - type='button' - className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover' + type="button" + className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover" onClick={() => { setActionOpen(false) setIsInstallModalOpen(true) diff --git a/web/app/components/workflow/block-selector/featured-triggers.tsx b/web/app/components/workflow/block-selector/featured-triggers.tsx index 561ebc1784..c986a8abc0 100644 --- a/web/app/components/workflow/block-selector/featured-triggers.tsx +++ b/web/app/components/workflow/block-selector/featured-triggers.tsx @@ -1,21 +1,21 @@ 'use client' -import { useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { BlockEnum } from '../types' import type { TriggerDefaultValue, TriggerWithProvider } from './types' import type { Plugin } from '@/app/components/plugins/types' -import { useGetLanguage } from '@/context/i18n' -import BlockIcon from '../block-icon' -import Tooltip from '@/app/components/base/tooltip' import { RiMoreLine } from '@remixicon/react' -import Loading from '@/app/components/base/loading' import Link from 'next/link' -import { getMarketplaceUrl } from '@/utils/var' -import TriggerPluginItem from './trigger-plugin/item' -import { formatNumber } from '@/utils/format' -import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' +import { useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' +import Loading from '@/app/components/base/loading' +import Tooltip from '@/app/components/base/tooltip' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' +import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' +import { useGetLanguage } from '@/context/i18n' +import { formatNumber } from '@/utils/format' +import { getMarketplaceUrl } from '@/utils/var' +import BlockIcon from '../block-icon' +import { BlockEnum } from '../types' +import TriggerPluginItem from './trigger-plugin/item' const MAX_RECOMMENDED_COUNT = 15 const INITIAL_VISIBLE_COUNT = 5 @@ -119,27 +119,27 @@ const FeaturedTriggers = ({ const showEmptyState = !isLoading && totalVisible === 0 return ( - <div className='px-3 pb-3 pt-2'> + <div className="px-3 pb-3 pt-2"> <button - type='button' - className='flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary' + type="button" + className="flex w-full items-center rounded-md px-0 py-1 text-left text-text-primary" onClick={() => setIsCollapsed(prev => !prev)} > - <span className='system-xs-medium text-text-primary'>{t('workflow.tabs.featuredTools')}</span> + <span className="system-xs-medium text-text-primary">{t('workflow.tabs.featuredTools')}</span> <ArrowDownRoundFill className={`ml-0.5 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} /> </button> {!isCollapsed && ( <> {isLoading && ( - <div className='py-3'> - <Loading type='app' /> + <div className="py-3"> + <Loading type="app" /> </div> )} {showEmptyState && ( - <p className='system-xs-regular py-2 text-text-tertiary'> - <Link className='text-text-accent' href={getMarketplaceUrl('', { category: 'trigger' })} target='_blank' rel='noopener noreferrer'> + <p className="system-xs-regular py-2 text-text-tertiary"> + <Link className="text-text-accent" href={getMarketplaceUrl('', { category: 'trigger' })} target="_blank" rel="noopener noreferrer"> {t('workflow.tabs.noFeaturedTriggers')} </Link> </p> @@ -148,7 +148,7 @@ const FeaturedTriggers = ({ {!showEmptyState && !isLoading && ( <> {visibleInstalledProviders.length > 0 && ( - <div className='mt-1'> + <div className="mt-1"> {visibleInstalledProviders.map(provider => ( <TriggerPluginItem key={provider.id} @@ -161,7 +161,7 @@ const FeaturedTriggers = ({ )} {visibleUninstalledPlugins.length > 0 && ( - <div className='mt-1 flex flex-col gap-1'> + <div className="mt-1 flex flex-col gap-1"> {visibleUninstalledPlugins.map(plugin => ( <FeaturedTriggerUninstalledItem key={plugin.plugin_id} @@ -180,7 +180,7 @@ const FeaturedTriggers = ({ {!isLoading && totalVisible > 0 && canToggleVisibility && ( <div - className='group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary' + className="group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary" onClick={() => { setVisibleCount((count) => { if (count >= maxAvailable) @@ -190,15 +190,17 @@ const FeaturedTriggers = ({ }) }} > - <div className='flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary'> - <RiMoreLine className='size-4 group-hover:hidden' /> - {isExpanded ? ( - <ArrowUpDoubleLine className='hidden size-4 group-hover:block' /> - ) : ( - <ArrowDownDoubleLine className='hidden size-4 group-hover:block' /> - )} + <div className="flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary"> + <RiMoreLine className="size-4 group-hover:hidden" /> + {isExpanded + ? ( + <ArrowUpDoubleLine className="hidden size-4 group-hover:block" /> + ) + : ( + <ArrowDownDoubleLine className="hidden size-4 group-hover:block" /> + )} </div> - <div className='system-xs-regular'> + <div className="system-xs-regular"> {t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')} </div> </div> @@ -248,28 +250,28 @@ function FeaturedTriggerUninstalledItem({ return ( <> <Tooltip - position='right' + position="right" needsDelay={false} - popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' + popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg" popupContent={( <div> - <BlockIcon size='md' className='mb-2' type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} /> - <div className='mb-1 text-sm leading-5 text-text-primary'>{label}</div> - <div className='text-xs leading-[18px] text-text-secondary'>{description}</div> + <BlockIcon size="md" className="mb-2" type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} /> + <div className="mb-1 text-sm leading-5 text-text-primary">{label}</div> + <div className="text-xs leading-[18px] text-text-secondary">{description}</div> </div> )} disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} > <div - className='group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover' + className="group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover" > - <div className='flex h-full min-w-0 items-center'> + <div className="flex h-full min-w-0 items-center"> <BlockIcon type={BlockEnum.TriggerPlugin} toolIcon={plugin.icon} /> - <div className='ml-2 min-w-0'> - <div className='system-sm-medium truncate text-text-secondary'>{label}</div> + <div className="ml-2 min-w-0"> + <div className="system-sm-medium truncate text-text-secondary">{label}</div> </div> </div> - <div className='ml-auto flex h-full items-center gap-1 pl-1'> + <div className="ml-auto flex h-full items-center gap-1 pl-1"> <span className={`system-xs-regular text-text-tertiary ${actionOpen ? 'hidden' : 'group-hover:hidden'}`}>{installCountLabel}</span> <div className={`system-xs-medium flex h-full items-center gap-1 text-components-button-secondary-accent-text [&_.action-btn]:h-6 [&_.action-btn]:min-h-0 [&_.action-btn]:w-6 [&_.action-btn]:rounded-lg [&_.action-btn]:p-0 ${actionOpen ? 'flex' : 'hidden group-hover:flex'}`} @@ -280,8 +282,8 @@ function FeaturedTriggerUninstalledItem({ }} > <button - type='button' - className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover' + type="button" + className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover" onClick={() => { setActionOpen(false) setIsInstallModalOpen(true) diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index e2dd14e16c..075a0b7d38 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -66,8 +66,7 @@ export const useTabs = ({ key: TabsEnum.Tools, name: t('workflow.tabs.tools'), show: !noTools, - }, - { + }, { key: TabsEnum.Start, name: t('workflow.tabs.start'), show: shouldShowStartTab, diff --git a/web/app/components/workflow/block-selector/index-bar.tsx b/web/app/components/workflow/block-selector/index-bar.tsx index f9a839a982..6b7a1af936 100644 --- a/web/app/components/workflow/block-selector/index-bar.tsx +++ b/web/app/components/workflow/block-selector/index-bar.tsx @@ -1,8 +1,8 @@ -import { pinyin } from 'pinyin-pro' import type { FC, RefObject } from 'react' import type { ToolWithProvider } from '../types' -import { CollectionType } from '../../tools/types' +import { pinyin } from 'pinyin-pro' import { cn } from '@/utils/classnames' +import { CollectionType } from '../../tools/types' export const CUSTOM_GROUP_NAME = '@@@custom@@@' export const WORKFLOW_GROUP_NAME = '@@@workflow@@@' diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 9f7989265a..5b9d86d6d4 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -1,11 +1,11 @@ +import type { NodeSelectorProps } from './main' import { useMemo, } from 'react' -import type { NodeSelectorProps } from './main' -import NodeSelector from './main' import { useHooksStore } from '@/app/components/workflow/hooks-store/store' import { BlockEnum } from '@/app/components/workflow/types' import { useStore } from '../store' +import NodeSelector from './main' const NodeSelectorWrapper = (props: NodeSelectorProps) => { const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index 1fff8528e2..130ed0a740 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -1,7 +1,17 @@ +import type { + OffsetOptions, + Placement, +} from '@floating-ui/react' import type { FC, MouseEventHandler, } from 'react' +import type { + CommonNodeType, + NodeDefault, + OnSelectBlock, + ToolWithProvider, +} from '../types' import { memo, useCallback, @@ -9,31 +19,21 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import useNodes from '@/app/components/workflow/store/workflow/use-nodes' -import type { - OffsetOptions, - Placement, -} from '@floating-ui/react' -import type { - CommonNodeType, - NodeDefault, - OnSelectBlock, - ToolWithProvider, -} from '../types' -import { BlockEnum, isTriggerNode } from '../types' -import Tabs from './tabs' -import { TabsEnum } from './types' -import { useTabs } from './hooks' +import { + Plus02, +} from '@/app/components/base/icons/src/vender/line/general' +import Input from '@/app/components/base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Input from '@/app/components/base/input' -import { - Plus02, -} from '@/app/components/base/icons/src/vender/line/general' import SearchBox from '@/app/components/plugins/marketplace/search-box' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import { BlockEnum, isTriggerNode } from '../types' +import { useTabs } from './hooks' +import Tabs from './tabs' +import { TabsEnum } from './types' export type NodeSelectorProps = { open?: boolean @@ -190,20 +190,20 @@ const NodeSelector: FC<NodeSelectorProps> = ({ trigger ? trigger(open) : ( - <div - className={` + <div + className={` z-10 flex h-4 w-4 cursor-pointer items-center justify-center rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover ${triggerClassName?.(open)} `} - style={triggerStyle} - > - <Plus02 className='h-2.5 w-2.5' /> - </div> - ) + style={triggerStyle} + > + <Plus02 className="h-2.5 w-2.5" /> + </div> + ) } </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> + <PortalToFollowElemContent className="z-[1000]"> <div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}> <Tabs tabs={tabs} @@ -211,8 +211,8 @@ const NodeSelector: FC<NodeSelectorProps> = ({ blocks={blocks} allowStartNodeSelection={canSelectUserInput} onActiveTabChange={handleActiveTabChange} - filterElem={ - <div className='relative m-2' onClick={e => e.stopPropagation()}> + filterElem={( + <div className="relative m-2" onClick={e => e.stopPropagation()}> {activeTab === TabsEnum.Start && ( <SearchBox autoFocus @@ -221,7 +221,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({ tags={tags} onTagsChange={setTags} placeholder={searchPlaceholder} - inputClassName='grow' + inputClassName="grow" /> )} {activeTab === TabsEnum.Blocks && ( @@ -254,11 +254,11 @@ const NodeSelector: FC<NodeSelectorProps> = ({ tags={tags} onTagsChange={setTags} placeholder={t('plugin.searchTools')!} - inputClassName='grow' + inputClassName="grow" /> )} </div> - } + )} onSelect={handleSelect} searchText={searchText} tags={tags} diff --git a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx index 3d0cc7dfe7..a23ca32b50 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx @@ -1,9 +1,10 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useTheme } from 'next-themes' -import { useTranslation } from 'react-i18next' import { RiMoreFill } from '@remixicon/react' +import { useQueryClient } from '@tanstack/react-query' +import { useTheme } from 'next-themes' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' // import Button from '@/app/components/base/button' import { @@ -11,11 +12,10 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { cn } from '@/utils/classnames' import { useDownloadPlugin } from '@/service/use-plugins' +import { cn } from '@/utils/classnames' import { downloadFile } from '@/utils/format' import { getMarketplaceUrl } from '@/utils/var' -import { useQueryClient } from '@tanstack/react-query' type Props = { open: boolean @@ -53,7 +53,8 @@ const OperationDropdown: FC<Props> = ({ }), [author, name, version]) const { data: blob, isLoading } = useDownloadPlugin(downloadInfo, needDownload) const handleDownload = useCallback(() => { - if (isLoading) return + if (isLoading) + return queryClient.removeQueries({ queryKey: ['plugins', 'downloadPlugin', downloadInfo], exact: true, @@ -76,7 +77,7 @@ const OperationDropdown: FC<Props> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 0, crossAxis: 0, @@ -84,13 +85,13 @@ const OperationDropdown: FC<Props> = ({ > <PortalToFollowElemTrigger onClick={handleTrigger}> <ActionButton className={cn(open && 'bg-state-base-hover')}> - <RiMoreFill className='h-4 w-4 text-components-button-secondary-accent-text' /> + <RiMoreFill className="h-4 w-4 text-components-button-secondary-accent-text" /> </ActionButton> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[9999]'> - <div className='min-w-[176px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> - <div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div> - <a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a> + <PortalToFollowElemContent className="z-[9999]"> + <div className="min-w-[176px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> + <div onClick={handleDownload} className="system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover">{t('common.operation.download')}</div> + <a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target="_blank" className="system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover">{t('common.operation.viewDetails')}</a> </div> </PortalToFollowElemContent> </PortalToFollowElem> diff --git a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx index 711bfadc7f..18d8629260 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx @@ -1,16 +1,16 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import { useContext } from 'use-context-selector' -import { useTranslation } from 'react-i18next' -import Action from './action' import type { Plugin } from '@/app/components/plugins/types.ts' +import { useBoolean } from 'ahooks' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import I18n from '@/context/i18n' import { cn } from '@/utils/classnames' import { formatNumber } from '@/utils/format' -import { useBoolean } from 'ahooks' +import Action from './action' enum ActionType { install = 'install', @@ -36,16 +36,16 @@ const Item: FC<Props> = ({ }] = useBoolean(false) return ( - <div className='group/plugin flex rounded-lg py-1 pl-3 pr-1 hover:bg-state-base-hover'> + <div className="group/plugin flex rounded-lg py-1 pl-3 pr-1 hover:bg-state-base-hover"> <div - className='relative h-6 w-6 shrink-0 rounded-md border-[0.5px] border-components-panel-border-subtle bg-contain bg-center bg-no-repeat' + className="relative h-6 w-6 shrink-0 rounded-md border-[0.5px] border-components-panel-border-subtle bg-contain bg-center bg-no-repeat" style={{ backgroundImage: `url(${payload.icon})` }} /> - <div className='ml-2 flex w-0 grow'> - <div className='w-0 grow'> - <div className='system-sm-medium h-4 truncate leading-4 text-text-primary '>{getLocalizedText(payload.label)}</div> - <div className='system-xs-regular h-5 truncate leading-5 text-text-tertiary'>{getLocalizedText(payload.brief)}</div> - <div className='system-xs-regular flex space-x-1 text-text-tertiary'> + <div className="ml-2 flex w-0 grow"> + <div className="w-0 grow"> + <div className="system-sm-medium h-4 truncate leading-4 text-text-primary ">{getLocalizedText(payload.label)}</div> + <div className="system-xs-regular h-5 truncate leading-5 text-text-tertiary">{getLocalizedText(payload.brief)}</div> + <div className="system-xs-regular flex space-x-1 text-text-tertiary"> <div>{payload.org}</div> <div>·</div> <div>{t('plugin.install', { num: formatNumber(payload.install_count || 0) })}</div> @@ -54,7 +54,7 @@ const Item: FC<Props> = ({ {/* Action */} <div className={cn(!open ? 'hidden' : 'flex', 'system-xs-medium h-4 items-center space-x-1 text-components-button-secondary-accent-text group-hover/plugin:flex')}> <div - className='cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover' + className="cursor-pointer rounded-md px-1.5 py-0.5 hover:bg-state-base-hover" onClick={showInstallModal} > {t('plugin.installAction')} diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index b2097c72cf..58724b4621 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -1,15 +1,15 @@ 'use client' -import { useEffect, useImperativeHandle, useMemo, useRef } from 'react' import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' -import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll' -import Item from './item' import type { Plugin, PluginCategoryEnum } from '@/app/components/plugins/types' -import { cn } from '@/utils/classnames' -import Link from 'next/link' import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react' import { noop } from 'lodash-es' +import Link from 'next/link' +import { useEffect, useImperativeHandle, useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' import { getMarketplaceUrl } from '@/utils/var' +import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll' +import Item from './item' export type ListProps = { wrapElemRef: React.RefObject<HTMLElement | null> @@ -79,12 +79,12 @@ const List = ({ return ( <Link - className='system-sm-medium sticky bottom-0 z-10 flex h-8 cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg' + className="system-sm-medium sticky bottom-0 z-10 flex h-8 cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg" href={getMarketplaceUrl('', { category })} - target='_blank' + target="_blank" > <span>{t('plugin.findMoreInMarketplace')}</span> - <RiArrowRightUpLine className='ml-0.5 h-3 w-3' /> + <RiArrowRightUpLine className="ml-0.5 h-3 w-3" /> </Link> ) } @@ -101,12 +101,12 @@ const List = ({ <span>{t('plugin.fromMarketplace')}</span> <Link href={urlWithSearchText} - target='_blank' - className='flex items-center text-text-accent-light-mode-only' + target="_blank" + className="flex items-center text-text-accent-light-mode-only" onClick={e => e.stopPropagation()} > <span>{t('plugin.searchInMarketplace')}</span> - <RiArrowRightUpLine className='ml-0.5 h-3 w-3' /> + <RiArrowRightUpLine className="ml-0.5 h-3 w-3" /> </Link> </div> )} @@ -119,14 +119,14 @@ const List = ({ /> ))} {hasRes && ( - <div className='mb-3 mt-2 flex items-center justify-center space-x-2'> + <div className="mb-3 mt-2 flex items-center justify-center space-x-2"> <div className="h-[2px] w-[90px] bg-gradient-to-l from-[rgba(16,24,40,0.08)] to-[rgba(255,255,255,0.01)]"></div> <Link href={urlWithSearchText} - target='_blank' - className='system-sm-medium flex h-4 shrink-0 items-center text-text-accent-light-mode-only' + target="_blank" + className="system-sm-medium flex h-4 shrink-0 items-center text-text-accent-light-mode-only" > - <RiSearchLine className='mr-0.5 h-3 w-3' /> + <RiSearchLine className="mr-0.5 h-3 w-3" /> <span>{t('plugin.searchInMarketplace')}</span> </Link> <div className="h-[2px] w-[90px] bg-gradient-to-l from-[rgba(255,255,255,0.01)] to-[rgba(16,24,40,0.08)]"></div> diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx index 47b158b9b2..e9255917aa 100644 --- a/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx +++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx @@ -1,17 +1,17 @@ 'use client' import type { Dispatch, SetStateAction } from 'react' +import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select' +import type { OnSelectBlock } from '@/app/components/workflow/types' +import { RiMoreLine } from '@remixicon/react' +import Link from 'next/link' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' -import type { OnSelectBlock } from '@/app/components/workflow/types' -import type { ViewType } from '@/app/components/workflow/block-selector/view-type-select' -import { RiMoreLine } from '@remixicon/react' -import Loading from '@/app/components/base/loading' -import Link from 'next/link' -import { getMarketplaceUrl } from '@/utils/var' -import { useRAGRecommendedPlugins } from '@/service/use-tools' -import List from './list' -import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows' +import Loading from '@/app/components/base/loading' +import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils' +import { useRAGRecommendedPlugins } from '@/service/use-tools' +import { getMarketplaceUrl } from '@/utils/var' +import List from './list' type RAGToolRecommendationsProps = { viewType: ViewType @@ -75,33 +75,33 @@ const RAGToolRecommendations = ({ }, [onTagsChange]) return ( - <div className='flex flex-col p-1'> + <div className="flex flex-col p-1"> <button - type='button' - className='flex w-full items-center rounded-md px-3 pb-0.5 pt-1 text-left text-text-tertiary' + type="button" + className="flex w-full items-center rounded-md px-3 pb-0.5 pt-1 text-left text-text-tertiary" onClick={() => setIsCollapsed(prev => !prev)} > - <span className='system-xs-medium text-text-tertiary'>{t('pipeline.ragToolSuggestions.title')}</span> + <span className="system-xs-medium text-text-tertiary">{t('pipeline.ragToolSuggestions.title')}</span> <ArrowDownRoundFill className={`ml-1 h-4 w-4 text-text-tertiary transition-transform ${isCollapsed ? '-rotate-90' : 'rotate-0'}`} /> </button> {!isCollapsed && ( <> {/* For first time loading, show loading */} {isLoadingRAGRecommendedPlugins && ( - <div className='py-2'> - <Loading type='app' /> + <div className="py-2"> + <Loading type="app" /> </div> )} {!isFetchingRAGRecommendedPlugins && recommendedPlugins.length === 0 && unInstalledPlugins.length === 0 && ( - <p className='system-xs-regular px-3 py-1 text-text-tertiary'> + <p className="system-xs-regular px-3 py-1 text-text-tertiary"> <Trans - i18nKey='pipeline.ragToolSuggestions.noRecommendationPlugins' + i18nKey="pipeline.ragToolSuggestions.noRecommendationPlugins" components={{ CustomLink: ( <Link - className='text-text-accent' - target='_blank' - rel='noopener noreferrer' + className="text-text-accent" + target="_blank" + rel="noopener noreferrer" href={getMarketplaceUrl('', { tags: 'rag' })} /> ), @@ -118,13 +118,13 @@ const RAGToolRecommendations = ({ viewType={viewType} /> <div - className='flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2' + className="flex cursor-pointer items-center gap-x-2 py-1 pl-3 pr-2" onClick={loadMore} > - <div className='px-1'> - <RiMoreLine className='size-4 text-text-tertiary' /> + <div className="px-1"> + <RiMoreLine className="size-4 text-text-tertiary" /> </div> - <div className='system-xs-regular text-text-tertiary'> + <div className="system-xs-regular text-text-tertiary"> {t('common.operation.more')} </div> </div> diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx index 2012d03598..a3babd0953 100644 --- a/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx +++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx @@ -1,15 +1,15 @@ -import { useCallback, useMemo, useRef } from 'react' import type { BlockEnum, ToolWithProvider } from '../../types' import type { ToolDefaultValue } from '../types' -import { ViewType } from '../view-type-select' -import { useGetLanguage } from '@/context/i18n' -import { groupItems } from '../index-bar' -import { cn } from '@/utils/classnames' -import ToolListTreeView from '../tool/tool-list-tree-view/list' -import ToolListFlatView from '../tool/tool-list-flat-view/list' -import UninstalledItem from './uninstalled-item' import type { Plugin } from '@/app/components/plugins/types' import type { OnSelectBlock } from '@/app/components/workflow/types' +import { useCallback, useMemo, useRef } from 'react' +import { useGetLanguage } from '@/context/i18n' +import { cn } from '@/utils/classnames' +import { groupItems } from '../index-bar' +import ToolListFlatView from '../tool/tool-list-flat-view/list' +import ToolListTreeView from '../tool/tool-list-tree-view/list' +import { ViewType } from '../view-type-select' +import UninstalledItem from './uninstalled-item' type ListProps = { onSelect: OnSelectBlock @@ -67,25 +67,27 @@ const List = ({ return ( <div className={cn('max-w-[100%] p-1', className)}> {!!tools.length && ( - isFlatView ? ( - <ToolListFlatView - toolRefs={toolRefs} - letters={letters} - payload={listViewToolData} - isShowLetterIndex={false} - hasSearchText={false} - onSelect={handleSelect} - canNotSelectMultiple - indexBar={null} - /> - ) : ( - <ToolListTreeView - payload={treeViewToolsData} - hasSearchText={false} - onSelect={handleSelect} - canNotSelectMultiple - /> - ) + isFlatView + ? ( + <ToolListFlatView + toolRefs={toolRefs} + letters={letters} + payload={listViewToolData} + isShowLetterIndex={false} + hasSearchText={false} + onSelect={handleSelect} + canNotSelectMultiple + indexBar={null} + /> + ) + : ( + <ToolListTreeView + payload={treeViewToolsData} + hasSearchText={false} + onSelect={handleSelect} + canNotSelectMultiple + /> + ) )} { unInstalledPlugins.map((item) => { diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx index 98395ec25a..9a351c4eff 100644 --- a/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx +++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx @@ -1,13 +1,13 @@ 'use client' -import React from 'react' -import { useContext } from 'use-context-selector' -import { useTranslation } from 'react-i18next' import type { Plugin } from '@/app/components/plugins/types' +import { useBoolean } from 'ahooks' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import I18n from '@/context/i18n' -import { useBoolean } from 'ahooks' -import { BlockEnum } from '../../types' import BlockIcon from '../../block-icon' +import { BlockEnum } from '../../types' type UninstalledItemProps = { payload: Plugin @@ -27,23 +27,23 @@ const UninstalledItem = ({ }] = useBoolean(false) return ( - <div className='flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover'> + <div className="flex h-8 items-center rounded-lg pl-3 pr-2 hover:bg-state-base-hover"> <BlockIcon - className='shrink-0' + className="shrink-0" type={BlockEnum.Tool} toolIcon={payload.icon} /> - <div className='ml-2 flex w-0 grow items-center'> - <div className='flex w-0 grow items-center gap-x-2'> - <span className='system-sm-regular truncate text-text-primary'> + <div className="ml-2 flex w-0 grow items-center"> + <div className="flex w-0 grow items-center gap-x-2"> + <span className="system-sm-regular truncate text-text-primary"> {getLocalizedText(payload.label)} </span> - <span className='system-xs-regular text-text-quaternary'> + <span className="system-xs-regular text-text-quaternary"> {payload.org} </span> </div> <div - className='system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text' + className="system-xs-medium cursor-pointer pl-1.5 text-components-button-secondary-accent-text" onClick={showInstallModal} > {t('plugin.installAction')} diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx index 07404460be..5c4311b805 100644 --- a/web/app/components/workflow/block-selector/start-blocks.tsx +++ b/web/app/components/workflow/block-selector/start-blocks.tsx @@ -1,19 +1,19 @@ +import type { BlockEnum, CommonNodeType } from '../types' +import type { TriggerDefaultValue } from './types' import { memo, useCallback, useEffect, useMemo, } from 'react' -import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import { useAvailableNodesMetaData } from '../../workflow-app/hooks' import BlockIcon from '../block-icon' -import type { BlockEnum, CommonNodeType } from '../types' import { BlockEnum as BlockEnumValues } from '../types' // import { useNodeMetaData } from '../hooks' import { START_BLOCKS } from './constants' -import type { TriggerDefaultValue } from './types' -import Tooltip from '@/app/components/base/tooltip' -import { useAvailableNodesMetaData } from '../../workflow-app/hooks' type StartBlocksProps = { searchText: string @@ -67,48 +67,49 @@ const StartBlocks = ({ onContentStateChange?.(!isEmpty) }, [isEmpty, onContentStateChange]) - const renderBlock = useCallback((block: { type: BlockEnum; title: string; description?: string }) => ( + const renderBlock = useCallback((block: { type: BlockEnum, title: string, description?: string }) => ( <Tooltip key={block.type} - position='right' - popupClassName='w-[224px] rounded-xl' + position="right" + popupClassName="w-[224px] rounded-xl" needsDelay={false} popupContent={( <div> <BlockIcon - size='md' - className='mb-2' + size="md" + className="mb-2" type={block.type} /> - <div className='system-md-medium mb-1 text-text-primary'> + <div className="system-md-medium mb-1 text-text-primary"> {block.type === BlockEnumValues.TriggerWebhook ? t('workflow.customWebhook') - : t(`workflow.blocks.${block.type}`) - } + : t(`workflow.blocks.${block.type}`)} </div> - <div className='system-xs-regular text-text-secondary'> + <div className="system-xs-regular text-text-secondary"> {t(`workflow.blocksAbout.${block.type}`)} </div> {(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && ( - <div className='system-xs-regular mb-1 mt-1 text-text-tertiary'> - {t('tools.author')} {t('workflow.difyTeam')} + <div className="system-xs-regular mb-1 mt-1 text-text-tertiary"> + {t('tools.author')} + {' '} + {t('workflow.difyTeam')} </div> )} </div> )} > <div - className='flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover' + className="flex h-8 w-full cursor-pointer items-center rounded-lg px-3 hover:bg-state-base-hover" onClick={() => onSelect(block.type)} > <BlockIcon - className='mr-2 shrink-0' + className="mr-2 shrink-0" type={block.type} /> - <div className='flex w-0 grow items-center justify-between text-sm text-text-secondary'> - <span className='truncate'>{t(`workflow.blocks.${block.type}`)}</span> + <div className="flex w-0 grow items-center justify-between text-sm text-text-secondary"> + <span className="truncate">{t(`workflow.blocks.${block.type}`)}</span> {block.type === BlockEnumValues.Start && ( - <span className='system-xs-regular ml-2 shrink-0 text-text-quaternary'>{t('workflow.blocks.originalStartNode')}</span> + <span className="system-xs-regular ml-2 shrink-0 text-text-quaternary">{t('workflow.blocks.originalStartNode')}</span> )} </div> </div> @@ -119,14 +120,14 @@ const StartBlocks = ({ return null return ( - <div className='p-1'> - <div className='mb-1'> + <div className="p-1"> + <div className="mb-1"> {filteredBlocks.map((block, index) => ( <div key={block.type}> {renderBlock(block)} {block.type === BlockEnumValues.Start && index < filteredBlocks.length - 1 && ( - <div className='my-1 px-3'> - <div className='border-t border-divider-subtle' /> + <div className="my-1 px-3"> + <div className="border-t border-divider-subtle" /> </div> )} </div> diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 0367208cfe..6b54122142 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -1,24 +1,24 @@ import type { Dispatch, FC, SetStateAction } from 'react' -import { memo, useEffect, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' import type { BlockEnum, NodeDefault, OnSelectBlock, ToolWithProvider, } from '../types' -import { TabsEnum } from './types' -import Blocks from './blocks' +import { memo, useEffect, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { useFeaturedToolsRecommendations } from '@/service/use-plugins' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' +import { cn } from '@/utils/classnames' +import { basePath } from '@/utils/var' +import { useWorkflowStore } from '../store' import AllStartBlocks from './all-start-blocks' import AllTools from './all-tools' +import Blocks from './blocks' import DataSources from './data-sources' -import { cn } from '@/utils/classnames' -import { useFeaturedToolsRecommendations } from '@/service/use-plugins' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { useWorkflowStore } from '../store' -import { basePath } from '@/utils/var' -import Tooltip from '@/app/components/base/tooltip' +import { TabsEnum } from './types' export type TabsProps = { activeTab: TabsEnum @@ -129,7 +129,7 @@ const Tabs: FC<TabsProps> = ({ <div onClick={e => e.stopPropagation()}> { !noBlocks && ( - <div className='relative flex bg-background-section-burn pl-1 pt-1'> + <div className="relative flex bg-background-section-burn pl-1 pt-1"> { tabs.map((tab) => { const commonProps = { @@ -152,8 +152,8 @@ const Tabs: FC<TabsProps> = ({ return ( <Tooltip key={tab.key} - position='top' - popupClassName='max-w-[200px]' + position="top" + popupClassName="max-w-[200px]" popupContent={t('workflow.tabs.startDisabledTip')} > <div {...commonProps}> @@ -178,7 +178,7 @@ const Tabs: FC<TabsProps> = ({ {filterElem} { activeTab === TabsEnum.Start && (!noBlocks || forceShowStartContent) && ( - <div className='border-t border-divider-subtle'> + <div className="border-t border-divider-subtle"> <AllStartBlocks allowUserInputSelection={allowStartNodeSelection} searchText={searchText} @@ -191,7 +191,7 @@ const Tabs: FC<TabsProps> = ({ } { activeTab === TabsEnum.Blocks && !noBlocks && ( - <div className='border-t border-divider-subtle'> + <div className="border-t border-divider-subtle"> <Blocks searchText={searchText} onSelect={onSelect} @@ -203,7 +203,7 @@ const Tabs: FC<TabsProps> = ({ } { activeTab === TabsEnum.Sources && !!dataSources.length && ( - <div className='border-t border-divider-subtle'> + <div className="border-t border-divider-subtle"> <DataSources searchText={searchText} onSelect={onSelect} diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index c10496006d..cbb8d5a01e 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -1,28 +1,29 @@ 'use client' +import type { + OffsetOptions, + Placement, +} from '@floating-ui/react' import type { FC } from 'react' -import React from 'react' -import { useMemo, useState } from 'react' +import type { ToolDefaultValue, ToolValue } from './types' +import type { CustomCollectionBackend } from '@/app/components/tools/types' +import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types' +import { useBoolean } from 'ahooks' +import React, { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { - OffsetOptions, - Placement, -} from '@floating-ui/react' -import AllTools from '@/app/components/workflow/block-selector/all-tools' -import type { ToolDefaultValue, ToolValue } from './types' -import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types' +import Toast from '@/app/components/base/toast' import SearchBox from '@/app/components/plugins/marketplace/search-box' -import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' +import AllTools from '@/app/components/workflow/block-selector/all-tools' +import { useGlobalPublicStore } from '@/context/global-public-context' import { createCustomCollection, } from '@/service/tools' -import type { CustomCollectionBackend } from '@/app/components/tools/types' -import Toast from '@/app/components/base/toast' +import { useFeaturedToolsRecommendations } from '@/service/use-plugins' import { useAllBuiltInTools, useAllCustomTools, @@ -33,8 +34,6 @@ import { useInvalidateAllMCPTools, useInvalidateAllWorkflowTools, } from '@/service/use-tools' -import { useFeaturedToolsRecommendations } from '@/service/use-plugins' -import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' type Props = { @@ -119,7 +118,8 @@ const ToolPicker: FC<Props> = ({ const handleAddedCustomTool = invalidateCustomTools const handleTriggerClick = () => { - if (disabled) return + if (disabled) + return onShowChange(true) } @@ -149,7 +149,7 @@ const ToolPicker: FC<Props> = ({ if (isShowEditCollectionToolModal) { return ( <EditCustomToolModal - dialogClassName='bg-background-overlay' + dialogClassName="bg-background-overlay" payload={null} onHide={hideEditCustomCollectionModal} onAdd={doCreateCustomToolCollection} @@ -170,9 +170,9 @@ const ToolPicker: FC<Props> = ({ {trigger} </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> + <PortalToFollowElemContent className="z-[1000]"> <div className={cn('relative min-h-20 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm', panelClassName)}> - <div className='p-2 pb-1'> + <div className="p-2 pb-1"> <SearchBox search={searchText} onSearchChange={setSearchText} @@ -182,12 +182,12 @@ const ToolPicker: FC<Props> = ({ supportAddCustomTool={supportAddCustomTool} onAddedCustomTool={handleAddedCustomTool} onShowAddCustomCollectionModal={showEditCustomCollectionModal} - inputClassName='grow' + inputClassName="grow" /> </div> <AllTools - className='mt-1' - toolContentClassName='max-w-[100%]' + className="mt-1" + toolContentClassName="max-w-[100%]" tags={tags} searchText={searchText} onSelect={handleSelect as OnSelectBlock} diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index 617a28ade2..60fac5e701 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' -import React, { useMemo } from 'react' import type { ToolWithProvider } from '../../types' -import { BlockEnum } from '../../types' import type { ToolDefaultValue } from '../types' -import Tooltip from '@/app/components/base/tooltip' import type { Tool } from '@/app/components/tools/types' -import { useGetLanguage } from '@/context/i18n' -import BlockIcon from '../../block-icon' -import { cn } from '@/utils/classnames' +import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' +import { trackEvent } from '@/app/components/base/amplitude' +import Tooltip from '@/app/components/base/tooltip' +import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' +import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' -import { trackEvent } from '@/app/components/base/amplitude' +import BlockIcon from '../../block-icon' +import { BlockEnum } from '../../types' const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => { if (!icon) @@ -59,27 +59,28 @@ const ToolItem: FC<Props> = ({ return ( <Tooltip key={payload.name} - position='right' + position="right" needsDelay={false} - popupClassName='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' + popupClassName="!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg" popupContent={( <div> <BlockIcon - size='md' - className='mb-2' + size="md" + className="mb-2" type={BlockEnum.Tool} toolIcon={providerIcon} /> - <div className='mb-1 text-sm leading-5 text-text-primary'>{payload.label[language]}</div> - <div className='text-xs leading-[18px] text-text-secondary'>{payload.description[language]}</div> + <div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div> + <div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div> </div> )} > <div key={payload.name} - className='flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover' + className="flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover" onClick={() => { - if (disabled) return + if (disabled) + return const params: Record<string, string> = {} if (payload.parameters) { payload.parameters.forEach((item) => { @@ -113,10 +114,10 @@ const ToolItem: FC<Props> = ({ <span className={cn(disabled && 'opacity-30')}>{payload.label[language]}</span> </div> {isAdded && ( - <div className='system-xs-regular mr-4 text-text-tertiary'>{t('tools.addToolModal.added')}</div> + <div className="system-xs-regular mr-4 text-text-tertiary">{t('tools.addToolModal.added')}</div> )} </div> - </Tooltip > + </Tooltip> ) } export default React.memo(ToolItem) diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx index 510d6f2f4b..54eb050e06 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx @@ -1,12 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import type { ToolWithProvider } from '../../../types' -import type { BlockEnum } from '../../../types' +import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' -import Tool from '../tool' +import React, { useMemo } from 'react' import { ViewType } from '../../view-type-select' -import { useMemo } from 'react' +import Tool from '../tool' type Props = { payload: ToolWithProvider[] @@ -45,8 +43,8 @@ const ToolViewFlatView: FC<Props> = ({ return res }, [payload, letters]) return ( - <div className='flex w-full'> - <div className='mr-1 grow'> + <div className="flex w-full"> + <div className="mr-1 grow"> {payload.map(tool => ( <div key={tool.id} diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx index a2833646f3..64a376e394 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx @@ -1,11 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import type { ToolWithProvider } from '../../../types' -import Tool from '../tool' -import type { BlockEnum } from '../../../types' -import { ViewType } from '../../view-type-select' +import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' +import React from 'react' +import { ViewType } from '../../view-type-select' +import Tool from '../tool' type Props = { groupName: string @@ -30,7 +29,7 @@ const Item: FC<Props> = ({ }) => { return ( <div> - <div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'> + <div className="flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary"> {groupName} </div> <div> diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx index 162b816069..0f790ab036 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -1,12 +1,11 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import type { ToolWithProvider } from '../../../types' -import type { BlockEnum } from '../../../types' +import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' -import Item from './item' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { AGENT_GROUP_NAME, CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar' +import Item from './item' type Props = { payload: Record<string, ToolWithProvider[]> @@ -41,7 +40,8 @@ const ToolListTreeView: FC<Props> = ({ return name }, [t]) - if (!payload) return null + if (!payload) + return null return ( <div> diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 622b06734b..366059b311 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -1,24 +1,24 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' -import { cn } from '@/utils/classnames' -import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' -import { useGetLanguage } from '@/context/i18n' import type { Tool as ToolType } from '../../../tools/types' -import { CollectionType } from '../../../tools/types' import type { ToolWithProvider } from '../../types' -import { BlockEnum } from '../../types' import type { ToolDefaultValue, ToolValue } from '../types' -import { ViewType } from '../view-type-select' -import ActionItem from './action-item' -import BlockIcon from '../../block-icon' -import { useTranslation } from 'react-i18next' +import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useHover } from 'ahooks' +import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { Mcp } from '@/app/components/base/icons/src/vender/other' +import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' -import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip' -import { Mcp } from '@/app/components/base/icons/src/vender/other' +import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' +import { CollectionType } from '../../../tools/types' +import BlockIcon from '../../block-icon' +import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip' +import { BlockEnum } from '../../types' +import { ViewType } from '../view-type-select' +import ActionItem from './action-item' const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => { if (!icon) @@ -78,7 +78,8 @@ const Tool: FC<Props> = ({ return normalizedIcon }, [theme, normalizedIcon, normalizedIconDark]) const getIsDisabled = useCallback((tool: ToolType) => { - if (!selectedTools || !selectedTools.length) return false + if (!selectedTools || !selectedTools.length) + return false return selectedTools.some(selectedTool => (selectedTool.provider_name === payload.name || selectedTool.provider_name === payload.id) && selectedTool.tool_name === tool.name) }, [payload.id, payload.name, selectedTools]) @@ -89,7 +90,7 @@ const Tool: FC<Props> = ({ const notShowProviderSelectInfo = useMemo(() => { if (isAllSelected) { return ( - <span className='system-xs-regular text-text-tertiary'> + <span className="system-xs-regular text-text-tertiary"> {t('tools.addToolModal.added')} </span> ) @@ -98,7 +99,8 @@ const Tool: FC<Props> = ({ const selectedInfo = useMemo(() => { if (isHovering && !isAllSelected) { return ( - <span className='system-xs-regular text-components-button-secondary-accent-text' + <span + className="system-xs-regular text-components-button-secondary-accent-text" onClick={() => { onSelectMultiple?.(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => { const params: Record<string, string> = {} @@ -135,11 +137,10 @@ const Tool: FC<Props> = ({ return <></> return ( - <span className='system-xs-regular text-text-tertiary'> + <span className="system-xs-regular text-text-tertiary"> {isAllSelected ? t('workflow.tabs.allAdded') - : `${selectedToolsNum} / ${totalToolsNum}` - } + : `${selectedToolsNum} / ${totalToolsNum}`} </span> ) }, [actions, getIsDisabled, isAllSelected, isHovering, language, onSelectMultiple, payload.id, payload.is_team_authorization, payload.name, payload.type, selectedToolsNum, t, totalToolsNum]) @@ -176,7 +177,7 @@ const Tool: FC<Props> = ({ > <div className={cn(className)}> <div - className='group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover' + className="group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover" onClick={() => { if (hasAction) { setFold(!isFold) @@ -210,20 +211,20 @@ const Tool: FC<Props> = ({ > <div className={cn('flex h-8 grow items-center', isShowCanNotChooseMCPTip && 'opacity-30')}> <BlockIcon - className='shrink-0' + className="shrink-0" type={BlockEnum.Tool} toolIcon={providerIcon} /> - <div className='ml-2 flex w-0 grow items-center text-sm text-text-primary'> - <span className='max-w-[250px] truncate'>{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span> + <div className="ml-2 flex w-0 grow items-center text-sm text-text-primary"> + <span className="max-w-[250px] truncate">{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span> {isFlatView && groupName && ( - <span className='system-xs-regular ml-2 shrink-0 text-text-quaternary'>{groupName}</span> + <span className="system-xs-regular ml-2 shrink-0 text-text-quaternary">{groupName}</span> )} - {isMCPTool && <Mcp className='ml-2 size-3.5 shrink-0 text-text-quaternary' />} + {isMCPTool && <Mcp className="ml-2 size-3.5 shrink-0 text-text-quaternary" />} </div> </div> - <div className='ml-2 flex items-center'> + <div className="ml-2 flex items-center"> {!isShowCanNotChooseMCPTip && !canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)} {isShowCanNotChooseMCPTip && <McpToolNotSupportTooltip />} {hasAction && ( diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index 788905323e..8035cc2dbf 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -1,14 +1,13 @@ -import { memo, useMemo, useRef } from 'react' import type { BlockEnum, ToolWithProvider } from '../types' -import IndexBar, { groupItems } from './index-bar' -import type { ToolDefaultValue, ToolValue } from './types' -import type { ToolTypeEnum } from './types' -import { ViewType } from './view-type-select' +import type { ToolDefaultValue, ToolTypeEnum, ToolValue } from './types' +import { memo, useMemo, useRef } from 'react' import Empty from '@/app/components/tools/provider/empty' import { useGetLanguage } from '@/context/i18n' -import ToolListTreeView from './tool/tool-list-tree-view/list' -import ToolListFlatView from './tool/tool-list-flat-view/list' import { cn } from '@/utils/classnames' +import IndexBar, { groupItems } from './index-bar' +import ToolListFlatView from './tool/tool-list-flat-view/list' +import ToolListTreeView from './tool/tool-list-tree-view/list' +import { ViewType } from './view-type-select' type ToolsProps = { onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void @@ -93,36 +92,38 @@ const Tools = ({ return ( <div className={cn('max-w-[100%] p-1', className)}> {!tools.length && !hasSearchText && ( - <div className='py-10'> + <div className="py-10"> <Empty type={toolType!} isAgent={isAgent} /> </div> )} {!!tools.length && ( - isFlatView ? ( - <ToolListFlatView - toolRefs={toolRefs} - letters={letters} - payload={listViewToolData} - isShowLetterIndex={isShowLetterIndex} - hasSearchText={hasSearchText} - onSelect={onSelect} - canNotSelectMultiple={canNotSelectMultiple} - onSelectMultiple={onSelectMultiple} - selectedTools={selectedTools} - canChooseMCPTool={canChooseMCPTool} - indexBar={<IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />} - /> - ) : ( - <ToolListTreeView - payload={treeViewToolsData} - hasSearchText={hasSearchText} - onSelect={onSelect} - canNotSelectMultiple={canNotSelectMultiple} - onSelectMultiple={onSelectMultiple} - selectedTools={selectedTools} - canChooseMCPTool={canChooseMCPTool} - /> - ) + isFlatView + ? ( + <ToolListFlatView + toolRefs={toolRefs} + letters={letters} + payload={listViewToolData} + isShowLetterIndex={isShowLetterIndex} + hasSearchText={hasSearchText} + onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} + onSelectMultiple={onSelectMultiple} + selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} + indexBar={<IndexBar letters={letters} itemRefs={toolRefs} className={indexBarClassName} />} + /> + ) + : ( + <ToolListTreeView + payload={treeViewToolsData} + hasSearchText={hasSearchText} + onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} + onSelectMultiple={onSelectMultiple} + selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} + /> + ) )} </div> ) diff --git a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx index e22712c248..c0edec474a 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx @@ -1,15 +1,14 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import type { TriggerWithProvider } from '../types' +import type { TriggerDefaultValue, TriggerWithProvider } from '../types' import type { Event } from '@/app/components/tools/types' -import { BlockEnum } from '../../types' -import type { TriggerDefaultValue } from '../types' +import React from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { useGetLanguage } from '@/context/i18n' -import BlockIcon from '../../block-icon' import { cn } from '@/utils/classnames' -import { useTranslation } from 'react-i18next' +import BlockIcon from '../../block-icon' +import { BlockEnum } from '../../types' type Props = { provider: TriggerWithProvider @@ -32,27 +31,28 @@ const TriggerPluginActionItem: FC<Props> = ({ return ( <Tooltip key={payload.name} - position='right' + position="right" needsDelay={false} - popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' + popupClassName="!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg" popupContent={( <div> <BlockIcon - size='md' - className='mb-2' + size="md" + className="mb-2" type={BlockEnum.TriggerPlugin} toolIcon={provider.icon} /> - <div className='mb-1 text-sm leading-5 text-text-primary'>{payload.label[language]}</div> - <div className='text-xs leading-[18px] text-text-secondary'>{payload.description[language]}</div> + <div className="mb-1 text-sm leading-5 text-text-primary">{payload.label[language]}</div> + <div className="text-xs leading-[18px] text-text-secondary">{payload.description[language]}</div> </div> )} > <div key={payload.name} - className='flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover' + className="flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover" onClick={() => { - if (disabled) return + if (disabled) + return const params: Record<string, string> = {} if (payload.parameters) { payload.parameters.forEach((item: any) => { @@ -81,10 +81,10 @@ const TriggerPluginActionItem: FC<Props> = ({ <span className={cn(disabled && 'opacity-30')}>{payload.label[language]}</span> </div> {isAdded && ( - <div className='system-xs-regular mr-4 text-text-tertiary'>{t('tools.addToolModal.added')}</div> + <div className="system-xs-regular mr-4 text-text-tertiary">{t('tools.addToolModal.added')}</div> )} </div> - </Tooltip > + </Tooltip> ) } export default React.memo(TriggerPluginActionItem) diff --git a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx index 15b8d638fe..a4de7b30dd 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx @@ -1,18 +1,18 @@ 'use client' -import { useGetLanguage } from '@/context/i18n' -import { cn } from '@/utils/classnames' -import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import type { FC } from 'react' +import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import React, { useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { CollectionType } from '@/app/components/tools/types' import BlockIcon from '@/app/components/workflow/block-icon' import { BlockEnum } from '@/app/components/workflow/types' -import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types' -import TriggerPluginActionItem from './action-item' -import { Theme } from '@/types/app' +import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' +import { Theme } from '@/types/app' +import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' +import TriggerPluginActionItem from './action-item' const normalizeProviderIcon = (icon?: TriggerWithProvider['icon']) => { if (!icon) @@ -93,7 +93,7 @@ const TriggerPluginItem: FC<Props> = ({ > <div className={cn(className)}> <div - className='group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover' + className="group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover" onClick={() => { if (hasAction) { setFold(!isFold) @@ -124,19 +124,19 @@ const TriggerPluginItem: FC<Props> = ({ }) }} > - <div className='flex h-8 grow items-center'> + <div className="flex h-8 grow items-center"> <BlockIcon - className='shrink-0' + className="shrink-0" type={BlockEnum.TriggerPlugin} toolIcon={providerIcon} /> - <div className='ml-2 flex min-w-0 flex-1 items-center text-sm text-text-primary'> - <span className='max-w-[200px] truncate'>{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span> - <span className='system-xs-regular ml-2 truncate text-text-quaternary'>{groupName}</span> + <div className="ml-2 flex min-w-0 flex-1 items-center text-sm text-text-primary"> + <span className="max-w-[200px] truncate">{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span> + <span className="system-xs-regular ml-2 truncate text-text-quaternary">{groupName}</span> </div> </div> - <div className='ml-2 flex items-center'> + <div className="ml-2 flex items-center"> {hasAction && ( <FoldIcon className={cn('h-4 w-4 shrink-0 text-text-tertiary group-hover/item:text-text-tertiary', isFold && 'text-text-quaternary')} /> )} diff --git a/web/app/components/workflow/block-selector/trigger-plugin/list.tsx b/web/app/components/workflow/block-selector/trigger-plugin/list.tsx index 3caf1149dd..126583be73 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/list.tsx @@ -1,10 +1,10 @@ 'use client' -import { memo, useEffect, useMemo } from 'react' -import { useAllTriggerPlugins } from '@/service/use-triggers' -import TriggerPluginItem from './item' import type { BlockEnum } from '../../types' import type { TriggerDefaultValue, TriggerWithProvider } from '../types' +import { memo, useEffect, useMemo } from 'react' import { useGetLanguage } from '@/context/i18n' +import { useAllTriggerPlugins } from '@/service/use-triggers' +import TriggerPluginItem from './item' type TriggerPluginListProps = { onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 1e5acbbeb3..6ed4d7f2d5 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -1,6 +1,6 @@ -import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods, TriggerEvent } from '../../plugins/types' import type { Collection, Event } from '../../tools/types' +import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' export enum TabsEnum { Start = 'start', @@ -99,7 +99,7 @@ export type DataSourceItem = { identity: { author: string description: TypeWithI18N - icon: string | { background: string; content: string } + icon: string | { background: string, content: string } label: TypeWithI18N name: string tags: string[] @@ -108,7 +108,7 @@ export type DataSourceItem = { description: TypeWithI18N identity: { author: string - icon?: string | { background: string; content: string } + icon?: string | { background: string, content: string } label: TypeWithI18N name: string provider: string @@ -130,7 +130,7 @@ export type TriggerParameter = { label: TypeWithI18N description?: TypeWithI18N type: 'string' | 'number' | 'boolean' | 'select' | 'file' | 'files' - | 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select' + | 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select' auto_generate?: { type: string value?: any @@ -154,7 +154,7 @@ export type TriggerParameter = { export type TriggerCredentialField = { type: 'secret-input' | 'text-input' | 'select' | 'boolean' - | 'app-selector' | 'model-selector' | 'tools-selector' + | 'app-selector' | 'model-selector' | 'tools-selector' name: string scope?: string | null required: boolean diff --git a/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts b/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts index 98986cf3b6..e8f5fc0559 100644 --- a/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts +++ b/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts @@ -5,7 +5,8 @@ const useCheckVerticalScrollbar = (ref: React.RefObject<HTMLElement>) => { useEffect(() => { const elem = ref.current - if (!elem) return + if (!elem) + return const checkScrollbar = () => { setHasVerticalScrollbar(elem.scrollHeight > elem.clientHeight) diff --git a/web/app/components/workflow/block-selector/use-sticky-scroll.ts b/web/app/components/workflow/block-selector/use-sticky-scroll.ts index 7933d63b39..67ea70d94e 100644 --- a/web/app/components/workflow/block-selector/use-sticky-scroll.ts +++ b/web/app/components/workflow/block-selector/use-sticky-scroll.ts @@ -1,5 +1,5 @@ -import React from 'react' import { useThrottleFn } from 'ahooks' +import React from 'react' export enum ScrollPosition { belowTheWrap = 'belowTheWrap', diff --git a/web/app/components/workflow/block-selector/utils.ts b/web/app/components/workflow/block-selector/utils.ts index 4272e61644..f4a0baf05b 100644 --- a/web/app/components/workflow/block-selector/utils.ts +++ b/web/app/components/workflow/block-selector/utils.ts @@ -1,5 +1,5 @@ -import type { Tool } from '@/app/components/tools/types' import type { DataSourceItem } from './types' +import type { Tool } from '@/app/components/tools/types' export const transformDataSourceToTool = (dataSourceItem: DataSourceItem) => { return { diff --git a/web/app/components/workflow/block-selector/view-type-select.tsx b/web/app/components/workflow/block-selector/view-type-select.tsx index 900453fedb..a4830d8e81 100644 --- a/web/app/components/workflow/block-selector/view-type-select.tsx +++ b/web/app/components/workflow/block-selector/view-type-select.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' import { RiNodeTree, RiSortAlphabetAsc } from '@remixicon/react' +import React, { useCallback } from 'react' import { cn } from '@/utils/classnames' export enum ViewType { @@ -27,30 +27,26 @@ const ViewTypeSelect: FC<Props> = ({ }, [viewType, onChange]) return ( - <div className='flex items-center rounded-lg bg-components-segmented-control-bg-normal p-px'> + <div className="flex items-center rounded-lg bg-components-segmented-control-bg-normal p-px"> <div className={ - cn('rounded-lg p-[3px]', - viewType === ViewType.flat - ? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs' - : 'cursor-pointer text-text-tertiary', - ) + cn('rounded-lg p-[3px]', viewType === ViewType.flat + ? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs' + : 'cursor-pointer text-text-tertiary') } onClick={handleChange(ViewType.flat)} > - <RiSortAlphabetAsc className='h-4 w-4' /> + <RiSortAlphabetAsc className="h-4 w-4" /> </div> <div className={ - cn('rounded-lg p-[3px]', - viewType === ViewType.tree - ? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs' - : 'cursor-pointer text-text-tertiary', - ) + cn('rounded-lg p-[3px]', viewType === ViewType.tree + ? 'bg-components-segmented-control-item-active-bg text-text-accent-light-mode-only shadow-xs' + : 'cursor-pointer text-text-tertiary') } onClick={handleChange(ViewType.tree)} > - <RiNodeTree className='h-4 w-4 ' /> + <RiNodeTree className="h-4 w-4 " /> </div> </div> ) diff --git a/web/app/components/workflow/candidate-node-main.tsx b/web/app/components/workflow/candidate-node-main.tsx index 41a38e0b2a..9df5510627 100644 --- a/web/app/components/workflow/candidate-node-main.tsx +++ b/web/app/components/workflow/candidate-node-main.tsx @@ -4,27 +4,27 @@ import type { import type { Node, } from '@/app/components/workflow/types' +import { useEventListener } from 'ahooks' +import { produce } from 'immer' import { memo, } from 'react' -import { produce } from 'immer' import { useReactFlow, useStoreApi, useViewport, } from 'reactflow' -import { useEventListener } from 'ahooks' +import { CUSTOM_NODE } from './constants' +import { useAutoGenerateWebhookUrl, useNodesInteractions, useNodesSyncDraft, useWorkflowHistory, WorkflowHistoryEvent } from './hooks' +import CustomNode from './nodes' +import CustomNoteNode from './note-node' +import { CUSTOM_NOTE_NODE } from './note-node/constants' import { useStore, useWorkflowStore, } from './store' -import { WorkflowHistoryEvent, useAutoGenerateWebhookUrl, useNodesInteractions, useNodesSyncDraft, useWorkflowHistory } from './hooks' -import { CUSTOM_NODE } from './constants' -import { getIterationStartNode, getLoopStartNode } from './utils' -import CustomNode from './nodes' -import CustomNoteNode from './note-node' -import { CUSTOM_NOTE_NODE } from './note-node/constants' import { BlockEnum } from './types' +import { getIterationStartNode, getLoopStartNode } from './utils' type Props = { candidateNode: Node @@ -94,7 +94,7 @@ const CandidateNodeMain: FC<Props> = ({ return ( <div - className='absolute z-10' + className="absolute z-10" style={{ left: mousePosition.elementX, top: mousePosition.elementY, diff --git a/web/app/components/workflow/candidate-node.tsx b/web/app/components/workflow/candidate-node.tsx index bdbb1f4433..2c61f0f8ad 100644 --- a/web/app/components/workflow/candidate-node.tsx +++ b/web/app/components/workflow/candidate-node.tsx @@ -2,10 +2,10 @@ import { memo, } from 'react' +import CandidateNodeMain from './candidate-node-main' import { useStore, } from './store' -import CandidateNodeMain from './candidate-node-main' const CandidateNode = () => { const candidateNode = useStore(s => s.candidateNode) diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index ad498ff65b..4d95db7fcf 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -1,5 +1,6 @@ import type { Var } from './types' import { BlockEnum, VarType } from './types' + export const MAX_ITERATION_PARALLEL_NUM = 10 export const MIN_ITERATION_PARALLEL_NUM = 1 export const DEFAULT_ITER_TIMES = 1 @@ -42,16 +43,18 @@ export const isInWorkflowPage = () => { export const getGlobalVars = (isChatMode: boolean): Var[] => { const isInWorkflow = isInWorkflowPage() const vars: Var[] = [ - ...(isChatMode ? [ - { - variable: 'sys.dialogue_count', - type: VarType.number, - }, - { - variable: 'sys.conversation_id', - type: VarType.string, - }, - ] : []), + ...(isChatMode + ? [ + { + variable: 'sys.dialogue_count', + type: VarType.number, + }, + { + variable: 'sys.conversation_id', + type: VarType.string, + }, + ] + : []), { variable: 'sys.user_id', type: VarType.string, @@ -68,12 +71,14 @@ export const getGlobalVars = (isChatMode: boolean): Var[] => { variable: 'sys.workflow_run_id', type: VarType.string, }, - ...((isInWorkflow && !isChatMode) ? [ - { - variable: 'sys.timestamp', - type: VarType.number, - }, - ] : []), + ...((isInWorkflow && !isChatMode) + ? [ + { + variable: 'sys.timestamp', + type: VarType.number, + }, + ] + : []), ] return vars } @@ -104,11 +109,25 @@ export const RETRIEVAL_OUTPUT_STRUCT = `{ }` export const SUPPORT_OUTPUT_VARS_NODE = [ - BlockEnum.Start, BlockEnum.TriggerWebhook, BlockEnum.TriggerPlugin, BlockEnum.LLM, BlockEnum.KnowledgeRetrieval, BlockEnum.Code, BlockEnum.TemplateTransform, - BlockEnum.HttpRequest, BlockEnum.Tool, BlockEnum.VariableAssigner, BlockEnum.VariableAggregator, BlockEnum.QuestionClassifier, - BlockEnum.ParameterExtractor, BlockEnum.Iteration, BlockEnum.Loop, - BlockEnum.DocExtractor, BlockEnum.ListFilter, - BlockEnum.Agent, BlockEnum.DataSource, + BlockEnum.Start, + BlockEnum.TriggerWebhook, + BlockEnum.TriggerPlugin, + BlockEnum.LLM, + BlockEnum.KnowledgeRetrieval, + BlockEnum.Code, + BlockEnum.TemplateTransform, + BlockEnum.HttpRequest, + BlockEnum.Tool, + BlockEnum.VariableAssigner, + BlockEnum.VariableAggregator, + BlockEnum.QuestionClassifier, + BlockEnum.ParameterExtractor, + BlockEnum.Iteration, + BlockEnum.Loop, + BlockEnum.DocExtractor, + BlockEnum.ListFilter, + BlockEnum.Agent, + BlockEnum.DataSource, ] export const AGENT_OUTPUT_STRUCT: Var[] = [ diff --git a/web/app/components/workflow/constants/node.ts b/web/app/components/workflow/constants/node.ts index 5b5007e748..5de9512752 100644 --- a/web/app/components/workflow/constants/node.ts +++ b/web/app/components/workflow/constants/node.ts @@ -1,25 +1,25 @@ -import llmDefault from '@/app/components/workflow/nodes/llm/default' -import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default' import agentDefault from '@/app/components/workflow/nodes/agent/default' - -import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default' - -import ifElseDefault from '@/app/components/workflow/nodes/if-else/default' -import iterationDefault from '@/app/components/workflow/nodes/iteration/default' -import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default' -import loopDefault from '@/app/components/workflow/nodes/loop/default' -import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default' -import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default' - -import codeDefault from '@/app/components/workflow/nodes/code/default' -import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default' -import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default' -import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default' import assignerDefault from '@/app/components/workflow/nodes/assigner/default' +import codeDefault from '@/app/components/workflow/nodes/code/default' + +import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default' + import httpRequestDefault from '@/app/components/workflow/nodes/http/default' -import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default' +import ifElseDefault from '@/app/components/workflow/nodes/if-else/default' +import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default' +import iterationDefault from '@/app/components/workflow/nodes/iteration/default' +import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default' import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default' + +import llmDefault from '@/app/components/workflow/nodes/llm/default' +import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default' +import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default' +import loopDefault from '@/app/components/workflow/nodes/loop/default' +import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default' +import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default' +import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default' import toolDefault from '@/app/components/workflow/nodes/tool/default' +import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default' export const WORKFLOW_COMMON_NODES = [ llmDefault, diff --git a/web/app/components/workflow/context.tsx b/web/app/components/workflow/context.tsx index 0b77239dd1..ca3185b714 100644 --- a/web/app/components/workflow/context.tsx +++ b/web/app/components/workflow/context.tsx @@ -1,12 +1,12 @@ +import type { StateCreator } from 'zustand' +import type { SliceFromInjection } from './store' import { createContext, useRef, } from 'react' -import type { SliceFromInjection } from './store' import { createWorkflowStore, } from './store' -import type { StateCreator } from 'zustand' type WorkflowStore = ReturnType<typeof createWorkflowStore> export const WorkflowContext = createContext<WorkflowStore | null>(null) diff --git a/web/app/components/workflow/custom-connection-line.tsx b/web/app/components/workflow/custom-connection-line.tsx index c187f16fe1..fb218722c4 100644 --- a/web/app/components/workflow/custom-connection-line.tsx +++ b/web/app/components/workflow/custom-connection-line.tsx @@ -1,8 +1,8 @@ -import { memo } from 'react' import type { ConnectionLineComponentProps } from 'reactflow' +import { memo } from 'react' import { - Position, getBezierPath, + Position, } from 'reactflow' const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) => { @@ -22,7 +22,7 @@ const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponen <g> <path fill="none" - stroke='#D0D5DD' + stroke="#D0D5DD" strokeWidth={2} d={edgePath} /> @@ -31,7 +31,7 @@ const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponen y={toY - 4} width={2} height={8} - fill='#2970FF' + fill="#2970FF" /> </g> ) diff --git a/web/app/components/workflow/custom-edge-linear-gradient-render.tsx b/web/app/components/workflow/custom-edge-linear-gradient-render.tsx index b799bb36b2..313fa2cafd 100644 --- a/web/app/components/workflow/custom-edge-linear-gradient-render.tsx +++ b/web/app/components/workflow/custom-edge-linear-gradient-render.tsx @@ -25,21 +25,21 @@ const CustomEdgeLinearGradientRender = ({ <defs> <linearGradient id={id} - gradientUnits='userSpaceOnUse' + gradientUnits="userSpaceOnUse" x1={x1} y1={y1} x2={x2} y2={y2} > <stop - offset='0%' + offset="0%" style={{ stopColor: startColor, stopOpacity: 1, }} /> <stop - offset='100%' + offset="100%" style={{ stopColor, stopOpacity: 1, diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 2a53abb327..3b73e07536 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -1,32 +1,32 @@ +import type { EdgeProps } from 'reactflow' +import type { + Edge, + OnSelectBlock, +} from './types' +import { intersection } from 'lodash-es' import { memo, useCallback, useMemo, useState, } from 'react' -import { intersection } from 'lodash-es' -import type { EdgeProps } from 'reactflow' import { BaseEdge, EdgeLabelRenderer, - Position, getBezierPath, + Position, } from 'reactflow' +import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import { cn } from '@/utils/classnames' +import BlockSelector from './block-selector' +import { ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX } from './constants' +import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render' import { useAvailableBlocks, useNodesInteractions, } from './hooks' -import BlockSelector from './block-selector' -import type { - Edge, - OnSelectBlock, -} from './types' import { NodeRunningStatus } from './types' import { getEdgeColor } from './utils' -import { ITERATION_CHILDREN_Z_INDEX, LOOP_CHILDREN_Z_INDEX } from './constants' -import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render' -import { cn } from '@/utils/classnames' -import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' const CustomEdge = ({ id, @@ -75,8 +75,9 @@ const CustomEdge = ({ || _targetRunningStatus === NodeRunningStatus.Exception || _targetRunningStatus === NodeRunningStatus.Running ) - ) + ) { return id + } }, [_sourceRunningStatus, _targetRunningStatus, id]) const handleOpenChange = useCallback((v: boolean) => { diff --git a/web/app/components/workflow/datasets-detail-store/provider.tsx b/web/app/components/workflow/datasets-detail-store/provider.tsx index a75b7e1d29..7cd3b88bfb 100644 --- a/web/app/components/workflow/datasets-detail-store/provider.tsx +++ b/web/app/components/workflow/datasets-detail-store/provider.tsx @@ -1,10 +1,10 @@ import type { FC } from 'react' -import { createContext, useCallback, useEffect, useRef } from 'react' -import { createDatasetsDetailStore } from './store' -import type { CommonNodeType, Node } from '../types' -import { BlockEnum } from '../types' import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types' +import type { CommonNodeType, Node } from '../types' +import { createContext, useCallback, useEffect, useRef } from 'react' import { fetchDatasets } from '@/service/datasets' +import { BlockEnum } from '../types' +import { createDatasetsDetailStore } from './store' type DatasetsDetailStoreApi = ReturnType<typeof createDatasetsDetailStore> @@ -33,12 +33,14 @@ const DatasetsDetailProvider: FC<DatasetsDetailProviderProps> = ({ }, []) useEffect(() => { - if (!storeRef.current) return + if (!storeRef.current) + return const knowledgeRetrievalNodes = nodes.filter(node => node.data.type === BlockEnum.KnowledgeRetrieval) const allDatasetIds = knowledgeRetrievalNodes.reduce<string[]>((acc, node) => { return Array.from(new Set([...acc, ...(node.data as CommonNodeType<KnowledgeRetrievalNodeType>).dataset_ids])) }, []) - if (allDatasetIds.length === 0) return + if (allDatasetIds.length === 0) + return updateDatasetsDetail(allDatasetIds) }, []) diff --git a/web/app/components/workflow/datasets-detail-store/store.ts b/web/app/components/workflow/datasets-detail-store/store.ts index 80f19bfea0..e2910b84b1 100644 --- a/web/app/components/workflow/datasets-detail-store/store.ts +++ b/web/app/components/workflow/datasets-detail-store/store.ts @@ -1,8 +1,8 @@ +import type { DataSet } from '@/models/datasets' +import { produce } from 'immer' import { useContext } from 'react' import { createStore, useStore } from 'zustand' -import type { DataSet } from '@/models/datasets' import { DatasetsDetailContext } from './provider' -import { produce } from 'immer' type DatasetsDetailStore = { datasetsDetail: Record<string, DataSet> diff --git a/web/app/components/workflow/dsl-export-confirm-modal.tsx b/web/app/components/workflow/dsl-export-confirm-modal.tsx index ff5498abc5..63100876c6 100644 --- a/web/app/components/workflow/dsl-export-confirm-modal.tsx +++ b/web/app/components/workflow/dsl-export-confirm-modal.tsx @@ -1,14 +1,14 @@ 'use client' +import type { EnvironmentVariable } from '@/app/components/workflow/types' +import { RiCloseLine, RiLock2Line } from '@remixicon/react' +import { noop } from 'lodash-es' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiCloseLine, RiLock2Line } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import Button from '@/app/components/base/button' +import Checkbox from '@/app/components/base/checkbox' import { Env } from '@/app/components/base/icons/src/vender/line/others' import Modal from '@/app/components/base/modal' -import Checkbox from '@/app/components/base/checkbox' -import Button from '@/app/components/base/button' -import type { EnvironmentVariable } from '@/app/components/workflow/types' -import { noop } from 'lodash-es' +import { cn } from '@/utils/classnames' export type DSLExportConfirmModalProps = { envList: EnvironmentVariable[] @@ -36,47 +36,47 @@ const DSLExportConfirmModal = ({ onClose={noop} className={cn('w-[480px] max-w-[480px]')} > - <div className='title-2xl-semi-bold relative pb-6 text-text-primary'>{t('workflow.env.export.title')}</div> - <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onClose}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="title-2xl-semi-bold relative pb-6 text-text-primary">{t('workflow.env.export.title')}</div> + <div className="absolute right-4 top-4 cursor-pointer p-2" onClick={onClose}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> - <div className='relative'> - <table className='radius-md w-full border-separate border-spacing-0 border border-divider-regular shadow-xs'> - <thead className='system-xs-medium-uppercase text-text-tertiary'> + <div className="relative"> + <table className="radius-md w-full border-separate border-spacing-0 border border-divider-regular shadow-xs"> + <thead className="system-xs-medium-uppercase text-text-tertiary"> <tr> - <td width={220} className='h-7 border-b border-r border-divider-regular pl-3'>NAME</td> - <td className='h-7 border-b border-divider-regular pl-3'>VALUE</td> + <td width={220} className="h-7 border-b border-r border-divider-regular pl-3">NAME</td> + <td className="h-7 border-b border-divider-regular pl-3">VALUE</td> </tr> </thead> <tbody> {envList.map((env, index) => ( <tr key={env.name}> <td className={cn('system-xs-medium h-7 border-r pl-3', index + 1 !== envList.length && 'border-b')}> - <div className='flex w-[200px] items-center gap-1'> - <Env className='h-4 w-4 shrink-0 text-util-colors-violet-violet-600' /> - <div className='truncate text-text-primary'>{env.name}</div> - <div className='shrink-0 text-text-tertiary'>Secret</div> - <RiLock2Line className='h-3 w-3 shrink-0 text-text-tertiary' /> + <div className="flex w-[200px] items-center gap-1"> + <Env className="h-4 w-4 shrink-0 text-util-colors-violet-violet-600" /> + <div className="truncate text-text-primary">{env.name}</div> + <div className="shrink-0 text-text-tertiary">Secret</div> + <RiLock2Line className="h-3 w-3 shrink-0 text-text-tertiary" /> </div> </td> <td className={cn('h-7 pl-3', index + 1 !== envList.length && 'border-b')}> - <div className='system-xs-regular truncate text-text-secondary'>{env.value}</div> + <div className="system-xs-regular truncate text-text-secondary">{env.value}</div> </td> </tr> ))} </tbody> </table> </div> - <div className='mt-4 flex gap-2'> + <div className="mt-4 flex gap-2"> <Checkbox - className='shrink-0' + className="shrink-0" checked={exportSecrets} onCheck={() => setExportSecrets(!exportSecrets)} /> - <div className='system-sm-medium cursor-pointer text-text-primary' onClick={() => setExportSecrets(!exportSecrets)}>{t('workflow.env.export.checkbox')}</div> + <div className="system-sm-medium cursor-pointer text-text-primary" onClick={() => setExportSecrets(!exportSecrets)}>{t('workflow.env.export.checkbox')}</div> </div> - <div className='flex flex-row-reverse pt-6'> - <Button className='ml-2' variant='primary' onClick={submit}>{exportSecrets ? t('workflow.env.export.export') : t('workflow.env.export.ignore')}</Button> + <div className="flex flex-row-reverse pt-6"> + <Button className="ml-2" variant="primary" onClick={submit}>{exportSecrets ? t('workflow.env.export.export') : t('workflow.env.export.ignore')}</Button> <Button onClick={onClose}>{t('common.operation.cancel')}</Button> </div> </Modal> diff --git a/web/app/components/workflow/features.tsx b/web/app/components/workflow/features.tsx index b54ffdf167..08c79a497a 100644 --- a/web/app/components/workflow/features.tsx +++ b/web/app/components/workflow/features.tsx @@ -1,19 +1,20 @@ +import type { StartNodeType } from './nodes/start/types' +import type { CommonNodeType, InputVar, Node } from './types' +import type { PromptVariable } from '@/models/debug' import { memo, useCallback, } from 'react' import { useNodes } from 'reactflow' -import { useStore } from './store' +import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' import { useIsChatMode, useNodesReadOnly, useNodesSyncDraft, } from './hooks' -import { type CommonNodeType, type InputVar, InputVarType, type Node } from './types' import useConfig from './nodes/start/use-config' -import type { StartNodeType } from './nodes/start/types' -import type { PromptVariable } from '@/models/debug' -import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' +import { useStore } from './store' +import { InputVarType } from './types' const Features = () => { const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel) diff --git a/web/app/components/workflow/header/chat-variable-button.tsx b/web/app/components/workflow/header/chat-variable-button.tsx index b424ecffdc..86c88969ca 100644 --- a/web/app/components/workflow/header/chat-variable-button.tsx +++ b/web/app/components/workflow/header/chat-variable-button.tsx @@ -28,9 +28,9 @@ const ChatVariableButton = ({ disabled }: { disabled: boolean }) => { )} disabled={disabled} onClick={handleClick} - variant='ghost' + variant="ghost" > - <BubbleX className='h-4 w-4 text-components-button-secondary-text' /> + <BubbleX className="h-4 w-4 text-components-button-secondary-text" /> </Button> ) } diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index e284cca791..c984ae6c48 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -1,3 +1,12 @@ +import type { ChecklistItem } from '../hooks/use-checklist' +import type { + BlockEnum, + CommonEdgeType, +} from '../types' +import { + RiCloseLine, + RiListCheck3, +} from '@remixicon/react' import { memo, useState, @@ -6,34 +15,23 @@ import { useTranslation } from 'react-i18next' import { useEdges, } from 'reactflow' +import { Warning } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' +import { IconR } from '@/app/components/base/icons/src/vender/line/arrows' import { - RiCloseLine, - RiListCheck3, -} from '@remixicon/react' -import BlockIcon from '../block-icon' -import { - useChecklist, - useNodesInteractions, -} from '../hooks' -import type { ChecklistItem } from '../hooks/use-checklist' -import type { - CommonEdgeType, -} from '../types' -import { cn } from '@/utils/classnames' + ChecklistSquare, +} from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { - ChecklistSquare, -} from '@/app/components/base/icons/src/vender/line/general' -import { Warning } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' -import { IconR } from '@/app/components/base/icons/src/vender/line/arrows' -import type { - BlockEnum, -} from '../types' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import { cn } from '@/utils/classnames' +import BlockIcon from '../block-icon' +import { + useChecklist, + useNodesInteractions, +} from '../hooks' type WorkflowChecklistProps = { disabled: boolean @@ -65,7 +63,7 @@ const WorkflowChecklist = ({ return ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 12, crossAxis: 4, @@ -89,35 +87,38 @@ const WorkflowChecklist = ({ </div> { !!needWarningNodes.length && ( - <div className='absolute -right-1.5 -top-1.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full border border-gray-100 bg-[#F79009] text-[11px] font-semibold text-white'> + <div className="absolute -right-1.5 -top-1.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full border border-gray-100 bg-[#F79009] text-[11px] font-semibold text-white"> {needWarningNodes.length} </div> ) } </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[12]'> + <PortalToFollowElemContent className="z-[12]"> <div - className='w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg' + className="w-[420px] overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg" style={{ maxHeight: 'calc(2 / 3 * 100vh)', }} > - <div className='text-md sticky top-0 z-[1] flex h-[44px] items-center bg-components-panel-bg pl-4 pr-3 pt-3 font-semibold text-text-primary'> - <div className='grow'>{t('workflow.panel.checklist')}{needWarningNodes.length ? `(${needWarningNodes.length})` : ''}</div> + <div className="text-md sticky top-0 z-[1] flex h-[44px] items-center bg-components-panel-bg pl-4 pr-3 pt-3 font-semibold text-text-primary"> + <div className="grow"> + {t('workflow.panel.checklist')} + {needWarningNodes.length ? `(${needWarningNodes.length})` : ''} + </div> <div - className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center' + className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center" onClick={() => setOpen(false)} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> - <div className='pb-2'> + <div className="pb-2"> { !!needWarningNodes.length && ( <> - <div className='px-4 pt-1 text-xs text-text-tertiary'>{t('workflow.panel.checklistTip')}</div> - <div className='px-4 py-2'> + <div className="px-4 pt-1 text-xs text-text-tertiary">{t('workflow.panel.checklistTip')}</div> + <div className="px-4 py-2"> { needWarningNodes.map(node => ( <div @@ -128,22 +129,22 @@ const WorkflowChecklist = ({ )} onClick={() => handleChecklistItemClick(node)} > - <div className='flex h-9 items-center p-2 text-xs font-medium text-text-secondary'> + <div className="flex h-9 items-center p-2 text-xs font-medium text-text-secondary"> <BlockIcon type={node.type as BlockEnum} - className='mr-1.5' + className="mr-1.5" toolIcon={node.toolIcon} /> - <span className='grow truncate'> + <span className="grow truncate"> {node.title} </span> { (showGoTo && node.canNavigate && !node.disableGoTo) && ( - <div className='flex h-4 w-[60px] shrink-0 items-center justify-center gap-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100'> - <span className='whitespace-nowrap text-xs font-medium leading-4 text-primary-600'> + <div className="flex h-4 w-[60px] shrink-0 items-center justify-center gap-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100"> + <span className="whitespace-nowrap text-xs font-medium leading-4 text-primary-600"> {t('workflow.panel.goTo')} </span> - <IconR className='h-3.5 w-3.5 text-primary-600' /> + <IconR className="h-3.5 w-3.5 text-primary-600" /> </div> ) } @@ -156,9 +157,9 @@ const WorkflowChecklist = ({ > { node.unConnected && ( - <div className='px-3 py-1 first:pt-1.5 last:pb-1.5'> - <div className='flex text-xs leading-4 text-text-tertiary'> - <Warning className='mr-2 mt-[2px] h-3 w-3 text-[#F79009]' /> + <div className="px-3 py-1 first:pt-1.5 last:pb-1.5"> + <div className="flex text-xs leading-4 text-text-tertiary"> + <Warning className="mr-2 mt-[2px] h-3 w-3 text-[#F79009]" /> {t('workflow.common.needConnectTip')} </div> </div> @@ -166,9 +167,9 @@ const WorkflowChecklist = ({ } { node.errorMessage && ( - <div className='px-3 py-1 first:pt-1.5 last:pb-1.5'> - <div className='flex text-xs leading-4 text-text-tertiary'> - <Warning className='mr-2 mt-[2px] h-3 w-3 text-[#F79009]' /> + <div className="px-3 py-1 first:pt-1.5 last:pb-1.5"> + <div className="flex text-xs leading-4 text-text-tertiary"> + <Warning className="mr-2 mt-[2px] h-3 w-3 text-[#F79009]" /> {node.errorMessage} </div> </div> @@ -184,8 +185,8 @@ const WorkflowChecklist = ({ } { !needWarningNodes.length && ( - <div className='mx-4 mb-3 rounded-lg bg-components-panel-bg py-4 text-center text-xs text-text-tertiary'> - <ChecklistSquare className='mx-auto mb-[5px] h-8 w-8 text-text-quaternary' /> + <div className="mx-4 mb-3 rounded-lg bg-components-panel-bg py-4 text-center text-xs text-text-tertiary"> + <ChecklistSquare className="mx-auto mb-[5px] h-8 w-8 text-text-quaternary" /> {t('workflow.panel.checklistResolved')} </div> ) diff --git a/web/app/components/workflow/header/editing-title.tsx b/web/app/components/workflow/header/editing-title.tsx index 81249b05bd..8fa03f3c26 100644 --- a/web/app/components/workflow/header/editing-title.tsx +++ b/web/app/components/workflow/header/editing-title.tsx @@ -1,7 +1,7 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' -import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { useStore } from '@/app/components/workflow/store' +import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import useTimestamp from '@/hooks/use-timestamp' const EditingTitle = () => { @@ -18,11 +18,13 @@ const EditingTitle = () => { { !!draftUpdatedAt && ( <> - {t('workflow.common.autoSaved')} {formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')} + {t('workflow.common.autoSaved')} + {' '} + {formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')} </> ) } - <span className='mx-1 flex items-center'>·</span> + <span className="mx-1 flex items-center">·</span> { publishedAt ? `${t('workflow.common.published')} ${formatTimeFromNow(publishedAt)}` @@ -31,7 +33,7 @@ const EditingTitle = () => { { isSyncingWorkflowDraft && ( <> - <span className='mx-1 flex items-center'>·</span> + <span className="mx-1 flex items-center">·</span> {t('workflow.common.syncingData')} </> ) diff --git a/web/app/components/workflow/header/env-button.tsx b/web/app/components/workflow/header/env-button.tsx index f053097a0d..12dc193261 100644 --- a/web/app/components/workflow/header/env-button.tsx +++ b/web/app/components/workflow/header/env-button.tsx @@ -1,10 +1,10 @@ import { memo } from 'react' import Button from '@/app/components/base/button' import { Env } from '@/app/components/base/icons/src/vender/line/others' +import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' import { useStore } from '@/app/components/workflow/store' import useTheme from '@/hooks/use-theme' import { cn } from '@/utils/classnames' -import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' const EnvButton = ({ disabled }: { disabled: boolean }) => { const { theme } = useTheme() @@ -29,11 +29,11 @@ const EnvButton = ({ disabled }: { disabled: boolean }) => { 'p-2', theme === 'dark' && showEnvPanel && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', )} - variant='ghost' + variant="ghost" disabled={disabled} onClick={handleClick} > - <Env className='h-4 w-4 text-components-button-secondary-text' /> + <Env className="h-4 w-4 text-components-button-secondary-text" /> </Button> ) } diff --git a/web/app/components/workflow/header/global-variable-button.tsx b/web/app/components/workflow/header/global-variable-button.tsx index 6859521aee..a2d78edc4b 100644 --- a/web/app/components/workflow/header/global-variable-button.tsx +++ b/web/app/components/workflow/header/global-variable-button.tsx @@ -1,10 +1,10 @@ import { memo } from 'react' import Button from '@/app/components/base/button' import { GlobalVariable } from '@/app/components/base/icons/src/vender/line/others' +import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' import { useStore } from '@/app/components/workflow/store' import useTheme from '@/hooks/use-theme' import { cn } from '@/utils/classnames' -import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' const GlobalVariableButton = ({ disabled }: { disabled: boolean }) => { const { theme } = useTheme() @@ -31,9 +31,9 @@ const GlobalVariableButton = ({ disabled }: { disabled: boolean }) => { )} disabled={disabled} onClick={handleClick} - variant='ghost' + variant="ghost" > - <GlobalVariable className='h-4 w-4 text-components-button-secondary-text' /> + <GlobalVariable className="h-4 w-4 text-components-button-secondary-text" /> </Button> ) } diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx index 20fdafaff5..52ffee4ed5 100644 --- a/web/app/components/workflow/header/header-in-normal.tsx +++ b/web/app/components/workflow/header/header-in-normal.tsx @@ -1,26 +1,26 @@ +import type { StartNodeType } from '../nodes/start/types' +import type { RunAndHistoryProps } from './run-and-history' import { useCallback, } from 'react' import { useNodes } from 'reactflow' -import { - useStore, - useWorkflowStore, -} from '../store' -import type { StartNodeType } from '../nodes/start/types' +import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' +import Divider from '../../base/divider' import { useNodesInteractions, useNodesReadOnly, useWorkflowRun, } from '../hooks' -import Divider from '../../base/divider' -import type { RunAndHistoryProps } from './run-and-history' -import RunAndHistory from './run-and-history' +import { + useStore, + useWorkflowStore, +} from '../store' import EditingTitle from './editing-title' import EnvButton from './env-button' -import VersionHistoryButton from './version-history-button' -import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' -import ScrollToSelectedNodeButton from './scroll-to-selected-node-button' import GlobalVariableButton from './global-variable-button' +import RunAndHistory from './run-and-history' +import ScrollToSelectedNodeButton from './scroll-to-selected-node-button' +import VersionHistoryButton from './version-history-button' export type HeaderInNormalProps = { components?: { @@ -64,18 +64,18 @@ const HeaderInNormal = ({ }, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel, setShowGlobalVariablePanel]) return ( - <div className='flex w-full items-center justify-between'> + <div className="flex w-full items-center justify-between"> <div> <EditingTitle /> </div> <div> <ScrollToSelectedNodeButton /> </div> - <div className='flex items-center gap-2'> + <div className="flex items-center gap-2"> {components?.left} - <Divider type='vertical' className='mx-auto h-3.5' /> + <Divider type="vertical" className="mx-auto h-3.5" /> <RunAndHistory {...runAndHistoryProps} /> - <div className='shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]'> + <div className="shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]"> {components?.chatVariableTrigger} <EnvButton disabled={nodesReadOnly} /> <GlobalVariableButton disabled={nodesReadOnly} /> diff --git a/web/app/components/workflow/header/header-in-restoring.tsx b/web/app/components/workflow/header/header-in-restoring.tsx index 53abe2375d..2abe031b61 100644 --- a/web/app/components/workflow/header/header-in-restoring.tsx +++ b/web/app/components/workflow/header/header-in-restoring.tsx @@ -1,8 +1,18 @@ +import { RiHistoryLine } from '@remixicon/react' import { useCallback, } from 'react' -import { RiHistoryLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import useTheme from '@/hooks/use-theme' +import { useInvalidAllLastRun } from '@/service/use-workflow' +import { cn } from '@/utils/classnames' +import Toast from '../../base/toast' +import { + useNodesSyncDraft, + useWorkflowRun, +} from '../hooks' +import { useHooksStore } from '../hooks-store' import { useStore, useWorkflowStore, @@ -10,17 +20,7 @@ import { import { WorkflowVersion, } from '../types' -import { - useNodesSyncDraft, - useWorkflowRun, -} from '../hooks' -import Toast from '../../base/toast' import RestoringTitle from './restoring-title' -import Button from '@/app/components/base/button' -import { useInvalidAllLastRun } from '@/service/use-workflow' -import { useHooksStore } from '../hooks-store' -import useTheme from '@/hooks/use-theme' -import { cn } from '@/utils/classnames' export type HeaderInRestoringProps = { onRestoreSettled?: () => void @@ -80,11 +80,11 @@ const HeaderInRestoring = ({ <div> <RestoringTitle /> </div> - <div className=' flex items-center justify-end gap-x-2'> + <div className=" flex items-center justify-end gap-x-2"> <Button onClick={handleRestore} disabled={!currentVersion || currentVersion.version === WorkflowVersion.Draft} - variant='primary' + variant="primary" className={cn( theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', )} @@ -98,9 +98,9 @@ const HeaderInRestoring = ({ theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', )} > - <div className='flex items-center gap-x-0.5'> - <RiHistoryLine className='h-4 w-4' /> - <span className='px-0.5'>{t('workflow.common.exitVersions')}</span> + <div className="flex items-center gap-x-0.5"> + <RiHistoryLine className="h-4 w-4" /> + <span className="px-0.5">{t('workflow.common.exitVersions')}</span> </div> </Button> </div> diff --git a/web/app/components/workflow/header/header-in-view-history.tsx b/web/app/components/workflow/header/header-in-view-history.tsx index 70189a9469..17b1dea52f 100644 --- a/web/app/components/workflow/header/header-in-view-history.tsx +++ b/web/app/components/workflow/header/header-in-view-history.tsx @@ -1,19 +1,19 @@ +import type { ViewHistoryProps } from './view-history' import { useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import { - useWorkflowStore, -} from '../store' +import Button from '@/app/components/base/button' +import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' +import Divider from '../../base/divider' import { useWorkflowRun, } from '../hooks' -import Divider from '../../base/divider' +import { + useWorkflowStore, +} from '../store' import RunningTitle from './running-title' -import type { ViewHistoryProps } from './view-history' import ViewHistory from './view-history' -import Button from '@/app/components/base/button' -import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' export type HeaderInHistoryProps = { viewHistoryProps?: ViewHistoryProps @@ -38,14 +38,14 @@ const HeaderInHistory = ({ <div> <RunningTitle /> </div> - <div className='flex items-center space-x-2'> + <div className="flex items-center space-x-2"> <ViewHistory {...viewHistoryProps} withText /> - <Divider type='vertical' className='mx-auto h-3.5' /> + <Divider type="vertical" className="mx-auto h-3.5" /> <Button - variant='primary' + variant="primary" onClick={handleGoBackToEdit} > - <ArrowNarrowLeft className='mr-1 h-4 w-4' /> + <ArrowNarrowLeft className="mr-1 h-4 w-4" /> {t('workflow.common.goBackToEdit')} </Button> </div> diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index e37a92638a..0590c016f2 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -1,13 +1,13 @@ +import type { HeaderInNormalProps } from './header-in-normal' +import type { HeaderInRestoringProps } from './header-in-restoring' +import type { HeaderInHistoryProps } from './header-in-view-history' +import dynamic from 'next/dynamic' import { usePathname } from 'next/navigation' import { useWorkflowMode, } from '../hooks' -import type { HeaderInNormalProps } from './header-in-normal' -import HeaderInNormal from './header-in-normal' -import type { HeaderInHistoryProps } from './header-in-view-history' -import type { HeaderInRestoringProps } from './header-in-restoring' import { useStore } from '../store' -import dynamic from 'next/dynamic' +import HeaderInNormal from './header-in-normal' const HeaderInHistory = dynamic(() => import('./header-in-view-history'), { ssr: false, @@ -38,9 +38,9 @@ const Header = ({ return ( <div - className='absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3' + className="absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3" > - {(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className='h-14 w-[52px]' />} + {(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className="h-14 w-[52px]" />} { normal && ( <HeaderInNormal diff --git a/web/app/components/workflow/header/restoring-title.tsx b/web/app/components/workflow/header/restoring-title.tsx index e6631d3684..737fe4fec5 100644 --- a/web/app/components/workflow/header/restoring-title.tsx +++ b/web/app/components/workflow/header/restoring-title.tsx @@ -1,9 +1,9 @@ import { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import useTimestamp from '@/hooks/use-timestamp' import { useStore } from '../store' import { WorkflowVersion } from '../types' -import useTimestamp from '@/hooks/use-timestamp' const RestoringTitle = () => { const { t } = useTranslation() @@ -20,16 +20,16 @@ const RestoringTitle = () => { }, [currentVersion, t, isDraft]) return ( - <div className='flex flex-col gap-y-0.5'> - <div className='flex items-center gap-x-1'> - <span className='system-sm-semibold text-text-primary'> + <div className="flex flex-col gap-y-0.5"> + <div className="flex items-center gap-x-1"> + <span className="system-sm-semibold text-text-primary"> {versionName} </span> - <span className='system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-1 py-0.5 text-text-accent-secondary'> + <span className="system-2xs-medium-uppercase rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-1 py-0.5 text-text-accent-secondary"> {t('workflow.common.viewOnly')} </span> </div> - <div className='system-xs-regular flex h-4 items-center gap-x-1 text-text-tertiary'> + <div className="system-xs-regular flex h-4 items-center gap-x-1 text-text-tertiary"> { currentVersion && ( <> diff --git a/web/app/components/workflow/header/run-and-history.tsx b/web/app/components/workflow/header/run-and-history.tsx index ae4b462b29..f4b115e255 100644 --- a/web/app/components/workflow/header/run-and-history.tsx +++ b/web/app/components/workflow/header/run-and-history.tsx @@ -1,17 +1,17 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' +import type { ViewHistoryProps } from './view-history' import { RiPlayLargeLine, } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' import { useNodesReadOnly, useWorkflowStartRun, } from '../hooks' -import type { ViewHistoryProps } from './view-history' -import ViewHistory from './view-history' import Checklist from './checklist' -import { cn } from '@/utils/classnames' import RunMode from './run-mode' +import ViewHistory from './view-history' const PreviewMode = memo(() => { const { t } = useTranslation() @@ -25,7 +25,7 @@ const PreviewMode = memo(() => { )} onClick={() => handleWorkflowStartRunInChatflow()} > - <RiPlayLargeLine className='mr-1 h-4 w-4' /> + <RiPlayLargeLine className="mr-1 h-4 w-4" /> {t('workflow.common.debugAndPreview')} </div> ) @@ -56,7 +56,7 @@ const RunAndHistory = ({ const { RunMode: CustomRunMode } = components || {} return ( - <div className='flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs'> + <div className="flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-0.5 shadow-xs"> { showRunButton && ( CustomRunMode ? <CustomRunMode text={runButtonText} /> : <RunMode text={runButtonText} /> @@ -65,7 +65,7 @@ const RunAndHistory = ({ { showPreviewButton && <PreviewMode /> } - <div className='mx-0.5 h-3.5 w-[1px] bg-divider-regular'></div> + <div className="mx-0.5 h-3.5 w-[1px] bg-divider-regular"></div> <ViewHistory {...viewHistoryProps} /> <Checklist disabled={nodesReadOnly} /> </div> diff --git a/web/app/components/workflow/header/run-mode.tsx b/web/app/components/workflow/header/run-mode.tsx index 6ab826cc48..82e33b5c30 100644 --- a/web/app/components/workflow/header/run-mode.tsx +++ b/web/app/components/workflow/header/run-mode.tsx @@ -1,18 +1,19 @@ +import type { TestRunMenuRef, TriggerOption } from './test-run-menu' +import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react' import React, { useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' +import { trackEvent } from '@/app/components/base/amplitude' +import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' +import { useToastContext } from '@/app/components/base/toast' import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks' import { useStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' +import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { cn } from '@/utils/classnames' -import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react' -import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options' -import TestRunMenu, { type TestRunMenuRef, type TriggerOption, TriggerType } from './test-run-menu' -import { useToastContext } from '@/app/components/base/toast' -import { trackEvent } from '@/app/components/base/amplitude' +import TestRunMenu, { TriggerType } from './test-run-menu' type RunModeProps = { text?: string @@ -112,57 +113,57 @@ const RunMode = ({ }) return ( - <div className='flex items-center gap-x-px'> + <div className="flex items-center gap-x-px"> { isRunning ? ( - <button - type='button' - className={cn( - 'system-xs-medium flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent', - )} - disabled={true} - > - <RiLoader2Line className='mr-1 size-4 animate-spin' /> - {isListening ? t('workflow.common.listening') : t('workflow.common.running')} - </button> - ) - : ( - <TestRunMenu - ref={testRunMenuRef} - options={dynamicOptions} - onSelect={handleTriggerSelect} - > - <div + <button + type="button" className={cn( - 'system-xs-medium flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent hover:bg-state-accent-hover', + 'system-xs-medium flex h-7 cursor-not-allowed items-center gap-x-1 rounded-l-md bg-state-accent-hover px-1.5 text-text-accent', )} - style={{ userSelect: 'none' }} + disabled={true} > - <RiPlayLargeLine className='mr-1 size-4' /> - {text ?? t('workflow.common.run')} - <div className='system-kbd flex items-center gap-x-0.5 text-text-tertiary'> - <div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'> - {getKeyboardKeyNameBySystem('alt')} - </div> - <div className='flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray'> - R + <RiLoader2Line className="mr-1 size-4 animate-spin" /> + {isListening ? t('workflow.common.listening') : t('workflow.common.running')} + </button> + ) + : ( + <TestRunMenu + ref={testRunMenuRef} + options={dynamicOptions} + onSelect={handleTriggerSelect} + > + <div + className={cn( + 'system-xs-medium flex h-7 cursor-pointer items-center gap-x-1 rounded-md px-1.5 text-text-accent hover:bg-state-accent-hover', + )} + style={{ userSelect: 'none' }} + > + <RiPlayLargeLine className="mr-1 size-4" /> + {text ?? t('workflow.common.run')} + <div className="system-kbd flex items-center gap-x-0.5 text-text-tertiary"> + <div className="flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray"> + {getKeyboardKeyNameBySystem('alt')} + </div> + <div className="flex size-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-gray"> + R + </div> </div> </div> - </div> - </TestRunMenu> - ) + </TestRunMenu> + ) } { isRunning && ( <button - type='button' + type="button" className={cn( 'flex size-7 items-center justify-center rounded-r-md bg-state-accent-active', )} onClick={handleStop} > - <StopCircle className='size-4 text-text-accent' /> + <StopCircle className="size-4 text-text-accent" /> </button> ) } diff --git a/web/app/components/workflow/header/running-title.tsx b/web/app/components/workflow/header/running-title.tsx index 95e763ead7..80a9361983 100644 --- a/web/app/components/workflow/header/running-title.tsx +++ b/web/app/components/workflow/header/running-title.tsx @@ -1,9 +1,9 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' +import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' import { useIsChatMode } from '../hooks' import { useStore } from '../store' import { formatWorkflowRunIdentifier } from '../utils' -import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' const RunningTitle = () => { const { t } = useTranslation() @@ -11,11 +11,11 @@ const RunningTitle = () => { const historyWorkflowData = useStore(s => s.historyWorkflowData) return ( - <div className='flex h-[18px] items-center text-xs text-gray-500'> - <ClockPlay className='mr-1 h-3 w-3 text-gray-500' /> + <div className="flex h-[18px] items-center text-xs text-gray-500"> + <ClockPlay className="mr-1 h-3 w-3 text-gray-500" /> <span>{isChatMode ? `Test Chat${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}` : `Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`}</span> - <span className='mx-1'>·</span> - <span className='ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600'> + <span className="mx-1">·</span> + <span className="ml-1 flex h-[18px] items-center rounded-[5px] border border-indigo-300 bg-white/[0.48] px-1 text-[10px] font-semibold uppercase text-indigo-600"> {t('workflow.common.viewOnly')} </span> </div> diff --git a/web/app/components/workflow/header/scroll-to-selected-node-button.tsx b/web/app/components/workflow/header/scroll-to-selected-node-button.tsx index 58aeccea1b..6606df5538 100644 --- a/web/app/components/workflow/header/scroll-to-selected-node-button.tsx +++ b/web/app/components/workflow/header/scroll-to-selected-node-button.tsx @@ -1,10 +1,10 @@ import type { FC } from 'react' -import { useCallback } from 'react' -import { useNodes } from 'reactflow' -import { useTranslation } from 'react-i18next' import type { CommonNodeType } from '../types' -import { scrollToWorkflowNode } from '../utils/node-navigation' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' import { cn } from '@/utils/classnames' +import { scrollToWorkflowNode } from '../utils/node-navigation' const ScrollToSelectedNodeButton: FC = () => { const { t } = useTranslation() @@ -12,7 +12,8 @@ const ScrollToSelectedNodeButton: FC = () => { const selectedNode = nodes.find(node => node.data.selected) const handleScrollToSelectedNode = useCallback(() => { - if (!selectedNode) return + if (!selectedNode) + return scrollToWorkflowNode(selectedNode.id) }, [selectedNode]) diff --git a/web/app/components/workflow/header/test-run-menu.tsx b/web/app/components/workflow/header/test-run-menu.tsx index 40aabab6f8..8f4af3b592 100644 --- a/web/app/components/workflow/header/test-run-menu.tsx +++ b/web/app/components/workflow/header/test-run-menu.tsx @@ -1,10 +1,9 @@ +import type { MouseEvent, MouseEventHandler, ReactElement } from 'react' import { - type MouseEvent, - type MouseEventHandler, - type ReactElement, cloneElement, forwardRef, isValidElement, + useCallback, useEffect, useImperativeHandle, @@ -158,14 +157,14 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({ return ( <div key={option.id} - className='system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' + className="system-md-regular flex cursor-pointer items-center rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover" onClick={() => handleSelect(option)} > - <div className='flex min-w-0 flex-1 items-center'> - <div className='flex h-6 w-6 shrink-0 items-center justify-center'> + <div className="flex min-w-0 flex-1 items-center"> + <div className="flex h-6 w-6 shrink-0 items-center justify-center"> {option.icon} </div> - <span className='ml-2 truncate'>{option.name}</span> + <span className="ml-2 truncate">{option.name}</span> </div> {shortcutKey && ( <ShortcutsName keys={[shortcutKey]} className="ml-2" textColor="secondary" /> @@ -214,7 +213,7 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 8, crossAxis: -4 }} > <PortalToFollowElemTrigger asChild onClick={() => setOpen(!open)}> @@ -222,16 +221,16 @@ const TestRunMenu = forwardRef<TestRunMenuRef, TestRunMenuProps>(({ {children} </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[12]'> - <div className='w-[284px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg'> - <div className='mb-2 px-3 pt-2 text-sm font-medium text-text-primary'> + <PortalToFollowElemContent className="z-[12]"> + <div className="w-[284px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg"> + <div className="mb-2 px-3 pt-2 text-sm font-medium text-text-primary"> {t('workflow.common.chooseStartNodeToRun')} </div> <div> {hasUserInput && renderOption(options.userInput!)} {(hasTriggers || hasRunAll) && hasUserInput && ( - <div className='mx-3 my-1 h-px bg-divider-subtle' /> + <div className="mx-3 my-1 h-px bg-divider-subtle" /> )} {hasRunAll && renderOption(options.runAll!)} diff --git a/web/app/components/workflow/header/undo-redo.tsx b/web/app/components/workflow/header/undo-redo.tsx index 4b2e9abc36..27e9af2865 100644 --- a/web/app/components/workflow/header/undo-redo.tsx +++ b/web/app/components/workflow/header/undo-redo.tsx @@ -1,18 +1,18 @@ import type { FC } from 'react' -import { memo, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' import { RiArrowGoBackLine, RiArrowGoForwardFill, } from '@remixicon/react' +import { memo, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history' +import { useNodesReadOnly } from '@/app/components/workflow/hooks' +import { cn } from '@/utils/classnames' +import Divider from '../../base/divider' import TipPopup from '../operator/tip-popup' import { useWorkflowHistoryStore } from '../workflow-history-store' -import Divider from '../../base/divider' -import { useNodesReadOnly } from '@/app/components/workflow/hooks' -import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history' -import { cn } from '@/utils/classnames' -export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void } +export type UndoRedoProps = { handleUndo: () => void, handleRedo: () => void } const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => { const { t } = useTranslation() const { store } = useWorkflowHistoryStore() @@ -31,34 +31,34 @@ const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => { const { nodesReadOnly } = useNodesReadOnly() return ( - <div className='flex items-center space-x-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-[5px]'> + <div className="flex items-center space-x-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-[5px]"> <TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}> <div - data-tooltip-id='workflow.undo' + data-tooltip-id="workflow.undo" className={ - cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', - (nodesReadOnly || buttonsDisabled.undo) - && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} + cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', (nodesReadOnly || buttonsDisabled.undo) + && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled') + } onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()} > - <RiArrowGoBackLine className='h-4 w-4' /> - </div> - </TipPopup > - <TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}> - <div - data-tooltip-id='workflow.redo' - className={ - cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', - (nodesReadOnly || buttonsDisabled.redo) - && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} - onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()} - > - <RiArrowGoForwardFill className='h-4 w-4' /> + <RiArrowGoBackLine className="h-4 w-4" /> </div> </TipPopup> - <Divider type='vertical' className="mx-0.5 h-3.5" /> + <TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}> + <div + data-tooltip-id="workflow.redo" + className={ + cn('system-sm-medium flex h-8 w-8 cursor-pointer select-none items-center rounded-md px-1.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', (nodesReadOnly || buttonsDisabled.redo) + && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled') + } + onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()} + > + <RiArrowGoForwardFill className="h-4 w-4" /> + </div> + </TipPopup> + <Divider type="vertical" className="mx-0.5 h-3.5" /> <ViewWorkflowHistory /> - </div > + </div> ) } diff --git a/web/app/components/workflow/header/version-history-button.tsx b/web/app/components/workflow/header/version-history-button.tsx index 416c6ef7e5..b29608a022 100644 --- a/web/app/components/workflow/header/version-history-button.tsx +++ b/web/app/components/workflow/header/version-history-button.tsx @@ -1,12 +1,13 @@ -import React, { type FC, useCallback } from 'react' +import type { FC } from 'react' import { RiHistoryLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' import { useKeyPress } from 'ahooks' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import useTheme from '@/hooks/use-theme' +import { cn } from '@/utils/classnames' import Button from '../../base/button' import Tooltip from '../../base/tooltip' import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '../utils' -import useTheme from '@/hooks/use-theme' -import { cn } from '@/utils/classnames' type VersionHistoryButtonProps = { onClick: () => Promise<unknown> | unknown @@ -17,15 +18,15 @@ const VERSION_HISTORY_SHORTCUT = ['ctrl', '⇧', 'H'] const PopupContent = React.memo(() => { const { t } = useTranslation() return ( - <div className='flex items-center gap-x-1'> - <div className='system-xs-medium px-0.5 text-text-secondary'> + <div className="flex items-center gap-x-1"> + <div className="system-xs-medium px-0.5 text-text-secondary"> {t('workflow.common.versionHistory')} </div> - <div className='flex items-center gap-x-0.5'> + <div className="flex items-center gap-x-0.5"> {VERSION_HISTORY_SHORTCUT.map(key => ( <span key={key} - className='system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary' + className="system-kbd rounded-[4px] bg-components-kbd-bg-white px-[1px] text-text-tertiary" > {getKeyboardKeyNameBySystem(key)} </span> @@ -50,22 +51,24 @@ const VersionHistoryButton: FC<VersionHistoryButtonProps> = ({ handleViewVersionHistory() }, { exactMatch: true, useCapture: true }) - return <Tooltip - popupContent={<PopupContent />} - noDecoration - popupClassName='rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg - shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5' - > - <Button - className={cn( - 'p-2', - theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', - )} - onClick={handleViewVersionHistory} + return ( + <Tooltip + popupContent={<PopupContent />} + noDecoration + popupClassName="rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg + shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5" > - <RiHistoryLine className='h-4 w-4 text-components-button-secondary-text' /> - </Button> - </Tooltip> + <Button + className={cn( + 'p-2', + theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', + )} + onClick={handleViewVersionHistory} + > + <RiHistoryLine className="h-4 w-4 text-components-button-secondary-text" /> + </Button> + </Tooltip> + ) } export default VersionHistoryButton diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 7e9e0ee3bb..63a2dd25ab 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -1,44 +1,44 @@ -import { - memo, - useState, -} from 'react' import type { Fetcher } from 'swr' -import useSWR from 'swr' -import { useTranslation } from 'react-i18next' -import { noop } from 'lodash-es' +import type { WorkflowRunHistoryResponse } from '@/types/workflow' import { RiCheckboxCircleLine, RiCloseLine, RiErrorWarningLine, } from '@remixicon/react' +import { noop } from 'lodash-es' import { - useIsChatMode, - useNodesInteractions, - useWorkflowInteractions, - useWorkflowRun, -} from '../hooks' -import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' -import { ControlMode, WorkflowRunningStatus } from '../types' -import { formatWorkflowRunIdentifier } from '../utils' -import { cn } from '@/utils/classnames' + memo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import useSWR from 'swr' +import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' +import { + ClockPlay, + ClockPlaySlim, +} from '@/app/components/base/icons/src/vender/line/time' +import Loading from '@/app/components/base/loading' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import Tooltip from '@/app/components/base/tooltip' -import { - ClockPlay, - ClockPlaySlim, -} from '@/app/components/base/icons/src/vender/line/time' -import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' -import Loading from '@/app/components/base/loading' +import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' import { useStore, useWorkflowStore, } from '@/app/components/workflow/store' -import type { WorkflowRunHistoryResponse } from '@/types/workflow' -import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks' +import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import { cn } from '@/utils/classnames' +import { + useIsChatMode, + useNodesInteractions, + useWorkflowInteractions, + useWorkflowRun, +} from '../hooks' +import { ControlMode, WorkflowRunningStatus } from '../types' +import { formatWorkflowRunIdentifier } from '../utils' export type ViewHistoryProps = { withText?: boolean @@ -92,9 +92,10 @@ const ViewHistory = ({ 'flex h-8 items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 shadow-xs', 'cursor-pointer text-[13px] font-medium text-components-button-secondary-text hover:bg-components-button-secondary-bg-hover', open && 'bg-components-button-secondary-bg-hover', - )}> + )} + > <ClockPlay - className={'mr-1 h-4 w-4'} + className="mr-1 h-4 w-4" /> {t('workflow.common.showRunHistory')} </div> @@ -117,40 +118,40 @@ const ViewHistory = ({ ) } </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[12]'> + <PortalToFollowElemContent className="z-[12]"> <div - className='ml-2 flex w-[240px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl' + className="ml-2 flex w-[240px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl" style={{ maxHeight: 'calc(2 / 3 * 100vh)', }} > - <div className='sticky top-0 flex items-center justify-between bg-components-panel-bg px-4 pt-3 text-base font-semibold text-text-primary'> - <div className='grow'>{t('workflow.common.runHistory')}</div> + <div className="sticky top-0 flex items-center justify-between bg-components-panel-bg px-4 pt-3 text-base font-semibold text-text-primary"> + <div className="grow">{t('workflow.common.runHistory')}</div> <div - className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center' + className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center" onClick={() => { onClearLogAndMessageModal?.() setOpen(false) }} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> { isLoading && ( - <div className='flex h-10 items-center justify-center'> + <div className="flex h-10 items-center justify-center"> <Loading /> </div> ) } { !isLoading && ( - <div className='p-2'> + <div className="p-2"> { !data?.data.length && ( - <div className='py-12'> - <ClockPlaySlim className='mx-auto mb-2 h-8 w-8 text-text-quaternary' /> - <div className='text-center text-[13px] text-text-quaternary'> + <div className="py-12"> + <ClockPlaySlim className="mx-auto mb-2 h-8 w-8 text-text-quaternary" /> + <div className="text-center text-[13px] text-text-quaternary"> {t('workflow.common.notRunning')} </div> </div> @@ -180,17 +181,17 @@ const ViewHistory = ({ > { !isChatMode && item.status === WorkflowRunningStatus.Stopped && ( - <AlertTriangle className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]' /> + <AlertTriangle className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F79009]" /> ) } { !isChatMode && item.status === WorkflowRunningStatus.Failed && ( - <RiErrorWarningLine className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]' /> + <RiErrorWarningLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#F04438]" /> ) } { !isChatMode && item.status === WorkflowRunningStatus.Succeeded && ( - <RiCheckboxCircleLine className='mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]' /> + <RiCheckboxCircleLine className="mr-1.5 mt-0.5 h-3.5 w-3.5 text-[#12B76A]" /> ) } <div> @@ -202,8 +203,11 @@ const ViewHistory = ({ > {`Test ${isChatMode ? 'Chat' : 'Run'}${formatWorkflowRunIdentifier(item.finished_at)}`} </div> - <div className='flex items-center text-xs leading-[18px] text-text-tertiary'> - {item.created_by_account?.name} · {formatTimeFromNow((item.finished_at || item.created_at) * 1000)} + <div className="flex items-center text-xs leading-[18px] text-text-tertiary"> + {item.created_by_account?.name} + {' '} + · + {formatTimeFromNow((item.finished_at || item.created_at) * 1000)} </div> </div> </div> diff --git a/web/app/components/workflow/header/view-workflow-history.tsx b/web/app/components/workflow/header/view-workflow-history.tsx index bfef85382e..3d52247075 100644 --- a/web/app/components/workflow/header/view-workflow-history.tsx +++ b/web/app/components/workflow/header/view-workflow-history.tsx @@ -1,30 +1,30 @@ +import type { WorkflowHistoryState } from '../workflow-history-store' +import { + RiCloseLine, + RiHistoryLine, +} from '@remixicon/react' import { memo, useCallback, useMemo, useState, } from 'react' -import { - RiCloseLine, - RiHistoryLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { useShallow } from 'zustand/react/shallow' import { useStoreApi } from 'reactflow' -import { - useNodesReadOnly, - useWorkflowHistory, -} from '../hooks' -import TipPopup from '../operator/tip-popup' -import type { WorkflowHistoryState } from '../workflow-history-store' -import Divider from '../../base/divider' +import { useShallow } from 'zustand/react/shallow' +import { useStore as useAppStore } from '@/app/components/app/store' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { useStore as useAppStore } from '@/app/components/app/store' import { cn } from '@/utils/classnames' +import Divider from '../../base/divider' +import { + useNodesReadOnly, + useWorkflowHistory, +} from '../hooks' +import TipPopup from '../operator/tip-popup' type ChangeHistoryEntry = { label: string @@ -96,10 +96,12 @@ const ViewWorkflowHistory = () => { index: reverse ? list.length - 1 - index - startIndex : index - startIndex, state: { ...state, - workflowHistoryEventMeta: state.workflowHistoryEventMeta ? { - ...state.workflowHistoryEventMeta, - nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle, - } : undefined, + workflowHistoryEventMeta: state.workflowHistoryEventMeta + ? { + ...state.workflowHistoryEventMeta, + nodeTitle: state.workflowHistoryEventMeta.nodeTitle || targetTitle, + } + : undefined, }, } }).filter(Boolean) @@ -127,7 +129,7 @@ const ViewWorkflowHistory = () => { return ( ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 131, @@ -141,9 +143,8 @@ const ViewWorkflowHistory = () => { > <div className={ - cn('flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', - open && 'bg-state-accent-active text-text-accent', - nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} + cn('flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', open && 'bg-state-accent-active text-text-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled') + } onClick={() => { if (nodesReadOnly) return @@ -151,110 +152,115 @@ const ViewWorkflowHistory = () => { setShowMessageLogModal(false) }} > - <RiHistoryLine className='h-4 w-4' /> + <RiHistoryLine className="h-4 w-4" /> </div> </TipPopup> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[12]'> + <PortalToFollowElemContent className="z-[12]"> <div - className='ml-2 flex min-w-[240px] max-w-[360px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]' + className="ml-2 flex min-w-[240px] max-w-[360px] flex-col overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]" > - <div className='sticky top-0 flex items-center justify-between px-4 pt-3'> - <div className='system-mg-regular grow text-text-secondary'>{t('workflow.changeHistory.title')}</div> + <div className="sticky top-0 flex items-center justify-between px-4 pt-3"> + <div className="system-mg-regular grow text-text-secondary">{t('workflow.changeHistory.title')}</div> <div - className='flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center' + className="flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center" onClick={() => { setCurrentLogItem() setShowMessageLogModal(false) setOpen(false) }} > - <RiCloseLine className='h-4 w-4 text-text-secondary' /> + <RiCloseLine className="h-4 w-4 text-text-secondary" /> + </div> + </div> + <div + className="overflow-y-auto p-2" + style={{ + maxHeight: 'calc(1 / 2 * 100vh)', + }} + > + { + !calculateChangeList.statesCount && ( + <div className="py-12"> + <RiHistoryLine className="mx-auto mb-2 h-8 w-8 text-text-tertiary" /> + <div className="text-center text-[13px] text-text-tertiary"> + {t('workflow.changeHistory.placeholder')} + </div> + </div> + ) + } + <div className="flex flex-col"> + { + calculateChangeList.futureStates.map((item: ChangeHistoryEntry) => ( + <div + key={item?.index} + className={cn( + 'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary hover:bg-state-base-hover', + item?.index === currentHistoryStateIndex && 'bg-state-base-hover', + )} + onClick={() => { + handleSetState(item) + setOpen(false) + }} + > + <div> + <div + className={cn( + 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', + )} + > + {composeHistoryItemLabel( + item?.state?.workflowHistoryEventMeta?.nodeTitle, + item?.label || t('workflow.changeHistory.sessionStart'), + )} + {' '} + ( + {calculateStepLabel(item?.index)} + {item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')} + ) + </div> + </div> + </div> + )) + } + { + calculateChangeList.pastStates.map((item: ChangeHistoryEntry) => ( + <div + key={item?.index} + className={cn( + 'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] hover:bg-state-base-hover', + item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover', + )} + onClick={() => { + handleSetState(item) + setOpen(false) + }} + > + <div> + <div + className={cn( + 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', + )} + > + {composeHistoryItemLabel( + item?.state?.workflowHistoryEventMeta?.nodeTitle, + item?.label || t('workflow.changeHistory.sessionStart'), + )} + {' '} + ( + {calculateStepLabel(item?.index)} + ) + </div> + </div> + </div> + )) + } </div> </div> - { - ( - <div - className='overflow-y-auto p-2' - style={{ - maxHeight: 'calc(1 / 2 * 100vh)', - }} - > - { - !calculateChangeList.statesCount && ( - <div className='py-12'> - <RiHistoryLine className='mx-auto mb-2 h-8 w-8 text-text-tertiary' /> - <div className='text-center text-[13px] text-text-tertiary'> - {t('workflow.changeHistory.placeholder')} - </div> - </div> - ) - } - <div className='flex flex-col'> - { - calculateChangeList.futureStates.map((item: ChangeHistoryEntry) => ( - <div - key={item?.index} - className={cn( - 'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary hover:bg-state-base-hover', - item?.index === currentHistoryStateIndex && 'bg-state-base-hover', - )} - onClick={() => { - handleSetState(item) - setOpen(false) - }} - > - <div> - <div - className={cn( - 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', - )} - > - {composeHistoryItemLabel( - item?.state?.workflowHistoryEventMeta?.nodeTitle, - item?.label || t('workflow.changeHistory.sessionStart'), - )} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')}) - </div> - </div> - </div> - )) - } - { - calculateChangeList.pastStates.map((item: ChangeHistoryEntry) => ( - <div - key={item?.index} - className={cn( - 'mb-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] hover:bg-state-base-hover', - item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover', - )} - onClick={() => { - handleSetState(item) - setOpen(false) - }} - > - <div> - <div - className={cn( - 'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary', - )} - > - {composeHistoryItemLabel( - item?.state?.workflowHistoryEventMeta?.nodeTitle, - item?.label || t('workflow.changeHistory.sessionStart'), - )} ({calculateStepLabel(item?.index)}) - </div> - </div> - </div> - )) - } - </div> - </div> - ) - } { !!calculateChangeList.statesCount && ( - <div className='px-0.5'> - <Divider className='m-0' /> + <div className="px-0.5"> + <Divider className="m-0" /> <div className={cn( 'my-0.5 flex cursor-pointer rounded-lg px-2 py-[7px] text-text-secondary', @@ -278,7 +284,7 @@ const ViewWorkflowHistory = () => { </div> ) } - <div className="w-[240px] px-3 py-2 text-xs text-text-tertiary" > + <div className="w-[240px] px-3 py-2 text-xs text-text-tertiary"> <div className="mb-1 flex h-[22px] items-center font-medium uppercase">{t('workflow.changeHistory.hint')}</div> <div className="mb-1 leading-[18px] text-text-tertiary">{t('workflow.changeHistory.hintText')}</div> </div> diff --git a/web/app/components/workflow/help-line/index.tsx b/web/app/components/workflow/help-line/index.tsx index d0dc984a5e..632df6e4a1 100644 --- a/web/app/components/workflow/help-line/index.tsx +++ b/web/app/components/workflow/help-line/index.tsx @@ -1,10 +1,10 @@ -import { memo } from 'react' -import { useViewport } from 'reactflow' -import { useStore } from '../store' import type { HelpLineHorizontalPosition, HelpLineVerticalPosition, } from './types' +import { memo } from 'react' +import { useViewport } from 'reactflow' +import { useStore } from '../store' const HelpLineHorizontal = memo(({ top, @@ -15,7 +15,7 @@ const HelpLineHorizontal = memo(({ return ( <div - className='absolute z-[9] h-px bg-primary-300' + className="absolute z-[9] h-px bg-primary-300" style={{ top: top * zoom + y, left: left * zoom + x, @@ -35,7 +35,7 @@ const HelpLineVertical = memo(({ return ( <div - className='absolute z-[9] w-[1px] bg-primary-300' + className="absolute z-[9] w-[1px] bg-primary-300" style={{ top: top * zoom + y, left: left * zoom + x, diff --git a/web/app/components/workflow/hooks-store/provider.tsx b/web/app/components/workflow/hooks-store/provider.tsx index 8233664004..365e97eb14 100644 --- a/web/app/components/workflow/hooks-store/provider.tsx +++ b/web/app/components/workflow/hooks-store/provider.tsx @@ -1,3 +1,4 @@ +import type { Shape } from './store' import { createContext, useEffect, @@ -7,7 +8,6 @@ import { useStore } from 'reactflow' import { createHooksStore, } from './store' -import type { Shape } from './store' type HooksStore = ReturnType<typeof createHooksStore> export const HooksStoreContext = createContext<HooksStore | null | undefined>(null) diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 3e205f9521..79cfb7dbce 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -1,26 +1,24 @@ -import { useContext } from 'react' +import type { FileUpload } from '../../base/features/types' +import type { + BlockEnum, + Node, + NodeDefault, + ToolWithProvider, + ValueSelector, +} from '@/app/components/workflow/types' +import type { IOtherOptions } from '@/service/base' +import type { SchemaTypeDefinition } from '@/service/use-common' +import type { FlowType } from '@/types/common' +import type { VarInInspect } from '@/types/workflow' import { noop, } from 'lodash-es' +import { useContext } from 'react' import { useStore as useZustandStore, } from 'zustand' import { createStore } from 'zustand/vanilla' import { HooksStoreContext } from './provider' -import type { - BlockEnum, - NodeDefault, - ToolWithProvider, -} from '@/app/components/workflow/types' -import type { IOtherOptions } from '@/service/base' -import type { VarInInspect } from '@/types/workflow' -import type { - Node, - ValueSelector, -} from '@/app/components/workflow/types' -import type { FlowType } from '@/types/common' -import type { FileUpload } from '../../base/features/types' -import type { SchemaTypeDefinition } from '@/service/use-common' export type AvailableNodesMetaData = { nodes: NodeDefault[] @@ -32,7 +30,7 @@ export type CommonHooksFnMap = { callback?: { onSuccess?: () => void onError?: () => void - onSettled?: () => void, + onSettled?: () => void }, ) => Promise<void> syncWorkflowDraftWhenPageClose: () => void @@ -50,7 +48,7 @@ export type CommonHooksFnMap = { handleWorkflowTriggerPluginRunInWorkflow: (nodeId?: string) => void handleWorkflowRunAllTriggersInWorkflow: (nodeIds: string[]) => void availableNodesMetaData?: AvailableNodesMetaData - getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string; traceUrl: string } + getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string, traceUrl: string } exportCheck?: () => Promise<void> handleExportDSL?: (include?: boolean, flowId?: string) => Promise<void> fetchInspectVars: (params: { passInVars?: boolean, vars?: VarInInspect[], passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>, passedInSchemaTypeDefinitions?: SchemaTypeDefinition[] }) => Promise<void> diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index 1131836b35..4b879738e7 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -1,26 +1,26 @@ +export * from './use-auto-generate-webhook-url' +export * from './use-available-blocks' +export * from './use-checklist' +export * from './use-DSL' export * from './use-edges-interactions' +export * from './use-inspect-vars-crud' export * from './use-node-data-update' export * from './use-nodes-interactions' -export * from './use-nodes-sync-draft' -export * from './use-workflow' -export * from './use-workflow-run' -export * from './use-checklist' -export * from './use-selection-interactions' -export * from './use-panel-interactions' -export * from './use-workflow-start-run' export * from './use-nodes-layout' -export * from './use-workflow-history' -export * from './use-workflow-variables' +export * from './use-nodes-meta-data' +export * from './use-nodes-sync-draft' +export * from './use-panel-interactions' +export * from './use-selection-interactions' +export * from './use-serial-async-callback' +export * from './use-set-workflow-vars-with-value' export * from './use-shortcuts' +export * from './use-tool-icon' +export * from './use-workflow' +export * from './use-workflow-history' export * from './use-workflow-interactions' export * from './use-workflow-mode' -export * from './use-nodes-meta-data' -export * from './use-available-blocks' export * from './use-workflow-refresh-draft' -export * from './use-tool-icon' -export * from './use-DSL' -export * from './use-inspect-vars-crud' -export * from './use-set-workflow-vars-with-value' +export * from './use-workflow-run' export * from './use-workflow-search' -export * from './use-auto-generate-webhook-url' -export * from './use-serial-async-callback' +export * from './use-workflow-start-run' +export * from './use-workflow-variables' diff --git a/web/app/components/workflow/hooks/use-auto-generate-webhook-url.ts b/web/app/components/workflow/hooks/use-auto-generate-webhook-url.ts index d7d66e31ef..a046346e6b 100644 --- a/web/app/components/workflow/hooks/use-auto-generate-webhook-url.ts +++ b/web/app/components/workflow/hooks/use-auto-generate-webhook-url.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react' import { produce } from 'immer' +import { useCallback } from 'react' import { useStoreApi } from 'reactflow' import { useStore as useAppStore } from '@/app/components/app/store' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/hooks/use-available-blocks.ts b/web/app/components/workflow/hooks/use-available-blocks.ts index e1a1919afd..4969cb9d89 100644 --- a/web/app/components/workflow/hooks/use-available-blocks.ts +++ b/web/app/components/workflow/hooks/use-available-blocks.ts @@ -23,8 +23,9 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean) const availablePrevBlocks = useMemo(() => { if (!nodeType || nodeType === BlockEnum.Start || nodeType === BlockEnum.DataSource || nodeType === BlockEnum.TriggerPlugin || nodeType === BlockEnum.TriggerWebhook - || nodeType === BlockEnum.TriggerSchedule) + || nodeType === BlockEnum.TriggerSchedule) { return [] + } return availableNodesType }, [availableNodesType, nodeType]) diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index cd1f051eb5..6e49bfa0be 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -1,10 +1,9 @@ -import { - useCallback, - useMemo, - useRef, -} from 'react' -import { useTranslation } from 'react-i18next' -import { useEdges, useStoreApi } from 'reactflow' +import type { AgentNodeType } from '../nodes/agent/types' +import type { DataSourceNodeType } from '../nodes/data-source/types' +import type { KnowledgeBaseNodeType } from '../nodes/knowledge-base/types' +import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types' +import type { ToolNodeType } from '../nodes/tool/types' +import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types' import type { CommonEdgeType, CommonNodeType, @@ -12,51 +11,52 @@ import type { Node, ValueSelector, } from '../types' -import { BlockEnum } from '../types' +import type { Emoji } from '@/app/components/tools/types' +import type { DataSet } from '@/models/datasets' +import { + useCallback, + useMemo, + useRef, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useEdges, useStoreApi } from 'reactflow' +import { useStore as useAppStore } from '@/app/components/app/store' +import { useToastContext } from '@/app/components/base/toast' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' +import { MAX_TREE_DEPTH } from '@/config' +import { useGetLanguage } from '@/context/i18n' +import { fetchDatasets } from '@/service/datasets' +import { useStrategyProviders } from '@/service/use-strategy' +import { + useAllBuiltInTools, + useAllCustomTools, + useAllWorkflowTools, +} from '@/service/use-tools' +import { useAllTriggerPlugins } from '@/service/use-triggers' +import { AppModeEnum } from '@/types/app' +import { + CUSTOM_NODE, +} from '../constants' +import { useDatasetsDetailStore } from '../datasets-detail-store/store' +import { + useGetToolIcon, + useNodesMetaData, +} from '../hooks' +import { getNodeUsedVars, isSpecialVar } from '../nodes/_base/components/variable/utils' import { useStore, useWorkflowStore, } from '../store' +import { BlockEnum } from '../types' import { getDataSourceCheckParams, getToolCheckParams, getValidTreeNodes, } from '../utils' import { getTriggerCheckParams } from '../utils/trigger' -import { - CUSTOM_NODE, -} from '../constants' -import { - useGetToolIcon, - useNodesMetaData, -} from '../hooks' -import type { ToolNodeType } from '../nodes/tool/types' -import type { DataSourceNodeType } from '../nodes/data-source/types' -import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types' -import { useToastContext } from '@/app/components/base/toast' -import { useGetLanguage } from '@/context/i18n' -import type { AgentNodeType } from '../nodes/agent/types' -import { useStrategyProviders } from '@/service/use-strategy' -import { useAllTriggerPlugins } from '@/service/use-triggers' -import { useDatasetsDetailStore } from '../datasets-detail-store/store' -import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types' -import type { DataSet } from '@/models/datasets' -import { fetchDatasets } from '@/service/datasets' -import { MAX_TREE_DEPTH } from '@/config' import useNodesAvailableVarList, { useGetNodesAvailableVarList } from './use-nodes-available-var-list' -import { getNodeUsedVars, isSpecialVar } from '../nodes/_base/components/variable/utils' -import type { Emoji } from '@/app/components/tools/types' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { KnowledgeBaseNodeType } from '../nodes/knowledge-base/types' -import { - useAllBuiltInTools, - useAllCustomTools, - useAllWorkflowTools, -} from '@/service/use-tools' -import { useStore as useAppStore } from '@/app/components/app/store' -import { AppModeEnum } from '@/types/app' -import useNodes from '@/app/components/workflow/store/workflow/use-nodes' export type ChecklistItem = { id: string diff --git a/web/app/components/workflow/hooks/use-config-vision.ts b/web/app/components/workflow/hooks/use-config-vision.ts index ab859a5ba3..12a2ad38ad 100644 --- a/web/app/components/workflow/hooks/use-config-vision.ts +++ b/web/app/components/workflow/hooks/use-config-vision.ts @@ -1,12 +1,12 @@ +import type { ModelConfig, VisionSetting } from '@/app/components/workflow/types' import { produce } from 'immer' import { useCallback } from 'react' -import { useIsChatMode } from './use-workflow' -import type { ModelConfig, VisionSetting } from '@/app/components/workflow/types' -import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelFeatureEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import { Resolution } from '@/types/app' +import { useIsChatMode } from './use-workflow' type Payload = { enabled: boolean diff --git a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx index 276e2f38e7..cc43857b7d 100644 --- a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx +++ b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx @@ -1,13 +1,15 @@ +import type { TestRunOptions, TriggerOption } from '../header/test-run-menu' +import type { CommonNodeType } from '../types' import { useMemo } from 'react' -import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useTranslation } from 'react-i18next' -import { BlockEnum, type CommonNodeType } from '../types' -import { getWorkflowEntryNode } from '../utils/workflow-entry' -import { type TestRunOptions, type TriggerOption, TriggerType } from '../header/test-run-menu' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' -import BlockIcon from '../block-icon' -import { useStore } from '../store' +import useNodes from '@/app/components/workflow/store/workflow/use-nodes' import { useAllTriggerPlugins } from '@/service/use-triggers' +import BlockIcon from '../block-icon' +import { TriggerType } from '../header/test-run-menu' +import { useStore } from '../store' +import { BlockEnum } from '../types' +import { getWorkflowEntryNode } from '../utils/workflow-entry' export const useDynamicTestRunOptions = (): TestRunOptions => { const { t } = useTranslation() @@ -25,7 +27,8 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { for (const node of nodes) { const nodeData = node.data as CommonNodeType - if (!nodeData?.type) continue + if (!nodeData?.type) + continue if (nodeData.type === BlockEnum.Start) { userInput = { @@ -35,7 +38,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { icon: ( <BlockIcon type={BlockEnum.Start} - size='md' + size="md" /> ), nodeId: node.id, @@ -50,7 +53,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { icon: ( <BlockIcon type={BlockEnum.TriggerSchedule} - size='md' + size="md" /> ), nodeId: node.id, @@ -65,7 +68,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { icon: ( <BlockIcon type={BlockEnum.TriggerWebhook} - size='md' + size="md" /> ), nodeId: node.id, @@ -83,7 +86,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { const icon = ( <BlockIcon type={BlockEnum.TriggerPlugin} - size='md' + size="md" toolIcon={triggerIcon} /> ) @@ -109,7 +112,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { icon: ( <BlockIcon type={BlockEnum.Start} - size='md' + size="md" /> ), nodeId: startNode.id, @@ -122,18 +125,20 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { .map(trigger => trigger.nodeId) .filter((nodeId): nodeId is string => Boolean(nodeId)) - const runAll: TriggerOption | undefined = triggerNodeIds.length > 1 ? { - id: 'run-all', - type: TriggerType.All, - name: t('workflow.common.runAllTriggers'), - icon: ( - <div className="flex h-6 w-6 items-center justify-center rounded-lg border-[0.5px] border-white/2 bg-util-colors-purple-purple-500 text-white shadow-md"> - <TriggerAll className="h-4.5 w-4.5" /> - </div> - ), - relatedNodeIds: triggerNodeIds, - enabled: true, - } : undefined + const runAll: TriggerOption | undefined = triggerNodeIds.length > 1 + ? { + id: 'run-all', + type: TriggerType.All, + name: t('workflow.common.runAllTriggers'), + icon: ( + <div className="flex h-6 w-6 items-center justify-center rounded-lg border-[0.5px] border-white/2 bg-util-colors-purple-purple-500 text-white shadow-md"> + <TriggerAll className="h-4.5 w-4.5" /> + </div> + ), + relatedNodeIds: triggerNodeIds, + enabled: true, + } + : undefined return { userInput, diff --git a/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts index e7b4fb0a42..99673b70f8 100644 --- a/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts +++ b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react' import { produce } from 'immer' +import { useCallback } from 'react' import { useStoreApi } from 'reactflow' export const useEdgesInteractionsWithoutSync = () => { diff --git a/web/app/components/workflow/hooks/use-edges-interactions.ts b/web/app/components/workflow/hooks/use-edges-interactions.ts index 297535ac24..5104b47ef4 100644 --- a/web/app/components/workflow/hooks/use-edges-interactions.ts +++ b/web/app/components/workflow/hooks/use-edges-interactions.ts @@ -1,19 +1,19 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { EdgeMouseHandler, OnEdgesChange, } from 'reactflow' -import { - useStoreApi, -} from 'reactflow' import type { Node, } from '../types' +import { produce } from 'immer' +import { useCallback } from 'react' +import { + useStoreApi, +} from 'reactflow' import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../utils' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useNodesReadOnly } from './use-workflow' -import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' +import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history' export const useEdgesInteractions = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts index 60f839b93d..54c2c77d0d 100644 --- a/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts +++ b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts @@ -1,22 +1,21 @@ -import type { NodeWithVar, VarInInspect } from '@/types/workflow' -import { useStore, useWorkflowStore } from '@/app/components/workflow/store' -import { useStoreApi } from 'reactflow' -import type { ToolWithProvider } from '@/app/components/workflow/types' -import type { Node } from '@/app/components/workflow/types' -import { fetchAllInspectVars } from '@/service/workflow' -import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow' -import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' -import type { FlowType } from '@/types/common' -import useMatchSchemaType, { getMatchedSchemaType } from '../nodes/_base/components/variable/use-match-schema-type' -import { toNodeOutputVars } from '../nodes/_base/components/variable/utils' +import type { Node, ToolWithProvider } from '@/app/components/workflow/types' import type { SchemaTypeDefinition } from '@/service/use-common' +import type { FlowType } from '@/types/common' +import type { NodeWithVar, VarInInspect } from '@/types/workflow' import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow' +import { fetchAllInspectVars } from '@/service/workflow' +import useMatchSchemaType, { getMatchedSchemaType } from '../nodes/_base/components/variable/use-match-schema-type' +import { toNodeOutputVars } from '../nodes/_base/components/variable/utils' type Params = { flowType: FlowType @@ -99,9 +98,9 @@ export const useSetWorkflowVarsWithValue = ({ } const fetchInspectVars = useCallback(async (params: { - passInVars?: boolean, - vars?: VarInInspect[], - passedInAllPluginInfoList?: Record<string, ToolWithProvider[]>, + passInVars?: boolean + vars?: VarInInspect[] + passedInAllPluginInfoList?: Record<string, ToolWithProvider[]> passedInSchemaTypeDefinitions?: SchemaTypeDefinition[] }) => { const { passInVars, vars, passedInAllPluginInfoList, passedInSchemaTypeDefinitions } = params diff --git a/web/app/components/workflow/hooks/use-helpline.ts b/web/app/components/workflow/hooks/use-helpline.ts index 55979904fb..681a7c9802 100644 --- a/web/app/components/workflow/hooks/use-helpline.ts +++ b/web/app/components/workflow/hooks/use-helpline.ts @@ -1,8 +1,8 @@ +import type { Node } from '../types' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import type { Node } from '../types' -import { BlockEnum, isTriggerNode } from '../types' import { useWorkflowStore } from '../store' +import { BlockEnum, isTriggerNode } from '../types' // Entry node (Start/Trigger) wrapper offsets // The EntryNodeContainer adds a wrapper with status indicator above the actual node diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts index 6b7acd0a85..316da71d9d 100644 --- a/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts +++ b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts @@ -1,29 +1,28 @@ -import { fetchNodeInspectVars } from '@/service/workflow' -import { useWorkflowStore } from '@/app/components/workflow/store' -import type { ValueSelector } from '@/app/components/workflow/types' +import type { Node, ValueSelector } from '@/app/components/workflow/types' +import type { SchemaTypeDefinition } from '@/service/use-common' +import type { FlowType } from '@/types/common' import type { VarInInspect } from '@/types/workflow' -import { VarInInspectType } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' +import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' import { isConversationVar, isENV, isSystemVar, toNodeOutputVars, } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { produce } from 'immer' -import type { Node } from '@/app/components/workflow/types' -import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' -import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' -import type { FlowType } from '@/types/common' +import { useWorkflowStore } from '@/app/components/workflow/store' import useFLow from '@/service/use-flow' -import { useStoreApi } from 'reactflow' -import type { SchemaTypeDefinition } from '@/service/use-common' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { fetchNodeInspectVars } from '@/service/workflow' +import { VarInInspectType } from '@/types/workflow' type Params = { flowId: string diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts index 0f58cf8be2..2a97019383 100644 --- a/web/app/components/workflow/hooks/use-inspect-vars-crud.ts +++ b/web/app/components/workflow/hooks/use-inspect-vars-crud.ts @@ -1,11 +1,11 @@ -import { useStore } from '../store' +import { produce } from 'immer' import { useHooksStore } from '@/app/components/workflow/hooks-store' import { useConversationVarValues, useSysVarValues, } from '@/service/use-workflow' import { FlowType } from '@/types/common' -import { produce } from 'immer' +import { useStore } from '../store' import { BlockEnum } from '../types' const varsAppendStartNodeKeys = ['query', 'files'] @@ -16,19 +16,19 @@ const useInspectVarsCrud = () => { const { data: conversationVars } = useConversationVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '') const { data: allSystemVars } = useSysVarValues(configsMap?.flowType, !isRagPipeline ? configsMap?.flowId : '') const { varsAppendStartNode, systemVars } = (() => { - if(allSystemVars?.length === 0) + if (allSystemVars?.length === 0) return { varsAppendStartNode: [], systemVars: [] } const varsAppendStartNode = allSystemVars?.filter(({ name }) => varsAppendStartNodeKeys.includes(name)) || [] const systemVars = allSystemVars?.filter(({ name }) => !varsAppendStartNodeKeys.includes(name)) || [] return { varsAppendStartNode, systemVars } })() const nodesWithInspectVars = (() => { - if(!partOfNodesWithInspectVars || partOfNodesWithInspectVars.length === 0) + if (!partOfNodesWithInspectVars || partOfNodesWithInspectVars.length === 0) return [] const nodesWithInspectVars = produce(partOfNodesWithInspectVars, (draft) => { draft.forEach((nodeWithVars) => { - if(nodeWithVars.nodeType === BlockEnum.Start) + if (nodeWithVars.nodeType === BlockEnum.Start) nodeWithVars.vars = [...nodeWithVars.vars, ...varsAppendStartNode] }) }) diff --git a/web/app/components/workflow/hooks/use-node-data-update.ts b/web/app/components/workflow/hooks/use-node-data-update.ts index ac7dca9e4c..e40b467db2 100644 --- a/web/app/components/workflow/hooks/use-node-data-update.ts +++ b/web/app/components/workflow/hooks/use-node-data-update.ts @@ -1,7 +1,7 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' import type { SyncCallback } from './use-nodes-sync-draft' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useNodesReadOnly } from './use-workflow' diff --git a/web/app/components/workflow/hooks/use-node-plugin-installation.ts b/web/app/components/workflow/hooks/use-node-plugin-installation.ts index 96e3919e67..d275759cc0 100644 --- a/web/app/components/workflow/hooks/use-node-plugin-installation.ts +++ b/web/app/components/workflow/hooks/use-node-plugin-installation.ts @@ -1,9 +1,10 @@ -import { useCallback, useMemo } from 'react' -import { BlockEnum, type CommonNodeType } from '../types' +import type { DataSourceNodeType } from '../nodes/data-source/types' import type { ToolNodeType } from '../nodes/tool/types' import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types' -import type { DataSourceNodeType } from '../nodes/data-source/types' +import type { CommonNodeType } from '../types' +import { useCallback, useMemo } from 'react' import { CollectionType } from '@/app/components/tools/types' +import { useInvalidDataSourceList } from '@/service/use-pipeline' import { useAllBuiltInTools, useAllCustomTools, @@ -15,9 +16,9 @@ import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins, } from '@/service/use-triggers' -import { useInvalidDataSourceList } from '@/service/use-pipeline' -import { useStore } from '../store' import { canFindTool } from '@/utils' +import { useStore } from '../store' +import { BlockEnum } from '../types' type InstallationState = { isChecking: boolean diff --git a/web/app/components/workflow/hooks/use-nodes-available-var-list.ts b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts index b78597ea37..cb04b43002 100644 --- a/web/app/components/workflow/hooks/use-nodes-available-var-list.ts +++ b/web/app/components/workflow/hooks/use-nodes-available-var-list.ts @@ -1,10 +1,12 @@ +import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useCallback } from 'react' import { useIsChatMode, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' -import { BlockEnum, type Node, type NodeOutPutVar, type ValueSelector, type Var } from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' + type Params = { onlyLeafNodeVar?: boolean hideEnv?: boolean diff --git a/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts b/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts index e3d661ff08..0c343f4eb8 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions-without-sync.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react' import { produce } from 'immer' +import { useCallback } from 'react' import { useStoreApi } from 'reactflow' import { NodeRunningStatus } from '../types' @@ -31,7 +31,7 @@ export const useNodesInteractionsWithoutSync = () => { const nodes = getNodes() const newNodes = produce(nodes, (draft) => { draft.forEach((node) => { - if(node.data._runningStatus === NodeRunningStatus.Succeeded) + if (node.data._runningStatus === NodeRunningStatus.Succeeded) node.data._runningStatus = undefined }) }) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index d56b85893e..fd9fe3300f 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1,7 +1,4 @@ import type { MouseEvent } from 'react' -import { useCallback, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { produce } from 'immer' import type { NodeDragHandler, NodeMouseHandler, @@ -10,16 +7,21 @@ import type { OnConnectStart, ResizeParamsWithDirection, } from 'reactflow' +import type { PluginDefaultValue } from '../block-selector/types' +import type { IterationNodeType } from '../nodes/iteration/types' +import type { LoopNodeType } from '../nodes/loop/types' +import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types' +import type { Edge, Node, OnNodeAdd } from '../types' +import type { RAGPipelineVariables } from '@/models/pipeline' +import { produce } from 'immer' +import { useCallback, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import { getConnectedEdges, getOutgoers, useReactFlow, useStoreApi, } from 'reactflow' -import type { PluginDefaultValue } from '../block-selector/types' -import type { Edge, Node, OnNodeAdd } from '../types' -import { BlockEnum, isTriggerNode } from '../types' -import { useWorkflowStore } from '../store' import { CUSTOM_EDGE, ITERATION_CHILDREN_Z_INDEX, @@ -30,39 +32,37 @@ import { X_OFFSET, Y_OFFSET, } from '../constants' +import { getNodeUsedVars } from '../nodes/_base/components/variable/utils' +import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants' +import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions' +import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants' +import { useNodeLoopInteractions } from '../nodes/loop/use-interactions' +import { CUSTOM_NOTE_NODE } from '../note-node/constants' +import { useWorkflowStore } from '../store' +import { BlockEnum, isTriggerNode } from '../types' import { - genNewNodeTitleFromOld, generateNewNode, + genNewNodeTitleFromOld, getNestedNodePosition, getNodeCustomTypeByNodeDataType, getNodesConnectedSourceOrTargetHandleIdsMap, getTopLeftNodePosition, } from '../utils' -import { CUSTOM_NOTE_NODE } from '../note-node/constants' -import type { IterationNodeType } from '../nodes/iteration/types' -import type { LoopNodeType } from '../nodes/loop/types' -import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants' -import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants' -import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types' -import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions' -import { useNodeLoopInteractions } from '../nodes/loop/use-interactions' import { useWorkflowHistoryStore } from '../workflow-history-store' -import { useNodesSyncDraft } from './use-nodes-sync-draft' +import { useAutoGenerateWebhookUrl } from './use-auto-generate-webhook-url' import { useHelpline } from './use-helpline' +import useInspectVarsCrud from './use-inspect-vars-crud' +import { useNodesMetaData } from './use-nodes-meta-data' +import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useNodesReadOnly, useWorkflow, useWorkflowReadOnly, } from './use-workflow' import { - WorkflowHistoryEvent, useWorkflowHistory, + WorkflowHistoryEvent, } from './use-workflow-history' -import { useNodesMetaData } from './use-nodes-meta-data' -import { useAutoGenerateWebhookUrl } from './use-auto-generate-webhook-url' -import type { RAGPipelineVariables } from '@/models/pipeline' -import useInspectVarsCrud from './use-inspect-vars-crud' -import { getNodeUsedVars } from '../nodes/_base/components/variable/utils' // Entry node deletion restriction has been removed to allow empty workflows @@ -89,8 +89,8 @@ export const useNodesInteractions = () => { const { handleNodeLoopChildDrag, handleNodeLoopChildrenCopy } = useNodeLoopInteractions() const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { - x: number; - y: number; + x: number + y: number }) const { nodesMap: nodesMetaDataMap } = useNodesMetaData() @@ -101,19 +101,22 @@ export const useNodesInteractions = () => { (_, node) => { workflowStore.setState({ nodeAnimation: false }) - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return if ( node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_NOTE_NODE - ) + ) { return + } if ( node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE - ) + ) { return + } dragNodeStartPosition.current = { x: node.position.x, @@ -125,11 +128,14 @@ export const useNodesInteractions = () => { const handleNodeDrag = useCallback<NodeDragHandler>( (e, node: Node) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return - if (node.type === CUSTOM_ITERATION_START_NODE) return + if (node.type === CUSTOM_ITERATION_START_NODE) + return - if (node.type === CUSTOM_LOOP_START_NODE) return + if (node.type === CUSTOM_LOOP_START_NODE) + return const { getNodes, setNodes } = store.getState() e.stopPropagation() @@ -211,7 +217,8 @@ export const useNodesInteractions = () => { const { setHelpLineHorizontal, setHelpLineVertical } = workflowStore.getState() - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { x, y } = dragNodeStartPosition.current if (!(x === node.position.x && y === node.position.y)) { @@ -237,19 +244,22 @@ export const useNodesInteractions = () => { const handleNodeEnter = useCallback<NodeMouseHandler>( (_, node) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return if ( node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE - ) + ) { return + } if ( node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE - ) + ) { return + } const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() @@ -257,7 +267,8 @@ export const useNodesInteractions = () => { = workflowStore.getState() if (connectingNodePayload) { - if (connectingNodePayload.nodeId === node.id) return + if (connectingNodePayload.nodeId === node.id) + return const connectingNode: Node = nodes.find( n => n.id === connectingNodePayload.nodeId, )! @@ -288,8 +299,9 @@ export const useNodesInteractions = () => { || connectingNode.data.type === BlockEnum.VariableAggregator) && node.data.type !== BlockEnum.IfElse && node.data.type !== BlockEnum.QuestionClassifier - ) + ) { n.data._isEntering = true + } }) }) setNodes(newNodes) @@ -300,7 +312,8 @@ export const useNodesInteractions = () => { connectedEdges.forEach((edge) => { const currentEdge = draft.find(e => e.id === edge.id) - if (currentEdge) currentEdge.data._connectedNodeIsHovering = true + if (currentEdge) + currentEdge.data._connectedNodeIsHovering = true }) }) setEdges(newEdges) @@ -310,19 +323,22 @@ export const useNodesInteractions = () => { const handleNodeLeave = useCallback<NodeMouseHandler>( (_, node) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return if ( node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE - ) + ) { return + } if ( node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE - ) + ) { return + } const { setEnteringNodePayload } = workflowStore.getState() setEnteringNodePayload(undefined) @@ -356,11 +372,13 @@ export const useNodesInteractions = () => { const nodes = getNodes() const selectedNode = nodes.find(node => node.data.selected) - if (!cancelSelection && selectedNode?.id === nodeId) return + if (!cancelSelection && selectedNode?.id === nodeId) + return const newNodes = produce(nodes, (draft) => { draft.forEach((node) => { - if (node.id === nodeId) node.data.selected = !cancelSelection + if (node.id === nodeId) + node.data.selected = !cancelSelection else node.data.selected = false }) }) @@ -395,10 +413,14 @@ export const useNodesInteractions = () => { const handleNodeClick = useCallback<NodeMouseHandler>( (_, node) => { - if (node.type === CUSTOM_ITERATION_START_NODE) return - if (node.type === CUSTOM_LOOP_START_NODE) return - if (node.data.type === BlockEnum.DataSourceEmpty) return - if (node.data._pluginInstallLocked) return + if (node.type === CUSTOM_ITERATION_START_NODE) + return + if (node.type === CUSTOM_LOOP_START_NODE) + return + if (node.data.type === BlockEnum.DataSourceEmpty) + return + if (node.data._pluginInstallLocked) + return handleNodeSelect(node.id) }, [handleNodeSelect], @@ -406,21 +428,25 @@ export const useNodesInteractions = () => { const handleNodeConnect = useCallback<OnConnect>( ({ source, sourceHandle, target, targetHandle }) => { - if (source === target) return - if (getNodesReadOnly()) return + if (source === target) + return + if (getNodesReadOnly()) + return const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() const targetNode = nodes.find(node => node.id === target!) const sourceNode = nodes.find(node => node.id === source!) - if (targetNode?.parentId !== sourceNode?.parentId) return + if (targetNode?.parentId !== sourceNode?.parentId) + return if ( sourceNode?.type === CUSTOM_NOTE_NODE || targetNode?.type === CUSTOM_NOTE_NODE - ) + ) { return + } if ( edges.find( @@ -430,8 +456,9 @@ export const useNodesInteractions = () => { && edge.target === target && edge.targetHandle === targetHandle, ) - ) + ) { return + } const parendNode = nodes.find(node => node.id === targetNode?.parentId) const isInIteration @@ -497,20 +524,24 @@ export const useNodesInteractions = () => { const handleNodeConnectStart = useCallback<OnConnectStart>( (_, { nodeId, handleType, handleId }) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return if (nodeId && handleType) { const { setConnectingNodePayload } = workflowStore.getState() const { getNodes } = store.getState() const node = getNodes().find(n => n.id === nodeId)! - if (node.type === CUSTOM_NOTE_NODE) return + if (node.type === CUSTOM_NOTE_NODE) + return if ( node.data.type === BlockEnum.VariableAggregator || node.data.type === BlockEnum.VariableAssigner - ) - if (handleType === 'target') return + ) { + if (handleType === 'target') + return + } setConnectingNodePayload({ nodeId, @@ -525,7 +556,8 @@ export const useNodesInteractions = () => { const handleNodeConnectEnd = useCallback<OnConnectEnd>( (e: any) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { connectingNodePayload, @@ -547,7 +579,8 @@ export const useNodesInteractions = () => { const toNode = nodes.find(n => n.id === enteringNodePayload.nodeId)! const toParentNode = nodes.find(n => n.id === toNode.parentId) - if (fromNode.parentId !== toNode.parentId) return + if (fromNode.parentId !== toNode.parentId) + return const { x, y } = screenToFlowPosition({ x: e.x, y: e.y }) @@ -602,7 +635,8 @@ export const useNodesInteractions = () => { const handleNodeDelete = useCallback( (nodeId: string) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, setNodes, edges, setEdges } = store.getState() @@ -610,13 +644,15 @@ export const useNodesInteractions = () => { const currentNodeIndex = nodes.findIndex(node => node.id === nodeId) const currentNode = nodes[currentNodeIndex] - if (!currentNode) return + if (!currentNode) + return if ( nodesMetaDataMap?.[currentNode.data.type as BlockEnum]?.metaData .isUndeletable - ) + ) { return + } deleteNodeInspectorVars(nodeId) if (currentNode.data.type === BlockEnum.Iteration) { @@ -706,7 +742,8 @@ export const useNodesInteractions = () => { if (ragPipelineVariables && setRagPipelineVariables) { const newRagPipelineVariables: RAGPipelineVariables = [] ragPipelineVariables.forEach((variable) => { - if (variable.belong_to_node_id === id) return + if (variable.belong_to_node_id === id) + return newRagPipelineVariables.push(variable) }) setRagPipelineVariables(newRagPipelineVariables) @@ -781,7 +818,8 @@ export const useNodesInteractions = () => { }, { prevNodeId, prevNodeSourceHandle, nextNodeId, nextNodeTargetHandle }, ) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() @@ -935,9 +973,11 @@ export const useNodesInteractions = () => { }) draft.push(newNode) - if (newIterationStartNode) draft.push(newIterationStartNode) + if (newIterationStartNode) + draft.push(newIterationStartNode) - if (newLoopStartNode) draft.push(newLoopStartNode) + if (newLoopStartNode) + draft.push(newLoopStartNode) }) if ( @@ -964,7 +1004,8 @@ export const useNodesInteractions = () => { _connectedNodeIsSelected: false, } }) - if (newEdge) draft.push(newEdge) + if (newEdge) + draft.push(newEdge) }) setNodes(newNodes) @@ -976,8 +1017,9 @@ export const useNodesInteractions = () => { if ( nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier - ) + ) { newNode.data._connectedSourceHandleIds = [sourceHandle] + } newNode.data._connectedTargetHandleIds = [] newNode.position = { x: nextNode.position.x, @@ -1101,8 +1143,10 @@ export const useNodesInteractions = () => { } }) draft.push(newNode) - if (newIterationStartNode) draft.push(newIterationStartNode) - if (newLoopStartNode) draft.push(newLoopStartNode) + if (newIterationStartNode) + draft.push(newIterationStartNode) + if (newLoopStartNode) + draft.push(newLoopStartNode) }) if (newEdge) { const newEdges = produce(edges, (draft) => { @@ -1192,10 +1236,10 @@ export const useNodesInteractions = () => { = nodes.find(node => node.id === nextNode.parentId) || null const isNextNodeInIteration = !!nextNodeParentNode - && nextNodeParentNode.data.type === BlockEnum.Iteration + && nextNodeParentNode.data.type === BlockEnum.Iteration const isNextNodeInLoop = !!nextNodeParentNode - && nextNodeParentNode.data.type === BlockEnum.Loop + && nextNodeParentNode.data.type === BlockEnum.Loop if ( nodeType !== BlockEnum.IfElse @@ -1274,8 +1318,10 @@ export const useNodesInteractions = () => { } }) draft.push(newNode) - if (newIterationStartNode) draft.push(newIterationStartNode) - if (newLoopStartNode) draft.push(newLoopStartNode) + if (newIterationStartNode) + draft.push(newIterationStartNode) + if (newLoopStartNode) + draft.push(newLoopStartNode) }) setNodes(newNodes) if ( @@ -1303,9 +1349,11 @@ export const useNodesInteractions = () => { _connectedNodeIsSelected: false, } }) - if (newPrevEdge) draft.push(newPrevEdge) + if (newPrevEdge) + draft.push(newPrevEdge) - if (newNextEdge) draft.push(newNextEdge) + if (newNextEdge) + draft.push(newNextEdge) }) setEdges(newEdges) } @@ -1330,7 +1378,8 @@ export const useNodesInteractions = () => { sourceHandle: string, pluginDefaultValue?: PluginDefaultValue, ) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() @@ -1388,8 +1437,10 @@ export const useNodesInteractions = () => { const index = draft.findIndex(node => node.id === currentNodeId) draft.splice(index, 1, newCurrentNode) - if (newIterationStartNode) draft.push(newIterationStartNode) - if (newLoopStartNode) draft.push(newLoopStartNode) + if (newIterationStartNode) + draft.push(newIterationStartNode) + if (newLoopStartNode) + draft.push(newLoopStartNode) }) setNodes(newNodes) const newEdges = produce(edges, (draft) => { @@ -1443,14 +1494,16 @@ export const useNodesInteractions = () => { if ( node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE - ) + ) { return + } if ( node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE - ) + ) { return + } e.preventDefault() const container = document.querySelector('#workflow-container') @@ -1469,7 +1522,8 @@ export const useNodesInteractions = () => { const handleNodesCopy = useCallback( (nodeId?: string) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { setClipboardElements } = workflowStore.getState() @@ -1489,15 +1543,19 @@ export const useNodesInteractions = () => { && node.data.type !== BlockEnum.KnowledgeBase && node.data.type !== BlockEnum.DataSourceEmpty, ) - if (nodeToCopy) setClipboardElements([nodeToCopy]) + if (nodeToCopy) + setClipboardElements([nodeToCopy]) } else { // If no nodeId is provided, fall back to the current behavior const bundledNodes = nodes.filter((node) => { - if (!node.data._isBundled) return false - if (node.type === CUSTOM_NOTE_NODE) return true + if (!node.data._isBundled) + return false + if (node.type === CUSTOM_NOTE_NODE) + return true const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum] - if (metaData.isSingleton) return false + if (metaData.isSingleton) + return false return !node.data.isInIteration && !node.data.isInLoop }) @@ -1507,20 +1565,24 @@ export const useNodesInteractions = () => { } const selectedNode = nodes.find((node) => { - if (!node.data.selected) return false - if (node.type === CUSTOM_NOTE_NODE) return true + if (!node.data.selected) + return false + if (node.type === CUSTOM_NOTE_NODE) + return true const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum] return !metaData.isSingleton }) - if (selectedNode) setClipboardElements([selectedNode]) + if (selectedNode) + setClipboardElements([selectedNode]) } }, [getNodesReadOnly, store, workflowStore], ) const handleNodesPaste = useCallback(() => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { clipboardElements, mousePosition } = workflowStore.getState() @@ -1647,7 +1709,8 @@ export const useNodesInteractions = () => { nodesToPaste.push(newNode) - if (newChildren.length) nodesToPaste.push(...newChildren) + if (newChildren.length) + nodesToPaste.push(...newChildren) }) // only handle edge when paste nested block @@ -1691,7 +1754,8 @@ export const useNodesInteractions = () => { const handleNodesDuplicate = useCallback( (nodeId?: string) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return handleNodesCopy(nodeId) handleNodesPaste() @@ -1700,7 +1764,8 @@ export const useNodesInteractions = () => { ) const handleNodesDelete = useCallback(() => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, edges } = store.getState() @@ -1716,18 +1781,21 @@ export const useNodesInteractions = () => { } const edgeSelected = edges.some(edge => edge.selected) - if (edgeSelected) return + if (edgeSelected) + return const selectedNode = nodes.find( node => node.data.selected, ) - if (selectedNode) handleNodeDelete(selectedNode.id) + if (selectedNode) + handleNodeDelete(selectedNode.id) }, [store, getNodesReadOnly, handleNodeDelete]) const handleNodeResize = useCallback( (nodeId: string, params: ResizeParamsWithDirection) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, setNodes } = store.getState() const { x, y, width, height } = params @@ -1752,8 +1820,9 @@ export const useNodesInteractions = () => { if ( n.position.y + n.height! > bottomNode.position.y + bottomNode.height! - ) + ) { bottomNode = n + } } else { bottomNode = n @@ -1772,8 +1841,9 @@ export const useNodesInteractions = () => { if ( height < bottomNode.position.y + bottomNode.height! + paddingMap.bottom - ) + ) { return + } } const newNodes = produce(nodes, (draft) => { draft.forEach((n) => { @@ -1796,7 +1866,8 @@ export const useNodesInteractions = () => { const handleNodeDisconnect = useCallback( (nodeId: string) => { - if (getNodesReadOnly()) return + if (getNodesReadOnly()) + return const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() @@ -1834,13 +1905,15 @@ export const useNodesInteractions = () => { ) const handleHistoryBack = useCallback(() => { - if (getNodesReadOnly() || getWorkflowReadOnly()) return + if (getNodesReadOnly() || getWorkflowReadOnly()) + return const { setEdges, setNodes } = store.getState() undo() const { edges, nodes } = workflowHistoryStore.getState() - if (edges.length === 0 && nodes.length === 0) return + if (edges.length === 0 && nodes.length === 0) + return setEdges(edges) setNodes(nodes) @@ -1853,13 +1926,15 @@ export const useNodesInteractions = () => { ]) const handleHistoryForward = useCallback(() => { - if (getNodesReadOnly() || getWorkflowReadOnly()) return + if (getNodesReadOnly() || getWorkflowReadOnly()) + return const { setEdges, setNodes } = store.getState() redo() const { edges, nodes } = workflowHistoryStore.getState() - if (edges.length === 0 && nodes.length === 0) return + if (edges.length === 0 && nodes.length === 0) + return setEdges(edges) setNodes(nodes) @@ -1874,12 +1949,14 @@ export const useNodesInteractions = () => { const [isDimming, setIsDimming] = useState(false) /** Add opacity-30 to all nodes except the nodeId */ const dimOtherNodes = useCallback(() => { - if (isDimming) return + if (isDimming) + return const { getNodes, setNodes, edges, setEdges } = store.getState() const nodes = getNodes() const selectedNode = nodes.find(n => n.data.selected) - if (!selectedNode) return + if (!selectedNode) + return setIsDimming(true) @@ -1890,8 +1967,10 @@ export const useNodesInteractions = () => { const dependencyNodes: Node[] = [] usedVars.forEach((valueSelector) => { const node = workflowNodes.find(node => node.id === valueSelector?.[0]) - if (node) - if (!dependencyNodes.includes(node)) dependencyNodes.push(node) + if (node) { + if (!dependencyNodes.includes(node)) + dependencyNodes.push(node) + } }) const outgoers = getOutgoers(selectedNode as Node, nodes as Node[], edges) @@ -1900,7 +1979,8 @@ export const useNodesInteractions = () => { const outgoersForNode = getOutgoers(node, nodes as Node[], edges) outgoersForNode.forEach((item) => { const existed = outgoers.some(v => v.id === item.id) - if (!existed) outgoers.push(item) + if (!existed) + outgoers.push(item) }) } @@ -1910,7 +1990,8 @@ export const useNodesInteractions = () => { const used = usedVars.some(v => v?.[0] === selectedNode.id) if (used) { const existed = dependentNodes.some(v => v.id === node.id) - if (!existed) dependentNodes.push(node) + if (!existed) + dependentNodes.push(node) } }) @@ -1919,7 +2000,8 @@ export const useNodesInteractions = () => { const newNodes = produce(nodes, (draft) => { draft.forEach((n) => { const dimNode = dimNodes.find(v => v.id === n.id) - if (!dimNode) n.data._dimmed = true + if (!dimNode) + n.data._dimmed = true }) }) diff --git a/web/app/components/workflow/hooks/use-nodes-layout.ts b/web/app/components/workflow/hooks/use-nodes-layout.ts index 594ac8b029..653b37008c 100644 --- a/web/app/components/workflow/hooks/use-nodes-layout.ts +++ b/web/app/components/workflow/hooks/use-nodes-layout.ts @@ -1,16 +1,16 @@ -import { useCallback } from 'react' -import ELK from 'elkjs/lib/elk.bundled.js' -import { - useReactFlow, - useStoreApi, -} from 'reactflow' -import { cloneDeep } from 'lodash-es' import type { Edge, Node, } from '../types' -import { useWorkflowStore } from '../store' +import ELK from 'elkjs/lib/elk.bundled.js' +import { cloneDeep } from 'lodash-es' +import { useCallback } from 'react' +import { + useReactFlow, + useStoreApi, +} from 'reactflow' import { AUTO_LAYOUT_OFFSET } from '../constants' +import { useWorkflowStore } from '../store' import { useNodesSyncDraft } from './use-nodes-sync-draft' const layoutOptions = { diff --git a/web/app/components/workflow/hooks/use-nodes-meta-data.ts b/web/app/components/workflow/hooks/use-nodes-meta-data.ts index fd63f23590..2ea2fd9e9f 100644 --- a/web/app/components/workflow/hooks/use-nodes-meta-data.ts +++ b/web/app/components/workflow/hooks/use-nodes-meta-data.ts @@ -1,17 +1,17 @@ -import { useMemo } from 'react' import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store' -import { useHooksStore } from '@/app/components/workflow/hooks-store' -import { BlockEnum } from '@/app/components/workflow/types' import type { Node } from '@/app/components/workflow/types' +import { useMemo } from 'react' import { CollectionType } from '@/app/components/tools/types' +import { useHooksStore } from '@/app/components/workflow/hooks-store' import { useStore } from '@/app/components/workflow/store' -import { canFindTool } from '@/utils' +import { BlockEnum } from '@/app/components/workflow/types' import { useGetLanguage } from '@/context/i18n' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools, } from '@/service/use-tools' +import { canFindTool } from '@/utils' export const useNodesMetaData = () => { const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) diff --git a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts index a4c9a45542..db65bfa6ad 100644 --- a/web/app/components/workflow/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow/hooks/use-nodes-sync-draft.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' +import { useHooksStore } from '@/app/components/workflow/hooks-store' import { useStore } from '../store' import { useNodesReadOnly } from './use-workflow' -import { useHooksStore } from '@/app/components/workflow/hooks-store' export type SyncCallback = { onSuccess?: () => void diff --git a/web/app/components/workflow/hooks/use-selection-interactions.ts b/web/app/components/workflow/hooks/use-selection-interactions.ts index 481483d762..dbdef306c5 100644 --- a/web/app/components/workflow/hooks/use-selection-interactions.ts +++ b/web/app/components/workflow/hooks/use-selection-interactions.ts @@ -1,14 +1,14 @@ import type { MouseEvent } from 'react' -import { - useCallback, -} from 'react' -import { produce } from 'immer' import type { OnSelectionChangeFunc, } from 'reactflow' +import type { Node } from '../types' +import { produce } from 'immer' +import { + useCallback, +} from 'react' import { useStoreApi } from 'reactflow' import { useWorkflowStore } from '../store' -import type { Node } from '../types' export const useSelectionInteractions = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/hooks/use-shortcuts.ts b/web/app/components/workflow/hooks/use-shortcuts.ts index 16502c97c4..00ed25bad0 100644 --- a/web/app/components/workflow/hooks/use-shortcuts.ts +++ b/web/app/components/workflow/hooks/use-shortcuts.ts @@ -1,13 +1,7 @@ -import { useReactFlow } from 'reactflow' import { useKeyPress } from 'ahooks' import { useCallback, useEffect } from 'react' +import { useReactFlow } from 'reactflow' import { ZEN_TOGGLE_EVENT } from '@/app/components/goto-anything/actions/commands/zen' -import { - getKeyboardKeyCodeBySystem, - isEventTargetInputArea, -} from '../utils' -import { useWorkflowHistoryStore } from '../workflow-history-store' -import { useWorkflowStore } from '../store' import { useEdgesInteractions, useNodesInteractions, @@ -16,6 +10,12 @@ import { useWorkflowMoveMode, useWorkflowOrganize, } from '.' +import { useWorkflowStore } from '../store' +import { + getKeyboardKeyCodeBySystem, + isEventTargetInputArea, +} from '../utils' +import { useWorkflowHistoryStore } from '../workflow-history-store' export const useShortcuts = (): void => { const { diff --git a/web/app/components/workflow/hooks/use-tool-icon.ts b/web/app/components/workflow/hooks/use-tool-icon.ts index faf962d450..a300021bad 100644 --- a/web/app/components/workflow/hooks/use-tool-icon.ts +++ b/web/app/components/workflow/hooks/use-tool-icon.ts @@ -1,9 +1,11 @@ -import { useCallback, useMemo } from 'react' +import type { TriggerWithProvider } from '../block-selector/types' +import type { DataSourceNodeType } from '../nodes/data-source/types' +import type { ToolNodeType } from '../nodes/tool/types' +import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types' import type { Node, ToolWithProvider } from '../types' -import { BlockEnum } from '../types' -import { useStore, useWorkflowStore } from '../store' +import { useCallback, useMemo } from 'react' import { CollectionType } from '@/app/components/tools/types' -import { canFindTool } from '@/utils' +import useTheme from '@/hooks/use-theme' import { useAllBuiltInTools, useAllCustomTools, @@ -11,11 +13,9 @@ import { useAllWorkflowTools, } from '@/service/use-tools' import { useAllTriggerPlugins } from '@/service/use-triggers' -import type { PluginTriggerNodeType } from '../nodes/trigger-plugin/types' -import type { ToolNodeType } from '../nodes/tool/types' -import type { DataSourceNodeType } from '../nodes/data-source/types' -import type { TriggerWithProvider } from '../block-selector/types' -import useTheme from '@/hooks/use-theme' +import { canFindTool } from '@/utils' +import { useStore, useWorkflowStore } from '../store' +import { BlockEnum } from '../types' const isTriggerPluginNode = (data: Node['data']): data is PluginTriggerNodeType => data.type === BlockEnum.TriggerPlugin diff --git a/web/app/components/workflow/hooks/use-workflow-history.ts b/web/app/components/workflow/hooks/use-workflow-history.ts index 58bbe415a8..3e6043a1fd 100644 --- a/web/app/components/workflow/hooks/use-workflow-history.ts +++ b/web/app/components/workflow/hooks/use-workflow-history.ts @@ -1,14 +1,15 @@ +import type { WorkflowHistoryEventMeta } from '../workflow-history-store' +import { debounce } from 'lodash-es' import { useCallback, - useRef, useState, + useRef, + useState, } from 'react' -import { debounce } from 'lodash-es' +import { useTranslation } from 'react-i18next' import { useStoreApi, } from 'reactflow' -import { useTranslation } from 'react-i18next' import { useWorkflowHistoryStore } from '../workflow-history-store' -import type { WorkflowHistoryEventMeta } from '../workflow-history-store' /** * All supported Events that create a new history state. diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts index e56c39d51e..7a58581a99 100644 --- a/web/app/components/workflow/hooks/use-workflow-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts @@ -1,16 +1,23 @@ +import type { WorkflowDataUpdater } from '../types' +import type { LayoutResult } from '../utils' +import { produce } from 'immer' import { useCallback, } from 'react' import { useReactFlow, useStoreApi } from 'reactflow' -import { produce } from 'immer' -import { useStore, useWorkflowStore } from '../store' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { CUSTOM_NODE, NODE_LAYOUT_HORIZONTAL_PADDING, NODE_LAYOUT_VERTICAL_PADDING, WORKFLOW_DATA_UPDATE, } from '../constants' -import type { WorkflowDataUpdater } from '../types' +import { + useNodesReadOnly, + useSelectionInteractions, + useWorkflowReadOnly, +} from '../hooks' +import { useStore, useWorkflowStore } from '../store' import { BlockEnum, ControlMode } from '../types' import { getLayoutByDagre, @@ -18,17 +25,10 @@ import { initialEdges, initialNodes, } from '../utils' -import type { LayoutResult } from '../utils' -import { - useNodesReadOnly, - useSelectionInteractions, - useWorkflowReadOnly, -} from '../hooks' import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync' import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-without-sync' import { useNodesSyncDraft } from './use-nodes-sync-draft' -import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' -import { useEventEmitterContextContext } from '@/context/event-emitter' +import { useWorkflowHistory, WorkflowHistoryEvent } from './use-workflow-history' export const useWorkflowInteractions = () => { const workflowStore = useWorkflowStore() @@ -99,8 +99,8 @@ export const useWorkflowOrganize = () => { const loopAndIterationNodes = nodes.filter( node => (node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration) - && !node.parentId - && node.type === CUSTOM_NODE, + && !node.parentId + && node.type === CUSTOM_NODE, ) const childLayoutEntries = await Promise.all( @@ -119,7 +119,8 @@ export const useWorkflowOrganize = () => { loopAndIterationNodes.forEach((parentNode) => { const childLayout = childLayoutsMap[parentNode.id] - if (!childLayout) return + if (!childLayout) + return const { bounds, @@ -141,7 +142,7 @@ export const useWorkflowOrganize = () => { const nodesWithUpdatedSizes = produce(nodes, (draft) => { draft.forEach((node) => { if ((node.data.type === BlockEnum.Loop || node.data.type === BlockEnum.Iteration) - && containerSizeChanges[node.id]) { + && containerSizeChanges[node.id]) { node.width = containerSizeChanges[node.id].width node.height = containerSizeChanges[node.id].height @@ -160,7 +161,7 @@ export const useWorkflowOrganize = () => { const layout = await getLayoutByDagre(nodesWithUpdatedSizes, edges) // Build layer map for vertical alignment - nodes in the same layer should align - const layerMap = new Map<number, { minY: number; maxHeight: number }>() + const layerMap = new Map<number, { minY: number, maxHeight: number }>() layout.nodes.forEach((layoutInfo) => { if (layoutInfo.layer !== undefined) { const existing = layerMap.get(layoutInfo.layer) diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/index.ts b/web/app/components/workflow/hooks/use-workflow-run-event/index.ts index 67bc6c15ef..3f31b423ad 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/index.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/index.ts @@ -1,15 +1,15 @@ -export * from './use-workflow-started' -export * from './use-workflow-finished' +export * from './use-workflow-agent-log' export * from './use-workflow-failed' -export * from './use-workflow-node-started' +export * from './use-workflow-finished' export * from './use-workflow-node-finished' -export * from './use-workflow-node-iteration-started' -export * from './use-workflow-node-iteration-next' export * from './use-workflow-node-iteration-finished' -export * from './use-workflow-node-loop-started' -export * from './use-workflow-node-loop-next' +export * from './use-workflow-node-iteration-next' +export * from './use-workflow-node-iteration-started' export * from './use-workflow-node-loop-finished' +export * from './use-workflow-node-loop-next' +export * from './use-workflow-node-loop-started' export * from './use-workflow-node-retry' +export * from './use-workflow-node-started' +export * from './use-workflow-started' export * from './use-workflow-text-chunk' export * from './use-workflow-text-replace' -export * from './use-workflow-agent-log' diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts index a9deb9bc70..08fb526000 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { AgentLogResponse } from '@/types/workflow' +import { produce } from 'immer' +import { useCallback } from 'react' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowAgentLog = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts index ff16aa1935..b2d9755ab5 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-failed.ts @@ -1,5 +1,5 @@ -import { useCallback } from 'react' import { produce } from 'immer' +import { useCallback } from 'react' import { useWorkflowStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts index 681a123b93..5fc8807873 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-finished.ts @@ -1,8 +1,8 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { WorkflowFinishedResponse } from '@/types/workflow' -import { useWorkflowStore } from '@/app/components/workflow/store' +import { produce } from 'immer' +import { useCallback } from 'react' import { getFilesInLogs } from '@/app/components/base/file-uploader/utils' +import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowFinished = () => { const workflowStore = useWorkflowStore() diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts index 9b90a2ebee..cf0d9bcef1 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-finished.ts @@ -1,13 +1,13 @@ +import type { NodeFinishedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { NodeFinishedResponse } from '@/types/workflow' +import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import { useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum, NodeRunningStatus, } from '@/app/components/workflow/types' -import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' -import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowNodeFinished = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-finished.ts index 50700a7fad..4491104c08 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-finished.ts @@ -1,9 +1,9 @@ +import type { IterationFinishedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { IterationFinishedResponse } from '@/types/workflow' -import { useWorkflowStore } from '@/app/components/workflow/store' import { DEFAULT_ITER_TIMES } from '@/app/components/workflow/constants' +import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowNodeIterationFinished = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-next.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-next.ts index 3b88a02bfc..70fe6fbf22 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-next.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-next.ts @@ -1,7 +1,7 @@ +import type { IterationNextResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { IterationNextResponse } from '@/types/workflow' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowNodeIterationNext = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-started.ts index c064809a10..388d3936ed 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-iteration-started.ts @@ -1,13 +1,13 @@ +import type { IterationStartedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useReactFlow, useStoreApi, } from 'reactflow' -import { produce } from 'immer' -import { useWorkflowStore } from '@/app/components/workflow/store' -import type { IterationStartedResponse } from '@/types/workflow' -import { NodeRunningStatus } from '@/app/components/workflow/types' import { DEFAULT_ITER_TIMES } from '@/app/components/workflow/constants' +import { useWorkflowStore } from '@/app/components/workflow/store' +import { NodeRunningStatus } from '@/app/components/workflow/types' export const useWorkflowNodeIterationStarted = () => { const store = useStoreApi() @@ -17,8 +17,8 @@ export const useWorkflowNodeIterationStarted = () => { const handleWorkflowNodeIterationStarted = useCallback(( params: IterationStartedResponse, containerParams: { - clientWidth: number, - clientHeight: number, + clientWidth: number + clientHeight: number }, ) => { const { data } = params diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-finished.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-finished.ts index a7605dfc55..24df8ac6aa 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-finished.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-finished.ts @@ -1,7 +1,7 @@ +import type { LoopFinishedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { LoopFinishedResponse } from '@/types/workflow' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowNodeLoopFinished = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-next.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-next.ts index 26f52bc23c..2f92e2bae1 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-next.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-next.ts @@ -1,7 +1,7 @@ +import type { LoopNextResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { LoopNextResponse } from '@/types/workflow' import { NodeRunningStatus } from '@/app/components/workflow/types' export const useWorkflowNodeLoopNext = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-started.ts index d83b9a2ab8..c5ace9c692 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-loop-started.ts @@ -1,11 +1,11 @@ +import type { LoopStartedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useReactFlow, useStoreApi, } from 'reactflow' -import { produce } from 'immer' import { useWorkflowStore } from '@/app/components/workflow/store' -import type { LoopStartedResponse } from '@/types/workflow' import { NodeRunningStatus } from '@/app/components/workflow/types' export const useWorkflowNodeLoopStarted = () => { @@ -16,8 +16,8 @@ export const useWorkflowNodeLoopStarted = () => { const handleWorkflowNodeLoopStarted = useCallback(( params: LoopStartedResponse, containerParams: { - clientWidth: number, - clientHeight: number, + clientWidth: number + clientHeight: number }, ) => { const { data } = params diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-retry.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-retry.ts index b7fb631a4f..5b09d27ca4 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-retry.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-retry.ts @@ -1,9 +1,9 @@ -import { useCallback } from 'react' -import { useStoreApi } from 'reactflow' -import { produce } from 'immer' import type { NodeFinishedResponse, } from '@/types/workflow' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowNodeRetry = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts index a3ae0407ea..03c7387d38 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-node-started.ts @@ -1,12 +1,12 @@ +import type { NodeStartedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useReactFlow, useStoreApi, } from 'reactflow' -import { produce } from 'immer' -import type { NodeStartedResponse } from '@/types/workflow' -import { NodeRunningStatus } from '@/app/components/workflow/types' import { useWorkflowStore } from '@/app/components/workflow/store' +import { NodeRunningStatus } from '@/app/components/workflow/types' export const useWorkflowNodeStarted = () => { const store = useStoreApi() @@ -16,8 +16,8 @@ export const useWorkflowNodeStarted = () => { const handleWorkflowNodeStarted = useCallback(( params: NodeStartedResponse, containerParams: { - clientWidth: number, - clientHeight: number, + clientWidth: number + clientHeight: number }, ) => { const { data } = params diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts index 2843cf0f1c..16ad976607 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-started.ts @@ -1,9 +1,9 @@ +import type { WorkflowStartedResponse } from '@/types/workflow' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { WorkflowStartedResponse } from '@/types/workflow' -import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { useWorkflowStore } from '@/app/components/workflow/store' +import { WorkflowRunningStatus } from '@/app/components/workflow/types' export const useWorkflowStarted = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts index 1667265548..dfca7f61a6 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-chunk.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { TextChunkResponse } from '@/types/workflow' +import { produce } from 'immer' +import { useCallback } from 'react' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowTextChunk = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts index 3efd287c9b..74bb0a7ad3 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-text-replace.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { TextReplaceResponse } from '@/types/workflow' +import { produce } from 'immer' +import { useCallback } from 'react' import { useWorkflowStore } from '@/app/components/workflow/store' export const useWorkflowTextReplace = () => { diff --git a/web/app/components/workflow/hooks/use-workflow-search.tsx b/web/app/components/workflow/hooks/use-workflow-search.tsx index 68ad9873f9..8ca597f94e 100644 --- a/web/app/components/workflow/hooks/use-workflow-search.tsx +++ b/web/app/components/workflow/hooks/use-workflow-search.tsx @@ -1,23 +1,23 @@ 'use client' +import type { LLMNodeType } from '../nodes/llm/types' +import type { CommonNodeType } from '../types' +import type { Emoji } from '@/app/components/tools/types' import { useCallback, useEffect, useMemo } from 'react' import { useNodes } from 'reactflow' -import { useNodesInteractions } from './use-nodes-interactions' -import type { CommonNodeType } from '../types' import { workflowNodesAction } from '@/app/components/goto-anything/actions/workflow-nodes' -import BlockIcon from '@/app/components/workflow/block-icon' -import { setupNodeSelectionListener } from '../utils/node-navigation' -import { BlockEnum } from '../types' -import type { Emoji } from '@/app/components/tools/types' import { CollectionType } from '@/app/components/tools/types' -import { canFindTool } from '@/utils' -import type { LLMNodeType } from '../nodes/llm/types' +import BlockIcon from '@/app/components/workflow/block-icon' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { canFindTool } from '@/utils' +import { BlockEnum } from '../types' +import { setupNodeSelectionListener } from '../utils/node-navigation' +import { useNodesInteractions } from './use-nodes-interactions' /** * Hook to register workflow nodes search functionality @@ -34,7 +34,8 @@ export const useWorkflowSearch = () => { // Extract tool icon logic - clean separation of concerns const getToolIcon = useCallback((nodeData: CommonNodeType): string | Emoji | undefined => { - if (nodeData?.type !== BlockEnum.Tool) return undefined + if (nodeData?.type !== BlockEnum.Tool) + return undefined const toolCollections: Record<string, any[]> = { [CollectionType.builtIn]: buildInTools || [], @@ -48,19 +49,23 @@ export const useWorkflowSearch = () => { // Extract model info logic - clean extraction const getModelInfo = useCallback((nodeData: CommonNodeType) => { - if (nodeData?.type !== BlockEnum.LLM) return {} + if (nodeData?.type !== BlockEnum.LLM) + return {} const llmNodeData = nodeData as LLMNodeType - return llmNodeData.model ? { - provider: llmNodeData.model.provider, - name: llmNodeData.model.name, - mode: llmNodeData.model.mode, - } : {} + return llmNodeData.model + ? { + provider: llmNodeData.model.provider, + name: llmNodeData.model.name, + mode: llmNodeData.model.mode, + } + : {} }, []) const searchableNodes = useMemo(() => { const filteredNodes = nodes.filter((node) => { - if (!node.id || !node.data || node.type === 'sticky') return false + if (!node.id || !node.data || node.type === 'sticky') + return false const nodeData = node.data as CommonNodeType const nodeType = nodeData?.type @@ -87,12 +92,13 @@ export const useWorkflowSearch = () => { // Calculate search score - clean scoring logic const calculateScore = useCallback((node: { - title: string; - type: string; - desc: string; - modelInfo: { provider?: string; name?: string; mode?: string } + title: string + type: string + desc: string + modelInfo: { provider?: string, name?: string, mode?: string } }, searchTerm: string): number => { - if (!searchTerm) return 1 + if (!searchTerm) + return 1 const titleMatch = node.title.toLowerCase() const typeMatch = node.type.toLowerCase() @@ -104,27 +110,36 @@ export const useWorkflowSearch = () => { let score = 0 // Title matching (exact prefix > partial match) - if (titleMatch.startsWith(searchTerm)) score += 100 - else if (titleMatch.includes(searchTerm)) score += 50 + if (titleMatch.startsWith(searchTerm)) + score += 100 + else if (titleMatch.includes(searchTerm)) + score += 50 // Type matching (exact > partial) - if (typeMatch === searchTerm) score += 80 - else if (typeMatch.includes(searchTerm)) score += 30 + if (typeMatch === searchTerm) + score += 80 + else if (typeMatch.includes(searchTerm)) + score += 30 // Description matching (additive) - if (descMatch.includes(searchTerm)) score += 20 + if (descMatch.includes(searchTerm)) + score += 20 // LLM model matching (additive - can combine multiple matches) - if (modelNameMatch && modelNameMatch.includes(searchTerm)) score += 60 - if (modelProviderMatch && modelProviderMatch.includes(searchTerm)) score += 40 - if (modelModeMatch && modelModeMatch.includes(searchTerm)) score += 30 + if (modelNameMatch && modelNameMatch.includes(searchTerm)) + score += 60 + if (modelProviderMatch && modelProviderMatch.includes(searchTerm)) + score += 40 + if (modelModeMatch && modelModeMatch.includes(searchTerm)) + score += 30 return score }, []) // Create search function for workflow nodes const searchWorkflowNodes = useCallback((query: string) => { - if (!searchableNodes.length) return [] + if (!searchableNodes.length) + return [] const searchTerm = query.toLowerCase().trim() @@ -132,32 +147,35 @@ export const useWorkflowSearch = () => { .map((node) => { const score = calculateScore(node, searchTerm) - return score > 0 ? { - id: node.id, - title: node.title, - description: node.desc || node.type, - type: 'workflow-node' as const, - path: `#${node.id}`, - icon: ( - <BlockIcon - type={node.blockType} - className="shrink-0" - size="sm" - toolIcon={node.toolIcon} - /> - ), - metadata: { - nodeId: node.id, - nodeData: node.nodeData, - }, - data: node.nodeData, - score, - } : null + return score > 0 + ? { + id: node.id, + title: node.title, + description: node.desc || node.type, + type: 'workflow-node' as const, + path: `#${node.id}`, + icon: ( + <BlockIcon + type={node.blockType} + className="shrink-0" + size="sm" + toolIcon={node.toolIcon} + /> + ), + metadata: { + nodeId: node.id, + nodeData: node.nodeData, + }, + data: node.nodeData, + score, + } + : null }) .filter((node): node is NonNullable<typeof node> => node !== null) .sort((a, b) => { // If no search term, sort alphabetically - if (!searchTerm) return a.title.localeCompare(b.title) + if (!searchTerm) + return a.title.localeCompare(b.title) // Sort by relevance score (higher score first) return (b.score || 0) - (a.score || 0) }) diff --git a/web/app/components/workflow/hooks/use-workflow-variables.ts b/web/app/components/workflow/hooks/use-workflow-variables.ts index 871937365a..d6a5b8bdd1 100644 --- a/web/app/components/workflow/hooks/use-workflow-variables.ts +++ b/web/app/components/workflow/hooks/use-workflow-variables.ts @@ -1,23 +1,23 @@ -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import { useWorkflowStore } from '../store' -import { getVarType, toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import type { Type } from '../nodes/llm/types' import type { Node, NodeOutPutVar, ValueSelector, Var, } from '@/app/components/workflow/types' -import { useIsChatMode } from './use-workflow' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' -import type { Type } from '../nodes/llm/types' -import useMatchSchemaType from '../nodes/_base/components/variable/use-match-schema-type' +import { getVarType, toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import useMatchSchemaType from '../nodes/_base/components/variable/use-match-schema-type' +import { useWorkflowStore } from '../store' +import { useIsChatMode } from './use-workflow' export const useWorkflowVariables = () => { const { t } = useTranslation() @@ -137,8 +137,8 @@ export const useWorkflowVariableType = () => { nodeId, valueSelector, }: { - nodeId: string, - valueSelector: ValueSelector, + nodeId: string + valueSelector: ValueSelector }) => { const node = getNodes().find(n => n.id === nodeId) const isInIteration = !!node?.data.isInIteration diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index e6746085b8..c958bb6b83 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -1,46 +1,46 @@ -import { - useCallback, -} from 'react' -import { uniqBy } from 'lodash-es' -import { - getIncomers, - getOutgoers, - useStoreApi, -} from 'reactflow' import type { Connection, } from 'reactflow' +import type { IterationNodeType } from '../nodes/iteration/types' +import type { LoopNodeType } from '../nodes/loop/types' import type { BlockEnum, Edge, Node, ValueSelector, } from '../types' +import { uniqBy } from 'lodash-es' import { - WorkflowRunningStatus, -} from '../types' + useCallback, +} from 'react' +import { + getIncomers, + getOutgoers, + useStoreApi, +} from 'reactflow' +import { useStore as useAppStore } from '@/app/components/app/store' +import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' +import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' +import { AppModeEnum } from '@/types/app' +import { useNodesMetaData } from '.' +import { + SUPPORT_OUTPUT_VARS_NODE, +} from '../constants' +import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' +import { CUSTOM_NOTE_NODE } from '../note-node/constants' + import { useStore, useWorkflowStore, } from '../store' +import { + WorkflowRunningStatus, +} from '../types' import { getWorkflowEntryNode, isWorkflowEntryNode, } from '../utils/workflow-entry' -import { - SUPPORT_OUTPUT_VARS_NODE, -} from '../constants' -import type { IterationNodeType } from '../nodes/iteration/types' -import type { LoopNodeType } from '../nodes/loop/types' -import { CUSTOM_NOTE_NODE } from '../note-node/constants' -import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' import { useAvailableBlocks } from './use-available-blocks' -import { useStore as useAppStore } from '@/app/components/app/store' - -import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' -import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' -import { useNodesMetaData } from '.' -import { AppModeEnum } from '@/types/app' export const useIsChatMode = () => { const appDetail = useAppStore(s => s.appDetail) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 4f6ee4e64a..ab31f36406 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -1,6 +1,21 @@ 'use client' import type { FC } from 'react' +import type { + Viewport, +} from 'reactflow' +import type { Shape as HooksStoreShape } from './hooks-store' +import type { + Edge, + Node, +} from './types' +import type { VarInInspect } from '@/types/workflow' +import { + useEventListener, +} from 'ahooks' +import { setAutoFreeze } from 'immer' +import { isEqual } from 'lodash-es' +import dynamic from 'next/dynamic' import { memo, useCallback, @@ -9,10 +24,6 @@ import { useRef, useState, } from 'react' -import { setAutoFreeze } from 'immer' -import { - useEventListener, -} from 'ahooks' import ReactFlow, { Background, ReactFlowProvider, @@ -24,18 +35,26 @@ import ReactFlow, { useReactFlow, useStoreApi, } from 'reactflow' -import type { - Viewport, -} from 'reactflow' -import 'reactflow/dist/style.css' -import './style.css' -import type { - Edge, - Node, -} from './types' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { - ControlMode, -} from './types' + useAllBuiltInTools, + useAllCustomTools, + useAllMCPTools, + useAllWorkflowTools, +} from '@/service/use-tools' +import { fetchAllInspectVars } from '@/service/workflow' +import { cn } from '@/utils/classnames' +import CandidateNode from './candidate-node' +import { + CUSTOM_EDGE, + CUSTOM_NODE, + ITERATION_CHILDREN_Z_INDEX, + WORKFLOW_DATA_UPDATE, +} from './constants' +import CustomConnectionLine from './custom-connection-line' +import CustomEdge from './custom-edge' +import DatasetsDetailProvider from './datasets-detail-store/provider' +import HelpLine from './help-line' import { useEdgesInteractions, useNodesInteractions, @@ -49,56 +68,37 @@ import { useWorkflowReadOnly, useWorkflowRefreshDraft, } from './hooks' +import { HooksStoreContextProvider, useHooksStore } from './hooks-store' +import { useWorkflowSearch } from './hooks/use-workflow-search' +import NodeContextmenu from './node-contextmenu' import CustomNode from './nodes' -import CustomNoteNode from './note-node' -import { CUSTOM_NOTE_NODE } from './note-node/constants' +import useMatchSchemaType from './nodes/_base/components/variable/use-match-schema-type' +import CustomDataSourceEmptyNode from './nodes/data-source-empty' +import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from './nodes/data-source-empty/constants' import CustomIterationStartNode from './nodes/iteration-start' import { CUSTOM_ITERATION_START_NODE } from './nodes/iteration-start/constants' import CustomLoopStartNode from './nodes/loop-start' import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants' +import CustomNoteNode from './note-node' +import { CUSTOM_NOTE_NODE } from './note-node/constants' +import Operator from './operator' +import Control from './operator/control' +import PanelContextmenu from './panel-contextmenu' +import SelectionContextmenu from './selection-contextmenu' import CustomSimpleNode from './simple-node' import { CUSTOM_SIMPLE_NODE } from './simple-node/constants' -import CustomDataSourceEmptyNode from './nodes/data-source-empty' -import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from './nodes/data-source-empty/constants' -import Operator from './operator' -import { useWorkflowSearch } from './hooks/use-workflow-search' -import Control from './operator/control' -import CustomEdge from './custom-edge' -import CustomConnectionLine from './custom-connection-line' -import HelpLine from './help-line' -import CandidateNode from './candidate-node' -import PanelContextmenu from './panel-contextmenu' -import NodeContextmenu from './node-contextmenu' -import SelectionContextmenu from './selection-contextmenu' -import SyncingDataModal from './syncing-data-modal' -import { setupScrollToNodeListener } from './utils/node-navigation' import { useStore, useWorkflowStore, } from './store' +import SyncingDataModal from './syncing-data-modal' import { - CUSTOM_EDGE, - CUSTOM_NODE, - ITERATION_CHILDREN_Z_INDEX, - WORKFLOW_DATA_UPDATE, -} from './constants' + ControlMode, +} from './types' +import { setupScrollToNodeListener } from './utils/node-navigation' import { WorkflowHistoryProvider } from './workflow-history-store' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import DatasetsDetailProvider from './datasets-detail-store/provider' -import { HooksStoreContextProvider, useHooksStore } from './hooks-store' -import type { Shape as HooksStoreShape } from './hooks-store' -import dynamic from 'next/dynamic' -import useMatchSchemaType from './nodes/_base/components/variable/use-match-schema-type' -import type { VarInInspect } from '@/types/workflow' -import { fetchAllInspectVars } from '@/service/workflow' -import { cn } from '@/utils/classnames' -import { - useAllBuiltInTools, - useAllCustomTools, - useAllMCPTools, - useAllWorkflowTools, -} from '@/service/use-tools' -import { isEqual } from 'lodash-es' +import 'reactflow/dist/style.css' +import './style.css' const Confirm = dynamic(() => import('@/app/components/base/confirm'), { ssr: false, @@ -362,7 +362,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ return ( <div - id='workflow-container' + id="workflow-container" className={cn( 'relative h-full w-full min-w-[960px]', workflowReadOnly && 'workflow-panel-animation', @@ -373,7 +373,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ <SyncingDataModal /> <CandidateNode /> <div - className='pointer-events-none absolute left-0 top-0 z-10 flex w-12 items-center justify-center p-1 pl-2' + className="pointer-events-none absolute left-0 top-0 z-10 flex w-12 items-center justify-center p-1 pl-2" style={{ height: controlHeight }} > <Control /> @@ -443,7 +443,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ gap={[14, 14]} size={2} className="bg-workflow-canvas-workflow-bg" - color='var(--color-workflow-canvas-workflow-dot-color)' + color="var(--color-workflow-canvas-workflow-dot-color)" /> </ReactFlow> </div> @@ -466,9 +466,9 @@ export const WorkflowWithInnerContext = memo(({ type WorkflowWithDefaultContextProps = Pick<WorkflowProps, 'edges' | 'nodes'> - & { - children: React.ReactNode - } + & { + children: React.ReactNode + } const WorkflowWithDefaultContext = ({ nodes, @@ -479,7 +479,8 @@ const WorkflowWithDefaultContext = ({ <ReactFlowProvider> <WorkflowHistoryProvider nodes={nodes} - edges={edges} > + edges={edges} + > <DatasetsDetailProvider nodes={nodes}> {children} </DatasetsDetailProvider> diff --git a/web/app/components/workflow/node-contextmenu.tsx b/web/app/components/workflow/node-contextmenu.tsx index 86708981fe..cd749fefc0 100644 --- a/web/app/components/workflow/node-contextmenu.tsx +++ b/web/app/components/workflow/node-contextmenu.tsx @@ -1,14 +1,14 @@ +import type { Node } from './types' +import { useClickAway } from 'ahooks' import { memo, useEffect, useRef, } from 'react' -import { useClickAway } from 'ahooks' import useNodes from '@/app/components/workflow/store/workflow/use-nodes' -import PanelOperatorPopup from './nodes/_base/components/panel-operator/panel-operator-popup' -import type { Node } from './types' -import { useStore } from './store' import { usePanelInteractions } from './hooks' +import PanelOperatorPopup from './nodes/_base/components/panel-operator/panel-operator-popup' +import { useStore } from './store' const NodeContextmenu = () => { const ref = useRef(null) @@ -30,7 +30,7 @@ const NodeContextmenu = () => { return ( <div - className='absolute z-[9]' + className="absolute z-[9]" style={{ left: nodeMenu.left, top: nodeMenu.top, diff --git a/web/app/components/workflow/nodes/_base/components/add-button.tsx b/web/app/components/workflow/nodes/_base/components/add-button.tsx index 12bf649cda..99ccc61fe5 100644 --- a/web/app/components/workflow/nodes/_base/components/add-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/add-button.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' -import React from 'react' import { RiAddLine, } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import React from 'react' import Button from '@/app/components/base/button' +import { cn } from '@/utils/classnames' type Props = { className?: string @@ -21,11 +21,11 @@ const AddButton: FC<Props> = ({ return ( <Button className={cn('w-full', className)} - variant='tertiary' - size='medium' + variant="tertiary" + size="medium" onClick={onClick} > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> + <RiAddLine className="mr-1 h-3.5 w-3.5" /> <div>{text}</div> </Button> ) diff --git a/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx b/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx index 6d54e38556..bdbe6c1415 100644 --- a/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx +++ b/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx @@ -1,22 +1,22 @@ +import type { + ValueSelector, + Var, + VarType, +} from '../../../types' +import { useClickAway } from 'ahooks' import { memo, useCallback, useMemo, useRef, } from 'react' -import { useClickAway } from 'ahooks' -import { useStore } from '../../../store' import { useIsChatMode, useNodeDataUpdate, useWorkflow, useWorkflowVariables, } from '../../../hooks' -import type { - ValueSelector, - Var, - VarType, -} from '../../../types' +import { useStore } from '../../../store' import { useVariableAssigner } from '../../variable-assigner/hooks' import { filterVar } from '../../variable-assigner/utils' import AddVariablePopup from './add-variable-popup' @@ -112,7 +112,7 @@ const AddVariablePopupWithPosition = ({ return ( <div - className='absolute z-10' + className="absolute z-10" style={{ left: showAssignVariablePopup.x, top: showAssignVariablePopup.y, diff --git a/web/app/components/workflow/nodes/_base/components/add-variable-popup.tsx b/web/app/components/workflow/nodes/_base/components/add-variable-popup.tsx index 663ed074e8..92176cad8a 100644 --- a/web/app/components/workflow/nodes/_base/components/add-variable-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/add-variable-popup.tsx @@ -1,11 +1,11 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' -import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import type { NodeOutPutVar, ValueSelector, Var, } from '@/app/components/workflow/types' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' export type AddVariablePopupProps = { availableVars: NodeOutPutVar[] @@ -18,11 +18,11 @@ export const AddVariablePopup = ({ const { t } = useTranslation() return ( - <div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg'> - <div className='flex h-[34px] items-center border-b-[0.5px] border-b-divider-regular px-4 text-[13px] font-semibold text-text-secondary'> + <div className="w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg"> + <div className="flex h-[34px] items-center border-b-[0.5px] border-b-divider-regular px-4 text-[13px] font-semibold text-text-secondary"> {t('workflow.nodes.variableAssigner.setAssignVariable')} </div> - <div className='p-1'> + <div className="p-1"> <VarReferenceVars hideSearch vars={availableVars} diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 96ae7e03e1..188d37bbff 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -1,59 +1,62 @@ -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import type { ReactNode } from 'react' -import { memo, useEffect, useMemo, useRef, useState } from 'react' -import type { Strategy } from './agent-strategy' -import { cn } from '@/utils/classnames' -import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react' -import Tooltip from '@/app/components/base/tooltip' -import Link from 'next/link' -import { InstallPluginButton } from './install-plugin-button' -import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select' -import SearchInput from '@/app/components/base/search-input' -import Tools from '../../../block-selector/tools' -import { useTranslation } from 'react-i18next' -import { useStrategyProviders } from '@/service/use-strategy' -import { PluginCategoryEnum, type StrategyPluginDetail } from '@/app/components/plugins/types' import type { ToolWithProvider } from '../../../types' -import { CollectionType } from '@/app/components/tools/types' -import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' -import { useStrategyInfo } from '../../agent/use-config' -import { SwitchPluginVersion } from './switch-plugin-version' -import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' -import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list' -import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' +import type { Strategy } from './agent-strategy' +import type { StrategyPluginDetail } from '@/app/components/plugins/types' +import type { ListProps, ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react' +import Link from 'next/link' +import { memo, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +import SearchInput from '@/app/components/base/search-input' +import Tooltip from '@/app/components/base/tooltip' import { ToolTipContent } from '@/app/components/base/tooltip/content' +import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' +import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks' +import { PluginCategoryEnum } from '@/app/components/plugins/types' +import { CollectionType } from '@/app/components/tools/types' +import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' import { useGlobalPublicStore } from '@/context/global-public-context' +import { useStrategyProviders } from '@/service/use-strategy' +import { cn } from '@/utils/classnames' +import Tools from '../../../block-selector/tools' +import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select' +import { useStrategyInfo } from '../../agent/use-config' +import { InstallPluginButton } from './install-plugin-button' +import { SwitchPluginVersion } from './switch-plugin-version' const DEFAULT_TAGS: ListProps['tags'] = [] const NotFoundWarn = (props: { - title: ReactNode, + title: ReactNode description: ReactNode }) => { const { title, description } = props const { t } = useTranslation() - return <Tooltip - popupContent={ - <div className='space-y-1 text-xs'> - <h3 className='font-semibold text-text-primary'> - {title} - </h3> - <p className='tracking-tight text-text-secondary'> - {description} - </p> - <p> - <Link href={'/plugins'} className='tracking-tight text-text-accent'> - {t('workflow.nodes.agent.linkToPlugin')} - </Link> - </p> + return ( + <Tooltip + popupContent={( + <div className="space-y-1 text-xs"> + <h3 className="font-semibold text-text-primary"> + {title} + </h3> + <p className="tracking-tight text-text-secondary"> + {description} + </p> + <p> + <Link href="/plugins" className="tracking-tight text-text-accent"> + {t('workflow.nodes.agent.linkToPlugin')} + </Link> + </p> + </div> + )} + > + <div> + <RiErrorWarningFill className="size-4 text-text-destructive" /> </div> - } - > - <div> - <RiErrorWarningFill className='size-4 text-text-destructive' /> - </div> - </Tooltip> + </Tooltip> + ) } function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => string): ToolWithProvider[] { @@ -87,9 +90,9 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s } export type AgentStrategySelectorProps = { - value?: Strategy, - onChange: (value?: Strategy) => void, - canChooseMCPTool: boolean, + value?: Strategy + onChange: (value?: Strategy) => void + canChooseMCPTool: boolean } export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { @@ -103,7 +106,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => const { getIconUrl } = useGetIcon() const list = stra.data ? formatStrategy(stra.data, getIconUrl) : undefined const filteredTools = useMemo(() => { - if (!list) return [] + if (!list) + return [] return list.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase())) }, [query, list]) const { strategyStatus, refetch: refetchStrategyInfo } = useStrategyInfo( @@ -136,7 +140,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => } = useMarketplacePlugins() useEffect(() => { - if (!enable_marketplace) return + if (!enable_marketplace) + return if (query) { fetchPlugins({ query, @@ -147,97 +152,115 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => const pluginRef = useRef<ListRef>(null) - return <PortalToFollowElem open={open} onOpenChange={setOpen} placement='bottom'> - <PortalToFollowElemTrigger className='w-full'> - <div - className='flex h-8 w-full select-none items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 hover:bg-state-base-hover-alt' - onClick={() => setOpen(o => !o)} - > - { } - {icon && <div className='flex h-6 w-6 items-center justify-center'><img - src={icon} - width={20} - height={20} - className='rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge' - alt='icon' - /></div>} - <p - className={cn(value ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', 'px-1 text-xs')} + return ( + <PortalToFollowElem open={open} onOpenChange={setOpen} placement="bottom"> + <PortalToFollowElemTrigger className="w-full"> + <div + className="flex h-8 w-full select-none items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 hover:bg-state-base-hover-alt" + onClick={() => setOpen(o => !o)} > - {value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')} - </p> - <div className='ml-auto flex items-center gap-1'> - {showInstallButton && value && <InstallPluginButton - onClick={e => e.stopPropagation()} - size={'small'} - uniqueIdentifier={value.plugin_unique_identifier} - />} - {showPluginNotInstalledWarn - ? <NotFoundWarn - title={t('workflow.nodes.agent.pluginNotInstalled')} - description={t('workflow.nodes.agent.pluginNotInstalledDesc')} - /> - : showUnsupportedStrategy - ? <NotFoundWarn - title={t('workflow.nodes.agent.unsupportedStrategy')} - description={t('workflow.nodes.agent.strategyNotFoundDesc')} + { } + {icon && ( + <div className="flex h-6 w-6 items-center justify-center"> + <img + src={icon} + width={20} + height={20} + className="rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge" + alt="icon" /> - : <RiArrowDownSLine className='size-4 text-text-tertiary' /> - } - {showSwitchVersion && <SwitchPluginVersion - uniqueIdentifier={value.plugin_unique_identifier} - tooltip={<ToolTipContent - title={t('workflow.nodes.agent.unsupportedStrategy')}> - {t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')} - </ToolTipContent>} - onChange={() => { - refetchStrategyInfo() - }} - />} + </div> + )} + <p + className={cn(value ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', 'px-1 text-xs')} + > + {value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')} + </p> + <div className="ml-auto flex items-center gap-1"> + {showInstallButton && value && ( + <InstallPluginButton + onClick={e => e.stopPropagation()} + size="small" + uniqueIdentifier={value.plugin_unique_identifier} + /> + )} + {showPluginNotInstalledWarn + ? ( + <NotFoundWarn + title={t('workflow.nodes.agent.pluginNotInstalled')} + description={t('workflow.nodes.agent.pluginNotInstalledDesc')} + /> + ) + : showUnsupportedStrategy + ? ( + <NotFoundWarn + title={t('workflow.nodes.agent.unsupportedStrategy')} + description={t('workflow.nodes.agent.strategyNotFoundDesc')} + /> + ) + : <RiArrowDownSLine className="size-4 text-text-tertiary" />} + {showSwitchVersion && ( + <SwitchPluginVersion + uniqueIdentifier={value.plugin_unique_identifier} + tooltip={( + <ToolTipContent + title={t('workflow.nodes.agent.unsupportedStrategy')} + > + {t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')} + </ToolTipContent> + )} + onChange={() => { + refetchStrategyInfo() + }} + /> + )} + </div> </div> - </div> - </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[388px] overflow-hidden rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow'> - <header className='flex gap-1 p-2'> - <SearchInput placeholder={t('workflow.nodes.agent.strategy.searchPlaceholder')} value={query} onChange={setQuery} className={'w-full'} /> - <ViewTypeSelect viewType={viewType} onChange={setViewType} /> - </header> - <main className="relative flex w-full flex-col overflow-hidden md:max-h-[300px] xl:max-h-[400px] 2xl:max-h-[564px]" ref={wrapElemRef}> - <Tools - tools={filteredTools} - viewType={viewType} - onSelect={(_, tool) => { - onChange({ - agent_strategy_name: tool!.tool_name, - agent_strategy_provider_name: tool!.provider_name, - agent_strategy_label: tool!.tool_label, - agent_output_schema: tool!.output_schema || {}, - plugin_unique_identifier: tool!.provider_id, - meta: tool!.meta, - }) - setOpen(false) - }} - className='h-full max-h-full max-w-none overflow-y-auto' - indexBarClassName='top-0 xl:top-36' - hasSearchText={false} - canNotSelectMultiple - canChooseMCPTool={canChooseMCPTool} - isAgent - /> - {enable_marketplace && <PluginList - ref={pluginRef} - wrapElemRef={wrapElemRef} - list={notInstalledPlugins} - searchText={query} - tags={DEFAULT_TAGS} - category={PluginCategoryEnum.agent} - disableMaxWidth - />} - </main> - </div> - </PortalToFollowElemContent> - </PortalToFollowElem> + </PortalToFollowElemTrigger> + <PortalToFollowElemContent className="z-10"> + <div className="w-[388px] overflow-hidden rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow"> + <header className="flex gap-1 p-2"> + <SearchInput placeholder={t('workflow.nodes.agent.strategy.searchPlaceholder')} value={query} onChange={setQuery} className="w-full" /> + <ViewTypeSelect viewType={viewType} onChange={setViewType} /> + </header> + <main className="relative flex w-full flex-col overflow-hidden md:max-h-[300px] xl:max-h-[400px] 2xl:max-h-[564px]" ref={wrapElemRef}> + <Tools + tools={filteredTools} + viewType={viewType} + onSelect={(_, tool) => { + onChange({ + agent_strategy_name: tool!.tool_name, + agent_strategy_provider_name: tool!.provider_name, + agent_strategy_label: tool!.tool_label, + agent_output_schema: tool!.output_schema || {}, + plugin_unique_identifier: tool!.provider_id, + meta: tool!.meta, + }) + setOpen(false) + }} + className="h-full max-h-full max-w-none overflow-y-auto" + indexBarClassName="top-0 xl:top-36" + hasSearchText={false} + canNotSelectMultiple + canChooseMCPTool={canChooseMCPTool} + isAgent + /> + {enable_marketplace && ( + <PluginList + ref={pluginRef} + wrapElemRef={wrapElemRef} + list={notInstalledPlugins} + searchText={query} + tags={DEFAULT_TAGS} + category={PluginCategoryEnum.agent} + disableMaxWidth + /> + )} + </main> + </div> + </PortalToFollowElemContent> + </PortalToFollowElem> + ) }) AgentStrategySelector.displayName = 'AgentStrategySelector' diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index c207c82037..d7592b3c6f 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -1,28 +1,29 @@ -import type { CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { type CredentialFormSchema, FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { ToolVarInputs } from '../../tool/types' -import ListEmpty from '@/app/components/base/list-empty' -import { AgentStrategySelector } from './agent-strategy-selector' -import Link from 'next/link' -import { useTranslation } from 'react-i18next' -import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' -import { Agent } from '@/app/components/base/icons/src/vender/workflow' -import { InputNumber } from '@/app/components/base/input-number' -import Slider from '@/app/components/base/slider' -import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' -import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' -import Field from './field' -import { type ComponentProps, memo } from 'react' -import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' -import Editor from './prompt/editor' -import { useWorkflowStore } from '../../../store' -import { useRenderI18nObject } from '@/hooks/use-i18n' -import type { NodeOutPutVar } from '../../../types' +import type { ComponentProps } from 'react' import type { Node } from 'reactflow' +import type { NodeOutPutVar } from '../../../types' +import type { ToolVarInputs } from '../../tool/types' +import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { PluginMeta } from '@/app/components/plugins/types' import { noop } from 'lodash-es' +import Link from 'next/link' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import { Agent } from '@/app/components/base/icons/src/vender/workflow' +import { InputNumber } from '@/app/components/base/input-number' +import ListEmpty from '@/app/components/base/list-empty' +import Slider from '@/app/components/base/slider' +import { FormTypeEnum, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' +import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' +import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import { useDocLink } from '@/context/i18n' +import { useRenderI18nObject } from '@/hooks/use-i18n' import { AppModeEnum } from '@/types/app' +import { useWorkflowStore } from '../../../store' +import { AgentStrategySelector } from './agent-strategy-selector' +import Field from './field' +import Editor from './prompt/editor' export type Strategy = { agent_strategy_provider_name: string @@ -39,8 +40,8 @@ export type AgentStrategyProps = { formSchema: CredentialFormSchema[] formValue: ToolVarInputs onFormValueChange: (value: ToolVarInputs) => void - nodeOutputVars?: NodeOutPutVar[], - availableNodes?: Node[], + nodeOutputVars?: NodeOutPutVar[] + availableNodes?: Node[] nodeId?: string canChooseMCPTool: boolean } @@ -78,38 +79,41 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { onChange(value) setControlPromptEditorRerenderKey(Math.random()) } - return <Editor - value={value} - onChange={onChange} - onGenerated={handleGenerated} - instanceId={instanceId} - key={instanceId} - title={renderI18nObject(schema.label)} - headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase' - containerBackgroundClassName='bg-transparent' - gradientBorder={false} - nodeId={nodeId} - isSupportPromptGenerator={!!def.auto_generate?.type} - titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)} - editorContainerClassName='px-0 bg-components-input-bg-normal focus-within:bg-components-input-bg-active rounded-lg' - availableNodes={availableNodes} - nodesOutputVars={nodeOutputVars} - isSupportJinja={def.template?.enabled} - required={def.required} - varList={[]} - modelConfig={ - defaultModel.data - ? { - mode: AppModeEnum.CHAT, - name: defaultModel.data.model, - provider: defaultModel.data.provider.provider, - completion_params: {}, - } : undefined - } - placeholderClassName='px-2 py-1' - titleClassName='system-sm-semibold-uppercase text-text-secondary text-[13px]' - inputClassName='px-2 py-1' - /> + return ( + <Editor + value={value} + onChange={onChange} + onGenerated={handleGenerated} + instanceId={instanceId} + key={instanceId} + title={renderI18nObject(schema.label)} + headerClassName="bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase" + containerBackgroundClassName="bg-transparent" + gradientBorder={false} + nodeId={nodeId} + isSupportPromptGenerator={!!def.auto_generate?.type} + titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)} + editorContainerClassName="px-0 bg-components-input-bg-normal focus-within:bg-components-input-bg-active rounded-lg" + availableNodes={availableNodes} + nodesOutputVars={nodeOutputVars} + isSupportJinja={def.template?.enabled} + required={def.required} + varList={[]} + modelConfig={ + defaultModel.data + ? { + mode: AppModeEnum.CHAT, + name: defaultModel.data.model, + provider: defaultModel.data.provider.provider, + completion_params: {}, + } + : undefined + } + placeholderClassName="px-2 py-1" + titleClassName="system-sm-semibold-uppercase text-text-secondary text-[13px]" + inputClassName="px-2 py-1" + /> + ) } case FormTypeEnum.textNumber: { const def = schema as CredentialFormSchemaNumberInput @@ -121,34 +125,40 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { const onChange = (value: number) => { props.onChange({ ...props.value, [schema.variable]: value }) } - return <Field - title={<> - {renderI18nObject(def.label)} {def.required && <span className='text-red-500'>*</span>} - </>} - key={def.variable} - tooltip={def.tooltip && renderI18nObject(def.tooltip)} - inline - > - <div className='flex w-[200px] items-center gap-3'> - <Slider - value={value} - onChange={onChange} - className='w-full' - min={def.min} - max={def.max} - /> - <InputNumber - value={value} - // TODO: maybe empty, handle this - onChange={onChange as any} - defaultValue={defaultValue} - size='regular' - min={def.min} - max={def.max} - className='w-12' - /> - </div> - </Field> + return ( + <Field + title={( + <> + {renderI18nObject(def.label)} + {' '} + {def.required && <span className="text-red-500">*</span>} + </> + )} + key={def.variable} + tooltip={def.tooltip && renderI18nObject(def.tooltip)} + inline + > + <div className="flex w-[200px] items-center gap-3"> + <Slider + value={value} + onChange={onChange} + className="w-full" + min={def.min} + max={def.max} + /> + <InputNumber + value={value} + // TODO: maybe empty, handle this + onChange={onChange as any} + defaultValue={defaultValue} + size="regular" + min={def.min} + max={def.max} + className="w-12" + /> + </div> + </Field> + ) } } }, @@ -162,9 +172,13 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { } return ( <Field - title={<> - {renderI18nObject(schema.label)} {schema.required && <span className='text-red-500'>*</span>} - </>} + title={( + <> + {renderI18nObject(schema.label)} + {' '} + {schema.required && <span className="text-red-500">*</span>} + </> + )} tooltip={schema.tooltip && renderI18nObject(schema.tooltip)} > <ToolSelector @@ -204,46 +218,59 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { } } } - return <div className='space-y-2'> - <AgentStrategySelector value={strategy} onChange={onStrategyChange} canChooseMCPTool={canChooseMCPTool} /> - { - strategy - ? <div> - <Form<CustomField> - formSchemas={[ - ...formSchema, - ]} - value={formValue} - onChange={onFormValueChange} - validating={false} - showOnVariableMap={{}} - isEditMode={true} - isAgentStrategy={true} - fieldLabelClassName='uppercase' - customRenderField={renderField} - override={override} - nodeId={nodeId} - nodeOutputVars={nodeOutputVars || []} - availableNodes={availableNodes || []} - canChooseMCPTool={canChooseMCPTool} - /> - </div> - : <ListEmpty - icon={<Agent className='h-5 w-5 shrink-0 text-text-accent' />} - title={t('workflow.nodes.agent.strategy.configureTip')} - description={<div className='text-xs text-text-tertiary'> - {t('workflow.nodes.agent.strategy.configureTipDesc')} <br /> - <Link href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', { - 'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略', - 'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択', - })} - className='text-text-accent-secondary' target='_blank'> - {t('workflow.nodes.agent.learnMore')} - </Link> - </div>} - /> - } - </div> + return ( + <div className="space-y-2"> + <AgentStrategySelector value={strategy} onChange={onStrategyChange} canChooseMCPTool={canChooseMCPTool} /> + { + strategy + ? ( + <div> + <Form<CustomField> + formSchemas={[ + ...formSchema, + ]} + value={formValue} + onChange={onFormValueChange} + validating={false} + showOnVariableMap={{}} + isEditMode={true} + isAgentStrategy={true} + fieldLabelClassName="uppercase" + customRenderField={renderField} + override={override} + nodeId={nodeId} + nodeOutputVars={nodeOutputVars || []} + availableNodes={availableNodes || []} + canChooseMCPTool={canChooseMCPTool} + /> + </div> + ) + : ( + <ListEmpty + icon={<Agent className="h-5 w-5 shrink-0 text-text-accent" />} + title={t('workflow.nodes.agent.strategy.configureTip')} + description={( + <div className="text-xs text-text-tertiary"> + {t('workflow.nodes.agent.strategy.configureTipDesc')} + {' '} + <br /> + <Link + href={docLink('/guides/workflow/node/agent#select-an-agent-strategy', { + 'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略', + 'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択', + })} + className="text-text-accent-secondary" + target="_blank" + > + {t('workflow.nodes.agent.learnMore')} + </Link> + </div> + )} + /> + ) + } + </div> + ) }) AgentStrategy.displayName = 'AgentStrategy' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx index 73219a551b..db32627dc2 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx @@ -1,8 +1,8 @@ 'use client' -import Checkbox from '@/app/components/base/checkbox' import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import Checkbox from '@/app/components/base/checkbox' type Props = { name: string @@ -22,15 +22,15 @@ const BoolInput: FC<Props> = ({ onChange(!value) }, [value, onChange]) return ( - <div className='flex h-6 items-center gap-2'> + <div className="flex h-6 items-center gap-2"> <Checkbox - className='!h-4 !w-4' + className="!h-4 !w-4" checked={!!value} onCheck={handleChange} /> - <div className='system-sm-medium flex items-center gap-1 text-text-secondary'> + <div className="system-sm-medium flex items-center gap-1 text-text-secondary"> {name} - {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} + {!required && <span className="system-xs-regular text-text-tertiary">{t('workflow.panel.optional')}</span>} </div> </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index 440cb1e338..c33deae438 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -1,31 +1,31 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { produce } from 'immer' +import type { InputVar } from '../../../../types' +import type { FileEntity } from '@/app/components/base/file-uploader/types' import { RiDeleteBinLine, } from '@remixicon/react' -import type { InputVar } from '../../../../types' -import { BlockEnum, InputVarType, SupportUploadFileTypes } from '../../../../types' -import CodeEditor from '../editor/code-editor' -import { CodeLanguage } from '../../../code/types' -import TextEditor from '../editor/text-editor' -import Select from '@/app/components/base/select' -import Input from '@/app/components/base/input' -import Textarea from '@/app/components/base/textarea' -import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader' +import { produce } from 'immer' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' -import { Resolution, TransferMethod } from '@/types/app' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader' +import Input from '@/app/components/base/input' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import { cn } from '@/utils/classnames' -import type { FileEntity } from '@/app/components/base/file-uploader/types' -import BoolInput from './bool-input' +import Select from '@/app/components/base/select' +import Textarea from '@/app/components/base/textarea' +import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { useHooksStore } from '@/app/components/workflow/hooks-store' +import { Resolution, TransferMethod } from '@/types/app' +import { cn } from '@/utils/classnames' +import { BlockEnum, InputVarType, SupportUploadFileTypes } from '../../../../types' +import { CodeLanguage } from '../../../code/types' +import CodeEditor from '../editor/code-editor' +import TextEditor from '../editor/text-editor' +import BoolInput from './bool-input' type Props = { payload: InputVar @@ -69,22 +69,22 @@ const FormItem: FC<Props> = ({ if (typeof payload.label === 'object') { const { nodeType, nodeName, variable, isChatVar } = payload.label return ( - <div className='flex h-full items-center'> + <div className="flex h-full items-center"> {!isChatVar && ( - <div className='flex items-center'> - <div className='p-[1px]'> + <div className="flex items-center"> + <div className="p-[1px]"> <VarBlockIcon type={nodeType || BlockEnum.Start} /> </div> - <div className='mx-0.5 max-w-[150px] truncate text-xs font-medium text-gray-700' title={nodeName}> + <div className="mx-0.5 max-w-[150px] truncate text-xs font-medium text-gray-700" title={nodeName}> {nodeName} </div> - <Line3 className='mr-0.5'></Line3> + <Line3 className="mr-0.5"></Line3> </div> )} - <div className='flex items-center text-primary-600'> - {!isChatVar && <Variable02 className='h-3.5 w-3.5' />} - {isChatVar && <BubbleX className='h-3.5 w-3.5 text-util-colors-teal-teal-700' />} - <div className={cn('ml-0.5 max-w-[150px] truncate text-xs font-medium', isChatVar && 'text-text-secondary')} title={variable} > + <div className="flex items-center text-primary-600"> + {!isChatVar && <Variable02 className="h-3.5 w-3.5" />} + {isChatVar && <BubbleX className="h-3.5 w-3.5 text-util-colors-teal-teal-700" />} + <div className={cn('ml-0.5 max-w-[150px] truncate text-xs font-medium', isChatVar && 'text-text-secondary')} title={variable}> {variable} </div> </div> @@ -117,24 +117,26 @@ const FormItem: FC<Props> = ({ return ( <div className={cn(className)}> {!isArrayLikeType && !isBooleanType && ( - <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> - <div className='truncate'> + <div className="system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary"> + <div className="truncate"> {typeof payload.label === 'object' ? nodeKey : payload.label} </div> - {payload.hide === true ? ( - <span className='system-xs-regular text-text-tertiary'> - {t('workflow.panel.optional_and_hidden')} - </span> - ) : ( - !payload.required && ( - <span className='system-xs-regular text-text-tertiary'> - {t('workflow.panel.optional')} - </span> - ) - )} + {payload.hide === true + ? ( + <span className="system-xs-regular text-text-tertiary"> + {t('workflow.panel.optional_and_hidden')} + </span> + ) + : ( + !payload.required && ( + <span className="system-xs-regular text-text-tertiary"> + {t('workflow.panel.optional')} + </span> + ) + )} </div> )} - <div className='grow'> + <div className="grow"> { type === InputVarType.textInput && ( <Input @@ -206,9 +208,9 @@ const FormItem: FC<Props> = ({ language={CodeLanguage.json} onChange={onChange} noWrapper - className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1' + className="bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1" placeholder={ - <div className='whitespace-pre'>{payload.json_schema}</div> + <div className="whitespace-pre">{payload.json_schema}</div> } /> )} @@ -219,19 +221,19 @@ const FormItem: FC<Props> = ({ fileConfig={{ allowed_file_types: inStepRun && (!payload.allowed_file_types || payload.allowed_file_types.length === 0) ? [ - SupportUploadFileTypes.image, - SupportUploadFileTypes.document, - SupportUploadFileTypes.audio, - SupportUploadFileTypes.video, - ] + SupportUploadFileTypes.image, + SupportUploadFileTypes.document, + SupportUploadFileTypes.audio, + SupportUploadFileTypes.video, + ] : payload.allowed_file_types, allowed_file_extensions: inStepRun && (!payload.allowed_file_extensions || payload.allowed_file_extensions.length === 0) ? [ - ...FILE_EXTS[SupportUploadFileTypes.image], - ...FILE_EXTS[SupportUploadFileTypes.document], - ...FILE_EXTS[SupportUploadFileTypes.audio], - ...FILE_EXTS[SupportUploadFileTypes.video], - ] + ...FILE_EXTS[SupportUploadFileTypes.image], + ...FILE_EXTS[SupportUploadFileTypes.document], + ...FILE_EXTS[SupportUploadFileTypes.audio], + ...FILE_EXTS[SupportUploadFileTypes.video], + ] : payload.allowed_file_extensions, allowed_file_upload_methods: inStepRun ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, number_limits: 1, @@ -246,19 +248,19 @@ const FormItem: FC<Props> = ({ fileConfig={{ allowed_file_types: (inStepRun || isIteratorItemFile) && (!payload.allowed_file_types || payload.allowed_file_types.length === 0) ? [ - SupportUploadFileTypes.image, - SupportUploadFileTypes.document, - SupportUploadFileTypes.audio, - SupportUploadFileTypes.video, - ] + SupportUploadFileTypes.image, + SupportUploadFileTypes.document, + SupportUploadFileTypes.audio, + SupportUploadFileTypes.video, + ] : payload.allowed_file_types, allowed_file_extensions: (inStepRun || isIteratorItemFile) && (!payload.allowed_file_extensions || payload.allowed_file_extensions.length === 0) ? [ - ...FILE_EXTS[SupportUploadFileTypes.image], - ...FILE_EXTS[SupportUploadFileTypes.document], - ...FILE_EXTS[SupportUploadFileTypes.audio], - ...FILE_EXTS[SupportUploadFileTypes.video], - ] + ...FILE_EXTS[SupportUploadFileTypes.image], + ...FILE_EXTS[SupportUploadFileTypes.document], + ...FILE_EXTS[SupportUploadFileTypes.audio], + ...FILE_EXTS[SupportUploadFileTypes.video], + ] : payload.allowed_file_extensions, allowed_file_upload_methods: (inStepRun || isIteratorItemFile) ? [TransferMethod.local_file, TransferMethod.remote_url] : payload.allowed_file_upload_methods, number_limits: (inStepRun || isIteratorItemFile) ? 5 : payload.max_length, @@ -286,7 +288,7 @@ const FormItem: FC<Props> = ({ { isContext && ( - <div className='space-y-2'> + <div className="space-y-2"> {(value || []).map((item: any, index: number) => ( <CodeEditor key={index} @@ -294,10 +296,12 @@ const FormItem: FC<Props> = ({ title={<span>JSON</span>} headerRight={ (value as any).length > 1 - ? (<RiDeleteBinLine - onClick={handleArrayItemRemove(index)} - className='mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary' - />) + ? ( + <RiDeleteBinLine + onClick={handleArrayItemRemove(index)} + className="mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary" + /> + ) : undefined } language={CodeLanguage.json} @@ -310,20 +314,29 @@ const FormItem: FC<Props> = ({ { (isIterator && !isIteratorItemFile) && ( - <div className='space-y-2'> + <div className="space-y-2"> {(value || []).map((item: any, index: number) => ( <TextEditor key={index} isInNode value={item} - title={<span>{t('appDebug.variableConfig.content')} {index + 1} </span>} + title={( + <span> + {t('appDebug.variableConfig.content')} + {' '} + {index + 1} + {' '} + </span> + )} onChange={handleArrayItemChange(index)} headerRight={ (value as any).length > 1 - ? (<RiDeleteBinLine - onClick={handleArrayItemRemove(index)} - className='mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary' - />) + ? ( + <RiDeleteBinLine + onClick={handleArrayItemRemove(index)} + className="mr-1 h-3.5 w-3.5 cursor-pointer text-text-tertiary" + /> + ) : undefined } /> diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx index 69873d8be2..e45f001924 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' -import { produce } from 'immer' import type { InputVar } from '../../../../types' -import FormItem from './form-item' -import { cn } from '@/utils/classnames' -import { InputVarType } from '@/app/components/workflow/types' +import { produce } from 'immer' +import React, { useCallback, useEffect, useMemo, useRef } from 'react' import AddButton from '@/app/components/base/button/add-button' import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants' +import { InputVarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import FormItem from './form-item' export type Props = { className?: string @@ -77,8 +77,8 @@ const Form: FC<Props> = ({ return ( <div className={cn(className, 'space-y-2')}> {label && ( - <div className='mb-1 flex items-center justify-between'> - <div className='system-xs-medium-uppercase flex h-6 items-center text-text-tertiary'>{label}</div> + <div className="mb-1 flex items-center justify-between"> + <div className="system-xs-medium-uppercase flex h-6 items-center text-text-tertiary">{label}</div> {isArrayLikeType && !isIteratorItemFile && ( <AddButton onClick={handleAddContext} /> )} diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index abde5bb7e8..9957d79cf6 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -1,20 +1,21 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useRef } from 'react' -import { useTranslation } from 'react-i18next' import type { Props as FormProps } from './form' -import Form from './form' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import { InputVarType } from '@/app/components/workflow/types' -import Toast from '@/app/components/base/toast' -import { TransferMethod } from '@/types/app' -import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' -import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types' import type { Emoji } from '@/app/components/tools/types' import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' +import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types' +import React, { useEffect, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' +import Toast from '@/app/components/base/toast' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import { InputVarType } from '@/app/components/workflow/types' +import { TransferMethod } from '@/types/app' +import { cn } from '@/utils/classnames' +import Form from './form' import PanelWrap from './panel-wrap' + const i18nPrefix = 'workflow.singleRun' export type BeforeRunFormProps = { @@ -32,9 +33,9 @@ export type BeforeRunFormProps = { } & Partial<SpecialResultPanelProps> function formatValue(value: string | any, type: InputVarType) { - if(type === InputVarType.checkbox) + if (type === InputVarType.checkbox) return !!value - if(value === undefined || value === null) + if (value === undefined || value === null) return value if (type === InputVarType.number) return Number.parseFloat(value) @@ -138,14 +139,14 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ const hasRun = useRef(false) useEffect(() => { // React 18 run twice in dev mode - if(hasRun.current) + if (hasRun.current) return hasRun.current = true - if(filteredExistVarForms.length === 0) + if (filteredExistVarForms.length === 0) onRun({}) }, [filteredExistVarForms, onRun]) - if(filteredExistVarForms.length === 0) + if (filteredExistVarForms.length === 0) return null return ( @@ -153,8 +154,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ nodeName={nodeName} onHide={onHide} > - <div className='h-0 grow overflow-y-auto pb-4'> - <div className='mt-3 space-y-4 px-4'> + <div className="h-0 grow overflow-y-auto pb-4"> + <div className="mt-3 space-y-4 px-4"> {filteredExistVarForms.map((form, index) => ( <div key={index}> <Form @@ -166,8 +167,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ </div> ))} </div> - <div className='mt-4 flex justify-between space-x-2 px-4' > - <Button disabled={!isFileLoaded} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}> + <div className="mt-4 flex justify-between space-x-2 px-4"> + <Button disabled={!isFileLoaded} variant="primary" className="w-0 grow space-x-2" onClick={handleRun}> <div>{t(`${i18nPrefix}.startRun`)}</div> </Button> </div> diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx index 7312adf6c6..61e614bb9e 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' import { RiCloseLine, } from '@remixicon/react' +import React from 'react' +import { useTranslation } from 'react-i18next' const i18nPrefix = 'workflow.singleRun' @@ -21,16 +21,21 @@ const PanelWrap: FC<Props> = ({ }) => { const { t } = useTranslation() return ( - <div className='absolute inset-0 z-10 rounded-2xl bg-background-overlay-alt'> - <div className='flex h-full flex-col rounded-2xl bg-components-panel-bg'> - <div className='flex h-8 shrink-0 items-center justify-between pl-4 pr-3 pt-3'> - <div className='truncate text-base font-semibold text-text-primary'> - {t(`${i18nPrefix}.testRun`)} {nodeName} + <div className="absolute inset-0 z-10 rounded-2xl bg-background-overlay-alt"> + <div className="flex h-full flex-col rounded-2xl bg-components-panel-bg"> + <div className="flex h-8 shrink-0 items-center justify-between pl-4 pr-3 pt-3"> + <div className="truncate text-base font-semibold text-text-primary"> + {t(`${i18nPrefix}.testRun`)} + {' '} + {nodeName} </div> - <div className='ml-2 shrink-0 cursor-pointer p-1' onClick={() => { - onHide() - }}> - <RiCloseLine className='h-4 w-4 text-text-tertiary ' /> + <div + className="ml-2 shrink-0 cursor-pointer p-1" + onClick={() => { + onHide() + }} + > + <RiCloseLine className="h-4 w-4 text-text-tertiary " /> </div> </div> {children} diff --git a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx index 58dec6baba..6888bff96c 100644 --- a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { useBoolean } from 'ahooks' -import { cn } from '@/utils/classnames' import type { CodeLanguage } from '../../code/types' -import { Generator } from '@/app/components/base/icons/src/vender/other' -import { ActionButton } from '@/app/components/base/action-button' -import { AppModeEnum } from '@/types/app' import type { GenRes } from '@/service/debug' +import { useBoolean } from 'ahooks' +import React, { useCallback } from 'react' import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res' +import { ActionButton } from '@/app/components/base/action-button' +import { Generator } from '@/app/components/base/icons/src/vender/other' +import { AppModeEnum } from '@/types/app' +import { cn } from '@/utils/classnames' import { useHooksStore } from '../../../hooks-store' type Props = { @@ -36,9 +36,10 @@ const CodeGenerateBtn: FC<Props> = ({ return ( <div className={cn(className)}> <ActionButton - className='hover:bg-[#155EFF]/8' - onClick={showAutomaticTrue}> - <Generator className='h-4 w-4 text-primary-600' /> + className="hover:bg-[#155EFF]/8" + onClick={showAutomaticTrue} + > + <Generator className="h-4 w-4 text-primary-600" /> </ActionButton> {showAutomatic && ( <GetCodeGeneratorResModal diff --git a/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx b/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx index 2390dfd74e..777bd82035 100644 --- a/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx +++ b/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx @@ -16,16 +16,16 @@ const FieldCollapse = ({ operations, }: FieldCollapseProps) => { return ( - <div className='py-4'> + <div className="py-4"> <Collapse trigger={ - <div className='system-sm-semibold-uppercase flex h-6 cursor-pointer items-center text-text-secondary'>{title}</div> + <div className="system-sm-semibold-uppercase flex h-6 cursor-pointer items-center text-text-secondary">{title}</div> } operations={operations} collapsed={collapsed} onCollapse={onCollapse} > - <div className='px-4'> + <div className="px-4"> {children} </div> </Collapse> diff --git a/web/app/components/workflow/nodes/_base/components/collapse/index.tsx b/web/app/components/workflow/nodes/_base/components/collapse/index.tsx index f7cf95ce7e..6aeff0242f 100644 --- a/web/app/components/workflow/nodes/_base/components/collapse/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/collapse/index.tsx @@ -40,9 +40,9 @@ const Collapse = ({ }, [collapsedMerged, disabled]) return ( <> - <div className='group/collapse flex items-center'> + <div className="group/collapse flex items-center"> <div - className='ml-4 flex grow items-center' + className="ml-4 flex grow items-center" onClick={() => { if (!disabled) { setCollapsedLocal(!collapsedMerged) @@ -52,7 +52,7 @@ const Collapse = ({ > {typeof trigger === 'function' ? trigger(collapseIcon) : trigger} {!hideCollapseIcon && ( - <div className='h-4 w-4 shrink-0'> + <div className="h-4 w-4 shrink-0"> {collapseIcon} </div> )} diff --git a/web/app/components/workflow/nodes/_base/components/config-vision.tsx b/web/app/components/workflow/nodes/_base/components/config-vision.tsx index 0132ce147e..3c2cc217a7 100644 --- a/web/app/components/workflow/nodes/_base/components/config-vision.tsx +++ b/web/app/components/workflow/nodes/_base/components/config-vision.tsx @@ -1,15 +1,17 @@ 'use client' import type { FC } from 'react' +import type { ValueSelector, Var, VisionSetting } from '@/app/components/workflow/types' +import { produce } from 'immer' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import VarReferencePicker from './variable/var-reference-picker' -import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker' -import Field from '@/app/components/workflow/nodes/_base/components/field' import Switch from '@/app/components/base/switch' -import { type ValueSelector, type Var, VarType, type VisionSetting } from '@/app/components/workflow/types' -import { Resolution } from '@/types/app' import Tooltip from '@/app/components/base/tooltip' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker' +import { VarType } from '@/app/components/workflow/types' +import { Resolution } from '@/types/app' +import VarReferencePicker from './variable/var-reference-picker' + const i18nPrefix = 'workflow.nodes.llm' type Props = { @@ -57,32 +59,32 @@ const ConfigVision: FC<Props> = ({ <Field title={t(`${i18nPrefix}.vision`)} tooltip={t('appDebug.vision.description')!} - operations={ + operations={( <Tooltip popupContent={t('appDebug.vision.onlySupportVisionModelTip')!} disabled={isVisionModel} > - <Switch disabled={readOnly || !isVisionModel} size='md' defaultValue={!isVisionModel ? false : enabled} onChange={onEnabledChange} /> + <Switch disabled={readOnly || !isVisionModel} size="md" defaultValue={!isVisionModel ? false : enabled} onChange={onEnabledChange} /> </Tooltip> - } + )} > {(enabled && isVisionModel) ? ( - <div> - <VarReferencePicker - className='mb-4' - filterVar={filterVar} - nodeId={nodeId} - value={config.variable_selector || []} - onChange={handleVarSelectorChange} - readonly={readOnly} - /> - <ResolutionPicker - value={config.detail} - onChange={handleVisionResolutionChange} - /> - </div> - ) + <div> + <VarReferencePicker + className="mb-4" + filterVar={filterVar} + nodeId={nodeId} + value={config.variable_selector || []} + onChange={handleVarSelectorChange} + readonly={readOnly} + /> + <ResolutionPicker + value={config.detail} + onChange={handleVisionResolutionChange} + /> + </div> + ) : null} </Field> diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index 0b88f8c67d..95aabc0ec0 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useRef, useState } from 'react' -import copy from 'copy-to-clipboard' -import ToggleExpandBtn from '../toggle-expand-btn' -import CodeGeneratorButton from '../code-generator-button' import type { CodeLanguage } from '../../../code/types' -import Wrap from './wrap' -import { cn } from '@/utils/classnames' +import type { FileEntity } from '@/app/components/base/file-uploader/types' +import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' +import copy from 'copy-to-clipboard' +import React, { useCallback, useRef, useState } from 'react' import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap' +import ActionButton from '@/app/components/base/action-button' +import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log' import { Copy, CopyCheck, } from '@/app/components/base/icons/src/vender/line/files' import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' -import type { FileEntity } from '@/app/components/base/file-uploader/types' -import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log' -import ActionButton from '@/app/components/base/action-button' -import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import CodeGeneratorButton from '../code-generator-button' +import ToggleExpandBtn from '../toggle-expand-btn' +import Wrap from './wrap' type Props = { nodeId?: string @@ -84,15 +84,18 @@ const Base: FC<Props> = ({ return ( <Wrap className={cn(wrapClassName)} style={wrapStyle} isInNode={isInNode} isExpand={isExpand}> <div ref={ref} className={cn(className, isExpand && 'h-full', 'rounded-lg border', !isFocus ? 'border-transparent bg-components-input-bg-normal' : 'overflow-hidden border-components-input-border-hover bg-components-input-bg-hover')}> - <div className='flex h-7 items-center justify-between pl-3 pr-2 pt-1'> - <div className='system-xs-semibold-uppercase text-text-secondary'>{title}</div> - <div className='flex items-center' onClick={(e) => { - e.nativeEvent.stopImmediatePropagation() - e.stopPropagation() - }}> + <div className="flex h-7 items-center justify-between pl-3 pr-2 pt-1"> + <div className="system-xs-semibold-uppercase text-text-secondary">{title}</div> + <div + className="flex items-center" + onClick={(e) => { + e.nativeEvent.stopImmediatePropagation() + e.stopPropagation() + }} + > {headerRight} {showCodeGenerator && codeLanguages && ( - <div className='ml-1'> + <div className="ml-1"> <CodeGeneratorButton onGenerated={onGenerated} codeLanguages={codeLanguages} @@ -101,29 +104,28 @@ const Base: FC<Props> = ({ /> </div> )} - <ActionButton className='ml-1' onClick={handleCopy}> + <ActionButton className="ml-1" onClick={handleCopy}> {!isCopied ? ( - <Copy className='h-4 w-4 cursor-pointer' /> - ) + <Copy className="h-4 w-4 cursor-pointer" /> + ) : ( - <CopyCheck className='h-4 w-4' /> - ) - } + <CopyCheck className="h-4 w-4" /> + )} </ActionButton> - <div className='ml-1'> + <div className="ml-1"> <ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} /> </div> </div> </div> - {tip && <div className='px-1 py-0.5'>{tip}</div>} + {tip && <div className="px-1 py-0.5">{tip}</div>} <PromptEditorHeightResizeWrap height={isExpand ? editorExpandHeight : editorContentHeight} minHeight={editorContentMinHeight} onHeightChange={setEditorContentHeight} hideResize={isExpand} > - <div className='h-full pb-2 pl-2'> + <div className="h-full pb-2 pl-2"> {children} </div> </PromptEditorHeightResizeWrap> diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx index 0c6ad12540..d4d43ae796 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' -import { useBoolean } from 'ahooks' -import { useTranslation } from 'react-i18next' import type { Props as EditorProps } from '.' -import Editor from '.' -import { cn } from '@/utils/classnames' -import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types' +import { useBoolean } from 'ahooks' +import React, { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' +import { cn } from '@/utils/classnames' +import Editor from '.' const TO_WINDOW_OFFSET = 8 @@ -149,7 +149,7 @@ const CodeEditor: FC<Props> = ({ {isShowVarPicker && ( <div ref={popupRef} - className='w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' + className="w-[228px] space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg" style={{ position: 'fixed', top: popupPosition.y, diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index 7ddea94036..b98e9085de 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -1,18 +1,18 @@ 'use client' import type { FC } from 'react' import Editor, { loader } from '@monaco-editor/react' +import { noop } from 'lodash-es' import React, { useEffect, useMemo, useRef, useState } from 'react' -import Base from '../base' -import { cn } from '@/utils/classnames' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { getFilesInLogs, } from '@/app/components/base/file-uploader/utils' -import { Theme } from '@/types/app' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import useTheme from '@/hooks/use-theme' -import './style.css' -import { noop } from 'lodash-es' +import { Theme } from '@/types/app' +import { cn } from '@/utils/classnames' import { basePath } from '@/utils/var' +import Base from '../base' +import './style.css' // load file from local instead of cdn https://github.com/suren-atoyan/monaco-react/issues/482 if (typeof window !== 'undefined') @@ -145,7 +145,7 @@ const CodeEditor: FC<Props> = ({ language={languageMap[language] || 'javascript'} theme={isMounted ? theme : 'default-theme'} // sometimes not load the default theme value={outPutValue} - loading={<span className='text-text-primary'>Loading...</span>} + loading={<span className="text-text-primary">Loading...</span>} onChange={handleEditorChange} // https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorOptions.html options={{ @@ -166,40 +166,45 @@ const CodeEditor: FC<Props> = ({ }} onMount={handleEditorDidMount} /> - {!outPutValue && !isFocus && <div className='pointer-events-none absolute left-[36px] top-0 text-[13px] font-normal leading-[18px] text-gray-300'>{placeholder}</div>} + {!outPutValue && !isFocus && <div className="pointer-events-none absolute left-[36px] top-0 text-[13px] font-normal leading-[18px] text-gray-300">{placeholder}</div>} </> ) return ( <div className={cn(isExpand && 'h-full', className)}> {noWrapper - ? <div className='no-wrapper relative' style={{ - height: isExpand ? '100%' : (editorContentHeight) / 2 + CODE_EDITOR_LINE_HEIGHT, // In IDE, the last line can always be in lop line. So there is some blank space in the bottom. - minHeight: CODE_EDITOR_LINE_HEIGHT, - }}> - {main} - </div> + ? ( + <div + className="no-wrapper relative" + style={{ + height: isExpand ? '100%' : (editorContentHeight) / 2 + CODE_EDITOR_LINE_HEIGHT, // In IDE, the last line can always be in lop line. So there is some blank space in the bottom. + minHeight: CODE_EDITOR_LINE_HEIGHT, + }} + > + {main} + </div> + ) : ( - <Base - nodeId={nodeId} - className='relative' - title={title} - value={outPutValue} - headerRight={headerRight} - isFocus={isFocus && !readOnly} - minHeight={minHeight} - isInNode={isInNode} - onGenerated={onGenerated} - codeLanguages={language} - fileList={fileList as any} - showFileList={showFileList} - showCodeGenerator={showCodeGenerator} - tip={tip} - footer={footer} - > - {main} - </Base> - )} + <Base + nodeId={nodeId} + className="relative" + title={title} + value={outPutValue} + headerRight={headerRight} + isFocus={isFocus && !readOnly} + minHeight={minHeight} + isInNode={isInNode} + onGenerated={onGenerated} + codeLanguages={language} + fileList={fileList as any} + showFileList={showFileList} + showCodeGenerator={showCodeGenerator} + tip={tip} + footer={footer} + > + {main} + </Base> + )} </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx b/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx index 252f69c246..6fa3c2adfd 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' import { useBoolean } from 'ahooks' +import React, { useCallback } from 'react' import Base from './base' type Props = { @@ -52,7 +52,7 @@ const TextEditor: FC<Props> = ({ onChange={e => onChange(e.target.value)} onFocus={setIsFocus} onBlur={handleBlur} - className='h-full w-full resize-none border-none bg-transparent px-3 text-[13px] font-normal leading-[18px] text-gray-900 placeholder:text-gray-300 focus:outline-none' + className="h-full w-full resize-none border-none bg-transparent px-3 text-[13px] font-normal leading-[18px] text-gray-900 placeholder:text-gray-300 focus:outline-none" placeholder={placeholder} readOnly={readonly} /> diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx index f9292be477..32c715a28a 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx @@ -1,10 +1,10 @@ +import type { DefaultValueForm } from './types' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { DefaultValueForm } from './types' import Input from '@/app/components/base/input' -import { VarType } from '@/app/components/workflow/types' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { VarType } from '@/app/components/workflow/types' import { useDocLink } from '@/context/i18n' type DefaultValueProps = { @@ -31,31 +31,31 @@ const DefaultValue = ({ }, [onFormChange]) return ( - <div className='px-4 pt-2'> - <div className='body-xs-regular mb-2 text-text-tertiary'> + <div className="px-4 pt-2"> + <div className="body-xs-regular mb-2 text-text-tertiary"> {t('workflow.nodes.common.errorHandle.defaultValue.desc')}   <a href={docLink('/guides/workflow/error-handling/README', { 'zh-Hans': '/guides/workflow/error-handling/readme', })} - target='_blank' - className='text-text-accent' + target="_blank" + className="text-text-accent" > {t('workflow.common.learnMore')} </a> </div> - <div className='space-y-1'> + <div className="space-y-1"> { forms.map((form, index) => { return ( <div key={index} - className='py-1' + className="py-1" > - <div className='mb-1 flex items-center'> - <div className='system-sm-medium mr-1 text-text-primary'>{form.key}</div> - <div className='system-xs-regular text-text-tertiary'>{form.type}</div> + <div className="mb-1 flex items-center"> + <div className="system-sm-medium mr-1 text-text-primary">{form.key}</div> + <div className="system-xs-regular text-text-tertiary">{form.type}</div> </div> { (form.type === VarType.string || form.type === VarType.number) && ( diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx index a786f5adf4..c6d5e01138 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx @@ -1,11 +1,11 @@ +import type { Node } from '@/app/components/workflow/types' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useUpdateNodeInternals } from 'reactflow' -import { NodeSourceHandle } from '../node-handle' -import { ErrorHandleTypeEnum } from './types' -import type { Node } from '@/app/components/workflow/types' import { NodeRunningStatus } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import { NodeSourceHandle } from '../node-handle' +import { ErrorHandleTypeEnum } from './types' type ErrorHandleOnNodeProps = Pick<Node, 'id' | 'data'> const ErrorHandleOnNode = ({ @@ -25,18 +25,20 @@ const ErrorHandleOnNode = ({ return null return ( - <div className='relative px-3 pb-2 pt-1'> + <div className="relative px-3 pb-2 pt-1"> <div className={cn( 'relative flex h-6 items-center justify-between rounded-md bg-workflow-block-parma-bg px-[5px]', data._runningStatus === NodeRunningStatus.Exception && 'border-[0.5px] border-components-badge-status-light-warning-halo bg-state-warning-hover', - )}> - <div className='system-xs-medium-uppercase text-text-tertiary'> + )} + > + <div className="system-xs-medium-uppercase text-text-tertiary"> {t('workflow.common.onFailure')} </div> <div className={cn( 'system-xs-medium text-text-secondary', data._runningStatus === NodeRunningStatus.Exception && 'text-text-warning', - )}> + )} + > { error_strategy === ErrorHandleTypeEnum.defaultValue && ( t('workflow.nodes.common.errorHandle.defaultValue.output') @@ -54,8 +56,8 @@ const ErrorHandleOnNode = ({ id={id} data={data} handleId={ErrorHandleTypeEnum.failBranch} - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg' - nodeSelectorClassName='!bg-workflow-link-line-failure-button-bg' + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg" + nodeSelectorClassName="!bg-workflow-link-line-failure-button-bg" /> ) } diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx index cfcbae80f3..bfd3097d27 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx @@ -1,20 +1,20 @@ -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import Collapse from '../collapse' -import { ErrorHandleTypeEnum } from './types' -import ErrorHandleTypeSelector from './error-handle-type-selector' -import FailBranchCard from './fail-branch-card' -import DefaultValue from './default-value' -import { - useDefaultValue, - useErrorHandle, -} from './hooks' import type { DefaultValueForm } from './types' import type { CommonNodeType, Node, } from '@/app/components/workflow/types' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' +import Collapse from '../collapse' +import DefaultValue from './default-value' +import ErrorHandleTypeSelector from './error-handle-type-selector' +import FailBranchCard from './fail-branch-card' +import { + useDefaultValue, + useErrorHandle, +} from './hooks' +import { ErrorHandleTypeEnum } from './types' type ErrorHandleProps = Pick<Node, 'id' | 'data'> const ErrorHandle = ({ @@ -44,7 +44,7 @@ const ErrorHandle = ({ return ( <> - <div className='py-4'> + <div className="py-4"> <Collapse disabled={!error_strategy} collapsed={collapsed} @@ -52,9 +52,9 @@ const ErrorHandle = ({ hideCollapseIcon trigger={ collapseIcon => ( - <div className='flex grow items-center justify-between pr-4'> - <div className='flex items-center'> - <div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'> + <div className="flex grow items-center justify-between pr-4"> + <div className="flex items-center"> + <div className="system-sm-semibold-uppercase mr-0.5 text-text-secondary"> {t('workflow.nodes.common.errorHandle.title')} </div> <Tooltip popupContent={t('workflow.nodes.common.errorHandle.tip')} /> @@ -65,7 +65,8 @@ const ErrorHandle = ({ onSelected={getHandleErrorHandleTypeChange(data)} /> </div> - )} + ) + } > <> { diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx index dc98c4003d..225185da36 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx @@ -1,6 +1,6 @@ +import { RiAlertFill } from '@remixicon/react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { RiAlertFill } from '@remixicon/react' import { ErrorHandleTypeEnum } from './types' type ErrorHandleTipProps = { @@ -24,16 +24,17 @@ const ErrorHandleTip = ({ return ( <div - className='relative flex rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 pr-[52px] shadow-xs' + className="relative flex rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 pr-[52px] shadow-xs" > <div - className='absolute inset-0 rounded-lg opacity-40' + className="absolute inset-0 rounded-lg opacity-40" style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)', }} - ></div> - <RiAlertFill className='mr-1 h-4 w-4 shrink-0 text-text-warning-secondary' /> - <div className='system-xs-medium grow text-text-primary'> + > + </div> + <RiAlertFill className="mr-1 h-4 w-4 shrink-0 text-text-warning-secondary" /> + <div className="system-xs-medium grow text-text-primary"> {text} </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx index d9516dfcf5..3f97687b14 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx @@ -1,16 +1,16 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' import { RiArrowDownSLine, RiCheckLine, } from '@remixicon/react' -import { ErrorHandleTypeEnum } from './types' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' +import { ErrorHandleTypeEnum } from './types' type ErrorHandleTypeSelectorProps = { value: ErrorHandleTypeEnum @@ -45,28 +45,29 @@ const ErrorHandleTypeSelector = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={4} > <PortalToFollowElemTrigger onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() setOpen(v => !v) - }}> + }} + > <Button - size='small' + size="small" > {selectedOption?.label} - <RiArrowDownSLine className='h-3.5 w-3.5' /> + <RiArrowDownSLine className="h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[11]'> - <div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[11]"> + <div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.value} - className='flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover' + className="flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover" onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() @@ -74,16 +75,16 @@ const ErrorHandleTypeSelector = ({ setOpen(false) }} > - <div className='mr-1 w-4 shrink-0'> + <div className="mr-1 w-4 shrink-0"> { value === option.value && ( - <RiCheckLine className='h-4 w-4 text-text-accent' /> + <RiCheckLine className="h-4 w-4 text-text-accent" /> ) } </div> - <div className='grow'> - <div className='system-sm-semibold mb-0.5 text-text-secondary'>{option.label}</div> - <div className='system-xs-regular text-text-tertiary'>{option.description}</div> + <div className="grow"> + <div className="system-sm-semibold mb-0.5 text-text-secondary">{option.label}</div> + <div className="system-xs-regular text-text-tertiary">{option.description}</div> </div> </div> )) diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx index fa9cff3dc8..793478b4dd 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx @@ -7,21 +7,21 @@ const FailBranchCard = () => { const docLink = useDocLink() return ( - <div className='px-4 pt-2'> - <div className='rounded-[10px] bg-workflow-process-bg p-4'> - <div className='mb-2 flex h-8 w-8 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg'> - <RiMindMap className='h-5 w-5 text-text-tertiary' /> + <div className="px-4 pt-2"> + <div className="rounded-[10px] bg-workflow-process-bg p-4"> + <div className="mb-2 flex h-8 w-8 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg"> + <RiMindMap className="h-5 w-5 text-text-tertiary" /> </div> - <div className='system-sm-medium mb-1 text-text-secondary'> + <div className="system-sm-medium mb-1 text-text-secondary"> {t('workflow.nodes.common.errorHandle.failBranch.customize')} </div> - <div className='system-xs-regular text-text-tertiary'> + <div className="system-xs-regular text-text-tertiary"> {t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}   <a href={docLink('/guides/workflow/error-handling/error-type')} - target='_blank' - className='text-text-accent' + target="_blank" + className="text-text-accent" > {t('workflow.common.learnMore')} </a> diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts b/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts index 06eb4fc48f..86f07c8d1e 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts +++ b/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts @@ -1,18 +1,18 @@ +import type { DefaultValueForm } from './types' +import type { + CommonNodeType, +} from '@/app/components/workflow/types' import { useCallback, useMemo, useState, } from 'react' -import { ErrorHandleTypeEnum } from './types' -import type { DefaultValueForm } from './types' -import { getDefaultValue } from './utils' -import type { - CommonNodeType, -} from '@/app/components/workflow/types' import { useEdgesInteractions, useNodeDataUpdate, } from '@/app/components/workflow/hooks' +import { ErrorHandleTypeEnum } from './types' +import { getDefaultValue } from './utils' export const useDefaultValue = ( id: string, diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts b/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts index eef9677c48..ac48407c03 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts @@ -1,9 +1,9 @@ +import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types' import type { CommonNodeType } from '@/app/components/workflow/types' import { BlockEnum, VarType, } from '@/app/components/workflow/types' -import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types' const getDefaultValueByType = (type: VarType) => { if (type === VarType.string) diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index b77fa511cb..9f46546700 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC, ReactNode } from 'react' -import React from 'react' import { RiArrowDownSLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { cn } from '@/utils/classnames' +import React from 'react' import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' type Props = { className?: string @@ -38,23 +38,26 @@ const Field: FC<Props> = ({ <div className={cn(className, inline && 'flex w-full items-center justify-between')}> <div onClick={() => supportFold && toggleFold()} - className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')}> - <div className='flex h-6 items-center'> + className={cn('flex items-center justify-between', supportFold && 'cursor-pointer')} + > + <div className="flex h-6 items-center"> <div className={cn(isSubTitle ? 'system-xs-medium-uppercase text-text-tertiary' : 'system-sm-semibold-uppercase text-text-secondary')}> - {title} {required && <span className='text-text-destructive'>*</span>} + {title} + {' '} + {required && <span className="text-text-destructive">*</span>} </div> {tooltip && ( <Tooltip popupContent={tooltip} - popupClassName='ml-1' - triggerClassName='w-4 h-4 ml-1' + popupClassName="ml-1" + triggerClassName="w-4 h-4 ml-1" /> )} </div> - <div className='flex'> + <div className="flex"> {operations && <div>{operations}</div>} {supportFold && ( - <RiArrowDownSLine className='h-4 w-4 cursor-pointer text-text-tertiary transition-transform' style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} /> + <RiArrowDownSLine className="h-4 w-4 cursor-pointer text-text-tertiary transition-transform" style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} /> )} </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx index fd07465225..3dc1e7b132 100644 --- a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' +import { noop } from 'lodash-es' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { SupportUploadFileTypes } from '../../../types' -import { cn } from '@/utils/classnames' -import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import TagInput from '@/app/components/base/tag-input' import Checkbox from '@/app/components/base/checkbox' import { FileTypeIcon } from '@/app/components/base/file-uploader' -import { noop } from 'lodash-es' +import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' +import TagInput from '@/app/components/base/tag-input' +import { cn } from '@/utils/classnames' +import { SupportUploadFileTypes } from '../../../types' type Props = { type: SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video | SupportUploadFileTypes.custom @@ -45,31 +45,31 @@ const FileTypeItem: FC<Props> = ({ > {isCustomSelected ? ( - <div> - <div className='flex items-center border-b border-divider-subtle p-3 pb-2'> - <FileTypeIcon className='shrink-0' type={type} size='lg' /> - <div className='system-sm-medium mx-2 grow text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div> - <Checkbox className='shrink-0' checked={selected} /> + <div> + <div className="flex items-center border-b border-divider-subtle p-3 pb-2"> + <FileTypeIcon className="shrink-0" type={type} size="lg" /> + <div className="system-sm-medium mx-2 grow text-text-primary">{t(`appDebug.variableConfig.file.${type}.name`)}</div> + <Checkbox className="shrink-0" checked={selected} /> + </div> + <div className="p-3" onClick={e => e.stopPropagation()}> + <TagInput + items={customFileTypes} + onChange={onCustomFileTypesChange} + placeholder={t('appDebug.variableConfig.file.custom.createPlaceholder')!} + /> + </div> </div> - <div className='p-3' onClick={e => e.stopPropagation()}> - <TagInput - items={customFileTypes} - onChange={onCustomFileTypesChange} - placeholder={t('appDebug.variableConfig.file.custom.createPlaceholder')!} - /> - </div> - </div> - ) + ) : ( - <div className='flex items-center'> - <FileTypeIcon className='shrink-0' type={type} size='lg' /> - <div className='mx-2 grow'> - <div className='system-sm-medium text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div> - <div className='system-2xs-regular-uppercase mt-1 text-text-tertiary'>{type !== SupportUploadFileTypes.custom ? FILE_EXTS[type].join(', ') : t('appDebug.variableConfig.file.custom.description')}</div> + <div className="flex items-center"> + <FileTypeIcon className="shrink-0" type={type} size="lg" /> + <div className="mx-2 grow"> + <div className="system-sm-medium text-text-primary">{t(`appDebug.variableConfig.file.${type}.name`)}</div> + <div className="system-2xs-regular-uppercase mt-1 text-text-tertiary">{type !== SupportUploadFileTypes.custom ? FILE_EXTS[type].join(', ') : t('appDebug.variableConfig.file.custom.description')}</div> + </div> + <Checkbox className="shrink-0" checked={selected} /> </div> - <Checkbox className='shrink-0' checked={selected} /> - </div> - )} + )} </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx index ce41777471..c6330daf4d 100644 --- a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx @@ -1,18 +1,18 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' import type { UploadFileSetting } from '../../../types' +import { produce } from 'immer' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Field from '@/app/components/app/configuration/config-var/config-modal/field' +import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' +import { useFileUploadConfig } from '@/service/use-common' +import { TransferMethod } from '@/types/app' +import { formatFileSize } from '@/utils/format' import { SupportUploadFileTypes } from '../../../types' -import OptionCard from './option-card' import FileTypeItem from './file-type-item' import InputNumberWithSlider from './input-number-with-slider' -import Field from '@/app/components/app/configuration/config-var/config-modal/field' -import { TransferMethod } from '@/types/app' -import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' -import { formatFileSize } from '@/utils/format' -import { useFileUploadConfig } from '@/service/use-common' +import OptionCard from './option-card' type Props = { payload: UploadFileSetting @@ -100,7 +100,7 @@ const FileUploadSetting: FC<Props> = ({ <Field title={t('appDebug.variableConfig.file.supportFileTypes')} > - <div className='space-y-1'> + <div className="space-y-1"> { [SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( <FileTypeItem @@ -123,9 +123,9 @@ const FileUploadSetting: FC<Props> = ({ )} <Field title={t('appDebug.variableConfig.uploadFileTypes')} - className='mt-4' + className="mt-4" > - <div className='grid grid-cols-3 gap-2'> + <div className="grid grid-cols-3 gap-2"> <OptionCard title={t('appDebug.variableConfig.localUpload')} selected={allowed_file_upload_methods.length === 1 && allowed_file_upload_methods.includes(TransferMethod.local_file)} @@ -145,16 +145,18 @@ const FileUploadSetting: FC<Props> = ({ </Field> {isMultiple && ( <Field - className='mt-4' + className="mt-4" title={t('appDebug.variableConfig.maxNumberOfUploads')!} > <div> - <div className='body-xs-regular mb-1.5 text-text-tertiary'>{t('appDebug.variableConfig.maxNumberTip', { - imgLimit: formatFileSize(imgSizeLimit), - docLimit: formatFileSize(docSizeLimit), - audioLimit: formatFileSize(audioSizeLimit), - videoLimit: formatFileSize(videoSizeLimit), - })}</div> + <div className="body-xs-regular mb-1.5 text-text-tertiary"> + {t('appDebug.variableConfig.maxNumberTip', { + imgLimit: formatFileSize(imgSizeLimit), + docLimit: formatFileSize(docSizeLimit), + audioLimit: formatFileSize(audioSizeLimit), + videoLimit: formatFileSize(videoSizeLimit), + })} + </div> <InputNumberWithSlider value={max_length} @@ -168,9 +170,9 @@ const FileUploadSetting: FC<Props> = ({ {inFeaturePanel && !hideSupportFileType && ( <Field title={t('appDebug.variableConfig.file.supportFileTypes')} - className='mt-4' + className="mt-4" > - <div className='space-y-1'> + <div className="space-y-1"> { [SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => ( <FileTypeItem diff --git a/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx b/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx index 2767d3b2fb..f5f82f8a71 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx @@ -12,7 +12,7 @@ const FormInputBoolean: FC<Props> = ({ onChange, }) => { return ( - <div className='flex w-full space-x-1'> + <div className="flex w-full space-x-1"> <div className={cn( 'system-sm-regular flex h-8 grow cursor-default items-center justify-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 text-text-secondary', @@ -20,7 +20,9 @@ const FormInputBoolean: FC<Props> = ({ value && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs', )} onClick={() => onChange(true)} - >True</div> + > + True + </div> <div className={cn( 'system-sm-regular flex h-8 grow cursor-default items-center justify-center rounded-md border border-components-option-card-option-border bg-components-option-card-option-bg px-2 text-text-secondary', @@ -28,7 +30,9 @@ const FormInputBoolean: FC<Props> = ({ !value && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs', )} onClick={() => onChange(false)} - >False</div> + > + False + </div> </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 7cf0043520..419f905fa5 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -1,35 +1,35 @@ 'use client' import type { FC } from 'react' -import { useEffect, useMemo, useState } from 'react' -import { type ResourceVarInputs, VarKindType } from '../types' +import type { ResourceVarInputs } from '../types' import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import type { Event, Tool } from '@/app/components/tools/types' +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' +import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react' +import { ChevronDownIcon } from '@heroicons/react/20/solid' + +import { RiCheckLine, RiLoader4Line } from '@remixicon/react' +import { useEffect, useMemo, useState } from 'react' +import CheckboxList from '@/app/components/base/checkbox-list' +import Input from '@/app/components/base/input' +import { SimpleSelect } from '@/app/components/base/select' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' +import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' +import { PluginCategoryEnum } from '@/app/components/plugins/types' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input' import { VarType } from '@/app/components/workflow/types' import { useFetchDynamicOptions } from '@/service/use-plugins' import { useTriggerPluginDynamicOptions } from '@/service/use-triggers' - -import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' -import type { Tool } from '@/app/components/tools/types' -import FormInputTypeSwitch from './form-input-type-switch' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import Input from '@/app/components/base/input' -import { SimpleSelect } from '@/app/components/base/select' -import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input' -import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' -import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { cn } from '@/utils/classnames' -import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react' -import { ChevronDownIcon } from '@heroicons/react/20/solid' -import { RiCheckLine, RiLoader4Line } from '@remixicon/react' -import type { Event } from '@/app/components/tools/types' -import { PluginCategoryEnum } from '@/app/components/plugins/types' -import CheckboxList from '@/app/components/base/checkbox-list' +import { VarKindType } from '../types' import FormInputBoolean from './form-input-boolean' +import FormInputTypeSwitch from './form-input-type-switch' type Props = { readOnly: boolean @@ -284,7 +284,7 @@ const FormInputItem: FC<Props> = ({ } const availableCheckboxOptions = useMemo(() => ( - (options || []).filter((option: { show_on?: Array<{ variable: string; value: any }> }) => { + (options || []).filter((option: { show_on?: Array<{ variable: string, value: any }> }) => { if (option.show_on?.length) return option.show_on.every(showOnItem => value[showOnItem.variable]?.value === showOnItem.value || value[showOnItem.variable] === showOnItem.value) return true @@ -292,7 +292,7 @@ const FormInputItem: FC<Props> = ({ ), [options, value]) const checkboxListOptions = useMemo(() => ( - availableCheckboxOptions.map((option: { value: string; label: Record<string, string> }) => ({ + availableCheckboxOptions.map((option: { value: string, label: Record<string, string> }) => ({ value: option.value, label: option.label?.[language] || option.label?.en_US || option.value, })) @@ -341,8 +341,8 @@ const FormInputItem: FC<Props> = ({ )} {isNumber && isConstant && ( <Input - className='h-8 grow' - type='number' + className="h-8 grow" + type="number" value={Number.isNaN(varInput?.value) ? '' : varInput?.value} onChange={e => handleValueChange(e.target.value)} placeholder={placeholder?.[language] || placeholder?.en_US} @@ -355,7 +355,7 @@ const FormInputItem: FC<Props> = ({ onChange={handleCheckboxListChange} options={checkboxListOptions} disabled={readOnly} - maxHeight='200px' + maxHeight="200px" /> )} {isBoolean && isConstant && ( @@ -366,7 +366,7 @@ const FormInputItem: FC<Props> = ({ )} {isSelect && isConstant && !isMultipleSelect && ( <SimpleSelect - wrapperClassName='h-8 grow' + wrapperClassName="h-8 grow" disabled={readOnly} defaultValue={varInput?.value} items={options.filter((option: { show_on: any[] }) => { @@ -374,21 +374,23 @@ const FormInputItem: FC<Props> = ({ return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) return true - }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({ + }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({ value: option.value, name: option.label[language] || option.label.en_US, icon: option.icon, }))} onSelect={item => handleValueChange(item.value as string)} placeholder={placeholder?.[language] || placeholder?.en_US} - renderOption={options.some((opt: any) => opt.icon) ? ({ item }) => ( - <div className="flex items-center"> - {item.icon && ( - <img src={item.icon} alt="" className="mr-2 h-4 w-4" /> - )} - <span>{item.name}</span> - </div> - ) : undefined} + renderOption={options.some((opt: any) => opt.icon) + ? ({ item }) => ( + <div className="flex items-center"> + {item.icon && ( + <img src={item.icon} alt="" className="mr-2 h-4 w-4" /> + )} + <span>{item.name}</span> + </div> + ) + : undefined} /> )} {isSelect && isConstant && isMultipleSelect && ( @@ -400,9 +402,7 @@ const FormInputItem: FC<Props> = ({ > <div className="group/simple-select relative h-8 grow"> <ListboxButton className="flex h-full w-full cursor-pointer items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6"> - <span className={cn('system-sm-regular block truncate text-left', - varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', - )}> + <span className={cn('system-sm-regular block truncate text-left', varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder')}> {getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'} </span> <span className="absolute inset-y-0 right-0 flex items-center pr-2"> @@ -417,15 +417,12 @@ const FormInputItem: FC<Props> = ({ if (option.show_on?.length) return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) return true - }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ( + }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ( <ListboxOption key={option.value} value={option.value} className={({ focus }) => - cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', - focus && 'bg-state-base-hover', - ) - } + cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', focus && 'bg-state-base-hover')} > {({ selected }) => ( <> @@ -452,7 +449,7 @@ const FormInputItem: FC<Props> = ({ )} {isDynamicSelect && !isMultipleSelect && ( <SimpleSelect - wrapperClassName='h-8 grow' + wrapperClassName="h-8 grow" disabled={readOnly || isLoadingOptions} defaultValue={varInput?.value} items={(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => { @@ -460,7 +457,7 @@ const FormInputItem: FC<Props> = ({ return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) return true - }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ({ + }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ({ value: option.value, name: option.label[language] || option.label.en_US, icon: option.icon, @@ -486,23 +483,25 @@ const FormInputItem: FC<Props> = ({ > <div className="group/simple-select relative h-8 grow"> <ListboxButton className="flex h-full w-full cursor-pointer items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6"> - <span className={cn('system-sm-regular block truncate text-left', - isLoadingOptions ? 'text-components-input-text-placeholder' - : varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', - )}> + <span className={cn('system-sm-regular block truncate text-left', isLoadingOptions + ? 'text-components-input-text-placeholder' + : varInput?.value?.length > 0 ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder')} + > {isLoadingOptions ? 'Loading...' : getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'} </span> <span className="absolute inset-y-0 right-0 flex items-center pr-2"> - {isLoadingOptions ? ( - <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' /> - ) : ( - <ChevronDownIcon - className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" - aria-hidden="true" - /> - )} + {isLoadingOptions + ? ( + <RiLoader4Line className="h-3.5 w-3.5 animate-spin text-text-secondary" /> + ) + : ( + <ChevronDownIcon + className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" + aria-hidden="true" + /> + )} </span> </ListboxButton> <ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-1 py-1 text-base shadow-lg backdrop-blur-sm focus:outline-none sm:text-sm"> @@ -510,15 +509,12 @@ const FormInputItem: FC<Props> = ({ if (option.show_on?.length) return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) return true - }).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => ( + }).map((option: { value: any, label: { [x: string]: any, en_US: any }, icon?: string }) => ( <ListboxOption key={option.value} value={option.value} className={({ focus }) => - cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', - focus && 'bg-state-base-hover', - ) - } + cn('relative cursor-pointer select-none rounded-lg py-2 pl-3 pr-9 text-text-secondary hover:bg-state-base-hover', focus && 'bg-state-base-hover')} > {({ selected }) => ( <> @@ -544,16 +540,16 @@ const FormInputItem: FC<Props> = ({ </Listbox> )} {isShowJSONEditor && isConstant && ( - <div className='mt-1 w-full'> + <div className="mt-1 w-full"> <CodeEditor - title='JSON' + title="JSON" value={varInput?.value as any} isExpand isInNode language={CodeLanguage.json} onChange={handleValueChange} - className='w-full' - placeholder={<div className='whitespace-pre'>{placeholder?.[language] || placeholder?.en_US}</div>} + className="w-full" + placeholder={<div className="whitespace-pre">{placeholder?.[language] || placeholder?.en_US}</div>} /> </div> )} @@ -567,7 +563,7 @@ const FormInputItem: FC<Props> = ({ )} {isModelSelector && isConstant && ( <ModelParameterModal - popupClassName='!w-[387px]' + popupClassName="!w-[387px]" isAdvancedMode isInWorkflow value={varInput?.value} @@ -579,7 +575,7 @@ const FormInputItem: FC<Props> = ({ {showVariableSelector && ( <VarReferencePicker zIndex={inPanel ? 1000 : undefined} - className='h-8 grow' + className="h-8 grow" readonly={readOnly} isShowNodeName nodeId={nodeId} diff --git a/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx b/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx index c7af679e23..04e711511f 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' -import { useTranslation } from 'react-i18next' import { RiEditLine, } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import Tooltip from '@/app/components/base/tooltip' import { VarType } from '@/app/components/workflow/nodes/tool/types' @@ -20,7 +20,7 @@ const FormInputTypeSwitch: FC<Props> = ({ }) => { const { t } = useTranslation() return ( - <div className='inline-flex h-8 shrink-0 gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5'> + <div className="inline-flex h-8 shrink-0 gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5"> <Tooltip popupContent={value === VarType.variable ? '' : t('workflow.nodes.common.typeSwitch.variable')} > @@ -28,7 +28,7 @@ const FormInputTypeSwitch: FC<Props> = ({ className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.variable && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')} onClick={() => onChange(VarType.variable)} > - <Variable02 className='h-4 w-4' /> + <Variable02 className="h-4 w-4" /> </div> </Tooltip> <Tooltip @@ -38,7 +38,7 @@ const FormInputTypeSwitch: FC<Props> = ({ className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.constant && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')} onClick={() => onChange(VarType.constant)} > - <RiEditLine className='h-4 w-4' /> + <RiEditLine className="h-4 w-4" /> </div> </Tooltip> </div> diff --git a/web/app/components/workflow/nodes/_base/components/group.tsx b/web/app/components/workflow/nodes/_base/components/group.tsx index 80157aca13..366f86df05 100644 --- a/web/app/components/workflow/nodes/_base/components/group.tsx +++ b/web/app/components/workflow/nodes/_base/components/group.tsx @@ -1,13 +1,15 @@ -import { cn } from '@/utils/classnames' import type { ComponentProps, FC, PropsWithChildren, ReactNode } from 'react' +import { cn } from '@/utils/classnames' export type GroupLabelProps = ComponentProps<'div'> export const GroupLabel: FC<GroupLabelProps> = (props) => { const { children, className, ...rest } = props - return <div {...rest} className={cn('system-2xs-medium-uppercase mb-1 text-text-tertiary', className)}> - {children} - </div> + return ( + <div {...rest} className={cn('system-2xs-medium-uppercase mb-1 text-text-tertiary', className)}> + {children} + </div> + ) } export type GroupProps = PropsWithChildren<{ @@ -16,10 +18,12 @@ export type GroupProps = PropsWithChildren<{ export const Group: FC<GroupProps> = (props) => { const { children, label } = props - return <div className={cn('py-1')}> - {label} - <div className='space-y-0.5'> - {children} + return ( + <div className={cn('py-1')}> + {label} + <div className="space-y-0.5"> + {children} + </div> </div> - </div> + ) } diff --git a/web/app/components/workflow/nodes/_base/components/help-link.tsx b/web/app/components/workflow/nodes/_base/components/help-link.tsx index 8bf74529f1..adc153f029 100644 --- a/web/app/components/workflow/nodes/_base/components/help-link.tsx +++ b/web/app/components/workflow/nodes/_base/components/help-link.tsx @@ -1,9 +1,9 @@ +import type { BlockEnum } from '@/app/components/workflow/types' +import { RiBookOpenLine } from '@remixicon/react' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import { RiBookOpenLine } from '@remixicon/react' -import { useNodeHelpLink } from '../hooks/use-node-help-link' import TooltipPlus from '@/app/components/base/tooltip' -import type { BlockEnum } from '@/app/components/workflow/types' +import { useNodeHelpLink } from '../hooks/use-node-help-link' type HelpLinkProps = { nodeType: BlockEnum @@ -23,10 +23,10 @@ const HelpLink = ({ > <a href={link} - target='_blank' - className='mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover' + target="_blank" + className="mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover" > - <RiBookOpenLine className='h-4 w-4 text-gray-500' /> + <RiBookOpenLine className="h-4 w-4 text-gray-500" /> </a> </TooltipPlus> diff --git a/web/app/components/workflow/nodes/_base/components/info-panel.tsx b/web/app/components/workflow/nodes/_base/components/info-panel.tsx index 88511b1de7..cc2426c24f 100644 --- a/web/app/components/workflow/nodes/_base/components/info-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/info-panel.tsx @@ -13,11 +13,11 @@ const InfoPanel: FC<Props> = ({ }) => { return ( <div> - <div className='flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]'> - <div className='system-2xs-semibold-uppercase uppercase text-text-secondary'> + <div className="flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]"> + <div className="system-2xs-semibold-uppercase uppercase text-text-secondary"> {title} </div> - <div className='system-xs-regular break-words text-text-tertiary'> + <div className="system-xs-regular break-words text-text-tertiary"> {content} </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/input-field/add.tsx b/web/app/components/workflow/nodes/_base/components/input-field/add.tsx index a104973399..fcefa8da57 100644 --- a/web/app/components/workflow/nodes/_base/components/input-field/add.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-field/add.tsx @@ -4,7 +4,7 @@ import ActionButton from '@/app/components/base/action-button' const Add = () => { return ( <ActionButton> - <RiAddLine className='h-4 w-4' /> + <RiAddLine className="h-4 w-4" /> </ActionButton> ) } diff --git a/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx b/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx index 17208fe54d..0a021402ba 100644 --- a/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx @@ -38,11 +38,11 @@ const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({ }, [onChange]) return ( - <div className='flex h-8 items-center justify-between space-x-2'> + <div className="flex h-8 items-center justify-between space-x-2"> <input value={value} - className='block h-8 w-12 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-[13px] text-components-input-text-filled outline-none' - type='number' + className="block h-8 w-12 shrink-0 appearance-none rounded-lg bg-components-input-bg-normal pl-3 text-[13px] text-components-input-text-filled outline-none" + type="number" min={min} max={max} step={1} @@ -51,7 +51,7 @@ const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({ disabled={readonly} /> <Slider - className='grow' + className="grow" value={value} min={min} max={max} diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index 8ffe301d67..c06fe55375 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' -import React, { useEffect } from 'react' -import { useBoolean } from 'ahooks' -import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' import type { Node, NodeOutPutVar, } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' -import PromptEditor from '@/app/components/base/prompt-editor' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import Tooltip from '@/app/components/base/tooltip' +import { useBoolean } from 'ahooks' import { noop } from 'lodash-es' +import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import PromptEditor from '@/app/components/base/prompt-editor' +import Tooltip from '@/app/components/base/tooltip' import { useStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' type Props = { instanceId?: string @@ -115,20 +115,20 @@ const Editor: FC<Props> = ({ onFocus={setFocus} /> {/* to patch Editor not support dynamic change editable status */} - {readOnly && <div className='absolute inset-0 z-10'></div>} + {readOnly && <div className="absolute inset-0 z-10"></div>} {isFocus && ( <div className={cn('absolute z-10', insertVarTipToLeft ? 'left-[-12px] top-1.5' : ' right-1 top-[-9px]')}> <Tooltip popupContent={`${t('workflow.common.insertVarTip')}`} > - <div className='cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg'> - <Variable02 className='h-3.5 w-3.5 text-components-button-secondary-accent-text' /> + <div className="cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg"> + <Variable02 className="h-3.5 w-3.5 text-components-button-secondary-accent-text" /> </div> </Tooltip> </div> )} </> - </div > + </div> ) } export default React.memo(Editor) diff --git a/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx b/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx index 70fd1051b9..7e529013cb 100644 --- a/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx @@ -1,6 +1,5 @@ 'use client' import type { FC } from 'react' -import React from 'react' import { RiAlignLeft, RiBracesLine, @@ -11,6 +10,7 @@ import { RiHashtag, RiTextSnippet, } from '@remixicon/react' +import React from 'react' import { InputVarType } from '../../../types' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx b/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx index 385b69ee43..f77a24c965 100644 --- a/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx @@ -1,12 +1,12 @@ -import Button from '@/app/components/base/button' -import { RiInstallLine, RiLoader2Line } from '@remixicon/react' import type { ComponentProps, MouseEventHandler } from 'react' +import { RiInstallLine, RiLoader2Line } from '@remixicon/react' import { useState } from 'react' -import { cn } from '@/utils/classnames' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status' import { TaskStatus } from '@/app/components/plugins/types' import { useCheckInstalled, useInstallPackageFromMarketPlace } from '@/service/use-plugins' +import { cn } from '@/utils/classnames' type InstallPluginButtonProps = Omit<ComponentProps<typeof Button>, 'children' | 'loading'> & { uniqueIdentifier: string @@ -83,22 +83,26 @@ export const InstallPluginButton = (props: InstallPluginButtonProps) => { }, }) } - if (!manifest.data) return null + if (!manifest.data) + return null const identifierSet = new Set(identifiers) const isInstalled = manifest.data.plugins.some(plugin => ( identifierSet.has(plugin.id) || (plugin.plugin_unique_identifier && identifierSet.has(plugin.plugin_unique_identifier)) || (plugin.plugin_id && identifierSet.has(plugin.plugin_id)) )) - if (isInstalled) return null - return <Button - variant={'secondary'} - disabled={isLoading} - {...rest} - onClick={handleInstall} - className={cn('flex items-center', className)} - > - {!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')} - {!isLoading ? <RiInstallLine className='ml-1 size-3.5' /> : <RiLoader2Line className='ml-1 size-3.5 animate-spin' />} - </Button> + if (isInstalled) + return null + return ( + <Button + variant="secondary" + disabled={isLoading} + {...rest} + onClick={handleInstall} + className={cn('flex items-center', className)} + > + {!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')} + {!isLoading ? <RiInstallLine className="ml-1 size-3.5" /> : <RiLoader2Line className="ml-1 size-3.5 animate-spin" />} + </Button> + ) } diff --git a/web/app/components/workflow/nodes/_base/components/layout/box-group-field.tsx b/web/app/components/workflow/nodes/_base/components/layout/box-group-field.tsx index f89f13e1c5..a6df026a5b 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/box-group-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/box-group-field.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from 'react' -import { memo } from 'react' import type { BoxGroupProps, FieldProps, } from '.' +import { memo } from 'react' import { BoxGroup, Field, diff --git a/web/app/components/workflow/nodes/_base/components/layout/box-group.tsx b/web/app/components/workflow/nodes/_base/components/layout/box-group.tsx index 7d2d4b0dcb..edf0c116ec 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/box-group.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/box-group.tsx @@ -1,13 +1,13 @@ import type { ReactNode } from 'react' +import type { + BoxProps, + GroupProps, +} from '.' import { memo } from 'react' import { Box, Group, } from '.' -import type { - BoxProps, - GroupProps, -} from '.' export type BoxGroupProps = { children?: ReactNode diff --git a/web/app/components/workflow/nodes/_base/components/layout/box.tsx b/web/app/components/workflow/nodes/_base/components/layout/box.tsx index ec4869d305..62e709efc6 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/box.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/box.tsx @@ -18,7 +18,8 @@ export const Box = memo(({ 'py-2', withBorderBottom && 'border-b border-divider-subtle', className, - )}> + )} + > {children} </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx b/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx index 1a19ac2ab4..e5e8fe950d 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/field-title.tsx @@ -33,7 +33,7 @@ export const FieldTitle = memo(({ return ( <div className={cn('mb-0.5', !!subTitle && 'mb-1')}> <div - className='group/collapse flex items-center justify-between py-1' + className="group/collapse flex items-center justify-between py-1" onClick={() => { if (!disabled) { setCollapsedLocal(!collapsedMerged) @@ -41,7 +41,7 @@ export const FieldTitle = memo(({ } }} > - <div className='system-sm-semibold-uppercase flex items-center text-text-secondary'> + <div className="system-sm-semibold-uppercase flex items-center text-text-secondary"> {title} { showArrow && ( @@ -57,7 +57,7 @@ export const FieldTitle = memo(({ tooltip && ( <Tooltip popupContent={tooltip} - triggerClassName='w-4 h-4 ml-1' + triggerClassName="w-4 h-4 ml-1" /> ) } diff --git a/web/app/components/workflow/nodes/_base/components/layout/field.tsx b/web/app/components/workflow/nodes/_base/components/layout/field.tsx index 46951d89e3..70f35e0ba7 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/field.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from 'react' +import type { FieldTitleProps } from '.' import { memo, useState, } from 'react' -import type { FieldTitleProps } from '.' import { FieldTitle } from '.' export type FieldProps = { diff --git a/web/app/components/workflow/nodes/_base/components/layout/group-field.tsx b/web/app/components/workflow/nodes/_base/components/layout/group-field.tsx index b7238f0c0c..800a21edb7 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/group-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/group-field.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from 'react' -import { memo } from 'react' import type { FieldProps, GroupProps, } from '.' +import { memo } from 'react' import { Field, Group, diff --git a/web/app/components/workflow/nodes/_base/components/layout/group.tsx b/web/app/components/workflow/nodes/_base/components/layout/group.tsx index 446588eb45..6e35cb7b69 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/group.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/group.tsx @@ -18,7 +18,8 @@ export const Group = memo(({ 'px-4 py-2', withBorderBottom && 'border-b border-divider-subtle', className, - )}> + )} + > {children} </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/layout/index.tsx b/web/app/components/workflow/nodes/_base/components/layout/index.tsx index fc5b211bdc..f470cbf20f 100644 --- a/web/app/components/workflow/nodes/_base/components/layout/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/layout/index.tsx @@ -1,7 +1,7 @@ export * from './box' -export * from './group' export * from './box-group' -export * from './field-title' -export * from './field' -export * from './group-field' export * from './box-group-field' +export * from './field' +export * from './field-title' +export * from './group' +export * from './group-field' diff --git a/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx b/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx index d4a7f07b7e..98e2dc4f29 100644 --- a/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx +++ b/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx @@ -10,7 +10,7 @@ const ListNoDataPlaceholder: FC<Props> = ({ children, }) => { return ( - <div className='system-xs-regular flex min-h-[42px] w-full items-center justify-center rounded-[10px] bg-background-section text-text-tertiary'> + <div className="system-xs-regular flex min-h-[42px] w-full items-center justify-center rounded-[10px] bg-background-section text-text-tertiary"> {children} </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx index b314082af2..33da12239b 100644 --- a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx +++ b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx @@ -1,21 +1,21 @@ 'use client' -import Tooltip from '@/app/components/base/tooltip' -import { RiAlertFill } from '@remixicon/react' import type { FC } from 'react' +import { RiAlertFill } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' const McpToolNotSupportTooltip: FC = () => { const { t } = useTranslation() return ( <Tooltip - popupContent={ - <div className='w-[256px]'> + popupContent={( + <div className="w-[256px]"> {t('plugin.detailPanel.toolSelector.unsupportedMCPTool')} </div> - } + )} > - <RiAlertFill className='size-4 text-text-warning-secondary' /> + <RiAlertFill className="size-4 text-text-warning-secondary" /> </Tooltip> ) } diff --git a/web/app/components/workflow/nodes/_base/components/memory-config.tsx b/web/app/components/workflow/nodes/_base/components/memory-config.tsx index 989f3f635d..1272f132a6 100644 --- a/web/app/components/workflow/nodes/_base/components/memory-config.tsx +++ b/web/app/components/workflow/nodes/_base/components/memory-config.tsx @@ -1,15 +1,15 @@ 'use client' import type { FC } from 'react' +import type { Memory } from '../../../types' +import { produce } from 'immer' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import type { Memory } from '../../../types' -import { MemoryRole } from '../../../types' -import { cn } from '@/utils/classnames' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Switch from '@/app/components/base/switch' -import Slider from '@/app/components/base/slider' import Input from '@/app/components/base/input' +import Slider from '@/app/components/base/slider' +import Switch from '@/app/components/base/switch' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { cn } from '@/utils/classnames' +import { MemoryRole } from '../../../types' const i18nPrefix = 'workflow.nodes.common.memory' const WINDOW_SIZE_MIN = 1 @@ -31,14 +31,15 @@ const RoleItem: FC<RoleItemProps> = ({ onChange(e.target.value) }, [onChange]) return ( - <div className='flex items-center justify-between'> - <div className='text-[13px] font-normal text-text-secondary'>{title}</div> + <div className="flex items-center justify-between"> + <div className="text-[13px] font-normal text-text-secondary">{title}</div> <Input readOnly={readonly} value={value} onChange={handleChange} - className='h-8 w-[200px]' - type='text' /> + className="h-8 w-[200px]" + type="text" + /> </div> ) } @@ -132,31 +133,31 @@ const MemoryConfig: FC<Props> = ({ <Field title={t(`${i18nPrefix}.memory`)} tooltip={t(`${i18nPrefix}.memoryTip`)!} - operations={ + operations={( <Switch defaultValue={!!payload} onChange={handleMemoryEnabledChange} - size='md' + size="md" disabled={readonly} /> - } + )} > {payload && ( <> {/* window size */} - <div className='flex justify-between'> - <div className='flex h-8 items-center space-x-2'> + <div className="flex justify-between"> + <div className="flex h-8 items-center space-x-2"> <Switch defaultValue={payload?.window?.enabled} onChange={handleWindowEnabledChange} - size='md' + size="md" disabled={readonly} /> - <div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.windowSize`)}</div> + <div className="system-xs-medium-uppercase text-text-tertiary">{t(`${i18nPrefix}.windowSize`)}</div> </div> - <div className='flex h-8 items-center space-x-2'> + <div className="flex h-8 items-center space-x-2"> <Slider - className='w-[144px]' + className="w-[144px]" value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number} min={WINDOW_SIZE_MIN} max={WINDOW_SIZE_MAX} @@ -166,9 +167,9 @@ const MemoryConfig: FC<Props> = ({ /> <Input value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number} - wrapperClassName='w-12' - className='appearance-none pr-0' - type='number' + wrapperClassName="w-12" + className="appearance-none pr-0" + type="number" min={WINDOW_SIZE_MIN} max={WINDOW_SIZE_MAX} step={1} @@ -179,9 +180,9 @@ const MemoryConfig: FC<Props> = ({ </div> </div> {canSetRoleName && ( - <div className='mt-4'> - <div className='text-xs font-medium uppercase leading-6 text-text-tertiary'>{t(`${i18nPrefix}.conversationRoleName`)}</div> - <div className='mt-1 space-y-2'> + <div className="mt-4"> + <div className="text-xs font-medium uppercase leading-6 text-text-tertiary">{t(`${i18nPrefix}.conversationRoleName`)}</div> + <div className="mt-1 space-y-2"> <RoleItem readonly={readonly} title={t(`${i18nPrefix}.user`)} diff --git a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/index.tsx index 5fc6bd0528..acc395439c 100644 --- a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/index.tsx @@ -1,15 +1,15 @@ +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' import { memo, } from 'react' import { useTranslation } from 'react-i18next' import PromptEditor from '@/app/components/base/prompt-editor' -import Placeholder from './placeholder' -import type { - Node, - NodeOutPutVar, -} from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import Placeholder from './placeholder' type MixedVariableTextInputProps = { readOnly?: boolean @@ -33,7 +33,7 @@ const MixedVariableTextInput = ({ 'hover:border-components-input-border-hover hover:bg-components-input-bg-hover', 'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs', )} - className='caret:text-text-accent' + className="caret:text-text-accent" editable={!readOnly} value={value} workflowVariableBlock={{ diff --git a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/placeholder.tsx b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/placeholder.tsx index 75d4c91996..b839f1a426 100644 --- a/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/placeholder.tsx +++ b/web/app/components/workflow/nodes/_base/components/mixed-variable-text-input/placeholder.tsx @@ -1,10 +1,9 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { $insertNodes, FOCUS_COMMAND } from 'lexical' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import { FOCUS_COMMAND } from 'lexical' -import { $insertNodes } from 'lexical' -import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node' import Badge from '@/app/components/base/badge' +import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node' const Placeholder = () => { const { t } = useTranslation() @@ -20,17 +19,17 @@ const Placeholder = () => { return ( <div - className='pointer-events-auto flex h-full w-full cursor-text items-center px-2' + className="pointer-events-auto flex h-full w-full cursor-text items-center px-2" onClick={(e) => { e.stopPropagation() handleInsert('') }} > - <div className='flex grow items-center'> + <div className="flex grow items-center"> {t('workflow.nodes.tool.insertPlaceholder1')} - <div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div> + <div className="system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder">/</div> <div - className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary' + className="system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary" onMouseDown={((e) => { e.preventDefault() e.stopPropagation() @@ -41,8 +40,8 @@ const Placeholder = () => { </div> </div> <Badge - className='shrink-0' - text='String' + className="shrink-0" + text="String" uppercase={false} /> </div> diff --git a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx index 3001274c31..6395d0b3ca 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx @@ -1,3 +1,10 @@ +import type { + CommonNodeType, + OnSelectBlock, +} from '@/app/components/workflow/types' +import { + RiAddLine, +} from '@remixicon/react' import { memo, useCallback, @@ -5,19 +12,12 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiAddLine, -} from '@remixicon/react' +import BlockSelector from '@/app/components/workflow/block-selector' import { useAvailableBlocks, useNodesInteractions, useNodesReadOnly, } from '@/app/components/workflow/hooks' -import BlockSelector from '@/app/components/workflow/block-selector' -import type { - CommonNodeType, - OnSelectBlock, -} from '@/app/components/workflow/types' type AddProps = { nodeId: string @@ -75,10 +75,10 @@ const Add = ({ ${nodesReadOnly && '!cursor-not-allowed'} `} > - <div className='mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px] bg-background-default-dimmed'> - <RiAddLine className='h-3 w-3' /> + <div className="mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px] bg-background-default-dimmed"> + <RiAddLine className="h-3 w-3" /> </div> - <div className='flex items-center uppercase'> + <div className="flex items-center uppercase"> {tip} </div> </div> @@ -91,10 +91,10 @@ const Add = ({ onOpenChange={handleOpenChange} disabled={nodesReadOnly} onSelect={handleSelect} - placement='top' + placement="top" offset={0} trigger={renderTrigger} - popupClassName='!w-[328px]' + popupClassName="!w-[328px]" availableBlocksTypes={availableNextBlocks} /> ) diff --git a/web/app/components/workflow/nodes/_base/components/next-step/container.tsx b/web/app/components/workflow/nodes/_base/components/next-step/container.tsx index 5971ec8598..3fa295a10e 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/container.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/container.tsx @@ -1,10 +1,10 @@ -import Add from './add' -import Item from './item' import type { CommonNodeType, Node, } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import Add from './add' +import Item from './item' type ContainerProps = { nodeId: string @@ -27,7 +27,8 @@ const Container = ({ <div className={cn( 'space-y-0.5 rounded-[10px] bg-background-section-burn p-0.5', isFailBranch && 'border-[0.5px] border-state-warning-hover-alt bg-state-warning-hover', - )}> + )} + > { branchName && ( <div @@ -47,7 +48,7 @@ const Container = ({ key={nextNode.id} nodeId={nextNode.id} data={nextNode.data} - sourceHandle='source' + sourceHandle="source" /> )) } diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx index 25d1a0aa63..0f23161261 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx @@ -1,21 +1,21 @@ +import type { + Node, +} from '../../../../types' +import { isEqual } from 'lodash-es' import { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { isEqual } from 'lodash-es' import { getConnectedEdges, getOutgoers, useStore, } from 'reactflow' -import { useToolIcon } from '../../../../hooks' -import BlockIcon from '../../../../block-icon' -import type { - Node, -} from '../../../../types' -import { BlockEnum } from '../../../../types' -import Line from './line' -import Container from './container' -import { hasErrorHandleNode } from '@/app/components/workflow/utils' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import { hasErrorHandleNode } from '@/app/components/workflow/utils' +import BlockIcon from '../../../../block-icon' +import { useToolIcon } from '../../../../hooks' +import { BlockEnum } from '../../../../types' +import Container from './container' +import Line from './line' type NextStepProps = { selectedNode: Node @@ -89,8 +89,8 @@ const NextStep = ({ }, [branches, connectedEdges, data.error_strategy, data.type, outgoers, t]) return ( - <div className='flex py-1'> - <div className='relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs'> + <div className="flex py-1"> + <div className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-background-default shadow-xs"> <BlockIcon type={selectedNode!.data.type} toolIcon={toolIcon} @@ -99,7 +99,7 @@ const NextStep = ({ <Line list={list.length ? list.map(item => item.nextNodes.length + 1) : [1]} /> - <div className='grow space-y-2'> + <div className="grow space-y-2"> { list.map((item, index) => { return ( diff --git a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx index d9998fd226..0b8a09aa87 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx @@ -1,21 +1,21 @@ +import type { + CommonNodeType, +} from '@/app/components/workflow/types' import { memo, useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import Operator from './operator' -import type { - CommonNodeType, -} from '@/app/components/workflow/types' +import Button from '@/app/components/base/button' import BlockIcon from '@/app/components/workflow/block-icon' import { useNodesInteractions, useNodesReadOnly, useToolIcon, } from '@/app/components/workflow/hooks' -import Button from '@/app/components/base/button' import { cn } from '@/utils/classnames' +import Operator from './operator' type ItemProps = { nodeId: string @@ -39,15 +39,15 @@ const Item = ({ return ( <div - className='group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover' + className="group relative flex h-9 cursor-pointer items-center rounded-lg border-[0.5px] border-divider-regular bg-background-default px-2 text-xs text-text-secondary shadow-xs last-of-type:mb-0 hover:bg-background-default-hover" > <BlockIcon type={data.type} toolIcon={toolIcon} - className='mr-1.5 shrink-0' + className="mr-1.5 shrink-0" /> <div - className='system-xs-medium grow truncate text-text-secondary' + className="system-xs-medium grow truncate text-text-secondary" title={data.title} > {data.title} @@ -56,8 +56,8 @@ const Item = ({ !nodesReadOnly && ( <> <Button - className='mr-1 hidden shrink-0 group-hover:flex' - size='small' + className="mr-1 hidden shrink-0 group-hover:flex" + size="small" onClick={() => handleNodeSelect(nodeId)} > {t('workflow.common.jumpToNode')} diff --git a/web/app/components/workflow/nodes/_base/components/next-step/line.tsx b/web/app/components/workflow/nodes/_base/components/next-step/line.tsx index 35b0a73ff2..862657e34b 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/line.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/line.tsx @@ -19,7 +19,7 @@ const Line = ({ const svgHeight = processedList[processedListLength - 1] + (processedListLength - 1) * 8 return ( - <svg className='w-6 shrink-0' style={{ height: svgHeight }}> + <svg className="w-6 shrink-0" style={{ height: svgHeight }}> { processedList.map((item, index) => { const prevItem = index > 0 ? processedList[index - 1] : 0 @@ -30,17 +30,17 @@ const Line = ({ index === 0 && ( <> <path - d='M0,18 L24,18' + d="M0,18 L24,18" strokeWidth={1} - fill='none' - className='stroke-divider-solid' + fill="none" + className="stroke-divider-solid" /> <rect x={0} y={16} width={1} height={4} - className='fill-divider-solid-alt' + className="fill-divider-solid-alt" /> </> ) @@ -50,8 +50,8 @@ const Line = ({ <path d={`M0,18 Q12,18 12,28 L12,${space - 10 + 2} Q12,${space + 2} 24,${space + 2}`} strokeWidth={1} - fill='none' - className='stroke-divider-solid' + fill="none" + className="stroke-divider-solid" /> ) } @@ -60,7 +60,7 @@ const Line = ({ y={space} width={1} height={4} - className='fill-divider-solid-alt' + className="fill-divider-solid-alt" /> </g> ) diff --git a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx index 7143e6fe43..1b550f035d 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx @@ -1,24 +1,24 @@ +import type { + CommonNodeType, + OnSelectBlock, +} from '@/app/components/workflow/types' +import { RiMoreFill } from '@remixicon/react' +import { intersection } from 'lodash-es' import { useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import { RiMoreFill } from '@remixicon/react' -import { intersection } from 'lodash-es' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' import BlockSelector from '@/app/components/workflow/block-selector' import { useAvailableBlocks, useNodesInteractions, } from '@/app/components/workflow/hooks' -import type { - CommonNodeType, - OnSelectBlock, -} from '@/app/components/workflow/types' type ChangeItemProps = { data: CommonNodeType @@ -44,7 +44,7 @@ const ChangeItem = ({ const renderTrigger = useCallback(() => { return ( - <div className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'> + <div className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover"> {t('workflow.panel.change')} </div> ) @@ -53,13 +53,13 @@ const ChangeItem = ({ return ( <BlockSelector onSelect={handleSelect} - placement='top-end' + placement="top-end" offset={{ mainAxis: 6, crossAxis: 8, }} trigger={renderTrigger} - popupClassName='!w-[328px]' + popupClassName="!w-[328px]" availableBlocksTypes={intersection(availablePrevBlocks, availableNextBlocks).filter(item => item !== data.type)} /> ) @@ -87,34 +87,34 @@ const Operator = ({ return ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: -4 }} open={open} onOpenChange={onOpenChange} > <PortalToFollowElemTrigger onClick={() => onOpenChange(!open)}> - <Button className='h-6 w-6 p-0'> - <RiMoreFill className='h-4 w-4' /> + <Button className="h-6 w-6 p-0"> + <RiMoreFill className="h-4 w-4" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='system-md-regular min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'> - <div className='p-1'> + <PortalToFollowElemContent className="z-10"> + <div className="system-md-regular min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg"> + <div className="p-1"> <ChangeItem data={data} nodeId={nodeId} sourceHandle={sourceHandle} /> <div - className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleNodeDisconnect(nodeId)} > {t('workflow.common.disconnect')} </div> </div> - <div className='p-1'> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleNodeDelete(nodeId)} > {t('common.operation.delete')} diff --git a/web/app/components/workflow/nodes/_base/components/node-control.tsx b/web/app/components/workflow/nodes/_base/components/node-control.tsx index 2a52737bbd..da99528622 100644 --- a/web/app/components/workflow/nodes/_base/components/node-control.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-control.tsx @@ -1,24 +1,25 @@ import type { FC } from 'react' +import type { Node } from '../../../types' +import { + RiPlayLargeLine, +} from '@remixicon/react' import { memo, useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiPlayLargeLine, -} from '@remixicon/react' -import { - useNodesInteractions, -} from '../../../hooks' -import { type Node, NodeRunningStatus } from '../../../types' -import { canRunBySingle } from '../../../utils' -import PanelOperator from './panel-operator' import { Stop, } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import Tooltip from '@/app/components/base/tooltip' import { useWorkflowStore } from '@/app/components/workflow/store' +import { + useNodesInteractions, +} from '../../../hooks' +import { NodeRunningStatus } from '../../../types' +import { canRunBySingle } from '../../../utils' +import PanelOperator from './panel-operator' type NodeControlProps = Pick<Node, 'id' | 'data'> const NodeControl: FC<NodeControlProps> = ({ @@ -45,7 +46,7 @@ const NodeControl: FC<NodeControlProps> = ({ `} > <div - className='flex h-6 items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg px-0.5 text-text-tertiary shadow-md backdrop-blur-[5px]' + className="flex h-6 items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg px-0.5 text-text-tertiary shadow-md backdrop-blur-[5px]" onClick={e => e.stopPropagation()} > { @@ -66,15 +67,15 @@ const NodeControl: FC<NodeControlProps> = ({ > { isSingleRunning - ? <Stop className='h-3 w-3' /> + ? <Stop className="h-3 w-3" /> : ( - <Tooltip - popupContent={t('workflow.panel.runThisStep')} - asChild={false} - > - <RiPlayLargeLine className='h-3 w-3' /> - </Tooltip> - ) + <Tooltip + popupContent={t('workflow.panel.runThisStep')} + asChild={false} + > + <RiPlayLargeLine className="h-3 w-3" /> + </Tooltip> + ) } </div> ) @@ -84,7 +85,7 @@ const NodeControl: FC<NodeControlProps> = ({ data={data} offset={0} onOpenChange={handleOpenChange} - triggerClassName='!w-5 !h-5' + triggerClassName="!w-5 !h-5" /> </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index 5b46e79616..85a4b18188 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -1,22 +1,19 @@ import type { MouseEvent } from 'react' +import type { PluginDefaultValue } from '../../../block-selector/types' +import type { Node } from '../../../types' import { memo, useCallback, useEffect, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { Handle, Position, } from 'reactflow' -import { useTranslation } from 'react-i18next' -import { - BlockEnum, - NodeRunningStatus, -} from '../../../types' -import type { Node } from '../../../types' +import { cn } from '@/utils/classnames' import BlockSelector from '../../../block-selector' -import type { PluginDefaultValue } from '../../../block-selector/types' import { useAvailableBlocks, useIsChatMode, @@ -27,7 +24,10 @@ import { useStore, useWorkflowStore, } from '../../../store' -import { cn } from '@/utils/classnames' +import { + BlockEnum, + NodeRunningStatus, +} from '../../../types' type NodeHandleProps = { handleId: string @@ -75,7 +75,7 @@ export const NodeTargetHandle = memo(({ <> <Handle id={handleId} - type='target' + type="target" position={Position.Left} className={cn( 'z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none', @@ -101,7 +101,7 @@ export const NodeTargetHandle = memo(({ onOpenChange={handleOpenChange} onSelect={handleSelect} asChild - placement='left' + placement="left" triggerClassName={open => ` hidden absolute left-0 top-0 pointer-events-none ${nodeSelectorClassName} @@ -186,7 +186,7 @@ export const NodeSourceHandle = memo(({ return ( <Handle id={handleId} - type='source' + type="source" position={Position.Right} className={cn( 'group/handle z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none', @@ -201,14 +201,14 @@ export const NodeSourceHandle = memo(({ isConnectable={isConnectable} onClick={handleHandleClick} > - <div className='absolute -top-1 left-1/2 hidden -translate-x-1/2 -translate-y-full rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg group-hover/handle:block'> - <div className='system-xs-regular text-text-tertiary'> - <div className=' whitespace-nowrap'> - <span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.click.title')}</span> + <div className="absolute -top-1 left-1/2 hidden -translate-x-1/2 -translate-y-full rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg group-hover/handle:block"> + <div className="system-xs-regular text-text-tertiary"> + <div className=" whitespace-nowrap"> + <span className="system-xs-medium text-text-secondary">{t('workflow.common.parallelTip.click.title')}</span> {t('workflow.common.parallelTip.click.desc')} </div> <div> - <span className='system-xs-medium text-text-secondary'>{t('workflow.common.parallelTip.drag.title')}</span> + <span className="system-xs-medium text-text-secondary">{t('workflow.common.parallelTip.drag.title')}</span> {t('workflow.common.parallelTip.drag.desc')} </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/node-resizer.tsx b/web/app/components/workflow/nodes/_base/components/node-resizer.tsx index 479e1ad56e..db55e1e533 100644 --- a/web/app/components/workflow/nodes/_base/components/node-resizer.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-resizer.tsx @@ -1,12 +1,12 @@ +import type { OnResize } from 'reactflow' +import type { CommonNodeType } from '../../../types' import { memo, useCallback, } from 'react' -import type { OnResize } from 'reactflow' import { NodeResizeControl } from 'reactflow' -import { useNodesInteractions } from '../../../hooks' -import type { CommonNodeType } from '../../../types' import { cn } from '@/utils/classnames' +import { useNodesInteractions } from '../../../hooks' const Icon = () => { return ( @@ -42,16 +42,17 @@ const NodeResizer = ({ <div className={cn( 'hidden group-hover:block', nodeData.selected && '!block', - )}> + )} + > <NodeResizeControl - position='bottom-right' - className='!border-none !bg-transparent' + position="bottom-right" + className="!border-none !bg-transparent" onResize={handleResize} minWidth={minWidth} minHeight={minHeight} maxWidth={maxWidth} > - <div className='absolute bottom-[1px] right-[1px]'>{icon}</div> + <div className="absolute bottom-[1px] right-[1px]">{icon}</div> </NodeResizeControl> </div> ) diff --git a/web/app/components/workflow/nodes/_base/components/option-card.tsx b/web/app/components/workflow/nodes/_base/components/option-card.tsx index ebbdc92b2d..7c3a06daeb 100644 --- a/web/app/components/workflow/nodes/_base/components/option-card.tsx +++ b/web/app/components/workflow/nodes/_base/components/option-card.tsx @@ -1,10 +1,10 @@ 'use client' -import type { FC } from 'react' -import React, { useCallback } from 'react' import type { VariantProps } from 'class-variance-authority' +import type { FC } from 'react' import { cva } from 'class-variance-authority' -import { cn } from '@/utils/classnames' +import React, { useCallback } from 'react' import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' const variants = cva([], { variants: { @@ -17,8 +17,7 @@ const variants = cva([], { defaultVariants: { align: 'center', }, -}, -) +}) type Props = { className?: string @@ -59,14 +58,15 @@ const OptionCard: FC<Props> = ({ > <span>{title}</span> {tooltip - && <Tooltip - popupContent={ - <div className='w-[240px]'> - {tooltip} - </div> - } - /> - } + && ( + <Tooltip + popupContent={( + <div className="w-[240px]"> + {tooltip} + </div> + )} + /> + )} </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/output-vars.tsx b/web/app/components/workflow/nodes/_base/components/output-vars.tsx index b1599ce541..2c646148b3 100644 --- a/web/app/components/workflow/nodes/_base/components/output-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/output-vars.tsx @@ -3,8 +3,8 @@ import type { FC, ReactNode } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' -import TreeIndentLine from './variable/object-child-tree-panel/tree-indent-line' import { cn } from '@/utils/classnames' +import TreeIndentLine from './variable/object-child-tree-panel/tree-indent-line' type Props = { className?: string @@ -56,17 +56,17 @@ export const VarItem: FC<VarItemProps> = ({ return ( <div className={cn('flex', isIndent && 'relative left-[-7px]')}> {isIndent && <TreeIndentLine depth={1} />} - <div className='py-1'> - <div className='flex'> - <div className='flex items-center leading-[18px]'> - <div className='code-sm-semibold text-text-secondary'>{name}</div> - <div className='system-xs-regular ml-2 text-text-tertiary'>{type}</div> + <div className="py-1"> + <div className="flex"> + <div className="flex items-center leading-[18px]"> + <div className="code-sm-semibold text-text-secondary">{name}</div> + <div className="system-xs-regular ml-2 text-text-tertiary">{type}</div> </div> </div> - <div className='system-xs-regular mt-0.5 text-text-tertiary'> + <div className="system-xs-regular mt-0.5 text-text-tertiary"> {description} {subItems && ( - <div className='ml-2 border-l border-gray-200 pl-2'> + <div className="ml-2 border-l border-gray-200 pl-2"> {subItems.map((item, index) => ( <VarItem key={index} diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx index 8b6d137127..47e1c4b22b 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx @@ -1,10 +1,14 @@ +import type { + Node, + OnSelectBlock, +} from '@/app/components/workflow/types' +import { intersection } from 'lodash-es' import { memo, useCallback, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { intersection } from 'lodash-es' import BlockSelector from '@/app/components/workflow/block-selector' import { useAvailableBlocks, @@ -12,10 +16,6 @@ import { useNodesInteractions, } from '@/app/components/workflow/hooks' import { useHooksStore } from '@/app/components/workflow/hooks-store' -import type { - Node, - OnSelectBlock, -} from '@/app/components/workflow/types' import { BlockEnum, isTriggerNode } from '@/app/components/workflow/types' import { FlowType } from '@/types/common' @@ -60,7 +60,7 @@ const ChangeBlock = ({ const renderTrigger = useCallback(() => { return ( - <div className='flex h-8 w-[232px] cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'> + <div className="flex h-8 w-[232px] cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover"> {t('workflow.panel.changeBlock')} </div> ) @@ -68,14 +68,14 @@ const ChangeBlock = ({ return ( <BlockSelector - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: -36, crossAxis: 4, }} onSelect={handleSelect} trigger={renderTrigger} - popupClassName='min-w-[240px]' + popupClassName="min-w-[240px]" availableBlocksTypes={availableNodes} showStartTab={showStartTab} ignoreNodeIds={ignoreNodeIds} diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx index e1af8cc84f..665b1441b0 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/index.tsx @@ -1,17 +1,17 @@ +import type { OffsetOptions } from '@floating-ui/react' +import type { Node } from '@/app/components/workflow/types' +import { RiMoreFill } from '@remixicon/react' import { memo, useCallback, useState, } from 'react' -import { RiMoreFill } from '@remixicon/react' -import type { OffsetOptions } from '@floating-ui/react' -import PanelOperatorPopup from './panel-operator-popup' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { Node } from '@/app/components/workflow/types' +import PanelOperatorPopup from './panel-operator-popup' type PanelOperatorProps = { id: string @@ -44,7 +44,7 @@ const PanelOperator = ({ return ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={offset} open={open} onOpenChange={handleOpenChange} @@ -58,10 +58,10 @@ const PanelOperator = ({ ${triggerClassName} `} > - <RiMoreFill className={'h-4 w-4 text-text-tertiary'} /> + <RiMoreFill className="h-4 w-4 text-text-tertiary" /> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[11]'> + <PortalToFollowElemContent className="z-[11]"> <PanelOperatorPopup id={id} data={data} diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx index 613744a50e..43419dc957 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx @@ -1,13 +1,11 @@ +import type { Node } from '@/app/components/workflow/types' import { memo, useMemo, } from 'react' import { useTranslation } from 'react-i18next' import { useEdges } from 'reactflow' -import ChangeBlock from './change-block' -import { - canRunBySingle, -} from '@/app/components/workflow/utils' +import { CollectionType } from '@/app/components/tools/types' import { useNodeDataUpdate, useNodeMetaData, @@ -16,11 +14,13 @@ import { useNodesSyncDraft, } from '@/app/components/workflow/hooks' import ShortcutsName from '@/app/components/workflow/shortcuts-name' -import type { Node } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' -import { CollectionType } from '@/app/components/tools/types' +import { + canRunBySingle, +} from '@/app/components/workflow/utils' import { useAllWorkflowTools } from '@/service/use-tools' import { canFindTool } from '@/utils' +import ChangeBlock from './change-block' type PanelOperatorPopupProps = { id: string @@ -53,17 +53,18 @@ const PanelOperatorPopup = ({ const { data: workflowTools } = useAllWorkflowTools() const isWorkflowTool = data.type === BlockEnum.Tool && data.provider_type === CollectionType.workflow const workflowAppId = useMemo(() => { - if (!isWorkflowTool || !workflowTools || !data.provider_id) return undefined + if (!isWorkflowTool || !workflowTools || !data.provider_id) + return undefined const workflowTool = workflowTools.find(item => canFindTool(item.id, data.provider_id)) return workflowTool?.workflow_app_id }, [isWorkflowTool, workflowTools, data.provider_id]) return ( - <div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> + <div className="w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"> { (showChangeBlock || canRunBySingle(data.type, isChildNode)) && ( <> - <div className='p-1'> + <div className="p-1"> { canRunBySingle(data.type, isChildNode) && ( <div @@ -92,7 +93,7 @@ const PanelOperatorPopup = ({ ) } </div> - <div className='h-px bg-divider-regular'></div> + <div className="h-px bg-divider-regular"></div> </> ) } @@ -102,9 +103,9 @@ const PanelOperatorPopup = ({ { !nodeMetaData.isSingleton && ( <> - <div className='p-1'> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => { onClosePopup() handleNodesCopy(id) @@ -114,7 +115,7 @@ const PanelOperatorPopup = ({ <ShortcutsName keys={['ctrl', 'c']} /> </div> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => { onClosePopup() handleNodesDuplicate(id) @@ -124,14 +125,14 @@ const PanelOperatorPopup = ({ <ShortcutsName keys={['ctrl', 'd']} /> </div> </div> - <div className='h-px bg-divider-regular'></div> + <div className="h-px bg-divider-regular"></div> </> ) } { !nodeMetaData.isUndeletable && ( <> - <div className='p-1'> + <div className="p-1"> <div className={` flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary @@ -143,7 +144,7 @@ const PanelOperatorPopup = ({ <ShortcutsName keys={['del']} /> </div> </div> - <div className='h-px bg-divider-regular'></div> + <div className="h-px bg-divider-regular"></div> </> ) } @@ -153,43 +154,45 @@ const PanelOperatorPopup = ({ { isWorkflowTool && workflowAppId && ( <> - <div className='p-1'> + <div className="p-1"> <a href={`/app/${workflowAppId}/workflow`} - target='_blank' - className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + target="_blank" + className="flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" > {t('workflow.panel.openWorkflow')} </a> </div> - <div className='h-px bg-divider-regular'></div> + <div className="h-px bg-divider-regular"></div> </> ) } { showHelpLink && nodeMetaData.helpLinkUri && ( <> - <div className='p-1'> + <div className="p-1"> <a href={nodeMetaData.helpLinkUri} - target='_blank' - className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + target="_blank" + className="flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" > {t('workflow.panel.helpLink')} </a> </div> - <div className='h-px bg-divider-regular'></div> + <div className="h-px bg-divider-regular"></div> </> ) } - <div className='p-1'> - <div className='px-3 py-2 text-xs text-text-tertiary'> - <div className='mb-1 flex h-[22px] items-center font-medium'> + <div className="p-1"> + <div className="px-3 py-2 text-xs text-text-tertiary"> + <div className="mb-1 flex h-[22px] items-center font-medium"> {t('workflow.panel.about').toLocaleUpperCase()} </div> - <div className='mb-1 leading-[18px] text-text-secondary'>{nodeMetaData.description}</div> - <div className='leading-[18px]'> - {t('workflow.panel.createdBy')} {nodeMetaData.author} + <div className="mb-1 leading-[18px] text-text-secondary">{nodeMetaData.description}</div> + <div className="leading-[18px]"> + {t('workflow.panel.createdBy')} + {' '} + {nodeMetaData.author} </div> </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index ff0e7c90b2..c2bc6481ff 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -1,41 +1,41 @@ 'use client' import type { FC, ReactNode } from 'react' -import React, { useCallback, useRef } from 'react' -import { - RiDeleteBinLine, -} from '@remixicon/react' -import copy from 'copy-to-clipboard' -import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import { BlockEnum, EditionType } from '../../../../types' import type { ModelConfig, Node, NodeOutPutVar, Variable, } from '../../../../types' +import { + RiDeleteBinLine, +} from '@remixicon/react' +import { useBoolean } from 'ahooks' +import copy from 'copy-to-clipboard' +import React, { useCallback, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import ActionButton from '@/app/components/base/action-button' -import Wrap from '../editor/wrap' -import { CodeLanguage } from '../../../code/types' -import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn' -import { cn } from '@/utils/classnames' -import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn' -import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' -import PromptEditor from '@/app/components/base/prompt-editor' import { Copy, CopyCheck, } from '@/app/components/base/icons/src/vender/line/files' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import ActionButton from '@/app/components/base/action-button' -import Tooltip from '@/app/components/base/tooltip' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' -import Switch from '@/app/components/base/switch' import { Jinja } from '@/app/components/base/icons/src/vender/workflow' -import { useStore } from '@/app/components/workflow/store' +import PromptEditor from '@/app/components/base/prompt-editor' +import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block' +import Switch from '@/app/components/base/switch' +import Tooltip from '@/app/components/base/tooltip' import { useWorkflowVariableType } from '@/app/components/workflow/hooks' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' +import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn' +import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' +import { useStore } from '@/app/components/workflow/store' +import { useEventEmitterContextContext } from '@/context/event-emitter' +import { cn } from '@/utils/classnames' +import { BlockEnum, EditionType } from '../../../../types' +import { CodeLanguage } from '../../../code/types' +import PromptGeneratorBtn from '../../../llm/components/prompt-generator-btn' +import Wrap from '../editor/wrap' type Props = { className?: string @@ -158,39 +158,43 @@ const Editor: FC<Props> = ({ <div ref={ref} className={cn(isFocus ? (gradientBorder && 'bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2') : 'bg-transparent', isExpand && 'h-full', '!rounded-[9px] p-0.5', containerClassName)}> <div className={cn(isFocus ? 'bg-background-default' : 'bg-components-input-bg-normal', isExpand && 'flex h-full flex-col', 'rounded-lg', containerClassName)}> <div className={cn('flex items-center justify-between pl-3 pr-2 pt-1', headerClassName)}> - <div className='flex gap-2'> - <div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}>{title} {required && <span className='text-text-destructive'>*</span>}</div> + <div className="flex gap-2"> + <div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}> + {title} + {' '} + {required && <span className="text-text-destructive">*</span>} + </div> {titleTooltip && <Tooltip popupContent={titleTooltip} />} </div> - <div className='flex items-center'> - <div className='text-xs font-medium leading-[18px] text-text-tertiary'>{value?.length || 0}</div> + <div className="flex items-center"> + <div className="text-xs font-medium leading-[18px] text-text-tertiary">{value?.length || 0}</div> {isSupportPromptGenerator && ( <PromptGeneratorBtn nodeId={nodeId!} editorId={editorId} - className='ml-[5px]' + className="ml-[5px]" onGenerated={onGenerated} modelConfig={modelConfig} currentPrompt={value} /> )} - <div className='ml-2 mr-2 h-3 w-px bg-divider-regular'></div> + <div className="ml-2 mr-2 h-3 w-px bg-divider-regular"></div> {/* Operations */} - <div className='flex items-center space-x-[2px]'> + <div className="flex items-center space-x-[2px]"> {isSupportJinja && ( <Tooltip - popupContent={ + popupContent={( <div> <div>{t('workflow.common.enableJinja')}</div> - <a className='text-text-accent' target='_blank' href='https://jinja.palletsprojects.com/en/2.10.x/'>{t('workflow.common.learnMore')}</a> + <a className="text-text-accent" target="_blank" href="https://jinja.palletsprojects.com/en/2.10.x/">{t('workflow.common.learnMore')}</a> </div> - } + )} > <div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}> - <Jinja className='h-3 w-6 text-text-quaternary' /> + <Jinja className="h-3 w-6 text-text-quaternary" /> <Switch - size='sm' + size="sm" defaultValue={editionType === EditionType.jinja2} onChange={(checked) => { onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic) @@ -205,27 +209,26 @@ const Editor: FC<Props> = ({ popupContent={`${t('workflow.common.insertVarTip')}`} > <ActionButton onClick={handleInsertVariable}> - <Variable02 className='h-4 w-4' /> + <Variable02 className="h-4 w-4" /> </ActionButton> </Tooltip> )} {showRemove && ( <ActionButton onClick={onRemove}> - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </ActionButton> )} {!isCopied ? ( - <ActionButton onClick={handleCopy}> - <Copy className='h-4 w-4' /> - </ActionButton> - ) + <ActionButton onClick={handleCopy}> + <Copy className="h-4 w-4" /> + </ActionButton> + ) : ( - <ActionButton> - <CopyCheck className='h-4 w-4' /> - </ActionButton> - ) - } + <ActionButton> + <CopyCheck className="h-4 w-4" /> + </ActionButton> + )} <ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} /> </div> @@ -236,83 +239,83 @@ const Editor: FC<Props> = ({ <div className={cn('pb-2', isExpand && 'flex grow flex-col')}> {!(isSupportJinja && editionType === EditionType.jinja2) ? ( - <div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}> - <PromptEditor - key={controlPromptEditorRerenderKey} - placeholder={placeholder} - placeholderClassName={placeholderClassName} - instanceId={instanceId} - compact - className={cn('min-h-[56px]', inputClassName)} - style={isExpand ? { height: editorExpandHeight - 5 } : {}} - value={value} - contextBlock={{ - show: justVar ? false : isShowContext, - selectable: !hasSetBlockStatus?.context, - canNotAddContext: true, - }} - historyBlock={{ - show: justVar ? false : isShowHistory, - selectable: !hasSetBlockStatus?.history, - history: { - user: 'Human', - assistant: 'Assistant', - }, - }} - queryBlock={{ - show: false, // use [sys.query] instead of query block - selectable: false, - }} - workflowVariableBlock={{ - show: true, - variables: nodesOutputVars || [], - getVarType: getVarType as any, - workflowNodesMap: availableNodes.reduce((acc, node) => { - acc[node.id] = { - title: node.data.title, - type: node.data.type, - width: node.width, - height: node.height, - position: node.position, - } - if (node.data.type === BlockEnum.Start) { - acc.sys = { - title: t('workflow.blocks.start'), - type: BlockEnum.Start, + <div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}> + <PromptEditor + key={controlPromptEditorRerenderKey} + placeholder={placeholder} + placeholderClassName={placeholderClassName} + instanceId={instanceId} + compact + className={cn('min-h-[56px]', inputClassName)} + style={isExpand ? { height: editorExpandHeight - 5 } : {}} + value={value} + contextBlock={{ + show: justVar ? false : isShowContext, + selectable: !hasSetBlockStatus?.context, + canNotAddContext: true, + }} + historyBlock={{ + show: justVar ? false : isShowHistory, + selectable: !hasSetBlockStatus?.history, + history: { + user: 'Human', + assistant: 'Assistant', + }, + }} + queryBlock={{ + show: false, // use [sys.query] instead of query block + selectable: false, + }} + workflowVariableBlock={{ + show: true, + variables: nodesOutputVars || [], + getVarType: getVarType as any, + workflowNodesMap: availableNodes.reduce((acc, node) => { + acc[node.id] = { + title: node.data.title, + type: node.data.type, + width: node.width, + height: node.height, + position: node.position, } - } - return acc - }, {} as any), - showManageInputField: !!pipelineId, - onManageInputField: () => setShowInputFieldPanel?.(true), - }} - onChange={onChange} - onBlur={setBlur} - onFocus={setFocus} - editable={!readOnly} - isSupportFileVar={isSupportFileVar} - /> - {/* to patch Editor not support dynamic change editable status */} - {readOnly && <div className='absolute inset-0 z-10'></div>} - </div> - ) + if (node.data.type === BlockEnum.Start) { + acc.sys = { + title: t('workflow.blocks.start'), + type: BlockEnum.Start, + } + } + return acc + }, {} as any), + showManageInputField: !!pipelineId, + onManageInputField: () => setShowInputFieldPanel?.(true), + }} + onChange={onChange} + onBlur={setBlur} + onFocus={setFocus} + editable={!readOnly} + isSupportFileVar={isSupportFileVar} + /> + {/* to patch Editor not support dynamic change editable status */} + {readOnly && <div className="absolute inset-0 z-10"></div>} + </div> + ) : ( - <div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}> - <CodeEditor - availableVars={nodesOutputVars || []} - varList={varList} - onAddVar={handleAddVariable} - isInNode - readOnly={readOnly} - language={CodeLanguage.python3} - value={value} - onChange={onChange} - noWrapper - isExpand={isExpand} - className={inputClassName} - /> - </div> - )} + <div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}> + <CodeEditor + availableVars={nodesOutputVars || []} + varList={varList} + onAddVar={handleAddVariable} + isInNode + readOnly={readOnly} + language={CodeLanguage.python3} + value={value} + onChange={onChange} + noWrapper + isExpand={isExpand} + className={inputClassName} + /> + </div> + )} </div> </div> </div> diff --git a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx index 062ce278e2..fbec61d516 100644 --- a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx @@ -1,13 +1,14 @@ 'use client' import type { FC } from 'react' import React from 'react' +import { + VariableLabelInText, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { cn } from '@/utils/classnames' import { useWorkflow } from '../../../hooks' import { BlockEnum } from '../../../types' import { getNodeInfoById, isSystemVar } from './variable/utils' -import { - VariableLabelInText, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' + type Props = { nodeId: string value: string @@ -29,29 +30,31 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({ const res = (() => { const vars: string[] = [] - const strWithVarPlaceholder = value.replaceAll(/{{#([^#]*)#}}/g, (_match, p1) => { + const strWithVarPlaceholder = value.replaceAll(/\{\{#([^#]*)#\}\}/g, (_match, p1) => { vars.push(p1) return VAR_PLACEHOLDER }) const html: React.JSX.Element[] = strWithVarPlaceholder.split(VAR_PLACEHOLDER).map((str, index) => { if (!vars[index]) - return <span className='relative top-[-3px] leading-[16px]' key={index}>{str}</span> + return <span className="relative top-[-3px] leading-[16px]" key={index}>{str}</span> const value = vars[index].split('.') const isSystem = isSystemVar(value) const node = (isSystem ? startNode : getNodeInfoById(availableNodes, value[0]))?.data const isShowAPart = value.length > 2 - return (<span key={index}> - <span className='relative top-[-3px] leading-[16px]'>{str}</span> - <VariableLabelInText - nodeTitle={node?.title} - nodeType={node?.type} - notShowFullPath={isShowAPart} - variables={value} - /> - </span>) + return ( + <span key={index}> + <span className="relative top-[-3px] leading-[16px]">{str}</span> + <VariableLabelInText + nodeTitle={node?.title} + nodeType={node?.type} + notShowFullPath={isShowAPart} + variables={value} + /> + </span> + ) }) return html })() diff --git a/web/app/components/workflow/nodes/_base/components/remove-button.tsx b/web/app/components/workflow/nodes/_base/components/remove-button.tsx index 62381f8c2a..7b77f956d3 100644 --- a/web/app/components/workflow/nodes/_base/components/remove-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/remove-button.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' -import React from 'react' import { RiDeleteBinLine } from '@remixicon/react' +import React from 'react' import ActionButton from '@/app/components/base/action-button' type Props = { @@ -13,8 +13,8 @@ const Remove: FC<Props> = ({ onClick, }) => { return ( - <ActionButton size='l' className='group shrink-0 hover:!bg-state-destructive-hover' onClick={onClick}> - <RiDeleteBinLine className='h-4 w-4 text-text-tertiary group-hover:text-text-destructive' /> + <ActionButton size="l" className="group shrink-0 hover:!bg-state-destructive-hover" onClick={onClick}> + <RiDeleteBinLine className="h-4 w-4 text-text-tertiary group-hover:text-text-destructive" /> </ActionButton> ) } diff --git a/web/app/components/workflow/nodes/_base/components/retry/hooks.ts b/web/app/components/workflow/nodes/_base/components/retry/hooks.ts index f8285358a0..9f3e68c9e9 100644 --- a/web/app/components/workflow/nodes/_base/components/retry/hooks.ts +++ b/web/app/components/workflow/nodes/_base/components/retry/hooks.ts @@ -1,12 +1,12 @@ +import type { WorkflowRetryConfig } from './types' +import type { NodeTracing } from '@/types/workflow' import { useCallback, useState, } from 'react' -import type { WorkflowRetryConfig } from './types' import { useNodeDataUpdate, } from '@/app/components/workflow/hooks' -import type { NodeTracing } from '@/types/workflow' export const useRetryConfig = ( id: string, diff --git a/web/app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx b/web/app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx index a7594a9567..6e9c5f962b 100644 --- a/web/app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx +++ b/web/app/components/workflow/nodes/_base/components/retry/retry-on-node.tsx @@ -1,11 +1,11 @@ -import { useMemo } from 'react' -import { useTranslation } from 'react-i18next' +import type { Node } from '@/app/components/workflow/types' import { RiAlertFill, RiCheckboxCircleFill, RiLoader2Line, } from '@remixicon/react' -import type { Node } from '@/app/components/workflow/types' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { NodeRunningStatus } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' @@ -38,14 +38,15 @@ const RetryOnNode = ({ return null return ( - <div className='mb-1 px-3'> + <div className="mb-1 px-3"> <div className={cn( 'system-xs-medium-uppercase flex items-center justify-between rounded-md border-[0.5px] border-transparent bg-workflow-block-parma-bg px-[5px] py-1 text-text-tertiary', isRunning && 'border-state-accent-active bg-state-accent-hover text-text-accent', isSuccessful && 'border-state-success-active bg-state-success-hover text-text-success', (isException || isFailed) && 'border-state-warning-active bg-state-warning-hover text-text-warning', - )}> - <div className='flex items-center'> + )} + > + <div className="flex items-center"> { showDefault && ( t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries }) @@ -54,7 +55,7 @@ const RetryOnNode = ({ { isRunning && ( <> - <RiLoader2Line className='mr-1 h-3.5 w-3.5 animate-spin' /> + <RiLoader2Line className="mr-1 h-3.5 w-3.5 animate-spin" /> {t('workflow.nodes.common.retry.retrying')} </> ) @@ -62,7 +63,7 @@ const RetryOnNode = ({ { isSuccessful && ( <> - <RiCheckboxCircleFill className='mr-1 h-3.5 w-3.5' /> + <RiCheckboxCircleFill className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.common.retry.retrySuccessful')} </> ) @@ -70,7 +71,7 @@ const RetryOnNode = ({ { (isFailed || isException) && ( <> - <RiAlertFill className='mr-1 h-3.5 w-3.5' /> + <RiAlertFill className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.common.retry.retryFailed')} </> ) @@ -79,7 +80,9 @@ const RetryOnNode = ({ { !showDefault && !!data._retryIndex && ( <div> - {data._retryIndex}/{data.retry_config?.max_retries} + {data._retryIndex} + / + {data.retry_config?.max_retries} </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/retry/retry-on-panel.tsx b/web/app/components/workflow/nodes/_base/components/retry/retry-on-panel.tsx index d074d0c60c..bf35e28ae2 100644 --- a/web/app/components/workflow/nodes/_base/components/retry/retry-on-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/retry/retry-on-panel.tsx @@ -1,13 +1,13 @@ -import { useTranslation } from 'react-i18next' -import { useRetryConfig } from './hooks' -import s from './style.module.css' -import Switch from '@/app/components/base/switch' -import Slider from '@/app/components/base/slider' -import Input from '@/app/components/base/input' import type { Node, } from '@/app/components/workflow/types' +import { useTranslation } from 'react-i18next' +import Input from '@/app/components/base/input' +import Slider from '@/app/components/base/slider' +import Switch from '@/app/components/base/switch' import Split from '@/app/components/workflow/nodes/_base/components/split' +import { useRetryConfig } from './hooks' +import s from './style.module.css' type RetryOnPanelProps = Pick<Node, 'id' | 'data'> const RetryOnPanel = ({ @@ -52,10 +52,10 @@ const RetryOnPanel = ({ return ( <> - <div className='pt-2'> - <div className='flex h-10 items-center justify-between px-4 py-2'> - <div className='flex items-center'> - <div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div> + <div className="pt-2"> + <div className="flex h-10 items-center justify-between px-4 py-2"> + <div className="flex items-center"> + <div className="system-sm-semibold-uppercase mr-0.5 text-text-secondary">{t('workflow.nodes.common.retry.retryOnFailure')}</div> </div> <Switch defaultValue={retry_config?.retry_enabled} @@ -64,45 +64,43 @@ const RetryOnPanel = ({ </div> { retry_config?.retry_enabled && ( - <div className='px-4 pb-2'> - <div className='mb-1 flex w-full items-center'> - <div className='system-xs-medium-uppercase mr-2 grow text-text-secondary'>{t('workflow.nodes.common.retry.maxRetries')}</div> + <div className="px-4 pb-2"> + <div className="mb-1 flex w-full items-center"> + <div className="system-xs-medium-uppercase mr-2 grow text-text-secondary">{t('workflow.nodes.common.retry.maxRetries')}</div> <Slider - className='mr-3 w-[108px]' + className="mr-3 w-[108px]" value={retry_config?.max_retries || 3} onChange={handleMaxRetriesChange} min={1} max={10} /> <Input - type='number' - wrapperClassName='w-[100px]' + type="number" + wrapperClassName="w-[100px]" value={retry_config?.max_retries || 3} onChange={e => - handleMaxRetriesChange(Number.parseInt(e.currentTarget.value, 10) || 3) - } + handleMaxRetriesChange(Number.parseInt(e.currentTarget.value, 10) || 3)} min={1} max={10} unit={t('workflow.nodes.common.retry.times') || ''} className={s.input} /> </div> - <div className='flex items-center'> - <div className='system-xs-medium-uppercase mr-2 grow text-text-secondary'>{t('workflow.nodes.common.retry.retryInterval')}</div> + <div className="flex items-center"> + <div className="system-xs-medium-uppercase mr-2 grow text-text-secondary">{t('workflow.nodes.common.retry.retryInterval')}</div> <Slider - className='mr-3 w-[108px]' + className="mr-3 w-[108px]" value={retry_config?.retry_interval || 1000} onChange={handleRetryIntervalChange} min={100} max={5000} /> <Input - type='number' - wrapperClassName='w-[100px]' + type="number" + wrapperClassName="w-[100px]" value={retry_config?.retry_interval || 1000} onChange={e => - handleRetryIntervalChange(Number.parseInt(e.currentTarget.value, 10) || 1000) - } + handleRetryIntervalChange(Number.parseInt(e.currentTarget.value, 10) || 1000)} min={100} max={5000} unit={t('workflow.nodes.common.retry.ms') || ''} @@ -113,7 +111,7 @@ const RetryOnPanel = ({ ) } </div> - <Split className='mx-4 mt-2' /> + <Split className="mx-4 mt-2" /> </> ) } diff --git a/web/app/components/workflow/nodes/_base/components/selector.tsx b/web/app/components/workflow/nodes/_base/components/selector.tsx index 7b02303b29..58b1ecfa31 100644 --- a/web/app/components/workflow/nodes/_base/components/selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/selector.tsx @@ -1,10 +1,11 @@ 'use client' import type { FC } from 'react' -import React from 'react' import { useBoolean, useClickAway } from 'ahooks' -import { cn } from '@/utils/classnames' +import React from 'react' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' import { Check } from '@/app/components/base/icons/src/vender/line/general' +import { cn } from '@/utils/classnames' + type Item = { value: string label: string @@ -55,21 +56,22 @@ const TypeSelector: FC<Props> = ({ <div className={cn(!trigger && !noLeft && 'left-[-8px]', 'relative select-none', className)} ref={ref}> {trigger ? ( - <div - onClick={toggleShow} - className={cn(!readonly && 'cursor-pointer')} - > - {trigger} - </div> - ) + <div + onClick={toggleShow} + className={cn(!readonly && 'cursor-pointer')} + > + {trigger} + </div> + ) : ( - <div - onClick={toggleShow} - className={cn(showOption && 'bg-state-base-hover', 'flex h-5 cursor-pointer items-center rounded-md pl-1 pr-0.5 text-xs font-semibold text-text-secondary hover:bg-state-base-hover')}> - <div className={cn('text-sm font-semibold', uppercase && 'uppercase', noValue && 'text-text-tertiary', triggerClassName)}>{!noValue ? item?.label : placeholder}</div> - {!readonly && <DropDownIcon className='h-3 w-3 ' />} - </div> - )} + <div + onClick={toggleShow} + className={cn(showOption && 'bg-state-base-hover', 'flex h-5 cursor-pointer items-center rounded-md pl-1 pr-0.5 text-xs font-semibold text-text-secondary hover:bg-state-base-hover')} + > + <div className={cn('text-sm font-semibold', uppercase && 'uppercase', noValue && 'text-text-tertiary', triggerClassName)}>{!noValue ? item?.label : placeholder}</div> + {!readonly && <DropDownIcon className="h-3 w-3 " />} + </div> + )} {(showOption && !readonly) && ( <div className={cn('absolute top-[24px] z-10 w-[120px] select-none rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg', popupClassName)}> @@ -83,13 +85,11 @@ const TypeSelector: FC<Props> = ({ className={cn(itemClassName, uppercase && 'uppercase', 'flex h-[30px] min-w-[44px] cursor-pointer items-center justify-between rounded-lg px-3 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover')} > <div>{item.label}</div> - {showChecked && item.value === value && <Check className='h-4 w-4 text-text-primary' />} + {showChecked && item.value === value && <Check className="h-4 w-4 text-text-primary" />} </div> - )) - } + ))} </div> - ) - } + )} </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/setting-item.tsx b/web/app/components/workflow/nodes/_base/components/setting-item.tsx index 5dbb962624..c629dba46b 100644 --- a/web/app/components/workflow/nodes/_base/components/setting-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/setting-item.tsx @@ -1,7 +1,8 @@ +import type { ComponentProps, PropsWithChildren, ReactNode } from 'react' +import { memo } from 'react' import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import { cn } from '@/utils/classnames' -import { type ComponentProps, type PropsWithChildren, type ReactNode, memo } from 'react' export type SettingItemProps = PropsWithChildren<{ label: string @@ -12,17 +13,19 @@ export type SettingItemProps = PropsWithChildren<{ export const SettingItem = memo(({ label, children, status, tooltip }: SettingItemProps) => { const indicator: ComponentProps<typeof Indicator>['color'] = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined const needTooltip = ['error', 'warning'].includes(status as any) - return <div className='relative flex items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1.5 py-1 text-xs font-normal'> - <div className={cn('system-xs-medium-uppercase max-w-full shrink-0 truncate text-text-tertiary', !!children && 'max-w-[100px]')}> - {label} - </div> - <Tooltip popupContent={tooltip} disabled={!needTooltip}> - <div className='system-xs-medium truncate text-right text-text-secondary'> - {children} + return ( + <div className="relative flex items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1.5 py-1 text-xs font-normal"> + <div className={cn('system-xs-medium-uppercase max-w-full shrink-0 truncate text-text-tertiary', !!children && 'max-w-[100px]')}> + {label} </div> - </Tooltip> - {indicator && <Indicator color={indicator} className='absolute -right-0.5 -top-0.5' />} - </div> + <Tooltip popupContent={tooltip} disabled={!needTooltip}> + <div className="system-xs-medium truncate text-right text-text-secondary"> + {children} + </div> + </Tooltip> + {indicator && <Indicator color={indicator} className="absolute -right-0.5 -top-0.5" />} + </div> + ) }) SettingItem.displayName = 'SettingItem' diff --git a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx index 816bac812f..d3753bb5ff 100644 --- a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx @@ -1,8 +1,9 @@ 'use client' import type { FC } from 'react' import React from 'react' -import { cn } from '@/utils/classnames' import VarHighlight from '@/app/components/app/configuration/base/var-highlight' +import { cn } from '@/utils/classnames' + type Props = { isFocus?: boolean onFocus?: () => void @@ -46,20 +47,21 @@ const SupportVarInput: FC<Props> = ({ <div className={ cn(wrapClassName, 'flex h-full w-full') - } onClick={onFocus} + } + onClick={onFocus} > {(isFocus && !readonly && children) ? ( - children - ) + children + ) : ( - <div - className={cn(textClassName, 'h-full w-0 grow truncate whitespace-nowrap')} - title={value} - > - {renderSafeContent(value || '')} - </div> - )} + <div + className={cn(textClassName, 'h-full w-0 grow truncate whitespace-nowrap')} + title={value} + > + {renderSafeContent(value || '')} + </div> + )} </div> ) } diff --git a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx index c9d698337d..28e0603707 100644 --- a/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx +++ b/web/app/components/workflow/nodes/_base/components/switch-plugin-version.tsx @@ -1,20 +1,20 @@ 'use client' -import Badge from '@/app/components/base/badge' -import Tooltip from '@/app/components/base/tooltip' -import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' +import type { FC, ReactNode } from 'react' import { RiArrowLeftRightLine, RiExternalLinkLine } from '@remixicon/react' -import type { ReactNode } from 'react' -import { type FC, useCallback, useState } from 'react' import { useBoolean } from 'ahooks' -import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins' -import { cn } from '@/utils/classnames' -import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model' +import Link from 'next/link' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Badge from '@/app/components/base/badge' +import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index' +import Tooltip from '@/app/components/base/tooltip' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils' -import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index' -import Link from 'next/link' -import { useTranslation } from 'react-i18next' +import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model' +import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' +import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins' +import { cn } from '@/utils/classnames' import { getMarketplaceUrl } from '@/utils/var' export type SwitchPluginVersionProps = { @@ -31,8 +31,8 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => { const [isShow, setIsShow] = useState(false) const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false) const [target, setTarget] = useState<{ - version: string, - pluginUniqueIden: string; + version: string + pluginUniqueIden: string }>() const pluginDetails = useCheckInstalled({ pluginIds: [pluginId], @@ -58,7 +58,8 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => { onSuccess() { handleUpdatedFromMarketplace() }, - }) + }, + ) } const { t } = useTranslation() @@ -66,67 +67,75 @@ export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => { if (!uniqueIdentifier || !pluginId) return null - return <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod='hover'> - <div className={cn('flex w-fit items-center justify-center', className)} onClick={e => e.stopPropagation()}> - {isShowUpdateModal && pluginDetail && <PluginMutationModel - onCancel={hideUpdateModal} - plugin={pluginManifestToCardPluginProps({ - ...pluginDetail.declaration, - icon: icon!, - })} - mutation={mutation} - mutate={install} - confirmButtonText={t('workflow.nodes.agent.installPlugin.install')} - cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')} - modelTitle={t('workflow.nodes.agent.installPlugin.title')} - description={t('workflow.nodes.agent.installPlugin.desc')} - cardTitleLeft={<> - <Badge2 className='mx-1' size="s" state={BadgeState.Warning}> - {`${pluginDetail.version} -> ${target!.version}`} - </Badge2> - </>} - modalBottomLeft={ - <Link - className='flex items-center justify-center gap-1' - href={getMarketplaceUrl(`/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`)} - target='_blank' - > - <span className='system-xs-regular text-xs text-text-accent'> - {t('workflow.nodes.agent.installPlugin.changelog')} - </span> - <RiExternalLinkLine className='size-3 text-text-accent' /> - </Link> - } - />} - {pluginDetail && <PluginVersionPicker - isShow={isShow} - onShowChange={setIsShow} - pluginID={pluginId} - currentVersion={pluginDetail.version} - onSelect={(state) => { - setTarget({ - pluginUniqueIden: state.unique_identifier, - version: state.version, - }) - showUpdateModal() - }} - trigger={ - <Badge - className={cn( - 'mx-1 flex hover:bg-state-base-hover', - isShow && 'bg-state-base-hover', - )} - uppercase={true} - text={ + return ( + <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod="hover"> + <div className={cn('flex w-fit items-center justify-center', className)} onClick={e => e.stopPropagation()}> + {isShowUpdateModal && pluginDetail && ( + <PluginMutationModel + onCancel={hideUpdateModal} + plugin={pluginManifestToCardPluginProps({ + ...pluginDetail.declaration, + icon: icon!, + })} + mutation={mutation} + mutate={install} + confirmButtonText={t('workflow.nodes.agent.installPlugin.install')} + cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')} + modelTitle={t('workflow.nodes.agent.installPlugin.title')} + description={t('workflow.nodes.agent.installPlugin.desc')} + cardTitleLeft={( <> - <div>{pluginDetail.version}</div> - <RiArrowLeftRightLine className='ml-1 h-3 w-3 text-text-tertiary' /> + <Badge2 className="mx-1" size="s" state={BadgeState.Warning}> + {`${pluginDetail.version} -> ${target!.version}`} + </Badge2> </> - } - hasRedCornerMark={true} + )} + modalBottomLeft={( + <Link + className="flex items-center justify-center gap-1" + href={getMarketplaceUrl(`/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`)} + target="_blank" + > + <span className="system-xs-regular text-xs text-text-accent"> + {t('workflow.nodes.agent.installPlugin.changelog')} + </span> + <RiExternalLinkLine className="size-3 text-text-accent" /> + </Link> + )} /> - } - />} - </div> - </Tooltip> + )} + {pluginDetail && ( + <PluginVersionPicker + isShow={isShow} + onShowChange={setIsShow} + pluginID={pluginId} + currentVersion={pluginDetail.version} + onSelect={(state) => { + setTarget({ + pluginUniqueIden: state.unique_identifier, + version: state.version, + }) + showUpdateModal() + }} + trigger={( + <Badge + className={cn( + 'mx-1 flex hover:bg-state-base-hover', + isShow && 'bg-state-base-hover', + )} + uppercase={true} + text={( + <> + <div>{pluginDetail.version}</div> + <RiArrowLeftRightLine className="ml-1 h-3 w-3 text-text-tertiary" /> + </> + )} + hasRedCornerMark={true} + /> + )} + /> + )} + </div> + </Tooltip> + ) } diff --git a/web/app/components/workflow/nodes/_base/components/title-description-input.tsx b/web/app/components/workflow/nodes/_base/components/title-description-input.tsx index 062190aee9..082aafade8 100644 --- a/web/app/components/workflow/nodes/_base/components/title-description-input.tsx +++ b/web/app/components/workflow/nodes/_base/components/title-description-input.tsx @@ -3,8 +3,8 @@ import { useCallback, useState, } from 'react' -import Textarea from 'react-textarea-autosize' import { useTranslation } from 'react-i18next' +import Textarea from 'react-textarea-autosize' type TitleInputProps = { value: string diff --git a/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx b/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx index f597b24b9f..116825ae95 100644 --- a/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx +++ b/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' import { RiCollapseDiagonalLine, RiExpandDiagonalLine, } from '@remixicon/react' +import React, { useCallback } from 'react' import ActionButton from '@/app/components/base/action-button' type Props = { @@ -23,7 +23,7 @@ const ExpandBtn: FC<Props> = ({ const Icon = isExpand ? RiCollapseDiagonalLine : RiExpandDiagonalLine return ( <ActionButton onClick={handleToggle}> - <Icon className='h-4 w-4' /> + <Icon className="h-4 w-4" /> </ActionButton> ) } diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx index 71af3ad4fd..c6a7d5c6d5 100644 --- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -1,18 +1,18 @@ -import { useCallback, useMemo } from 'react' -import { useNodes, useReactFlow, useStoreApi } from 'reactflow' -import { useTranslation } from 'react-i18next' import type { CommonNodeType, Node, ValueSelector, VarType, } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes, useReactFlow, useStoreApi } from 'reactflow' import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { isExceptionVariable } from '@/app/components/workflow/utils' import { VariableLabelInSelect, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { BlockEnum } from '@/app/components/workflow/types' +import { isExceptionVariable } from '@/app/components/workflow/utils' type VariableTagProps = { valueSelector: ValueSelector diff --git a/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx index 0e086fabcd..7907c83fc3 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import VarReferenceVars from './var-reference-vars' -import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import ListEmpty from '@/app/components/base/list-empty' +import VarReferenceVars from './var-reference-vars' type Props = { vars: NodeOutPutVar[] @@ -19,21 +19,24 @@ const AssignedVarReferencePopup: FC<Props> = ({ const { t } = useTranslation() // max-h-[300px] overflow-y-auto todo: use portal to handle long list return ( - <div className='bg-components-panel-bg-bur w-[352px] rounded-lg border-[0.5px] border-components-panel-border p-1 shadow-lg' > + <div className="bg-components-panel-bg-bur w-[352px] rounded-lg border-[0.5px] border-components-panel-border p-1 shadow-lg"> {(!vars || vars.length === 0) - ? <ListEmpty - title={t('workflow.nodes.assigner.noAssignedVars') || ''} - description={t('workflow.nodes.assigner.assignedVarsDescription')} - /> - : <VarReferenceVars - searchBoxClassName='mt-1' - vars={vars} - onChange={onChange} - itemWidth={itemWidth} - isSupportFileVar - /> - } - </div > + ? ( + <ListEmpty + title={t('workflow.nodes.assigner.noAssignedVars') || ''} + description={t('workflow.nodes.assigner.assignedVarsDescription')} + /> + ) + : ( + <VarReferenceVars + searchBoxClassName="mt-1" + vars={vars} + onChange={onChange} + itemWidth={itemWidth} + isSupportFileVar + /> + )} + </div> ) } export default React.memo(AssignedVarReferencePopup) diff --git a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx index 0d965e2d22..310e82ff12 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Var } from '@/app/components/workflow/types' +import React, { useCallback } from 'react' +import { SimpleSelect } from '@/app/components/base/select' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import type { Var } from '@/app/components/workflow/types' -import { SimpleSelect } from '@/app/components/base/select' type Props = { schema: Partial<CredentialFormSchema> @@ -42,8 +42,8 @@ const ConstantField: FC<Props> = ({ <> {(schema.type === FormTypeEnum.select || schema.type === FormTypeEnum.dynamicSelect) && ( <SimpleSelect - wrapperClassName='w-full !h-8' - className='flex items-center' + wrapperClassName="w-full !h-8" + className="flex items-center" disabled={readonly} defaultValue={value} items={(schema as CredentialFormSchemaSelect).options.map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))} @@ -55,8 +55,8 @@ const ConstantField: FC<Props> = ({ )} {schema.type === FormTypeEnum.textNumber && ( <input - type='number' - className='h-8 w-full overflow-hidden rounded-lg bg-workflow-block-parma-bg p-2 text-[13px] font-normal leading-8 text-text-secondary placeholder:text-gray-400 focus:outline-none' + type="number" + className="h-8 w-full overflow-hidden rounded-lg bg-workflow-block-parma-bg p-2 text-[13px] font-normal leading-8 text-text-secondary placeholder:text-gray-400 focus:outline-none" value={value} onChange={handleStaticChange} readOnly={readonly} diff --git a/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx index a0588959ea..87ca719e88 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx @@ -1,5 +1,5 @@ -import { useTranslation } from 'react-i18next' import { RiAddLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' type ManageInputFieldProps = { onManage: () => void @@ -11,22 +11,22 @@ const ManageInputField = ({ const { t } = useTranslation() return ( - <div className='flex items-center border-t border-divider-subtle pt-1'> + <div className="flex items-center border-t border-divider-subtle pt-1"> <div - className='flex h-8 grow cursor-pointer items-center px-3' + className="flex h-8 grow cursor-pointer items-center px-3" onClick={onManage} > - <RiAddLine className='mr-1 h-4 w-4 text-text-tertiary' /> + <RiAddLine className="mr-1 h-4 w-4 text-text-tertiary" /> <div - className='system-xs-medium truncate text-text-tertiary' - title='Create user input field' + className="system-xs-medium truncate text-text-tertiary" + title="Create user input field" > {t('pipeline.inputField.create')} </div> </div> - <div className='mx-1 h-3 w-[1px] shrink-0 bg-divider-regular'></div> + <div className="mx-1 h-3 w-[1px] shrink-0 bg-divider-regular"></div> <div - className='system-xs-medium flex h-8 shrink-0 cursor-pointer items-center justify-center px-3 text-text-tertiary' + className="system-xs-medium flex h-8 shrink-0 cursor-pointer items-center justify-center px-3 text-text-tertiary" onClick={onManage} > {t('pipeline.inputField.manage')} diff --git a/web/app/components/workflow/nodes/_base/components/variable/match-schema-type.ts b/web/app/components/workflow/nodes/_base/components/variable/match-schema-type.ts index 9ca4981065..79b95942e0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/match-schema-type.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/match-schema-type.ts @@ -7,7 +7,7 @@ function matchTheSchemaType(scheme: AnyObj, target: AnyObj): boolean { const isMatch = (schema: AnyObj, t: AnyObj): boolean => { const oSchema = isObj(schema) const oT = isObj(t) - if(!oSchema) + if (!oSchema) return true if (!oT) { // ignore the object without type // deep find oSchema has type @@ -24,14 +24,18 @@ function matchTheSchemaType(scheme: AnyObj, target: AnyObj): boolean { const ty = (t as any).type const isTypeValueObj = isObj(tx) - if(!isTypeValueObj) // caution: type can be object, so that it would not be compare by value - if (tx !== ty) return false + if (!isTypeValueObj) { // caution: type can be object, so that it would not be compare by value + if (tx !== ty) + return false + } // recurse into all keys const keys = new Set([...Object.keys(schema as object), ...Object.keys(t as object)]) for (const k of keys) { - if (k === 'type' && !isTypeValueObj) continue // already checked - if (!isMatch((schema as any)[k], (t as any)[k])) return false + if (k === 'type' && !isTypeValueObj) + continue // already checked + if (!isMatch((schema as any)[k], (t as any)[k])) + return false } return true } diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx index ca8317e8d7..f533c33108 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' +import type { Field as FieldType } from '../../../../../llm/types' +import type { ValueSelector } from '@/app/components/workflow/types' +import { RiMoreFill } from '@remixicon/react' import React from 'react' +import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' import { Type } from '../../../../../llm/types' import { getFieldType } from '../../../../../llm/utils' -import type { Field as FieldType } from '../../../../../llm/types' -import { cn } from '@/utils/classnames' import TreeIndentLine from '../tree-indent-line' -import { RiMoreFill } from '@remixicon/react' -import Tooltip from '@/app/components/base/tooltip' -import type { ValueSelector } from '@/app/components/workflow/types' -import { useTranslation } from 'react-i18next' const MAX_DEPTH = 10 type Props = { valueSelector: ValueSelector - name: string, - payload: FieldType, + name: string + payload: FieldType depth?: number readonly?: boolean onSelect?: (valueSelector: ValueSelector) => void @@ -43,15 +43,17 @@ const Field: FC<Props> = ({ className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')} onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])} > - <div className='flex grow items-stretch'> + <div className="flex grow items-stretch"> <TreeIndentLine depth={depth} /> - {depth === MAX_DEPTH + 1 ? ( - <RiMoreFill className='h-3 w-3 text-text-tertiary' /> - ) : (<div className={cn('system-sm-medium h-6 w-0 grow truncate leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)} + {depth === MAX_DEPTH + 1 + ? ( + <RiMoreFill className="h-3 w-3 text-text-tertiary" /> + ) + : (<div className={cn('system-sm-medium h-6 w-0 grow truncate leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)} </div> {depth < MAX_DEPTH + 1 && ( - <div className='system-xs-regular ml-2 shrink-0 text-text-tertiary'>{getFieldType(payload)}</div> + <div className="system-xs-regular ml-2 shrink-0 text-text-tertiary">{getFieldType(payload)}</div> )} </div> </Tooltip> diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx index 219d46df9c..baf3cfcbd2 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' -import React, { useRef } from 'react' import type { StructuredOutput } from '../../../../../llm/types' -import Field from './field' -import { cn } from '@/utils/classnames' -import { useHover } from 'ahooks' import type { ValueSelector } from '@/app/components/workflow/types' +import { useHover } from 'ahooks' +import React, { useRef } from 'react' +import { cn } from '@/utils/classnames' +import Field from './field' type Props = { className?: string @@ -42,17 +42,17 @@ export const PickerPanelMain: FC<Props> = ({ return ( <div className={cn(className)} ref={ref}> {/* Root info */} - <div className='flex items-center justify-between px-2 py-1'> - <div className='flex'> + <div className="flex items-center justify-between px-2 py-1"> + <div className="flex"> {root.nodeName && ( <> - <div className='system-sm-medium max-w-[100px] truncate text-text-tertiary'>{root.nodeName}</div> - <div className='system-sm-medium text-text-tertiary'>.</div> + <div className="system-sm-medium max-w-[100px] truncate text-text-tertiary">{root.nodeName}</div> + <div className="system-sm-medium text-text-tertiary">.</div> </> )} - <div className='system-sm-medium text-text-secondary'>{root.attrName}</div> + <div className="system-sm-medium text-text-secondary">{root.attrName}</div> </div> - <div className='system-xs-regular ml-2 truncate text-text-tertiary' title={root.attrAlias || 'object'}>{root.attrAlias || 'object'}</div> + <div className="system-xs-regular ml-2 truncate text-text-tertiary" title={root.attrAlias || 'object'}>{root.attrAlias || 'object'}</div> </div> {fieldNames.map(name => ( <Field diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx index e101d91021..fc23cda205 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx @@ -1,20 +1,20 @@ 'use client' -import { cn } from '@/utils/classnames' +import type { FC } from 'react' +import type { Field as FieldType } from '../../../../../llm/types' import { RiArrowDropDownLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import type { Field as FieldType } from '../../../../../llm/types' +import { cn } from '@/utils/classnames' import { Type } from '../../../../../llm/types' import { getFieldType } from '../../../../../llm/utils' import TreeIndentLine from '../tree-indent-line' type Props = { - name: string, - payload: FieldType, - required: boolean, - depth?: number, + name: string + payload: FieldType + required: boolean + depth?: number rootClassName?: string } @@ -36,8 +36,8 @@ const Field: FC<Props> = ({ <div> <div className={cn('flex pr-2')}> <TreeIndentLine depth={depth} /> - <div className='w-0 grow'> - <div className='relative flex select-none'> + <div className="w-0 grow"> + <div className="relative flex select-none"> {hasChildren && ( <RiArrowDropDownLine className={cn('absolute left-[-18px] top-[50%] h-4 w-4 translate-y-[-50%] cursor-pointer bg-components-panel-bg text-text-tertiary', fold && 'rotate-[270deg] text-text-accent')} @@ -45,20 +45,20 @@ const Field: FC<Props> = ({ /> )} <div className={cn('system-sm-medium ml-[7px] h-6 truncate leading-6 text-text-secondary', isRoot && rootClassName)}>{name}</div> - <div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'> + <div className="system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary"> {getFieldType(payload)} {(payload.schemaType && payload.schemaType !== 'file' && ` (${payload.schemaType})`)} </div> - {required && <div className='system-2xs-medium-uppercase ml-3 leading-6 text-text-warning'>{t('app.structOutput.required')}</div>} + {required && <div className="system-2xs-medium-uppercase ml-3 leading-6 text-text-warning">{t('app.structOutput.required')}</div>} </div> {payload.description && ( - <div className='ml-[7px] flex'> - <div className='system-xs-regular w-0 grow truncate text-text-tertiary'>{payload.description}</div> + <div className="ml-[7px] flex"> + <div className="system-xs-regular w-0 grow truncate text-text-tertiary">{payload.description}</div> </div> )} {hasEnum && ( - <div className='ml-[7px] flex'> - <div className='system-xs-regular w-0 grow text-text-quaternary'> + <div className="ml-[7px] flex"> + <div className="system-xs-regular w-0 grow text-text-quaternary"> {payload.enum!.map((value, index) => ( <span key={index}> {typeof value === 'string' ? `"${value}"` : value} diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx index 86f707af13..deaab09e6c 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' -import React from 'react' import type { StructuredOutput } from '../../../../../llm/types' -import Field from './field' +import React from 'react' import { useTranslation } from 'react-i18next' +import Field from './field' type Props = { payload: StructuredOutput @@ -23,7 +23,7 @@ const ShowPanel: FC<Props> = ({ }, } return ( - <div className='relative left-[-7px]'> + <div className="relative left-[-7px]"> {Object.keys(schema.schema.properties!).map(name => ( <Field key={name} diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx index bbec1d7a00..9e45a4cccc 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx @@ -4,8 +4,8 @@ import React from 'react' import { cn } from '@/utils/classnames' type Props = { - depth?: number, - className?: string, + depth?: number + className?: string } const TreeIndentLine: FC<Props> = ({ diff --git a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx index 3c2415bf9d..7eccbe23de 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx @@ -1,17 +1,17 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' import type { OutputVar } from '../../../code/types' +import type { ToastHandle } from '@/app/components/base/toast' +import type { VarType } from '@/app/components/workflow/types' +import { useDebounceFn } from 'ahooks' +import { produce } from 'immer' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' import RemoveButton from '../remove-button' import VarTypePicker from './var-type-picker' -import Input from '@/app/components/base/input' -import type { VarType } from '@/app/components/workflow/types' -import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' -import type { ToastHandle } from '@/app/components/base/toast' -import Toast from '@/app/components/base/toast' -import { useDebounceFn } from 'ahooks' type Props = { readonly: boolean @@ -93,14 +93,14 @@ const OutputVarList: FC<Props> = ({ }, [onRemove]) return ( - <div className='space-y-2'> + <div className="space-y-2"> {list.map((item, index) => ( - <div className='flex items-center space-x-1' key={index}> + <div className="flex items-center space-x-1" key={index}> <Input readOnly={readonly} value={item.variable} onChange={handleVarNameChange(index)} - wrapperClassName='grow' + wrapperClassName="grow" /> <VarTypePicker readonly={readonly} @@ -108,7 +108,7 @@ const OutputVarList: FC<Props> = ({ onChange={handleVarTypeChange(index)} /> <RemoveButton - className='!bg-gray-100 !p-2 hover:!bg-gray-200' + className="!bg-gray-100 !p-2 hover:!bg-gray-200" onClick={handleVarRemove(index)} /> </div> diff --git a/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts index 2c0c91b7f5..67fb230eeb 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/use-match-schema-type.ts @@ -1,10 +1,11 @@ +import type { AnyObj } from './match-schema-type' import type { SchemaTypeDefinition } from '@/service/use-common' import { useSchemaTypeDefinitions } from '@/service/use-common' -import type { AnyObj } from './match-schema-type' import matchTheSchemaType from './match-schema-type' export const getMatchedSchemaType = (obj: AnyObj, schemaTypeDefinitions?: SchemaTypeDefinition[]): string => { - if(!schemaTypeDefinitions || obj === undefined || obj === null) return '' + if (!schemaTypeDefinitions || obj === undefined || obj === null) + return '' const matched = schemaTypeDefinitions.find(def => matchTheSchemaType(obj, def.schema)) return matched ? matched.name : '' } diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index eb76021c40..5a5a9b826a 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -1,32 +1,26 @@ -import { produce } from 'immer' -import { isArray, uniq } from 'lodash-es' -import type { CodeNodeType } from '../../../code/types' -import type { EndNodeType } from '../../../end/types' +import type { AgentNodeType } from '../../../agent/types' import type { AnswerNodeType } from '../../../answer/types' -import { - type LLMNodeType, - type StructuredOutput, - Type, -} from '../../../llm/types' -import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types' -import type { IfElseNodeType } from '../../../if-else/types' -import type { TemplateTransformNodeType } from '../../../template-transform/types' -import type { QuestionClassifierNodeType } from '../../../question-classifier/types' -import type { HttpNodeType } from '../../../http/types' -import { VarType as ToolVarType } from '../../../tool/types' -import type { ToolNodeType } from '../../../tool/types' -import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types' -import type { IterationNodeType } from '../../../iteration/types' -import type { LoopNodeType } from '../../../loop/types' -import type { ListFilterNodeType } from '../../../list-operator/types' -import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants' +import type { CodeNodeType } from '../../../code/types' import type { DocExtractorNodeType } from '../../../document-extractor/types' -import { - BlockEnum, - InputVarType, - VarType, -} from '@/app/components/workflow/types' +import type { EndNodeType } from '../../../end/types' +import type { HttpNodeType } from '../../../http/types' +import type { IfElseNodeType } from '../../../if-else/types' +import type { IterationNodeType } from '../../../iteration/types' +import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types' +import type { ListFilterNodeType } from '../../../list-operator/types' +import type { LLMNodeType, StructuredOutput } from '../../../llm/types' +import type { LoopNodeType } from '../../../loop/types' +import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types' +import type { QuestionClassifierNodeType } from '../../../question-classifier/types' +import type { TemplateTransformNodeType } from '../../../template-transform/types' +import type { ToolNodeType } from '../../../tool/types' +import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' +import type { CaseItem, Condition } from '@/app/components/workflow/nodes/if-else/types' +import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types' import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' +import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types' +import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types' +import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types' import type { ConversationVariable, EnvironmentVariable, @@ -36,16 +30,15 @@ import type { ValueSelector, Var, } from '@/app/components/workflow/types' -import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types' -import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types' +import type { PromptItem } from '@/models/debug' import type { RAGPipelineVariable } from '@/models/pipeline' -import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types' -import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types' -import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default' -import type { CaseItem, Condition } from '@/app/components/workflow/nodes/if-else/types' +import type { SchemaTypeDefinition } from '@/service/use-common' +import { produce } from 'immer' +import { isArray, uniq } from 'lodash-es' import { AGENT_OUTPUT_STRUCT, FILE_STRUCT, + getGlobalVars, HTTP_REQUEST_OUTPUT_STRUCT, KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT, LLM_OUTPUT_STRUCT, @@ -54,23 +47,31 @@ import { SUPPORT_OUTPUT_VARS_NODE, TEMPLATE_TRANSFORM_OUTPUT_STRUCT, TOOL_OUTPUT_STRUCT, - getGlobalVars, } from '@/app/components/workflow/constants' -import ToolNodeDefault from '@/app/components/workflow/nodes/tool/default' import DataSourceNodeDefault from '@/app/components/workflow/nodes/data-source/default' -import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' -import type { PromptItem } from '@/models/debug' +import ToolNodeDefault from '@/app/components/workflow/nodes/tool/default' +import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default' +import { + BlockEnum, + InputVarType, + VarType, +} from '@/app/components/workflow/types' import { VAR_REGEX } from '@/config' -import type { AgentNodeType } from '../../../agent/types' -import type { SchemaTypeDefinition } from '@/service/use-common' import { AppModeEnum } from '@/types/app' +import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants' +import { + + Type, +} from '../../../llm/types' +import { VarType as ToolVarType } from '../../../tool/types' export const isSystemVar = (valueSelector: ValueSelector) => { return valueSelector[0] === 'sys' || valueSelector[1] === 'sys' } export const isGlobalVar = (valueSelector: ValueSelector) => { - if (!isSystemVar(valueSelector)) return false + if (!isSystemVar(valueSelector)) + return false const second = valueSelector[1] if (['query', 'files'].includes(second)) @@ -87,7 +88,8 @@ export const isConversationVar = (valueSelector: ValueSelector) => { } export const isRagVariableVar = (valueSelector: ValueSelector) => { - if (!valueSelector) return false + if (!valueSelector) + return false return valueSelector[0] === 'rag' } @@ -247,8 +249,7 @@ const findExceptVarInObject = ( isFile?: boolean, ): Var => { const { children } = obj - const isStructuredOutput = !!(children as StructuredOutput)?.schema - ?.properties + const isStructuredOutput = !!(children as StructuredOutput)?.schema?.properties let childrenResult: Var[] | StructuredOutput | undefined @@ -281,9 +282,12 @@ const findExceptVarInObject = ( if ( (item.type === VarType.object || item.type === VarType.file) && itemChildren - ) + ) { passesFilter = itemHasValidChildren || filterVar(item, currSelector) - else passesFilter = itemHasValidChildren + } + else { + passesFilter = itemHasValidChildren + } return { item, @@ -294,7 +298,8 @@ const findExceptVarInObject = ( .filter(({ passesFilter }) => passesFilter) .map(({ item, filteredObj }) => { const { children: itemChildren } = item - if (!itemChildren || !filteredObj) return item + if (!itemChildren || !filteredObj) + return item return { ...item, @@ -415,11 +420,11 @@ const formatItem = ( const { outputs } = data as CodeNodeType res.vars = outputs ? Object.keys(outputs).map((key) => { - return { - variable: key, - type: outputs[key].type, - } - }) + return { + variable: key, + type: outputs[key].type, + } + }) : [] break } @@ -562,7 +567,8 @@ const formatItem = ( } case BlockEnum.ListFilter: { - if (!(data as ListFilterNodeType).var_type) break + if (!(data as ListFilterNodeType).var_type) + break res.vars = [ { @@ -690,12 +696,14 @@ const formatItem = ( (() => { const variableArr = v.variable.split('.') const [first] = variableArr - if (isSpecialVar(first)) return variableArr + if (isSpecialVar(first)) + return variableArr return [...selector, ...variableArr] })(), ) - if (isCurrentMatched) return true + if (isCurrentMatched) + return true const isFile = v.type === VarType.file const children = (() => { @@ -710,7 +718,8 @@ const formatItem = ( } return v.children })() - if (!children) return false + if (!children) + return false const obj = findExceptVarInObject( isFile ? { ...v, children } : v, @@ -737,7 +746,8 @@ const formatItem = ( return v })() - if (!children) return v + if (!children) + return v return findExceptVarInObject( isFile ? { ...v, children } : v, @@ -813,14 +823,22 @@ export const toNodeOutputVars = ( } // Sort nodes in reverse chronological order (most recent first) const sortedNodes = [...nodes].sort((a, b) => { - if (a.data.type === BlockEnum.Start) return 1 - if (b.data.type === BlockEnum.Start) return -1 - if (a.data.type === 'env') return 1 - if (b.data.type === 'env') return -1 - if (a.data.type === 'conversation') return 1 - if (b.data.type === 'conversation') return -1 - if (a.data.type === 'global') return 1 - if (b.data.type === 'global') return -1 + if (a.data.type === BlockEnum.Start) + return 1 + if (b.data.type === BlockEnum.Start) + return -1 + if (a.data.type === 'env') + return 1 + if (b.data.type === 'env') + return -1 + if (a.data.type === 'conversation') + return 1 + if (b.data.type === 'conversation') + return -1 + if (a.data.type === 'global') + return 1 + if (b.data.type === 'global') + return -1 // sort nodes by x position return (b.position?.x || 0) - (a.position?.x || 0) }) @@ -872,8 +890,8 @@ const getIterationItemType = ({ valueSelector, beforeNodesOutputVars, }: { - valueSelector: ValueSelector; - beforeNodesOutputVars: NodeOutPutVar[]; + valueSelector: ValueSelector + beforeNodesOutputVars: NodeOutPutVar[] }): VarType => { const outputVarNodeId = valueSelector[0] const isSystem = isSystemVar(valueSelector) @@ -883,7 +901,8 @@ const getIterationItemType = ({ ? beforeNodesOutputVars.find(v => v.isStartNode) : beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId) - if (!targetVar) return VarType.string + if (!targetVar) + return VarType.string let arrayType: VarType = VarType.string @@ -899,7 +918,8 @@ const getIterationItemType = ({ const isLast = i === valueSelector.length - 1 curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : [] - if (isLast) arrayType = curr?.type + if (isLast) + arrayType = curr?.type else if (curr?.type === VarType.object || curr?.type === VarType.file) curr = curr.children || [] } @@ -927,8 +947,8 @@ const getLoopItemType = ({ valueSelector, beforeNodesOutputVars, }: { - valueSelector: ValueSelector; - beforeNodesOutputVars: NodeOutPutVar[]; + valueSelector: ValueSelector + beforeNodesOutputVars: NodeOutPutVar[] }): VarType => { const outputVarNodeId = valueSelector[0] const isSystem = isSystemVar(valueSelector) @@ -936,7 +956,8 @@ const getLoopItemType = ({ const targetVar = isSystem ? beforeNodesOutputVars.find(v => v.isStartNode) : beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId) - if (!targetVar) return VarType.string + if (!targetVar) + return VarType.string let arrayType: VarType = VarType.string @@ -993,21 +1014,22 @@ export const getVarType = ({ schemaTypeDefinitions, preferSchemaType, }: { - valueSelector: ValueSelector; - parentNode?: Node | null; - isIterationItem?: boolean; - isLoopItem?: boolean; - availableNodes: any[]; - isChatMode: boolean; - isConstant?: boolean; - environmentVariables?: EnvironmentVariable[]; - conversationVariables?: ConversationVariable[]; - ragVariables?: RAGPipelineVariable[]; - allPluginInfoList: Record<string, ToolWithProvider[]>; - schemaTypeDefinitions?: SchemaTypeDefinition[]; - preferSchemaType?: boolean; + valueSelector: ValueSelector + parentNode?: Node | null + isIterationItem?: boolean + isLoopItem?: boolean + availableNodes: any[] + isChatMode: boolean + isConstant?: boolean + environmentVariables?: EnvironmentVariable[] + conversationVariables?: ConversationVariable[] + ragVariables?: RAGPipelineVariable[] + allPluginInfoList: Record<string, ToolWithProvider[]> + schemaTypeDefinitions?: SchemaTypeDefinition[] + preferSchemaType?: boolean }): VarType => { - if (isConstant) return VarType.string + if (isConstant) + return VarType.string const beforeNodesOutputVars = toNodeOutputVars( availableNodes, @@ -1035,7 +1057,8 @@ export const getVarType = ({ }) return itemType } - if (valueSelector[1] === 'index') return VarType.number + if (valueSelector[1] === 'index') + return VarType.number } const isLoopInnerVar = parentNode?.data.type === BlockEnum.Loop @@ -1053,7 +1076,8 @@ export const getVarType = ({ }) return itemType } - if (valueSelector[1] === 'index') return VarType.number + if (valueSelector[1] === 'index') + return VarType.number } const isGlobal = isGlobalVar(valueSelector) @@ -1070,16 +1094,20 @@ export const getVarType = ({ }) const targetVarNodeId = (() => { - if (isInStartNodeSysVar) return startNode?.id - if (isGlobal) return 'global' - if (isInNodeRagVariable) return valueSelector[1] + if (isInStartNodeSysVar) + return startNode?.id + if (isGlobal) + return 'global' + if (isInNodeRagVariable) + return valueSelector[1] return valueSelector[0] })() const targetVar = beforeNodesOutputVars.find( v => v.nodeId === targetVarNodeId, ) - if (!targetVar) return VarType.string + if (!targetVar) + return VarType.string let type: VarType = VarType.string let curr: any = targetVar.vars @@ -1091,12 +1119,15 @@ export const getVarType = ({ } else { const targetVar = curr.find((v: any) => { - if (isInNodeRagVariable) return v.variable === valueSelector.join('.') + if (isInNodeRagVariable) + return v.variable === valueSelector.join('.') return v.variable === valueSelector[1] }) - if (!targetVar) return VarType.string + if (!targetVar) + return VarType.string - if (isInNodeRagVariable) return targetVar.type + if (isInNodeRagVariable) + return targetVar.type const isStructuredOutputVar = !!targetVar.children?.schema?.properties if (isStructuredOutputVar) { @@ -1109,10 +1140,12 @@ export const getVarType = ({ let currProperties = targetVar.children.schema; (valueSelector as ValueSelector).slice(2).forEach((key, i) => { const isLast = i === valueSelector.length - 3 - if (!currProperties) return + if (!currProperties) + return currProperties = currProperties.properties[key] - if (isLast) type = structTypeToVarType(currProperties?.type) + if (isLast) + type = structTypeToVarType(currProperties?.type) }) return type } @@ -1148,20 +1181,20 @@ export const toNodeAvailableVars = ({ allPluginInfoList, schemaTypeDefinitions, }: { - parentNode?: Node | null; - t?: any; + parentNode?: Node | null + t?: any // to get those nodes output vars - beforeNodes: Node[]; - isChatMode: boolean; + beforeNodes: Node[] + isChatMode: boolean // env - environmentVariables?: EnvironmentVariable[]; + environmentVariables?: EnvironmentVariable[] // chat var - conversationVariables?: ConversationVariable[]; + conversationVariables?: ConversationVariable[] // rag variables - ragVariables?: RAGPipelineVariable[]; - filterVar: (payload: Var, selector: ValueSelector) => boolean; - allPluginInfoList: Record<string, ToolWithProvider[]>; - schemaTypeDefinitions?: SchemaTypeDefinition[]; + ragVariables?: RAGPipelineVariable[] + filterVar: (payload: Var, selector: ValueSelector) => boolean + allPluginInfoList: Record<string, ToolWithProvider[]> + schemaTypeDefinitions?: SchemaTypeDefinition[] }): NodeOutPutVar[] => { const beforeNodesOutputVars = toNodeOutputVars( beforeNodes, @@ -1190,13 +1223,13 @@ export const toNodeAvailableVars = ({ const itemChildren = itemType === VarType.file ? { - children: OUTPUT_FILE_SUB_VARIABLES.map((key) => { - return { - variable: key, - type: key === 'size' ? VarType.number : VarType.string, - } - }), - } + children: OUTPUT_FILE_SUB_VARIABLES.map((key) => { + return { + variable: key, + type: key === 'size' ? VarType.number : VarType.string, + } + }), + } : {} const iterationVar = { nodeId: iterationNode?.id, @@ -1216,24 +1249,28 @@ export const toNodeAvailableVars = ({ const iterationIndex = beforeNodesOutputVars.findIndex( v => v.nodeId === iterationNode?.id, ) - if (iterationIndex > -1) beforeNodesOutputVars.splice(iterationIndex, 1) + if (iterationIndex > -1) + beforeNodesOutputVars.splice(iterationIndex, 1) beforeNodesOutputVars.unshift(iterationVar) } return beforeNodesOutputVars } export const getNodeInfoById = (nodes: any, id: string) => { - if (!isArray(nodes)) return + if (!isArray(nodes)) + return return nodes.find((node: any) => node.id === id) } const matchNotSystemVars = (prompts: string[]) => { - if (!prompts) return [] + if (!prompts) + return [] const allVars: string[] = [] prompts.forEach((prompt) => { VAR_REGEX.lastIndex = 0 - if (typeof prompt !== 'string') return + if (typeof prompt !== 'string') + return allVars.push(...(prompt.match(VAR_REGEX) || [])) }) const uniqVars = uniq(allVars).map(v => @@ -1247,9 +1284,11 @@ const replaceOldVarInText = ( oldVar: ValueSelector, newVar: ValueSelector, ) => { - if (!text || typeof text !== 'string') return text + if (!text || typeof text !== 'string') + return text - if (!newVar || newVar.length === 0) return text + if (!newVar || newVar.length === 0) + return text return text.replaceAll( `{{#${oldVar.join('.')}#}}`, @@ -1308,7 +1347,8 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => { .flatMap(c => c.conditions || []) .flatMap((c) => { const selectors: ValueSelector[] = [] - if (c.variable_selector) selectors.push(c.variable_selector) + if (c.variable_selector) + selectors.push(c.variable_selector) // Handle sub-variable conditions if (c.sub_variable_condition && c.sub_variable_condition.conditions) { selectors.push( @@ -1391,7 +1431,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => { payload.datasource_parameters[key].type === ToolVarType.variable, ) .map(key => payload.datasource_parameters[key].value as string) - || [] + || [] res = [...(mixVars as ValueSelector[]), ...(vars as any)] break } @@ -1436,7 +1476,8 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => { case BlockEnum.Agent: { const payload = data as AgentNodeType const valueSelectors: ValueSelector[] = [] - if (!payload.agent_parameters) break + if (!payload.agent_parameters) + break Object.keys(payload.agent_parameters || {}).forEach((key) => { const { value } = payload.agent_parameters![key] @@ -1490,7 +1531,8 @@ export const getNodeUsedVarPassToServerKey = ( return undefined } const targetVar = findConditionInCases((data as IfElseNodeType).cases || []) - if (targetVar) res = `#${valueSelector.join('.')}#` + if (targetVar) + res = `#${valueSelector.join('.')}#` break } case BlockEnum.Code: { @@ -1500,7 +1542,8 @@ export const getNodeUsedVarPassToServerKey = ( && v.value_selector && v.value_selector.join('.') === valueSelector.join('.'), ) - if (targetVar) res = targetVar.variable + if (targetVar) + res = targetVar.variable break } case BlockEnum.TemplateTransform: { @@ -1510,7 +1553,8 @@ export const getNodeUsedVarPassToServerKey = ( && v.value_selector && v.value_selector.join('.') === valueSelector.join('.'), ) - if (targetVar) res = targetVar.variable + if (targetVar) + res = targetVar.variable break } case BlockEnum.QuestionClassifier: { @@ -1552,7 +1596,8 @@ export const findUsedVarNodes = ( const res: Node[] = [] availableNodes.forEach((node) => { const vars = getNodeUsedVars(node) - if (vars.find(v => v.join('.') === varSelector.join('.'))) res.push(node) + if (vars.find(v => v.join('.') === varSelector.join('.'))) + res.push(node) }) return res } @@ -1626,8 +1671,9 @@ export const updateNodeVars = ( if ( payload.context?.variable_selector?.join('.') === oldVarSelector.join('.') - ) + ) { payload.context.variable_selector = newVarSelector + } break } @@ -1661,8 +1707,9 @@ export const updateNodeVars = ( if ( subC.variable_selector?.join('.') === oldVarSelector.join('.') - ) + ) { subC.variable_selector = newVarSelector + } return subC }) } @@ -1820,7 +1867,8 @@ export const updateNodeVars = ( const payload = data as VariableAssignerNodeType if (payload.variables) { payload.variables = payload.variables.map((v) => { - if (v.join('.') === oldVarSelector.join('.')) v = newVarSelector + if (v.join('.') === oldVarSelector.join('.')) + v = newVarSelector return v }) } @@ -1831,7 +1879,8 @@ export const updateNodeVars = ( const payload = data as VariableAssignerNodeType if (payload.variables) { payload.variables = payload.variables.map((v) => { - if (v.join('.') === oldVarSelector.join('.')) v = newVarSelector + if (v.join('.') === oldVarSelector.join('.')) + v = newVarSelector return v }) } @@ -1882,11 +1931,11 @@ const varToValueSelectorList = ( parentValueSelector: ValueSelector, res: ValueSelector[], ) => { - if (!v.variable) return + if (!v.variable) + return res.push([...parentValueSelector, v.variable]) - const isStructuredOutput = !!(v.children as StructuredOutput)?.schema - ?.properties + const isStructuredOutput = !!(v.children as StructuredOutput)?.schema?.properties if ((v.children as Var[])?.length > 0) { (v.children as Var[]).forEach((child) => { @@ -1897,8 +1946,7 @@ const varToValueSelectorList = ( Object.keys( (v.children as StructuredOutput)?.schema?.properties || {}, ).forEach((key) => { - const type = (v.children as StructuredOutput)?.schema?.properties[key] - .type + const type = (v.children as StructuredOutput)?.schema?.properties[key].type const isArray = type === Type.array const arrayType = (v.children as StructuredOutput)?.schema?.properties[ key diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx index 54e27b5e38..744567daae 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' -import React from 'react' import type { Field, StructuredOutput, TypeWithArray } from '../../../llm/types' -import { Type } from '../../../llm/types' -import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' +import React from 'react' import BlockIcon from '@/app/components/workflow/block-icon' +import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' import { BlockEnum } from '@/app/components/workflow/types' +import { Type } from '../../../llm/types' type Props = { nodeName: string @@ -35,20 +35,20 @@ const VarFullPathPanel: FC<Props> = ({ type: isLast ? varType : Type.object, properties: {}, } as Field - current = current.properties[name] as { type: Type.object; properties: { [key: string]: Field; }; required: never[]; additionalProperties: false; } + current = current.properties[name] as { type: Type.object, properties: { [key: string]: Field }, required: never[], additionalProperties: false } } return { schema, } })() return ( - <div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-0 shadow-lg backdrop-blur-[5px]'> - <div className='flex space-x-1 border-b-[0.5px] border-divider-subtle p-3 pb-2 '> - <BlockIcon size='xs' type={nodeType} /> - <div className='system-xs-medium w-0 grow truncate text-text-secondary'>{nodeName}</div> + <div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pb-0 shadow-lg backdrop-blur-[5px]"> + <div className="flex space-x-1 border-b-[0.5px] border-divider-subtle p-3 pb-2 "> + <BlockIcon size="xs" type={nodeType} /> + <div className="system-xs-medium w-0 grow truncate text-text-secondary">{nodeName}</div> </div> <Panel - className='px-1 pb-3 pt-2' + className="px-1 pb-3 pt-2" root={{ attrName: path[0] }} payload={schema} readonly diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index d37851f187..c9cad91236 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -1,21 +1,21 @@ 'use client' import type { FC } from 'react' +import type { ToastHandle } from '@/app/components/base/toast' +import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' +import { RiDraggable } from '@remixicon/react' +import { useDebounceFn } from 'ahooks' +import { produce } from 'immer' import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import RemoveButton from '../remove-button' -import VarReferencePicker from './var-reference-picker' -import Input from '@/app/components/base/input' -import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' -import type { ToastHandle } from '@/app/components/base/toast' -import Toast from '@/app/components/base/toast' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' -import { RiDraggable } from '@remixicon/react' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { cn } from '@/utils/classnames' -import { useDebounceFn } from 'ahooks' +import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' +import RemoveButton from '../remove-button' +import VarReferencePicker from './var-reference-picker' type Props = { nodeId: string @@ -131,11 +131,11 @@ const VarList: FC<Props> = ({ return ( <ReactSortable - className='space-y-2' + className="space-y-2" list={listWithIds} setList={(list) => { onChange(list.map(item => item.variable)) }} - handle='.handle' - ghostClass='opacity-50' + handle=".handle" + ghostClass="opacity-50" animation={150} > {list.map((variable, index) => { @@ -147,7 +147,7 @@ const VarList: FC<Props> = ({ return ( <div className={cn('flex items-center space-x-1', 'group relative')} key={index}> <Input - wrapperClassName='w-[120px]' + wrapperClassName="w-[120px]" disabled={readonly} value={variable.variable} onChange={handleVarNameChange(index)} @@ -157,7 +157,7 @@ const VarList: FC<Props> = ({ nodeId={nodeId} readonly={readonly} isShowNodeName - className='grow' + className="grow" value={variable.variable_type === VarKindType.constant ? (variable.value || '') : (variable.value_selector || [])} isSupportConstantValue={isSupportConstantValue} onChange={handleVarReferenceChange(index)} @@ -167,12 +167,15 @@ const VarList: FC<Props> = ({ isSupportFileVar={isSupportFileVar} /> {!readonly && ( - <RemoveButton onClick={handleVarRemove(index)}/> + <RemoveButton onClick={handleVarRemove(index)} /> + )} + {canDrag && ( + <RiDraggable className={cn( + 'handle absolute -left-4 top-2.5 hidden h-3 w-3 cursor-pointer text-text-quaternary', + 'group-hover:block', + )} + /> )} - {canDrag && <RiDraggable className={cn( - 'handle absolute -left-4 top-2.5 hidden h-3 w-3 cursor-pointer text-text-quaternary', - 'group-hover:block', - )} />} </div> ) })} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index f532754aed..3692cb6413 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -1,7 +1,9 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { CredentialFormSchema, CredentialFormSchemaSelect, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Tool } from '@/app/components/tools/types' +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import { RiArrowDownSLine, RiCloseLine, @@ -10,23 +12,16 @@ import { RiMoreLine, } from '@remixicon/react' import { produce } from 'immer' +import { noop } from 'lodash-es' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import { useNodes, useReactFlow, useStoreApi, } from 'reactflow' -import RemoveButton from '../remove-button' -import useAvailableVarList from '../../hooks/use-available-var-list' -import VarReferencePopup from './var-reference-popup' -import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar, removeFileVars, varTypeToStructType } from './utils' -import ConstantField from './constant-field' -import { cn } from '@/utils/classnames' -import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' -import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { BlockEnum } from '@/app/components/workflow/types' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' +import Badge from '@/app/components/base/badge' +import AddButton from '@/app/components/base/button/add-button' import { Line3 } from '@/app/components/base/icons/src/public/common' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { @@ -34,23 +29,28 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import Tooltip from '@/app/components/base/tooltip' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { VarBlockIcon } from '@/app/components/workflow/block-icon' +import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' import { useIsChatMode, useWorkflowVariables, } from '@/app/components/workflow/hooks' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' // import type { BaseResource, BaseResourceProvider } from '@/app/components/workflow/nodes/_base/types' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' -import AddButton from '@/app/components/base/button/add-button' -import Badge from '@/app/components/base/badge' -import Tooltip from '@/app/components/base/tooltip' -import { isExceptionVariable } from '@/app/components/workflow/utils' -import VarFullPathPanel from './var-full-path-panel' -import { noop } from 'lodash-es' -import type { Tool } from '@/app/components/tools/types' -import { useFetchDynamicOptions } from '@/service/use-plugins' import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' -import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { isExceptionVariable } from '@/app/components/workflow/utils' +import { useFetchDynamicOptions } from '@/service/use-plugins' +import { cn } from '@/utils/classnames' +import useAvailableVarList from '../../hooks/use-available-var-list' +import RemoveButton from '../remove-button' +import ConstantField from './constant-field' +import { getNodeInfoById, isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar, removeFileVars, varTypeToStructType } from './utils' +import VarFullPathPanel from './var-full-path-panel' +import VarReferencePopup from './var-reference-popup' const TRIGGER_DEFAULT_WIDTH = 227 @@ -207,7 +207,7 @@ const VarReferencePicker: FC<Props> = ({ if (!hasValue) return '' const showName = VAR_SHOW_NAME_MAP[(value as ValueSelector).join('.')] - if(showName) + if (showName) return showName const isSystem = isSystemVar(value as ValueSelector) @@ -336,7 +336,8 @@ const VarReferencePicker: FC<Props> = ({ path={(value as ValueSelector).slice(1)} varType={varTypeToStructType(type)} nodeType={outputVarNode?.type} - />) + /> + ) } if (!isValidVar && hasValue) return t('workflow.errorMsg.invalidVariable') @@ -347,7 +348,10 @@ const VarReferencePicker: FC<Props> = ({ const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null) const [isLoading, setIsLoading] = useState(false) const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions( - currentProvider?.plugin_id || '', currentProvider?.name || '', currentTool?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '', + currentProvider?.plugin_id || '', + currentProvider?.name || '', + currentTool?.name || '', + (schema as CredentialFormSchemaSelect)?.variable || '', 'tool', ) const handleFetchDynamicOptions = async () => { @@ -398,11 +402,16 @@ const VarReferencePicker: FC<Props> = ({ }, [schema, dynamicOptions, isLoading, value]) const variableCategory = useMemo(() => { - if (isEnv) return 'environment' - if (isChatVar) return 'conversation' - if (isGlobal) return 'global' - if (isLoopVar) return 'loop' - if (isRagVar) return 'rag' + if (isEnv) + return 'environment' + if (isChatVar) + return 'conversation' + if (isGlobal) + return 'global' + if (isLoopVar) + return 'loop' + if (isRagVar) + return 'rag' return 'system' }, [isEnv, isChatVar, isGlobal, isLoopVar, isRagVar]) @@ -413,166 +422,210 @@ const VarReferencePicker: FC<Props> = ({ onOpenChange={setOpen} placement={isAddBtnTrigger ? 'bottom-end' : 'bottom-start'} > - <WrapElem onClick={() => { - if (readonly) - return - if (!isConstant) - setOpen(!open) - else - setControlFocus(Date.now()) - }} className='group/picker-trigger-wrap relative !flex'> + <WrapElem + onClick={() => { + if (readonly) + return + if (!isConstant) + setOpen(!open) + else + setControlFocus(Date.now()) + }} + className="group/picker-trigger-wrap relative !flex" + > <> {isAddBtnTrigger ? ( - <div> - <AddButton onClick={noop}></AddButton> - </div> - ) - : (<div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'group/wrap relative flex h-8 w-full items-center', !isSupportConstantValue && 'rounded-lg bg-components-input-bg-normal p-1', isInTable && 'border-none bg-transparent', readonly && 'bg-components-input-bg-disabled')}> - {isSupportConstantValue - ? <div onClick={(e) => { - e.stopPropagation() - setOpen(false) - setControlFocus(Date.now()) - }} className='mr-1 flex h-full items-center space-x-1'> - <TypeSelector - noLeft - trigger={ - <div className='radius-md flex h-8 items-center bg-components-input-bg-normal px-2'> - <div className='system-sm-regular mr-1 text-components-input-text-filled'>{varKindTypes.find(item => item.value === varKindType)?.label}</div> - <RiArrowDownSLine className='h-4 w-4 text-text-quaternary' /> - </div> - } - popupClassName='top-8' - readonly={readonly} - value={varKindType} - options={varKindTypes} - onChange={handleVarKindTypeChange} - showChecked - /> + <div> + <AddButton onClick={noop}></AddButton> </div> - : (!hasValue && <div className='ml-1.5 mr-1'> - <Variable02 className={`h-4 w-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} /> - </div>)} - {isConstant - ? ( - <ConstantField - value={value as string} - onChange={onChange as ((value: string | number, varKindType: VarKindType, varInfo?: Var) => void)} - schema={schemaWithDynamicSelect as CredentialFormSchema} - readonly={readonly} - isLoading={isLoading} - /> - ) - : ( - <VarPickerWrap - onClick={() => { - if (readonly) - return - if (!isConstant) - setOpen(!open) - else - setControlFocus(Date.now()) - }} - className='h-full grow' - > - <div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center rounded-lg bg-components-panel-bg py-1 pl-1')}> - <Tooltip noDecoration={isShowAPart} popupContent={tooltipPopup}> - <div className={cn('h-full items-center rounded-[5px] px-1.5', hasValue ? 'inline-flex bg-components-badge-white-to-dark' : 'flex')}> - {hasValue - ? ( - <> - {isShowNodeName && !isEnv && !isChatVar && !isGlobal && !isRagVar && ( - <div className='flex items-center' onClick={(e) => { - if (e.metaKey || e.ctrlKey) { - e.stopPropagation() - handleVariableJump(outputVarNode?.id) - } - }}> - <div className='h-3 px-[1px]'> - {outputVarNode?.type && <VarBlockIcon - className='!text-text-primary' - type={outputVarNode.type} - />} - </div> - <div className='mx-0.5 truncate text-xs font-medium text-text-secondary' title={outputVarNode?.title} style={{ - maxWidth: maxNodeNameWidth, - }}>{outputVarNode?.title}</div> - <Line3 className='mr-0.5'></Line3> - </div> - )} - {isShowAPart && ( - <div className='flex items-center'> - <RiMoreLine className='h-3 w-3 text-text-secondary' /> - <Line3 className='mr-0.5 text-divider-deep'></Line3> - </div> - )} - <div className='flex items-center text-text-accent'> - {isLoading && <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' />} - <VariableIconWithColor - variables={value as ValueSelector} - variableCategory={variableCategory} - isExceptionVariable={isException} - /> - <div className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning', isGlobal && 'text-util-colors-orange-orange-600')} title={varName} style={{ - maxWidth: maxVarNameWidth, - }}>{varName}</div> - </div> - <div className='system-xs-regular ml-0.5 truncate text-center capitalize text-text-tertiary' title={type} style={{ - maxWidth: maxTypeWidth, - }}>{type}</div> - {!isValidVar && <RiErrorWarningFill className='ml-0.5 h-3 w-3 text-text-destructive' />} - </> - ) - : <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}> - {isLoading ? ( - <div className='flex items-center'> - <RiLoader4Line className='mr-1 h-3.5 w-3.5 animate-spin text-text-secondary' /> - <span>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</span> - </div> - ) : ( - placeholder ?? t('workflow.common.setVarValuePlaceholder') - )} - </div>} + ) + : ( + <div ref={!isSupportConstantValue ? triggerRef : null} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'group/wrap relative flex h-8 w-full items-center', !isSupportConstantValue && 'rounded-lg bg-components-input-bg-normal p-1', isInTable && 'border-none bg-transparent', readonly && 'bg-components-input-bg-disabled')}> + {isSupportConstantValue + ? ( + <div + onClick={(e) => { + e.stopPropagation() + setOpen(false) + setControlFocus(Date.now()) + }} + className="mr-1 flex h-full items-center space-x-1" + > + <TypeSelector + noLeft + trigger={( + <div className="radius-md flex h-8 items-center bg-components-input-bg-normal px-2"> + <div className="system-sm-regular mr-1 text-components-input-text-filled">{varKindTypes.find(item => item.value === varKindType)?.label}</div> + <RiArrowDownSLine className="h-4 w-4 text-text-quaternary" /> + </div> + )} + popupClassName="top-8" + readonly={readonly} + value={varKindType} + options={varKindTypes} + onChange={handleVarKindTypeChange} + showChecked + /> </div> - </Tooltip> - </div> + ) + : (!hasValue && ( + <div className="ml-1.5 mr-1"> + <Variable02 className={`h-4 w-4 ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'}`} /> + </div> + ))} + {isConstant + ? ( + <ConstantField + value={value as string} + onChange={onChange as ((value: string | number, varKindType: VarKindType, varInfo?: Var) => void)} + schema={schemaWithDynamicSelect as CredentialFormSchema} + readonly={readonly} + isLoading={isLoading} + /> + ) + : ( + <VarPickerWrap + onClick={() => { + if (readonly) + return + if (!isConstant) + setOpen(!open) + else + setControlFocus(Date.now()) + }} + className="h-full grow" + > + <div ref={isSupportConstantValue ? triggerRef : null} className={cn('h-full', isSupportConstantValue && 'flex items-center rounded-lg bg-components-panel-bg py-1 pl-1')}> + <Tooltip noDecoration={isShowAPart} popupContent={tooltipPopup}> + <div className={cn('h-full items-center rounded-[5px] px-1.5', hasValue ? 'inline-flex bg-components-badge-white-to-dark' : 'flex')}> + {hasValue + ? ( + <> + {isShowNodeName && !isEnv && !isChatVar && !isGlobal && !isRagVar && ( + <div + className="flex items-center" + onClick={(e) => { + if (e.metaKey || e.ctrlKey) { + e.stopPropagation() + handleVariableJump(outputVarNode?.id) + } + }} + > + <div className="h-3 px-[1px]"> + {outputVarNode?.type && ( + <VarBlockIcon + className="!text-text-primary" + type={outputVarNode.type} + /> + )} + </div> + <div + className="mx-0.5 truncate text-xs font-medium text-text-secondary" + title={outputVarNode?.title} + style={{ + maxWidth: maxNodeNameWidth, + }} + > + {outputVarNode?.title} + </div> + <Line3 className="mr-0.5"></Line3> + </div> + )} + {isShowAPart && ( + <div className="flex items-center"> + <RiMoreLine className="h-3 w-3 text-text-secondary" /> + <Line3 className="mr-0.5 text-divider-deep"></Line3> + </div> + )} + <div className="flex items-center text-text-accent"> + {isLoading && <RiLoader4Line className="h-3.5 w-3.5 animate-spin text-text-secondary" />} + <VariableIconWithColor + variables={value as ValueSelector} + variableCategory={variableCategory} + isExceptionVariable={isException} + /> + <div + className={cn('ml-0.5 truncate text-xs font-medium', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning', isGlobal && 'text-util-colors-orange-orange-600')} + title={varName} + style={{ + maxWidth: maxVarNameWidth, + }} + > + {varName} + </div> + </div> + <div + className="system-xs-regular ml-0.5 truncate text-center capitalize text-text-tertiary" + title={type} + style={{ + maxWidth: maxTypeWidth, + }} + > + {type} + </div> + {!isValidVar && <RiErrorWarningFill className="ml-0.5 h-3 w-3 text-text-destructive" />} + </> + ) + : ( + <div className={`overflow-hidden ${readonly ? 'text-components-input-text-disabled' : 'text-components-input-text-placeholder'} system-sm-regular text-ellipsis`}> + {isLoading + ? ( + <div className="flex items-center"> + <RiLoader4Line className="mr-1 h-3.5 w-3.5 animate-spin text-text-secondary" /> + <span>{placeholder ?? t('workflow.common.setVarValuePlaceholder')}</span> + </div> + ) + : ( + placeholder ?? t('workflow.common.setVarValuePlaceholder') + )} + </div> + )} + </div> + </Tooltip> + </div> - </VarPickerWrap> - )} - {(hasValue && !readonly && !isInTable) && (<div - className='group invisible absolute right-1 top-[50%] h-5 translate-y-[-50%] cursor-pointer rounded-md p-1 hover:bg-state-base-hover group-hover/wrap:visible' - onClick={handleClearVar} - > - <RiCloseLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-secondary' /> - </div>)} - {!hasValue && valueTypePlaceHolder && ( - <Badge - className=' absolute right-1 top-[50%] translate-y-[-50%] capitalize' - text={valueTypePlaceHolder} - uppercase={false} - /> + </VarPickerWrap> + )} + {(hasValue && !readonly && !isInTable) && ( + <div + className="group invisible absolute right-1 top-[50%] h-5 translate-y-[-50%] cursor-pointer rounded-md p-1 hover:bg-state-base-hover group-hover/wrap:visible" + onClick={handleClearVar} + > + <RiCloseLine className="h-3.5 w-3.5 text-text-tertiary group-hover:text-text-secondary" /> + </div> + )} + {!hasValue && valueTypePlaceHolder && ( + <Badge + className=" absolute right-1 top-[50%] translate-y-[-50%] capitalize" + text={valueTypePlaceHolder} + uppercase={false} + /> + )} + </div> )} - </div>)} {!readonly && isInTable && ( <RemoveButton - className='absolute right-1 top-0.5 hidden group-hover/picker-trigger-wrap:block' + className="absolute right-1 top-0.5 hidden group-hover/picker-trigger-wrap:block" onClick={() => onRemove?.()} /> )} {!hasValue && typePlaceHolder && ( <Badge - className='absolute right-2 top-1.5' + className="absolute right-2 top-1.5" text={typePlaceHolder} uppercase={false} /> )} </> </WrapElem> - <PortalToFollowElemContent style={{ - zIndex: zIndex || 100, - }} className='mt-1'> + <PortalToFollowElemContent + style={{ + zIndex: zIndex || 100, + }} + className="mt-1" + > {!isConstant && ( <VarReferencePopup vars={outputVars} @@ -586,7 +639,7 @@ const VarReferencePicker: FC<Props> = ({ )} </PortalToFollowElemContent> </PortalToFollowElem> - </div > + </div> ) } export default React.memo(VarReferencePicker) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx index 5f68bcbf0c..45ad5d9f8c 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import VarReferenceVars from './var-reference-vars' -import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import ListEmpty from '@/app/components/base/list-empty' import { useStore } from '@/app/components/workflow/store' import { useDocLink } from '@/context/i18n' +import VarReferenceVars from './var-reference-vars' type Props = { vars: NodeOutPutVar[] @@ -33,48 +33,59 @@ const VarReferencePopup: FC<Props> = ({ const docLink = useDocLink() // max-h-[300px] overflow-y-auto todo: use portal to handle long list return ( - <div className='space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg' style={{ - width: itemWidth || 228, - }}> + <div + className="space-y-1 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg" + style={{ + width: itemWidth || 228, + }} + > {((!vars || vars.length === 0) && popupFor) ? (popupFor === 'toAssigned' - ? ( - <ListEmpty - title={t('workflow.variableReference.noAvailableVars') || ''} - description={<div className='system-xs-regular text-text-tertiary'> - {t('workflow.variableReference.noVarsForOperation')} - </div>} + ? ( + <ListEmpty + title={t('workflow.variableReference.noAvailableVars') || ''} + description={( + <div className="system-xs-regular text-text-tertiary"> + {t('workflow.variableReference.noVarsForOperation')} + </div> + )} + /> + ) + : ( + <ListEmpty + title={t('workflow.variableReference.noAssignedVars') || ''} + description={( + <div className="system-xs-regular text-text-tertiary"> + {t('workflow.variableReference.assignedVarsDescription')} + <a + target="_blank" + rel="noopener noreferrer" + className="text-text-accent-secondary" + href={docLink('/guides/workflow/variables#conversation-variables', { + 'zh-Hans': '/guides/workflow/variables#会话变量', + 'ja-JP': '/guides/workflow/variables#会話変数', + })} + > + {t('workflow.variableReference.conversationVars')} + </a> + </div> + )} + /> + )) + : ( + <VarReferenceVars + searchBoxClassName="mt-1" + vars={vars} + onChange={onChange} + itemWidth={itemWidth} + isSupportFileVar={isSupportFileVar} + zIndex={zIndex} + showManageInputField={showManageRagInputFields} + onManageInputField={() => setShowInputFieldPanel?.(true)} + preferSchemaType={preferSchemaType} /> - ) - : ( - <ListEmpty - title={t('workflow.variableReference.noAssignedVars') || ''} - description={<div className='system-xs-regular text-text-tertiary'> - {t('workflow.variableReference.assignedVarsDescription')} - <a target='_blank' rel='noopener noreferrer' - className='text-text-accent-secondary' - href={docLink('/guides/workflow/variables#conversation-variables', { - 'zh-Hans': '/guides/workflow/variables#会话变量', - 'ja-JP': '/guides/workflow/variables#会話変数', - })}> - {t('workflow.variableReference.conversationVars')} - </a> - </div>} - /> - )) - : <VarReferenceVars - searchBoxClassName='mt-1' - vars={vars} - onChange={onChange} - itemWidth={itemWidth} - isSupportFileVar={isSupportFileVar} - zIndex={zIndex} - showManageInputField={showManageRagInputFields} - onManageInputField={() => setShowInputFieldPanel?.(true)} - preferSchemaType={preferSchemaType} - /> - } - </div > + )} + </div> ) } export default React.memo(VarReferencePopup) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 8461f0e5f6..5482eea996 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -1,29 +1,30 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import type { StructuredOutput } from '../../../llm/types' +import type { Field } from '@/app/components/workflow/nodes/llm/types' +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useHover } from 'ahooks' +import { noop } from 'lodash-es' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' -import { type NodeOutPutVar, type ValueSelector, type Var, VarType } from '@/app/components/workflow/types' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' +import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import Input from '@/app/components/base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Input from '@/app/components/base/input' -import { checkKeys } from '@/utils/var' -import type { StructuredOutput } from '../../../llm/types' -import { Type } from '../../../llm/types' -import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' -import { isSpecialVar, varTypeToStructType } from './utils' -import type { Field } from '@/app/components/workflow/nodes/llm/types' -import { noop } from 'lodash-es' -import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' -import ManageInputField from './manage-input-field' -import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' +import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import { checkKeys } from '@/utils/var' +import { Type } from '../../../llm/types' +import ManageInputField from './manage-input-field' +import { isSpecialVar, varTypeToStructType } from './utils' type ItemProps = { nodeId: string @@ -74,16 +75,16 @@ const Item: FC<ItemProps> = ({ switch (variable) { case 'current': Icon = isInCodeGeneratorInstructionEditor ? CodeAssistant : MagicEdit - return <Icon className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' /> + return <Icon className="h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600" /> case 'error_message': - return <Variable02 className='h-3.5 w-3.5 shrink-0 text-util-colors-orange-dark-orange-dark-600' /> + return <Variable02 className="h-3.5 w-3.5 shrink-0 text-util-colors-orange-dark-orange-dark-600" /> default: - return <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' /> + return <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" /> } }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) const varName = useMemo(() => { - if(VAR_SHOW_NAME_MAP[itemData.variable]) + if (VAR_SHOW_NAME_MAP[itemData.variable]) return VAR_SHOW_NAME_MAP[itemData.variable] if (!isFlat) @@ -95,7 +96,8 @@ const Item: FC<ItemProps> = ({ }, [isFlat, isInCodeGeneratorInstructionEditor, itemData.variable]) const objStructuredOutput: StructuredOutput | null = useMemo(() => { - if (!isObj) return null + if (!isObj) + return null const properties: Record<string, Field> = {} const childrenVars = (itemData.children as Var[]) || [] childrenVars.forEach((c) => { @@ -160,19 +162,23 @@ const Item: FC<ItemProps> = ({ } } const variableCategory = useMemo(() => { - if (isEnv) return 'environment' - if (isChatVar) return 'conversation' - if (isLoopVar) return 'loop' - if (isRagVariable) return 'rag' + if (isEnv) + return 'environment' + if (isChatVar) + return 'conversation' + if (isLoopVar) + return 'loop' + if (isRagVariable) + return 'rag' return 'system' }, [isEnv, isChatVar, isSys, isLoopVar, isRagVariable]) return ( <PortalToFollowElem open={open} onOpenChange={noop} - placement='left-start' + placement="left-start" > - <PortalToFollowElemTrigger className='w-full'> + <PortalToFollowElemTrigger className="w-full"> <div ref={itemRef} className={cn( @@ -180,43 +186,45 @@ const Item: FC<ItemProps> = ({ isHovering && ((isObj || isStructureOutput) ? 'bg-components-panel-on-panel-item-bg-hover' : 'bg-state-base-hover'), 'relative flex h-6 w-full cursor-pointer items-center rounded-md pl-3', className, - ) - } + )} onClick={handleChosen} onMouseDown={e => e.preventDefault()} > - <div className='flex w-0 grow items-center'> - {!isFlat && <VariableIconWithColor - variables={itemData.variable.split('.')} - variableCategory={variableCategory} - isExceptionVariable={isException} - />} + <div className="flex w-0 grow items-center"> + {!isFlat && ( + <VariableIconWithColor + variables={itemData.variable.split('.')} + variableCategory={variableCategory} + isExceptionVariable={isException} + /> + )} {isFlat && flatVarIcon} {!isEnv && !isChatVar && !isRagVariable && ( - <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div> + <div title={itemData.variable} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{varName}</div> )} {isEnv && ( - <div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('env.', '')}</div> + <div title={itemData.variable} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.replace('env.', '')}</div> )} {isChatVar && ( - <div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div> + <div title={itemData.des} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.replace('conversation.', '')}</div> )} {isRagVariable && ( - <div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div> + <div title={itemData.des} className="system-sm-medium ml-1 w-0 grow truncate text-text-secondary">{itemData.variable.split('.').slice(-1)[0]}</div> )} </div> - <div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div> + <div className="ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary">{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div> { (isObj || isStructureOutput) && ( <ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} /> ) } - </div > - </PortalToFollowElemTrigger > + </div> + </PortalToFollowElemTrigger> <PortalToFollowElemContent style={{ zIndex: zIndex || 100, - }}> + }} + > {(isStructureOutput || isObj) && ( <PickerStructurePanel root={{ nodeId, nodeName: title, attrName: itemData.variable, attrAlias: itemData.schemaType }} @@ -228,7 +236,7 @@ const Item: FC<ItemProps> = ({ /> )} </PortalToFollowElemContent> - </PortalToFollowElem > + </PortalToFollowElem> ) } @@ -308,7 +316,7 @@ const VarReferenceVars: FC<Props> = ({ <> <div className={cn('var-search-input-wrapper mx-2 mb-2 mt-2', searchBoxClassName)} onClick={e => e.stopPropagation()}> <Input - className='var-search-input' + className="var-search-input" showLeftIcon showClearIcon value={searchText} @@ -320,54 +328,63 @@ const VarReferenceVars: FC<Props> = ({ autoFocus={autoFocus} /> </div> - <div className='relative left-[-4px] h-[0.5px] bg-black/5' style={{ - width: 'calc(100% + 8px)', - }}></div> + <div + className="relative left-[-4px] h-[0.5px] bg-black/5" + style={{ + width: 'calc(100% + 8px)', + }} + > + </div> </> ) } {filteredVars.length > 0 - ? <div className={cn('max-h-[85vh] overflow-y-auto', maxHeightClass)}> + ? ( + <div className={cn('max-h-[85vh] overflow-y-auto', maxHeightClass)}> - { - filteredVars.map((item, i) => ( - <div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}> - {!item.isFlat && ( - <div - className='system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary' - title={item.title} - >{item.title}</div> - )} - {item.vars.map((v, j) => ( - <Item - key={j} - title={item.title} - nodeId={item.nodeId} - objPath={[]} - itemData={v} - onChange={onChange} - itemWidth={itemWidth} - isSupportFileVar={isSupportFileVar} - isException={v.isException} - isLoopVar={item.isLoop} - isFlat={item.isFlat} - isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor} - zIndex={zIndex} - preferSchemaType={preferSchemaType} - /> - ))} - {item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && ( - <div className='relative mt-[14px] flex items-center space-x-1'> - <div className='h-0 w-3 shrink-0 border border-divider-subtle'></div> - <div className='system-2xs-semibold-uppercase text-text-tertiary'>{t('workflow.debug.lastOutput')}</div> - <div className='h-0 shrink-0 grow border border-divider-subtle'></div> + { + filteredVars.map((item, i) => ( + <div key={i} className={cn(!item.isFlat && 'mt-3', i === 0 && item.isFlat && 'mt-2')}> + {!item.isFlat && ( + <div + className="system-xs-medium-uppercase truncate px-3 leading-[22px] text-text-tertiary" + title={item.title} + > + {item.title} + </div> + )} + {item.vars.map((v, j) => ( + <Item + key={j} + title={item.title} + nodeId={item.nodeId} + objPath={[]} + itemData={v} + onChange={onChange} + itemWidth={itemWidth} + isSupportFileVar={isSupportFileVar} + isException={v.isException} + isLoopVar={item.isLoop} + isFlat={item.isFlat} + isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor} + zIndex={zIndex} + preferSchemaType={preferSchemaType} + /> + ))} + {item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && ( + <div className="relative mt-[14px] flex items-center space-x-1"> + <div className="h-0 w-3 shrink-0 border border-divider-subtle"></div> + <div className="system-2xs-semibold-uppercase text-text-tertiary">{t('workflow.debug.lastOutput')}</div> + <div className="h-0 shrink-0 grow border border-divider-subtle"></div> + </div> + )} </div> - )} - </div>)) - } - </div> - : <div className='mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>} + )) + } + </div> + ) + : <div className="mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500">{t('workflow.common.noVar')}</div>} { showManageInputField && ( <ManageInputField diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx index fa2b9c1d6a..b6b08bc799 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx @@ -1,15 +1,15 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' import { RiArrowDownSLine } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import React, { useCallback, useState } from 'react' +import { Check } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { Check } from '@/app/components/base/icons/src/vender/line/general' import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' type Props = { className?: string @@ -39,27 +39,28 @@ const VarReferencePicker: FC<Props> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={4} > - <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='w-[120px] cursor-pointer'> - <div className='flex h-8 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2.5 text-[13px] text-text-primary'> - <div className='w-0 grow truncate capitalize' title={value}>{value}</div> - <RiArrowDownSLine className='h-3.5 w-3.5 shrink-0 text-text-secondary' /> + <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className="w-[120px] cursor-pointer"> + <div className="flex h-8 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2.5 text-[13px] text-text-primary"> + <div className="w-0 grow truncate capitalize" title={value}>{value}</div> + <RiArrowDownSLine className="h-3.5 w-3.5 shrink-0 text-text-secondary" /> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent style={{ zIndex: 100, - }}> - <div className='w-[120px] rounded-lg bg-components-panel-bg p-1 shadow-sm'> + }} + > + <div className="w-[120px] rounded-lg bg-components-panel-bg p-1 shadow-sm"> {TYPES.map(type => ( <div key={type} - className='flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-text-primary hover:bg-state-base-hover' + className="flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-text-primary hover:bg-state-base-hover" onClick={handleChange(type)} > - <div className='w-0 grow truncate capitalize'>{type}</div> - {type === value && <Check className='h-4 w-4 shrink-0 text-text-accent' />} + <div className="w-0 grow truncate capitalize">{type}</div> + {type === value && <Check className="h-4 w-4 shrink-0 text-text-accent" />} </div> ))} </div> diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx index b97287da1e..c515cd9e7a 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon.tsx @@ -1,7 +1,7 @@ +import type { VarInInspectType } from '@/types/workflow' import { memo } from 'react' import { cn } from '@/utils/classnames' import { useVarIcon } from '../hooks' -import type { VarInInspectType } from '@/types/workflow' export type VariableIconProps = { className?: string diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx index 3eb31ae5e0..63b392482f 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx @@ -1,17 +1,17 @@ -import { memo } from 'react' -import { capitalize } from 'lodash-es' +import type { VariablePayload } from '../types' import { RiErrorWarningFill, RiMoreLine, } from '@remixicon/react' -import type { VariablePayload } from '../types' +import { capitalize } from 'lodash-es' +import { memo } from 'react' +import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' +import { isConversationVar, isENV, isGlobalVar, isRagVariableVar } from '../../utils' import { useVarColor } from '../hooks' -import VariableNodeLabel from './variable-node-label' import VariableIcon from './variable-icon' import VariableName from './variable-name' -import { cn } from '@/utils/classnames' -import Tooltip from '@/app/components/base/tooltip' -import { isConversationVar, isENV, isGlobalVar, isRagVariableVar } from '../../utils' +import VariableNodeLabel from './variable-node-label' const VariableLabel = ({ nodeType, @@ -46,8 +46,8 @@ const VariableLabel = ({ { notShowFullPath && ( <> - <RiMoreLine className='h-3 w-3 shrink-0 text-text-secondary' /> - <div className='system-xs-regular shrink-0 text-divider-deep'>/</div> + <RiMoreLine className="h-3 w-3 shrink-0 text-text-secondary" /> + <div className="system-xs-regular shrink-0 text-divider-deep">/</div> </> ) } @@ -62,7 +62,7 @@ const VariableLabel = ({ /> { variableType && ( - <div className='system-xs-regular shrink-0 text-text-tertiary'> + <div className="system-xs-regular shrink-0 text-text-tertiary"> {capitalize(variableType)} </div> ) @@ -73,7 +73,7 @@ const VariableLabel = ({ popupContent={errorMsg} asChild > - <RiErrorWarningFill className='h-3 w-3 shrink-0 text-text-destructive' /> + <RiErrorWarningFill className="h-3 w-3 shrink-0 text-text-destructive" /> </Tooltip> ) } diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx index ea1ee539ed..d3d9b6bbcf 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-name.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' -import { useVarName } from '../hooks' import { cn } from '@/utils/classnames' +import { useVarName } from '../hooks' type VariableNameProps = { variables: string[] diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx index 35b539d97a..35434141ca 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-node-label.tsx @@ -1,6 +1,6 @@ +import type { BlockEnum } from '@/app/components/workflow/types' import { memo } from 'react' import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import type { BlockEnum } from '@/app/components/workflow/types' type VariableNodeLabelProps = { nodeType?: BlockEnum @@ -17,19 +17,19 @@ const VariableNodeLabel = ({ <> <VarBlockIcon type={nodeType} - className='shrink-0 text-text-secondary' + className="shrink-0 text-text-secondary" /> { nodeTitle && ( <div - className='system-xs-medium max-w-[60px] truncate text-text-secondary' + className="system-xs-medium max-w-[60px] truncate text-text-secondary" title={nodeTitle} > {nodeTitle} </div> ) } - <div className='system-xs-regular shrink-0 text-divider-deep'>/</div> + <div className="system-xs-regular shrink-0 text-divider-deep">/</div> </> ) } diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts b/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts index bb388d429a..a892bd9987 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/hooks.ts @@ -1,8 +1,10 @@ import { useMemo } from 'react' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { BubbleX, Env, GlobalVariable } from '@/app/components/base/icons/src/vender/line/others' -import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { InputField } from '@/app/components/base/icons/src/vender/pipeline' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { Loop } from '@/app/components/base/icons/src/vender/workflow' +import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' +import { VarInInspectType } from '@/types/workflow' import { isConversationVar, isENV, @@ -10,8 +12,6 @@ import { isRagVariableVar, isSystemVar, } from '../utils' -import { VarInInspectType } from '@/types/workflow' -import { VAR_SHOW_NAME_MAP } from '@/app/components/workflow/constants' export const useVarIcon = (variables: string[], variableCategory?: VarInInspectType | string) => { if (variableCategory === 'loop') diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx index 012522e0aa..9cb2176fcd 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/index.tsx @@ -1,5 +1,5 @@ -export { default as VariableLabelInSelect } from './variable-label-in-select' +export { default as VariableIconWithColor } from './variable-icon-with-color' export { default as VariableLabelInEditor } from './variable-label-in-editor' export { default as VariableLabelInNode } from './variable-label-in-node' +export { default as VariableLabelInSelect } from './variable-label-in-select' export { default as VariableLabelInText } from './variable-label-in-text' -export { default as VariableIconWithColor } from './variable-icon-with-color' diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx index 793f6a93e5..bbe580e79e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-icon-with-color.tsx @@ -1,8 +1,8 @@ -import { memo } from 'react' -import VariableIcon from './base/variable-icon' import type { VariableIconProps } from './base/variable-icon' -import { useVarColor } from './hooks' +import { memo } from 'react' import { cn } from '@/utils/classnames' +import VariableIcon from './base/variable-icon' +import { useVarColor } from './hooks' type VariableIconWithColorProps = { isExceptionVariable?: boolean diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx index 05774e59c2..8a65996dc0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-editor.tsx @@ -1,8 +1,8 @@ -import { memo } from 'react' import type { VariablePayload } from './types' +import { memo } from 'react' +import { cn } from '@/utils/classnames' import VariableLabel from './base/variable-label' import { useVarBgColorInEditor } from './hooks' -import { cn } from '@/utils/classnames' type VariableLabelInEditorProps = { isSelected?: boolean diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx index db3484affa..5aebf00e11 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-node.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' import type { VariablePayload } from './types' -import VariableLabel from './base/variable-label' +import { memo } from 'react' import { cn } from '@/utils/classnames' +import VariableLabel from './base/variable-label' const VariableLabelInNode = (variablePayload: VariablePayload) => { return ( diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx index 34e7b5f461..259fc26689 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-select.tsx @@ -1,5 +1,5 @@ -import { memo } from 'react' import type { VariablePayload } from './types' +import { memo } from 'react' import VariableLabel from './base/variable-label' const VariableLabelInSelect = (variablePayload: VariablePayload) => { diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx index eb66943fbc..2636ad747b 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/variable-label-in-text.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' import type { VariablePayload } from './types' -import VariableLabel from './base/variable-label' +import { memo } from 'react' import { cn } from '@/utils/classnames' +import VariableLabel from './base/variable-label' const VariableLabelInText = (variablePayload: VariablePayload) => { return ( diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index 309b88ffe2..3624e10bb1 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -1,6 +1,27 @@ +import type { FC, ReactNode } from 'react' +import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list' +import type { CustomRunFormProps } from '@/app/components/workflow/nodes/data-source/types' +import type { Node } from '@/app/components/workflow/types' +import { + RiCloseLine, + RiPlayLargeLine, +} from '@remixicon/react' +import { debounce } from 'lodash-es' +import React, { + cloneElement, + memo, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import Tooltip from '@/app/components/base/tooltip' +import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { AuthCategory, @@ -10,11 +31,9 @@ import { PluginAuthInDataSourceNode, } from '@/app/components/plugins/plugin-auth' import { usePluginStore } from '@/app/components/plugins/plugin-detail-panel/store' -import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list' import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' import BlockIcon from '@/app/components/workflow/block-icon' import { - WorkflowHistoryEvent, useAvailableBlocks, useNodeDataUpdate, useNodesInteractions, @@ -22,17 +41,17 @@ import { useNodesReadOnly, useToolIcon, useWorkflowHistory, + WorkflowHistoryEvent, } from '@/app/components/workflow/hooks' import { useHooksStore } from '@/app/components/workflow/hooks-store' import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' import Split from '@/app/components/workflow/nodes/_base/components/split' import DataSourceBeforeRunForm from '@/app/components/workflow/nodes/data-source/before-run-form' -import type { CustomRunFormProps } from '@/app/components/workflow/nodes/data-source/types' import { DataSourceClassification } from '@/app/components/workflow/nodes/data-source/types' import { useLogs } from '@/app/components/workflow/run/hooks' import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel' import { useStore } from '@/app/components/workflow/store' -import { BlockEnum, type Node, NodeRunningStatus } from '@/app/components/workflow/types' +import { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types' import { canRunBySingle, hasErrorHandleNode, @@ -45,24 +64,6 @@ import { useAllTriggerPlugins } from '@/service/use-triggers' import { FlowType } from '@/types/common' import { canFindTool } from '@/utils' import { cn } from '@/utils/classnames' -import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' -import { - RiCloseLine, - RiPlayLargeLine, -} from '@remixicon/react' -import { debounce } from 'lodash-es' -import type { FC, ReactNode } from 'react' -import React, { - cloneElement, - memo, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import { useShallow } from 'zustand/react/shallow' import { useResizePanel } from '../../hooks/use-resize-panel' import BeforeRunForm from '../before-run-form' import PanelWrap from '../before-run-form/panel-wrap' @@ -83,7 +84,14 @@ const getCustomRunForm = (params: CustomRunFormProps): React.JSX.Element => { case BlockEnum.DataSource: return <DataSourceBeforeRunForm {...params} /> default: - return <div>Custom Run Form: {nodeType} not found</div> + return ( + <div> + Custom Run Form: + {nodeType} + {' '} + not found + </div> + ) } } @@ -362,7 +370,7 @@ const BasePanel: FC<BasePanelProps> = ({ default: break } - return !pluginDetail ? null : <ReadmeEntrance pluginDetail={pluginDetail as any} className='mt-auto' /> + return !pluginDetail ? null : <ReadmeEntrance pluginDetail={pluginDetail as any} className="mt-auto" /> }, [data.type, currToolCollection, currentDataSource, currentTriggerPlugin]) const selectedNode = useMemo(() => ({ @@ -373,7 +381,8 @@ const BasePanel: FC<BasePanelProps> = ({ return ( <div className={cn( 'relative mr-1 h-full', - )}> + )} + > <div ref={containerRef} className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')} @@ -385,7 +394,7 @@ const BasePanel: FC<BasePanelProps> = ({ nodeName={data.title} onHide={hideSingleRun} > - <div className='h-0 grow overflow-y-auto pb-4'> + <div className="h-0 grow overflow-y-auto pb-4"> <SpecialResultPanel {...passedLogParams} /> </div> </PanelWrap> @@ -412,7 +421,8 @@ const BasePanel: FC<BasePanelProps> = ({ return ( <div className={cn( 'relative mr-1 h-full', - )}> + )} + > <div ref={containerRef} className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')} @@ -420,20 +430,22 @@ const BasePanel: FC<BasePanelProps> = ({ width: `${nodePanelWidth}px`, }} > - {isSupportCustomRunForm(data.type) ? ( - form - ) : ( - <BeforeRunForm - nodeName={data.title} - nodeType={data.type} - onHide={hideSingleRun} - onRun={handleRunWithParams} - {...singleRunParams!} - {...passedLogParams} - existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)} - filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)} - /> - )} + {isSupportCustomRunForm(data.type) + ? ( + form + ) + : ( + <BeforeRunForm + nodeName={data.title} + nodeType={data.type} + onHide={hideSingleRun} + onRun={handleRunWithParams} + {...singleRunParams!} + {...passedLogParams} + existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)} + filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)} + /> + )} </div> </div> @@ -452,8 +464,9 @@ const BasePanel: FC<BasePanelProps> = ({ > <div ref={triggerRef} - className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'> - <div className='h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid'></div> + className="absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center" + > + <div className="h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid"></div> </div> <div ref={containerRef} @@ -462,28 +475,28 @@ const BasePanel: FC<BasePanelProps> = ({ width: `${nodePanelWidth}px`, }} > - <div className='sticky top-0 z-10 shrink-0 border-b-[0.5px] border-divider-regular bg-components-panel-bg'> - <div className='flex items-center px-4 pb-1 pt-4'> + <div className="sticky top-0 z-10 shrink-0 border-b-[0.5px] border-divider-regular bg-components-panel-bg"> + <div className="flex items-center px-4 pb-1 pt-4"> <BlockIcon - className='mr-1 shrink-0' + className="mr-1 shrink-0" type={data.type} toolIcon={toolIcon} - size='md' + size="md" /> <TitleInput value={data.title || ''} onBlur={handleTitleBlur} /> - <div className='flex shrink-0 items-center text-text-tertiary'> + <div className="flex shrink-0 items-center text-text-tertiary"> { isSupportSingleRun && !nodesReadOnly && ( <Tooltip popupContent={t('workflow.panel.runThisStep')} - popupClassName='mr-1' + popupClassName="mr-1" disabled={isSingleRunning} > <div - className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover' + className="mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover" onClick={() => { if (isSingleRunning) handleStop() @@ -492,8 +505,9 @@ const BasePanel: FC<BasePanelProps> = ({ }} > { - isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' /> - : <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' /> + isSingleRunning + ? <Stop className="h-4 w-4 text-text-tertiary" /> + : <RiPlayLargeLine className="h-4 w-4 text-text-tertiary" /> } </div> </Tooltip> @@ -501,16 +515,16 @@ const BasePanel: FC<BasePanelProps> = ({ } <HelpLink nodeType={data.type} /> <PanelOperator id={id} data={data} showHelpLink={false} /> - <div className='mx-3 h-3.5 w-[1px] bg-divider-regular' /> + <div className="mx-3 h-3.5 w-[1px] bg-divider-regular" /> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={() => handleNodeSelect(id, true)} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='p-2'> + <div className="p-2"> <DescriptionInput value={data.desc || ''} onChange={handleDescriptionChange} @@ -519,7 +533,7 @@ const BasePanel: FC<BasePanelProps> = ({ { needsToolAuth && ( <PluginAuth - className='px-4 pb-2' + className="px-4 pb-2" pluginPayload={{ provider: currToolCollection?.name || '', providerType: currToolCollection?.type || '', @@ -527,7 +541,7 @@ const BasePanel: FC<BasePanelProps> = ({ detail: currToolCollection as any, }} > - <div className='flex items-center justify-between pl-4 pr-3'> + <div className="flex items-center justify-between pl-4 pr-3"> <Tab value={tabType} onChange={setTabType} @@ -552,7 +566,7 @@ const BasePanel: FC<BasePanelProps> = ({ onJumpToDataSourcePage={handleJumpToDataSourcePage} isAuthorized={currentDataSource.is_authorized} > - <div className='flex items-center justify-between pl-4 pr-3'> + <div className="flex items-center justify-between pl-4 pr-3"> <Tab value={tabType} onChange={setTabType} @@ -580,7 +594,7 @@ const BasePanel: FC<BasePanelProps> = ({ } { !needsToolAuth && !currentDataSource && !currentTriggerPlugin && ( - <div className='flex items-center justify-between pl-4 pr-3'> + <div className="flex items-center justify-between pl-4 pr-3"> <Tab value={tabType} onChange={setTabType} @@ -591,7 +605,7 @@ const BasePanel: FC<BasePanelProps> = ({ <Split /> </div> {tabType === TabType.settings && ( - <div className='flex flex-1 flex-col overflow-y-auto'> + <div className="flex flex-1 flex-col overflow-y-auto"> <div> {cloneElement(children as any, { id, @@ -625,11 +639,11 @@ const BasePanel: FC<BasePanelProps> = ({ } { !!availableNextBlocks.length && ( - <div className='border-t-[0.5px] border-divider-regular p-4'> - <div className='system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary'> + <div className="border-t-[0.5px] border-divider-regular p-4"> + <div className="system-sm-semibold-uppercase mb-1 flex items-center text-text-secondary"> {t('workflow.panel.nextStep').toLocaleUpperCase()} </div> - <div className='system-xs-regular mb-2 text-text-tertiary'> + <div className="system-xs-regular mb-2 text-text-tertiary"> {t('workflow.panel.addNextStep')} </div> <NextStep selectedNode={selectedNode} /> diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx index 43dab49ed8..93d5debb51 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx @@ -1,15 +1,15 @@ 'use client' +import type { FC } from 'react' import type { ResultPanelProps } from '@/app/components/workflow/run/result-panel' +import type { NodeTracing } from '@/types/workflow' +import { RiLoader2Line } from '@remixicon/react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useHooksStore } from '@/app/components/workflow/hooks-store' import ResultPanel from '@/app/components/workflow/run/result-panel' import { NodeRunningStatus } from '@/app/components/workflow/types' -import type { FC } from 'react' -import React, { useCallback, useEffect, useMemo, useState } from 'react' -import NoData from './no-data' import { useLastRun } from '@/service/use-workflow' -import { RiLoader2Line } from '@remixicon/react' -import type { NodeTracing } from '@/types/workflow' -import { useHooksStore } from '@/app/components/workflow/hooks-store' import { FlowType } from '@/types/common' +import NoData from './no-data' type Props = { appId: string @@ -49,10 +49,10 @@ const LastRun: FC<Props> = ({ const canRunLastRun = !isRunAfterSingleRun || isOneStepRunSucceed || isOneStepRunFailed || (pageHasHide && hidePageOneStepRunFinished) const { data: lastRunResult, isFetching, error } = useLastRun(configsMap?.flowType || FlowType.appFlow, configsMap?.flowId || '', nodeId, canRunLastRun) const isRunning = useMemo(() => { - if(isPaused) + if (isPaused) return false - if(!isRunAfterSingleRun) + if (!isRunAfterSingleRun) return isFetching return [NodeRunningStatus.Running, NodeRunningStatus.NotStart].includes(oneStepRunRunningStatus!) }, [isFetching, isPaused, isRunAfterSingleRun, oneStepRunRunningStatus]) @@ -86,7 +86,7 @@ const LastRun: FC<Props> = ({ }, [isOneStepRunSucceed, isOneStepRunFailed, oneStepRunRunningStatus]) useEffect(() => { - if([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(oneStepRunRunningStatus!)) + if ([NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(oneStepRunRunningStatus!)) setHidePageOneStepFinishedStatus(oneStepRunRunningStatus!) }, [oneStepRunRunningStatus]) @@ -110,13 +110,14 @@ const LastRun: FC<Props> = ({ if (isFetching && !isRunAfterSingleRun) { return ( - <div className='flex h-0 grow flex-col items-center justify-center'> - <RiLoader2Line className='size-4 animate-spin text-text-tertiary' /> - </div>) + <div className="flex h-0 grow flex-col items-center justify-center"> + <RiLoader2Line className="size-4 animate-spin text-text-tertiary" /> + </div> + ) } if (isRunning) - return <ResultPanel status='running' showSteps={false} /> + return <ResultPanel status="running" showSteps={false} /> if (!isPaused && (noLastRun || !runResult)) { return ( <NoData canSingleRun={canSingleRun} onSingleRun={onSingleRunClicked} /> diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx index ad0058efae..4ae6ccd31f 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' -import Button from '@/app/components/base/button' import { RiPlayLine } from '@remixicon/react' +import React from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' type Props = { canSingleRun: boolean @@ -17,16 +17,16 @@ const NoData: FC<Props> = ({ }) => { const { t } = useTranslation() return ( - <div className='flex h-0 grow flex-col items-center justify-center'> - <ClockPlay className='h-8 w-8 text-text-quaternary' /> - <div className='system-xs-regular my-2 text-text-tertiary'>{t('workflow.debug.noData.description')}</div> + <div className="flex h-0 grow flex-col items-center justify-center"> + <ClockPlay className="h-8 w-8 text-text-quaternary" /> + <div className="system-xs-regular my-2 text-text-tertiary">{t('workflow.debug.noData.description')}</div> {canSingleRun && ( <Button - className='flex' - size='small' + className="flex" + size="small" onClick={onSingleRun} > - <RiPlayLine className='mr-1 h-3.5 w-3.5' /> + <RiPlayLine className="mr-1 h-3.5 w-3.5" /> <div>{t('workflow.debug.noData.runThisNode')}</div> </Button> )} diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts index ac9f2051c3..0de98db032 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts @@ -1,42 +1,42 @@ -import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' -import type { Params as OneStepRunParams } from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' -import { useCallback, useEffect, useState } from 'react' -import { TabType } from '../tab' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import useStartSingleRunFormParams from '@/app/components/workflow/nodes/start/use-single-run-form-params' -import useLLMSingleRunFormParams from '@/app/components/workflow/nodes/llm/use-single-run-form-params' -import useKnowledgeRetrievalSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params' -import useCodeSingleRunFormParams from '@/app/components/workflow/nodes/code/use-single-run-form-params' -import useTemplateTransformSingleRunFormParams from '@/app/components/workflow/nodes/template-transform/use-single-run-form-params' -import useQuestionClassifierSingleRunFormParams from '@/app/components/workflow/nodes/question-classifier/use-single-run-form-params' -import useParameterExtractorSingleRunFormParams from '@/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params' -import useHttpRequestSingleRunFormParams from '@/app/components/workflow/nodes/http/use-single-run-form-params' -import useToolSingleRunFormParams from '@/app/components/workflow/nodes/tool/use-single-run-form-params' -import useIterationSingleRunFormParams from '@/app/components/workflow/nodes/iteration/use-single-run-form-params' -import useAgentSingleRunFormParams from '@/app/components/workflow/nodes/agent/use-single-run-form-params' -import useDocExtractorSingleRunFormParams from '@/app/components/workflow/nodes/document-extractor/use-single-run-form-params' -import useLoopSingleRunFormParams from '@/app/components/workflow/nodes/loop/use-single-run-form-params' -import useIfElseSingleRunFormParams from '@/app/components/workflow/nodes/if-else/use-single-run-form-params' -import useVariableAggregatorSingleRunFormParams from '@/app/components/workflow/nodes/variable-assigner/use-single-run-form-params' -import useVariableAssignerSingleRunFormParams from '@/app/components/workflow/nodes/assigner/use-single-run-form-params' -import useKnowledgeBaseSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-base/use-single-run-form-params' - -import useToolGetDataForCheckMore from '@/app/components/workflow/nodes/tool/use-get-data-for-check-more' -import useTriggerPluginGetDataForCheckMore from '@/app/components/workflow/nodes/trigger-plugin/use-check-params' -import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config' - +import type { Params as OneStepRunParams } from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' // import import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' +import { useCallback, useEffect, useState } from 'react' +import Toast from '@/app/components/base/toast' import { useNodesSyncDraft, } from '@/app/components/workflow/hooks' import { useWorkflowRunValidation } from '@/app/components/workflow/hooks/use-checklist' import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' -import { useInvalidLastRun } from '@/service/use-workflow' +import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' +import useAgentSingleRunFormParams from '@/app/components/workflow/nodes/agent/use-single-run-form-params' +import useVariableAssignerSingleRunFormParams from '@/app/components/workflow/nodes/assigner/use-single-run-form-params' +import useCodeSingleRunFormParams from '@/app/components/workflow/nodes/code/use-single-run-form-params' +import useDocExtractorSingleRunFormParams from '@/app/components/workflow/nodes/document-extractor/use-single-run-form-params' +import useHttpRequestSingleRunFormParams from '@/app/components/workflow/nodes/http/use-single-run-form-params' +import useIfElseSingleRunFormParams from '@/app/components/workflow/nodes/if-else/use-single-run-form-params' +import useIterationSingleRunFormParams from '@/app/components/workflow/nodes/iteration/use-single-run-form-params' +import useKnowledgeBaseSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-base/use-single-run-form-params' +import useKnowledgeRetrievalSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params' +import useLLMSingleRunFormParams from '@/app/components/workflow/nodes/llm/use-single-run-form-params' +import useLoopSingleRunFormParams from '@/app/components/workflow/nodes/loop/use-single-run-form-params' +import useParameterExtractorSingleRunFormParams from '@/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params' +import useQuestionClassifierSingleRunFormParams from '@/app/components/workflow/nodes/question-classifier/use-single-run-form-params' + +import useStartSingleRunFormParams from '@/app/components/workflow/nodes/start/use-single-run-form-params' +import useTemplateTransformSingleRunFormParams from '@/app/components/workflow/nodes/template-transform/use-single-run-form-params' +import useToolGetDataForCheckMore from '@/app/components/workflow/nodes/tool/use-get-data-for-check-more' + +import useToolSingleRunFormParams from '@/app/components/workflow/nodes/tool/use-single-run-form-params' +import useTriggerPluginGetDataForCheckMore from '@/app/components/workflow/nodes/trigger-plugin/use-check-params' +import useVariableAggregatorSingleRunFormParams from '@/app/components/workflow/nodes/variable-assigner/use-single-run-form-params' import { useStore, useWorkflowStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' import { isSupportCustomRunForm } from '@/app/components/workflow/utils' -import Toast from '@/app/components/base/toast' +import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config' +import { useInvalidLastRun } from '@/service/use-workflow' +import { TabType } from '../tab' const singleRunFormParamsHooks: Record<BlockEnum, any> = { [BlockEnum.LLM]: useLLMSingleRunFormParams, diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx index 08bbdf4068..53ae913a8e 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx @@ -1,8 +1,8 @@ 'use client' -import TabHeader from '@/app/components/base/tab-header' import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' +import TabHeader from '@/app/components/base/tab-header' export enum TabType { settings = 'settings', @@ -11,7 +11,7 @@ export enum TabType { } type Props = { - value: TabType, + value: TabType onChange: (value: TabType) => void } @@ -26,7 +26,7 @@ const Tab: FC<Props> = ({ { id: TabType.settings, name: t('workflow.debug.settingsTab').toLocaleUpperCase() }, { id: TabType.lastRun, name: t('workflow.debug.lastRunTab').toLocaleUpperCase() }, ]} - itemClassName='ml-0' + itemClassName="ml-0" value={value} onChange={onChange as any} /> diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/trigger-subscription.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/trigger-subscription.tsx index 52c6d4fe18..a6dd65ea81 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/trigger-subscription.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/trigger-subscription.tsx @@ -1,9 +1,9 @@ +import type { FC } from 'react' import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list' import { CreateButtonType, CreateSubscriptionButton } from '@/app/components/plugins/plugin-detail-panel/subscription-list/create' import { SubscriptionSelectorEntry } from '@/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry' import { useSubscriptionList } from '@/app/components/plugins/plugin-detail-panel/subscription-list/use-subscription-list' import { cn } from '@/utils/classnames' -import type { FC } from 'react' type TriggerSubscriptionProps = { subscriptionIdSelected?: string @@ -15,12 +15,16 @@ export const TriggerSubscription: FC<TriggerSubscriptionProps> = ({ subscription const { subscriptions } = useSubscriptionList() const subscriptionCount = subscriptions?.length || 0 - return <div className={cn('px-4', subscriptionCount > 0 && 'flex items-center justify-between pr-3')}> - {!subscriptionCount && <CreateSubscriptionButton buttonType={CreateButtonType.FULL_BUTTON} />} - {children} - {subscriptionCount > 0 && <SubscriptionSelectorEntry - selectedId={subscriptionIdSelected} - onSelect={onSubscriptionChange} - />} - </div> + return ( + <div className={cn('px-4', subscriptionCount > 0 && 'flex items-center justify-between pr-3')}> + {!subscriptionCount && <CreateSubscriptionButton buttonType={CreateButtonType.FULL_BUTTON} />} + {children} + {subscriptionCount > 0 && ( + <SubscriptionSelectorEntry + selectedId={subscriptionIdSelected} + onSelect={onSubscriptionChange} + /> + )} + </div> + ) } diff --git a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts index 2ce9dfe809..f226900899 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-available-var-list.ts @@ -1,13 +1,13 @@ -import useNodeInfo from './use-node-info' +import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useIsChatMode, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' -import type { NodeOutPutVar } from '@/app/components/workflow/types' -import { BlockEnum, type Node, type ValueSelector, type Var } from '@/app/components/workflow/types' import { useStore as useWorkflowStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' import { inputVarTypeToVarType } from '../../data-source/utils' +import useNodeInfo from './use-node-info' type Params = { onlyLeafNodeVar?: boolean diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts index 51d2fdb80c..d1741f0bbb 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-crud.ts @@ -1,5 +1,6 @@ -import { useNodeDataUpdate } from '@/app/components/workflow/hooks' import type { CommonNodeType } from '@/app/components/workflow/types' +import { useNodeDataUpdate } from '@/app/components/workflow/hooks' + const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => { const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts index e40f6271ef..d97a87bfba 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react' import type { BlockEnum } from '@/app/components/workflow/types' +import { useMemo } from 'react' import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeHelpLink = (nodeType: BlockEnum) => { diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index dad62ae2a4..59774ab96a 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -1,15 +1,39 @@ +import type { CommonNodeType, InputVar, TriggerNodeType, ValueSelector, Var, Variable } from '@/app/components/workflow/types' +import type { FlowType } from '@/types/common' +import type { NodeRunResult, NodeTracing } from '@/types/workflow' +import { produce } from 'immer' + +import { noop, unionBy } from 'lodash-es' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { unionBy } from 'lodash-es' -import { produce } from 'immer' +import { + useStoreApi, +} from 'reactflow' +import { trackEvent } from '@/app/components/base/amplitude' +import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants' +import Toast from '@/app/components/base/toast' import { useIsChatMode, useNodeDataUpdate, useWorkflow, } from '@/app/components/workflow/hooks' +import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' import { getNodeInfoById, isConversationVar, isENV, isSystemVar, toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' - -import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types' +import Assigner from '@/app/components/workflow/nodes/assigner/default' +import CodeDefault from '@/app/components/workflow/nodes/code/default' +import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default' +import HTTPDefault from '@/app/components/workflow/nodes/http/default' +import IfElseDefault from '@/app/components/workflow/nodes/if-else/default' +import IterationDefault from '@/app/components/workflow/nodes/iteration/default' +import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default' +import LLMDefault from '@/app/components/workflow/nodes/llm/default' +import LoopDefault from '@/app/components/workflow/nodes/loop/default' +import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default' +import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-classifier/default' +import TemplateTransformDefault from '@/app/components/workflow/nodes/template-transform/default' +import ToolDefault from '@/app/components/workflow/nodes/tool/default' +import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum, InputVarType, @@ -17,29 +41,19 @@ import { VarType, WorkflowRunningStatus, } from '@/app/components/workflow/types' -import type { TriggerNodeType } from '@/app/components/workflow/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' -import { useStore, useWorkflowStore } from '@/app/components/workflow/store' -import { fetchNodeInspectVars, getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow' -import Toast from '@/app/components/base/toast' -import LLMDefault from '@/app/components/workflow/nodes/llm/default' -import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default' -import IfElseDefault from '@/app/components/workflow/nodes/if-else/default' -import CodeDefault from '@/app/components/workflow/nodes/code/default' -import TemplateTransformDefault from '@/app/components/workflow/nodes/template-transform/default' -import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-classifier/default' -import HTTPDefault from '@/app/components/workflow/nodes/http/default' -import ToolDefault from '@/app/components/workflow/nodes/tool/default' -import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default' -import Assigner from '@/app/components/workflow/nodes/assigner/default' -import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default' -import IterationDefault from '@/app/components/workflow/nodes/iteration/default' -import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default' -import LoopDefault from '@/app/components/workflow/nodes/loop/default' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { post, ssePost } from '@/service/base' -import { noop } from 'lodash-es' -import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants' -import type { NodeRunResult, NodeTracing } from '@/types/workflow' +import { + useAllBuiltInTools, + useAllCustomTools, + useAllMCPTools, + useAllWorkflowTools, +} from '@/service/use-tools' +import { useInvalidLastRun } from '@/service/use-workflow' +import { fetchNodeInspectVars, getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow' +import useMatchSchemaType from '../components/variable/use-match-schema-type' + const { checkValid: checkLLMValid } = LLMDefault const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault const { checkValid: checkIfElseValid } = IfElseDefault @@ -54,21 +68,6 @@ const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault const { checkValid: checkIterationValid } = IterationDefault const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault const { checkValid: checkLoopValid } = LoopDefault -import { - useStoreApi, -} from 'reactflow' -import { useInvalidLastRun } from '@/service/use-workflow' -import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' -import type { FlowType } from '@/types/common' -import useMatchSchemaType from '../components/variable/use-match-schema-type' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { - useAllBuiltInTools, - useAllCustomTools, - useAllMCPTools, - useAllWorkflowTools, -} from '@/service/use-tools' -import { trackEvent } from '@/app/components/base/amplitude' // eslint-disable-next-line ts/no-unsafe-function-type const checkValidFns: Partial<Record<BlockEnum, Function>> = { diff --git a/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts index 87a5f69c95..09b8fde0b5 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts @@ -1,6 +1,3 @@ -import { useCallback, useRef, useState } from 'react' -import { produce } from 'immer' -import { useBoolean, useDebounceFn } from 'ahooks' import type { CodeNodeType, OutputVar, @@ -8,15 +5,18 @@ import type { import type { ValueSelector, } from '@/app/components/workflow/types' -import { - BlockEnum, - VarType, -} from '@/app/components/workflow/types' +import { useBoolean, useDebounceFn } from 'ahooks' +import { produce } from 'immer' +import { useCallback, useRef, useState } from 'react' import { useWorkflow, } from '@/app/components/workflow/hooks' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils' +import { + BlockEnum, + VarType, +} from '@/app/components/workflow/types' import useInspectVarsCrud from '../../../hooks/use-inspect-vars-crud' type Params<T> = { @@ -74,7 +74,7 @@ function useOutputVarList<T>({ if (newKey) { handleOutVarRenameChange(id, [id, outputKeyOrders[changedIndex!]], [id, newKey]) - if(!(id in oldNameRecord.current)) + if (!(id in oldNameRecord.current)) oldNameRecord.current[id] = outputKeyOrders[changedIndex!] renameInspectNameWithDebounce(id, newKey) } @@ -82,7 +82,7 @@ function useOutputVarList<T>({ const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { return varItem.name === Object.keys(newVars)[0] })?.id - if(varId) + if (varId) deleteInspectVar(id, varId) } }, [inputs, setInputs, varKey, outputKeyOrders, onOutputKeyOrdersChange, handleOutVarRenameChange, id, renameInspectNameWithDebounce, nodesWithInspectVars, deleteInspectVar]) @@ -120,7 +120,7 @@ function useOutputVarList<T>({ const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { return varItem.name === removedVar[1] })?.id - if(varId) + if (varId) deleteInspectVar(id, varId) removeUsedVarInNodes(removedVar) hideRemoveVarConfirm() @@ -145,7 +145,7 @@ function useOutputVarList<T>({ const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { return varItem.name === key })?.id - if(varId) + if (varId) deleteInspectVar(id, varId) }, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, nodesWithInspectVars, deleteInspectVar, showRemoveVarConfirm, varKey]) diff --git a/web/app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts b/web/app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts index 48c1e95eb2..c123c00e2d 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-toggle-expend.ts @@ -11,7 +11,8 @@ const useToggleExpend = ({ ref, hasFooter = true, isInNode }: Params) => { const [wrapHeight, setWrapHeight] = useState(ref?.current?.clientHeight) const editorExpandHeight = isExpand ? wrapHeight! - (hasFooter ? 56 : 29) : 0 useEffect(() => { - if (!ref?.current) return + if (!ref?.current) + return setWrapHeight(ref.current?.clientHeight) }, [isExpand]) @@ -26,8 +27,8 @@ const useToggleExpend = ({ ref, hasFooter = true, isInNode }: Params) => { })() const wrapStyle = isExpand ? { - boxShadow: '0px 0px 12px -4px rgba(16, 24, 40, 0.05), 0px -3px 6px -2px rgba(16, 24, 40, 0.03)', - } + boxShadow: '0px 0px 12px -4px rgba(16, 24, 40, 0.05), 0px -3px 6px -2px rgba(16, 24, 40, 0.03)', + } : {} return { wrapClassName, diff --git a/web/app/components/workflow/nodes/_base/hooks/use-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-var-list.ts index ffacf9a2df..9b3bbbb9aa 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-var-list.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-var-list.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { Variable } from '@/app/components/workflow/types' +import { produce } from 'immer' +import { useCallback } from 'react' type Params<T> = { inputs: T diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 263732cd70..c2d0816449 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -2,6 +2,14 @@ import type { FC, ReactElement, } from 'react' +import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' +import type { NodeProps } from '@/app/components/workflow/types' +import { + RiAlertFill, + RiCheckboxCircleFill, + RiErrorWarningFill, + RiLoader2Line, +} from '@remixicon/react' import { cloneElement, memo, @@ -9,40 +17,32 @@ import { useMemo, useRef, } from 'react' -import { - RiAlertFill, - RiCheckboxCircleFill, - RiErrorWarningFill, - RiLoader2Line, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from '@/app/components/workflow/types' -import { - BlockEnum, - NodeRunningStatus, - isTriggerNode, -} from '@/app/components/workflow/types' +import Tooltip from '@/app/components/base/tooltip' +import BlockIcon from '@/app/components/workflow/block-icon' +import { ToolTypeEnum } from '@/app/components/workflow/block-selector/types' import { useNodesReadOnly, useToolIcon } from '@/app/components/workflow/hooks' -import { hasErrorHandleNode, hasRetryNode } from '@/app/components/workflow/utils' +import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' import { useNodeIterationInteractions } from '@/app/components/workflow/nodes/iteration/use-interactions' import { useNodeLoopInteractions } from '@/app/components/workflow/nodes/loop/use-interactions' -import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' import CopyID from '@/app/components/workflow/nodes/tool/components/copy-id' +import { + BlockEnum, + isTriggerNode, + NodeRunningStatus, +} from '@/app/components/workflow/types' +import { hasErrorHandleNode, hasRetryNode } from '@/app/components/workflow/utils' +import { cn } from '@/utils/classnames' +import AddVariablePopupWithPosition from './components/add-variable-popup-with-position' +import EntryNodeContainer, { StartNodeTypeEnum } from './components/entry-node-container' +import ErrorHandleOnNode from './components/error-handle/error-handle-on-node' +import NodeControl from './components/node-control' import { NodeSourceHandle, NodeTargetHandle, } from './components/node-handle' import NodeResizer from './components/node-resizer' -import NodeControl from './components/node-control' -import ErrorHandleOnNode from './components/error-handle/error-handle-on-node' import RetryOnNode from './components/retry/retry-on-node' -import AddVariablePopupWithPosition from './components/add-variable-popup-with-position' -import EntryNodeContainer, { StartNodeTypeEnum } from './components/entry-node-container' -import { cn } from '@/utils/classnames' -import BlockIcon from '@/app/components/workflow/block-icon' -import Tooltip from '@/app/components/base/tooltip' -import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' -import { ToolTypeEnum } from '@/app/components/workflow/block-selector/types' type NodeChildProps = { id: string @@ -160,13 +160,13 @@ const BaseNode: FC<BaseNodeProps> = ({ ? 'pointer-events-auto z-30 bg-workflow-block-parma-bg opacity-80 backdrop-blur-[2px]' : 'pointer-events-none z-20 bg-workflow-block-parma-bg opacity-50', )} - data-testid='workflow-node-install-overlay' + data-testid="workflow-node-install-overlay" /> )} { data.type === BlockEnum.DataSource && ( - <div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'> - <div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'> + <div className="absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]"> + <div className="system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary"> {t('workflow.blocks.datasource')} </div> </div> @@ -215,8 +215,8 @@ const BaseNode: FC<BaseNodeProps> = ({ <NodeTargetHandle id={id} data={data} - handleClassName='!top-4 !-left-[9px] !translate-y-0' - handleId='target' + handleClassName="!top-4 !-left-[9px] !translate-y-0" + handleId="target" /> ) } @@ -225,8 +225,8 @@ const BaseNode: FC<BaseNodeProps> = ({ <NodeSourceHandle id={id} data={data} - handleClassName='!top-4 !-right-[9px] !translate-y-0' - handleId='source' + handleClassName="!top-4 !-right-[9px] !translate-y-0" + handleId="source" /> ) } @@ -241,31 +241,33 @@ const BaseNode: FC<BaseNodeProps> = ({ <div className={cn( 'flex items-center rounded-t-2xl px-3 pb-2 pt-3', (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) && 'bg-transparent', - )}> + )} + > <BlockIcon - className='mr-2 shrink-0' + className="mr-2 shrink-0" type={data.type} - size='md' + size="md" toolIcon={toolIcon} /> <div title={data.title} - className='system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary' + className="system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary" > <div> {data.title} </div> { data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && ( - <Tooltip popupContent={ - <div className='w-[180px]'> - <div className='font-extrabold'> + <Tooltip popupContent={( + <div className="w-[180px]"> + <div className="font-extrabold"> {t('workflow.nodes.iteration.parallelModeEnableTitle')} </div> {t('workflow.nodes.iteration.parallelModeEnableDesc')} - </div>} + </div> + )} > - <div className='system-2xs-medium-uppercase ml-1 flex items-center justify-center rounded-[5px] border-[1px] border-text-warning px-[5px] py-[3px] text-text-warning '> + <div className="system-2xs-medium-uppercase ml-1 flex items-center justify-center rounded-[5px] border-[1px] border-text-warning px-[5px] py-[3px] text-text-warning "> {t('workflow.nodes.iteration.parallelModeUpper')} </div> </Tooltip> @@ -274,8 +276,10 @@ const BaseNode: FC<BaseNodeProps> = ({ </div> { data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && ( - <div className='mr-1.5 text-xs font-medium text-text-accent'> - {data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength} + <div className="mr-1.5 text-xs font-medium text-text-accent"> + {data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex} + / + {data._iterationLength} </div> ) } @@ -284,14 +288,14 @@ const BaseNode: FC<BaseNodeProps> = ({ } { isLoading - ? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' /> + ? <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-text-accent" /> : data._runningStatus === NodeRunningStatus.Failed - ? <RiErrorWarningFill className='h-3.5 w-3.5 text-text-destructive' /> + ? <RiErrorWarningFill className="h-3.5 w-3.5 text-text-destructive" /> : data._runningStatus === NodeRunningStatus.Exception - ? <RiAlertFill className='h-3.5 w-3.5 text-text-warning-secondary' /> + ? <RiAlertFill className="h-3.5 w-3.5 text-text-warning-secondary" /> : (data._runningStatus === NodeRunningStatus.Succeeded || hasVarValue) - ? <RiCheckboxCircleFill className='h-3.5 w-3.5 text-text-success' /> - : null + ? <RiCheckboxCircleFill className="h-3.5 w-3.5 text-text-success" /> + : null } </div> { @@ -301,7 +305,7 @@ const BaseNode: FC<BaseNodeProps> = ({ } { (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) && ( - <div className='grow pb-1 pl-1 pr-1'> + <div className="grow pb-1 pl-1 pr-1"> {cloneElement(children, { id, data } as any)} </div> ) @@ -324,13 +328,13 @@ const BaseNode: FC<BaseNodeProps> = ({ } { data.desc && data.type !== BlockEnum.Iteration && data.type !== BlockEnum.Loop && ( - <div className='system-xs-regular whitespace-pre-line break-words px-3 pb-2 pt-1 text-text-tertiary'> + <div className="system-xs-regular whitespace-pre-line break-words px-3 pb-2 pt-1 text-text-tertiary"> {data.desc} </div> ) } {data.type === BlockEnum.Tool && data.provider_type === ToolTypeEnum.MCP && ( - <div className='px-3 pb-2'> + <div className="px-3 pb-2"> <CopyID content={data.provider_id || ''} /> </div> )} @@ -341,13 +345,15 @@ const BaseNode: FC<BaseNodeProps> = ({ const isStartNode = data.type === BlockEnum.Start const isEntryNode = isTriggerNode(data.type as any) || isStartNode - return isEntryNode ? ( - <EntryNodeContainer - nodeType={isStartNode ? StartNodeTypeEnum.Start : StartNodeTypeEnum.Trigger} - > - {nodeContent} - </EntryNodeContainer> - ) : nodeContent + return isEntryNode + ? ( + <EntryNodeContainer + nodeType={isStartNode ? StartNodeTypeEnum.Start : StartNodeTypeEnum.Trigger} + > + {nodeContent} + </EntryNodeContainer> + ) + : nodeContent } export default memo(BaseNode) diff --git a/web/app/components/workflow/nodes/agent/components/model-bar.tsx b/web/app/components/workflow/nodes/agent/components/model-bar.tsx index 1b2007070f..2f13cb84b2 100644 --- a/web/app/components/workflow/nodes/agent/components/model-bar.tsx +++ b/web/app/components/workflow/nodes/agent/components/model-bar.tsx @@ -1,10 +1,11 @@ +import type { FC } from 'react' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' import Indicator from '@/app/components/header/indicator' -import { type FC, useMemo } from 'react' -import { useTranslation } from 'react-i18next' export type ModelBarProps = { provider: string @@ -35,41 +36,46 @@ export const ModelBar: FC<ModelBarProps> = (props) => { const { t } = useTranslation() const modelList = useAllModel() if (!('provider' in props)) { - return <Tooltip - popupContent={t('workflow.nodes.agent.modelNotSelected')} - triggerMethod='hover' - > - <div className='relative'> - <ModelSelector - modelList={[]} - triggerClassName='bg-workflow-block-parma-bg !h-6 !rounded-md' - defaultModel={undefined} - showDeprecatedWarnIcon={false} - readonly - deprecatedClassName='opacity-50' - /> - <Indicator color={'red'} className='absolute -right-0.5 -top-0.5' /> - </div> - </Tooltip> + return ( + <Tooltip + popupContent={t('workflow.nodes.agent.modelNotSelected')} + triggerMethod="hover" + > + <div className="relative"> + <ModelSelector + modelList={[]} + triggerClassName="bg-workflow-block-parma-bg !h-6 !rounded-md" + defaultModel={undefined} + showDeprecatedWarnIcon={false} + readonly + deprecatedClassName="opacity-50" + /> + <Indicator color="red" className="absolute -right-0.5 -top-0.5" /> + </div> + </Tooltip> + ) } const modelInstalled = modelList?.some( - provider => provider.provider === props.provider && provider.models.some(model => model.model === props.model)) + provider => provider.provider === props.provider && provider.models.some(model => model.model === props.model), + ) const showWarn = modelList && !modelInstalled - return modelList && <Tooltip - popupContent={t('workflow.nodes.agent.modelNotInstallTooltip')} - triggerMethod='hover' - disabled={!modelList || modelInstalled} - > - <div className='relative'> - <ModelSelector - modelList={modelList} - triggerClassName='bg-workflow-block-parma-bg !h-6 !rounded-md' - defaultModel={props} - showDeprecatedWarnIcon={false} - readonly - deprecatedClassName='opacity-50' - /> - {showWarn && <Indicator color={'red'} className='absolute -right-0.5 -top-0.5' />} - </div> - </Tooltip> + return modelList && ( + <Tooltip + popupContent={t('workflow.nodes.agent.modelNotInstallTooltip')} + triggerMethod="hover" + disabled={!modelList || modelInstalled} + > + <div className="relative"> + <ModelSelector + modelList={modelList} + triggerClassName="bg-workflow-block-parma-bg !h-6 !rounded-md" + defaultModel={props} + showDeprecatedWarnIcon={false} + readonly + deprecatedClassName="opacity-50" + /> + {showWarn && <Indicator color="red" className="absolute -right-0.5 -top-0.5" />} + </div> + </Tooltip> + ) } diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index 0d2be2bee4..129458f623 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -1,12 +1,12 @@ +import { memo, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import AppIcon from '@/app/components/base/app-icon' +import { Group } from '@/app/components/base/icons/src/vender/other' import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' -import { cn } from '@/utils/classnames' -import { memo, useMemo, useRef, useState } from 'react' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' +import { cn } from '@/utils/classnames' import { getIconFromMarketPlace } from '@/utils/get-icon' -import { useTranslation } from 'react-i18next' -import { Group } from '@/app/components/base/icons/src/vender/other' -import AppIcon from '@/app/components/base/app-icon' type Status = 'not-installed' | 'not-authorized' | undefined @@ -33,63 +33,75 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { const author = providerNameParts[0] const name = providerNameParts[1] const icon = useMemo(() => { - if (!isDataReady) return '' - if (currentProvider) return currentProvider.icon + if (!isDataReady) + return '' + if (currentProvider) + return currentProvider.icon const iconFromMarketPlace = getIconFromMarketPlace(`${author}/${name}`) return iconFromMarketPlace }, [author, currentProvider, name, isDataReady]) const status: Status = useMemo(() => { - if (!isDataReady) return undefined - if (!currentProvider) return 'not-installed' - if (currentProvider.is_team_authorization === false) return 'not-authorized' + if (!isDataReady) + return undefined + if (!currentProvider) + return 'not-installed' + if (currentProvider.is_team_authorization === false) + return 'not-authorized' return undefined }, [currentProvider, isDataReady]) const indicator = status === 'not-installed' ? 'red' : status === 'not-authorized' ? 'yellow' : undefined const notSuccess = (['not-installed', 'not-authorized'] as Array<Status>).includes(status) const { t } = useTranslation() const tooltip = useMemo(() => { - if (!notSuccess) return undefined - if (status === 'not-installed') return t('workflow.nodes.agent.toolNotInstallTooltip', { tool: name }) - if (status === 'not-authorized') return t('workflow.nodes.agent.toolNotAuthorizedTooltip', { tool: name }) + if (!notSuccess) + return undefined + if (status === 'not-installed') + return t('workflow.nodes.agent.toolNotInstallTooltip', { tool: name }) + if (status === 'not-authorized') + return t('workflow.nodes.agent.toolNotAuthorizedTooltip', { tool: name }) throw new Error('Unknown status') }, [name, notSuccess, status, t]) const [iconFetchError, setIconFetchError] = useState(false) - return <Tooltip - triggerMethod='hover' - popupContent={tooltip} - disabled={!notSuccess} - > - <div - className={cn('relative')} - ref={containerRef} + return ( + <Tooltip + triggerMethod="hover" + popupContent={tooltip} + disabled={!notSuccess} > - <div className="flex size-5 items-center justify-center overflow-hidden rounded-[6px] border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge"> - {(() => { - if (iconFetchError || !icon) + <div + className={cn('relative')} + ref={containerRef} + > + <div className="flex size-5 items-center justify-center overflow-hidden rounded-[6px] border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge"> + {(() => { + if (iconFetchError || !icon) + return <Group className="h-3 w-3 opacity-35" /> + if (typeof icon === 'string') { + return ( + <img + src={icon} + alt="tool icon" + className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')} + onError={() => setIconFetchError(true)} + /> + ) + } + if (typeof icon === 'object') { + return ( + <AppIcon + className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')} + icon={icon?.content} + background={icon?.background} + /> + ) + } return <Group className="h-3 w-3 opacity-35" /> - if (typeof icon === 'string') { - return <img - src={icon} - alt='tool icon' - className={cn('size-3.5 h-full w-full object-cover', - notSuccess && 'opacity-50')} - onError={() => setIconFetchError(true)} - /> - } - if (typeof icon === 'object') { - return <AppIcon - className={cn('size-3.5 h-full w-full object-cover', - notSuccess && 'opacity-50')} - icon={icon?.content} - background={icon?.background} - /> - } - return <Group className="h-3 w-3 opacity-35" /> - })()} + })()} + </div> + {indicator && <Indicator color={indicator} className="absolute -right-[1px] -top-[1px]" />} </div> - {indicator && <Indicator color={indicator} className="absolute -right-[1px] -top-[1px]" />} - </div> - </Tooltip> + </Tooltip> + ) }) ToolIcon.displayName = 'ToolIcon' diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 5038ed424b..4f4e604e2f 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -1,14 +1,15 @@ -import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types' -import { BlockEnum, type NodeDefault } from '../../types' +import type { NodeDefault } from '../../types' import type { AgentNodeType } from './types' +import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { renderI18nObject } from '@/i18n-config' +import { BlockEnum } from '../../types' import { genNodeMetaData } from '../../utils' const metaData = genNodeMetaData({ sort: 3, type: BlockEnum.Agent, }) -import { renderI18nObject } from '@/i18n-config' const nodeDefault: NodeDefault<AgentNodeType> = { metaData, @@ -16,7 +17,7 @@ const nodeDefault: NodeDefault<AgentNodeType> = { tool_node_version: '2', }, checkValid(payload, t, moreDataForCheckValid: { - strategyProvider?: StrategyPluginDetail, + strategyProvider?: StrategyPluginDetail strategy?: StrategyDetail language: string isReadyForCheckValid: boolean diff --git a/web/app/components/workflow/nodes/agent/node.tsx b/web/app/components/workflow/nodes/agent/node.tsx index fe87bc7cda..d2e552f6f4 100644 --- a/web/app/components/workflow/nodes/agent/node.tsx +++ b/web/app/components/workflow/nodes/agent/node.tsx @@ -1,22 +1,24 @@ -import { type FC, memo, useMemo } from 'react' +import type { FC } from 'react' import type { NodeProps } from '../../types' -import type { AgentNodeType } from './types' -import { SettingItem } from '../_base/components/setting-item' -import { Group, GroupLabel } from '../_base/components/group' import type { ToolIconProps } from './components/tool-icon' -import { ToolIcon } from './components/tool-icon' -import useConfig from './use-config' +import type { AgentNodeType } from './types' +import { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useRenderI18nObject } from '@/hooks/use-i18n' +import { Group, GroupLabel } from '../_base/components/group' +import { SettingItem } from '../_base/components/setting-item' import { ModelBar } from './components/model-bar' +import { ToolIcon } from './components/tool-icon' +import useConfig from './use-config' const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { const { inputs, currentStrategy, currentStrategyStatus, pluginDetail } = useConfig(props.id, props.data) const renderI18nObject = useRenderI18nObject() const { t } = useTranslation() const models = useMemo(() => { - if (!inputs) return [] + if (!inputs) + return [] // if selected, show in node // if required and not selected, show empty selector // if not required and not selected, show nothing @@ -65,49 +67,64 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { }) return tools }, [currentStrategy?.parameters, inputs.agent_parameters]) - return <div className='mb-1 space-y-1 px-3'> - {inputs.agent_strategy_name - ? <SettingItem - label={t('workflow.nodes.agent.strategy.shortLabel')} - status={ - currentStrategyStatus && !currentStrategyStatus.isExistInPlugin - ? 'error' - : undefined - } - tooltip={ - (currentStrategyStatus && !currentStrategyStatus.isExistInPlugin) - ? t('workflow.nodes.agent.strategyNotInstallTooltip', { - plugin: pluginDetail?.declaration.label - ? renderI18nObject(pluginDetail?.declaration.label) - : undefined, - strategy: inputs.agent_strategy_label, - }) - : undefined - } - > - {inputs.agent_strategy_label} - </SettingItem> - : <SettingItem label={t('workflow.nodes.agent.strategyNotSet')} />} - {models.length > 0 && <Group - label={<GroupLabel className='mt-1'> - {t('workflow.nodes.agent.model')} - </GroupLabel>} - > - {models.map((model) => { - return <ModelBar - {...model} - key={model.param} - /> - })} - </Group>} - {tools.length > 0 && <Group label={<GroupLabel className='mt-1'> - {t('workflow.nodes.agent.toolbox')} - </GroupLabel>}> - <div className='grid grid-cols-10 gap-0.5'> - {tools.map((tool, i) => <ToolIcon {...tool} key={tool.id + i} />)} - </div> - </Group>} - </div> + return ( + <div className="mb-1 space-y-1 px-3"> + {inputs.agent_strategy_name + ? ( + <SettingItem + label={t('workflow.nodes.agent.strategy.shortLabel')} + status={ + currentStrategyStatus && !currentStrategyStatus.isExistInPlugin + ? 'error' + : undefined + } + tooltip={ + (currentStrategyStatus && !currentStrategyStatus.isExistInPlugin) + ? t('workflow.nodes.agent.strategyNotInstallTooltip', { + plugin: pluginDetail?.declaration.label + ? renderI18nObject(pluginDetail?.declaration.label) + : undefined, + strategy: inputs.agent_strategy_label, + }) + : undefined + } + > + {inputs.agent_strategy_label} + </SettingItem> + ) + : <SettingItem label={t('workflow.nodes.agent.strategyNotSet')} />} + {models.length > 0 && ( + <Group + label={( + <GroupLabel className="mt-1"> + {t('workflow.nodes.agent.model')} + </GroupLabel> + )} + > + {models.map((model) => { + return ( + <ModelBar + {...model} + key={model.param} + /> + ) + })} + </Group> + )} + {tools.length > 0 && ( + <Group label={( + <GroupLabel className="mt-1"> + {t('workflow.nodes.agent.toolbox')} + </GroupLabel> + )} + > + <div className="grid grid-cols-10 gap-0.5"> + {tools.map((tool, i) => <ToolIcon {...tool} key={tool.id + i} />)} + </div> + </Group> + )} + </div> + ) } AgentNode.displayName = 'AgentNode' diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 7c87da8121..d0f3f83b99 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -1,18 +1,20 @@ import type { FC } from 'react' -import { memo } from 'react' import type { NodePanelProps } from '../../types' -import { AgentFeature, type AgentNodeType } from './types' -import Field from '../_base/components/field' -import { AgentStrategy } from '../_base/components/agent-strategy' -import useConfig from './use-config' -import { useTranslation } from 'react-i18next' -import OutputVars, { VarItem } from '../_base/components/output-vars' -import type { StrategyParamItem } from '@/app/components/plugins/types' +import type { AgentNodeType } from './types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { StrategyParamItem } from '@/app/components/plugins/types' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import { toType } from '@/app/components/tools/utils/to-form-schema' import { useStore } from '../../store' -import Split from '../_base/components/split' +import { AgentStrategy } from '../_base/components/agent-strategy' +import Field from '../_base/components/field' import MemoryConfig from '../_base/components/memory-config' +import OutputVars, { VarItem } from '../_base/components/output-vars' +import Split from '../_base/components/split' +import { AgentFeature } from './types' +import useConfig from './use-config' + const i18nPrefix = 'workflow.nodes.agent' export function strategyParamToCredientialForm(param: StrategyParamItem): CredentialFormSchema { @@ -43,89 +45,94 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => { const { t } = useTranslation() const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) - return <div className='my-2'> - <Field - required - title={t('workflow.nodes.agent.strategy.label')} - className='px-4 py-2' - tooltip={t('workflow.nodes.agent.strategy.tooltip')} > - <AgentStrategy - strategy={inputs.agent_strategy_name ? { - agent_strategy_provider_name: inputs.agent_strategy_provider_name!, - agent_strategy_name: inputs.agent_strategy_name!, - agent_strategy_label: inputs.agent_strategy_label!, - agent_output_schema: inputs.output_schema, - plugin_unique_identifier: inputs.plugin_unique_identifier!, - meta: inputs.meta, - } : undefined} - onStrategyChange={(strategy) => { - setInputs({ - ...inputs, - agent_strategy_provider_name: strategy?.agent_strategy_provider_name, - agent_strategy_name: strategy?.agent_strategy_name, - agent_strategy_label: strategy?.agent_strategy_label, - output_schema: strategy!.agent_output_schema, - plugin_unique_identifier: strategy!.plugin_unique_identifier, - meta: strategy?.meta, - }) - resetEditor(Date.now()) - }} - formSchema={currentStrategy?.parameters?.map(strategyParamToCredientialForm) || []} - formValue={formData} - onFormValueChange={onFormChange} - nodeOutputVars={availableVars} - availableNodes={availableNodesWithParent} - nodeId={props.id} - canChooseMCPTool={canChooseMCPTool} - /> - </Field> - <div className='px-4 py-2'> - {isChatMode && currentStrategy?.features?.includes(AgentFeature.HISTORY_MESSAGES) && ( - <> - <Split /> - <MemoryConfig - className='mt-4' - readonly={readOnly} - config={{ data: inputs.memory }} - onChange={handleMemoryChange} - canSetRoleName={false} - /> - </> - )} - </div> - <div> - <OutputVars> - <VarItem - name='text' - type='String' - description={t(`${i18nPrefix}.outputVars.text`)} + return ( + <div className="my-2"> + <Field + required + title={t('workflow.nodes.agent.strategy.label')} + className="px-4 py-2" + tooltip={t('workflow.nodes.agent.strategy.tooltip')} + > + <AgentStrategy + strategy={inputs.agent_strategy_name + ? { + agent_strategy_provider_name: inputs.agent_strategy_provider_name!, + agent_strategy_name: inputs.agent_strategy_name!, + agent_strategy_label: inputs.agent_strategy_label!, + agent_output_schema: inputs.output_schema, + plugin_unique_identifier: inputs.plugin_unique_identifier!, + meta: inputs.meta, + } + : undefined} + onStrategyChange={(strategy) => { + setInputs({ + ...inputs, + agent_strategy_provider_name: strategy?.agent_strategy_provider_name, + agent_strategy_name: strategy?.agent_strategy_name, + agent_strategy_label: strategy?.agent_strategy_label, + output_schema: strategy!.agent_output_schema, + plugin_unique_identifier: strategy!.plugin_unique_identifier, + meta: strategy?.meta, + }) + resetEditor(Date.now()) + }} + formSchema={currentStrategy?.parameters?.map(strategyParamToCredientialForm) || []} + formValue={formData} + onFormValueChange={onFormChange} + nodeOutputVars={availableVars} + availableNodes={availableNodesWithParent} + nodeId={props.id} + canChooseMCPTool={canChooseMCPTool} /> - <VarItem - name='usage' - type='object' - description={t(`${i18nPrefix}.outputVars.usage`)} - /> - <VarItem - name='files' - type='Array[File]' - description={t(`${i18nPrefix}.outputVars.files.title`)} - /> - <VarItem - name='json' - type='Array[Object]' - description={t(`${i18nPrefix}.outputVars.json`)} - /> - {outputSchema.map(({ name, type, description }) => ( + </Field> + <div className="px-4 py-2"> + {isChatMode && currentStrategy?.features?.includes(AgentFeature.HISTORY_MESSAGES) && ( + <> + <Split /> + <MemoryConfig + className="mt-4" + readonly={readOnly} + config={{ data: inputs.memory }} + onChange={handleMemoryChange} + canSetRoleName={false} + /> + </> + )} + </div> + <div> + <OutputVars> <VarItem - key={name} - name={name} - type={type} - description={description} + name="text" + type="String" + description={t(`${i18nPrefix}.outputVars.text`)} /> - ))} - </OutputVars> + <VarItem + name="usage" + type="object" + description={t(`${i18nPrefix}.outputVars.usage`)} + /> + <VarItem + name="files" + type="Array[File]" + description={t(`${i18nPrefix}.outputVars.files.title`)} + /> + <VarItem + name="json" + type="Array[Object]" + description={t(`${i18nPrefix}.outputVars.json`)} + /> + {outputSchema.map(({ name, type, description }) => ( + <VarItem + key={name} + name={name} + type={type} + description={description} + /> + ))} + </OutputVars> + </div> </div> - </div> + ) } AgentPanel.displayName = 'AgentPanel' diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index f163b3572a..efc7c0cd9a 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -1,6 +1,6 @@ -import type { CommonNodeType, Memory } from '@/app/components/workflow/types' import type { ToolVarInputs } from '../tool/types' import type { PluginMeta } from '@/app/components/plugins/types' +import type { CommonNodeType, Memory } from '@/app/components/workflow/types' export type AgentNodeType = CommonNodeType & { agent_strategy_provider_name?: string diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 90d6736f51..49af451f4c 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -1,21 +1,22 @@ -import { useStrategyProviderDetail } from '@/service/use-strategy' -import useNodeCrud from '../_base/hooks/use-node-crud' -import useVarList from '../_base/hooks/use-var-list' +import type { Memory, Var } from '../../types' +import type { ToolVarInputs } from '../tool/types' import type { AgentNodeType } from './types' +import { produce } from 'immer' +import { useCallback, useEffect, useMemo } from 'react' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { generateAgentToolValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useIsChatMode, useNodesReadOnly, } from '@/app/components/workflow/hooks' -import { useCallback, useEffect, useMemo } from 'react' -import { type ToolVarInputs, VarType } from '../tool/types' import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins' -import type { Memory, Var } from '../../types' +import { useStrategyProviderDetail } from '@/service/use-strategy' +import { isSupportMCP } from '@/utils/plugin-version-feature' import { VarType as VarKindType } from '../../types' import useAvailableVarList from '../_base/hooks/use-available-var-list' -import { produce } from 'immer' -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { isSupportMCP } from '@/utils/plugin-version-feature' -import { generateAgentToolValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import useNodeCrud from '../_base/hooks/use-node-crud' +import useVarList from '../_base/hooks/use-var-list' +import { VarType } from '../tool/types' export type StrategyStatus = { plugin: { @@ -100,7 +101,8 @@ const useConfig = (id: string, payload: AgentNodeType) => { const isVariable = currentStrategy?.parameters.some( param => param.name === paramName && param.type === FormTypeEnum.any, ) - if (isVariable) return VarType.variable + if (isVariable) + return VarType.variable return VarType.constant }, [currentStrategy?.parameters]) diff --git a/web/app/components/workflow/nodes/agent/use-single-run-form-params.ts b/web/app/components/workflow/nodes/agent/use-single-run-form-params.ts index 0b9a40aea4..f12c1074ce 100644 --- a/web/app/components/workflow/nodes/agent/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/agent/use-single-run-form-params.ts @@ -1,17 +1,17 @@ import type { RefObject } from 'react' -import type { InputVar, Variable } from '@/app/components/workflow/types' -import { useMemo } from 'react' -import useNodeCrud from '../_base/hooks/use-node-crud' import type { AgentNodeType } from './types' -import { useTranslation } from 'react-i18next' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import { useStrategyInfo } from './use-config' +import type { InputVar, Variable } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' import formatTracing from '@/app/components/workflow/run/utils/format-log' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { useStrategyInfo } from './use-config' type Params = { - id: string, - payload: AgentNodeType, + id: string + payload: AgentNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] diff --git a/web/app/components/workflow/nodes/answer/default.ts b/web/app/components/workflow/nodes/answer/default.ts index f115274900..0011fd09ef 100644 --- a/web/app/components/workflow/nodes/answer/default.ts +++ b/web/app/components/workflow/nodes/answer/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { AnswerNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: 2.1, diff --git a/web/app/components/workflow/nodes/answer/node.tsx b/web/app/components/workflow/nodes/answer/node.tsx index bb28066d95..12c7b10a1a 100644 --- a/web/app/components/workflow/nodes/answer/node.tsx +++ b/web/app/components/workflow/nodes/answer/node.tsx @@ -1,10 +1,11 @@ import type { FC } from 'react' +import type { AnswerNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' import InfoPanel from '../_base/components/info-panel' import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' -import type { AnswerNodeType } from './types' -import type { NodeProps } from '@/app/components/workflow/types' + const Node: FC<NodeProps<AnswerNodeType>> = ({ id, data, @@ -12,13 +13,16 @@ const Node: FC<NodeProps<AnswerNodeType>> = ({ const { t } = useTranslation() return ( - <div className='mb-1 px-3 py-1'> - <InfoPanel title={t('workflow.nodes.answer.answer')} content={ - <ReadonlyInputWithSelectVar - value={data.answer} - nodeId={id} - /> - } /> + <div className="mb-1 px-3 py-1"> + <InfoPanel + title={t('workflow.nodes.answer.answer')} + content={( + <ReadonlyInputWithSelectVar + value={data.answer} + nodeId={id} + /> + )} + /> </div> ) } diff --git a/web/app/components/workflow/nodes/answer/panel.tsx b/web/app/components/workflow/nodes/answer/panel.tsx index 2a4b70ee32..170cd17bf8 100644 --- a/web/app/components/workflow/nodes/answer/panel.tsx +++ b/web/app/components/workflow/nodes/answer/panel.tsx @@ -1,11 +1,12 @@ import type { FC } from 'react' +import type { AnswerNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import useConfig from './use-config' -import type { AnswerNodeType } from './types' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import type { NodePanelProps } from '@/app/components/workflow/types' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import useConfig from './use-config' + const i18nPrefix = 'workflow.nodes.answer' const Panel: FC<NodePanelProps<AnswerNodeType>> = ({ @@ -29,7 +30,7 @@ const Panel: FC<NodePanelProps<AnswerNodeType>> = ({ }) return ( - <div className='mb-2 mt-2 space-y-4 px-4'> + <div className="mb-2 mt-2 space-y-4 px-4"> <Editor readOnly={readOnly} justVar diff --git a/web/app/components/workflow/nodes/answer/use-config.ts b/web/app/components/workflow/nodes/answer/use-config.ts index c00b2bd7c5..aad75d6591 100644 --- a/web/app/components/workflow/nodes/answer/use-config.ts +++ b/web/app/components/workflow/nodes/answer/use-config.ts @@ -1,13 +1,13 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import useVarList from '../_base/hooks/use-var-list' import type { Var } from '../../types' -import { VarType } from '../../types' import type { AnswerNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback } from 'react' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarType } from '../../types' +import useVarList from '../_base/hooks/use-var-list' const useConfig = (id: string, payload: AnswerNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx b/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx index 986a1b034b..fd346e632d 100644 --- a/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx +++ b/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx @@ -1,20 +1,20 @@ import type { FC } from 'react' -import { useState } from 'react' +import type { WriteMode } from '../types' +import type { VarType } from '@/app/components/workflow/types' import { RiArrowDownSLine, RiCheckLine, } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import { useState } from 'react' import { useTranslation } from 'react-i18next' -import type { WriteMode } from '../types' -import { getOperationItems } from '../utils' +import Divider from '@/app/components/base/divider' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { VarType } from '@/app/components/workflow/types' -import Divider from '@/app/components/base/divider' +import { cn } from '@/utils/classnames' +import { getOperationItems } from '../utils' type Item = { value: string | number @@ -58,19 +58,16 @@ const OperationSelector: FC<OperationSelectorProps> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={4} > <PortalToFollowElemTrigger onClick={() => !disabled && setOpen(v => !v)} > <div - className={cn('flex items-center gap-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1', - disabled ? 'cursor-not-allowed !bg-components-input-bg-disabled' : 'cursor-pointer hover:bg-state-base-hover-alt', - open && 'bg-state-base-hover-alt', - className)} + className={cn('flex items-center gap-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1', disabled ? 'cursor-not-allowed !bg-components-input-bg-disabled' : 'cursor-pointer hover:bg-state-base-hover-alt', open && 'bg-state-base-hover-alt', className)} > - <div className='flex items-center p-1'> + <div className="flex items-center p-1"> <span className={`system-sm-regular overflow-hidden truncate text-ellipsis ${selectedItem ? 'text-components-input-text-filled' : 'text-components-input-text-disabled'}`} @@ -83,36 +80,35 @@ const OperationSelector: FC<OperationSelectorProps> = ({ </PortalToFollowElemTrigger> <PortalToFollowElemContent className={`z-20 ${popupClassName}`}> - <div className='flex w-[140px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> - <div className='flex flex-col items-start self-stretch p-1'> - <div className='flex items-start self-stretch px-3 pb-0.5 pt-1'> - <div className='system-xs-medium-uppercase flex grow text-text-tertiary'>{t(`${i18nPrefix}.operations.title`)}</div> + <div className="flex w-[140px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> + <div className="flex flex-col items-start self-stretch p-1"> + <div className="flex items-start self-stretch px-3 pb-0.5 pt-1"> + <div className="system-xs-medium-uppercase flex grow text-text-tertiary">{t(`${i18nPrefix}.operations.title`)}</div> </div> {items.map(item => ( item.value === 'divider' ? ( - <Divider key="divider" className="my-1" /> - ) + <Divider key="divider" className="my-1" /> + ) : ( - <div - key={item.value} - className={cn('flex items-center gap-1 self-stretch rounded-lg px-2 py-1', - 'cursor-pointer hover:bg-state-base-hover')} - onClick={() => { - onSelect(item) - setOpen(false) - }} - > - <div className='flex min-h-5 grow items-center gap-1 px-1'> - <span className={'system-sm-medium flex grow text-text-secondary'}>{t(`${i18nPrefix}.operations.${item.name}`)}</span> - </div> - {item.value === value && ( - <div className='flex items-center justify-center'> - <RiCheckLine className='h-4 w-4 text-text-accent' /> + <div + key={item.value} + className={cn('flex items-center gap-1 self-stretch rounded-lg px-2 py-1', 'cursor-pointer hover:bg-state-base-hover')} + onClick={() => { + onSelect(item) + setOpen(false) + }} + > + <div className="flex min-h-5 grow items-center gap-1 px-1"> + <span className="system-sm-medium flex grow text-text-secondary">{t(`${i18nPrefix}.operations.${item.name}`)}</span> </div> - )} - </div> - ) + {item.value === value && ( + <div className="flex items-center justify-center"> + <RiCheckLine className="h-4 w-4 text-text-accent" /> + </div> + )} + </div> + ) ))} </div> </div> diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx index b81277d740..3f99121835 100644 --- a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx @@ -1,23 +1,23 @@ 'use client' import type { FC } from 'react' -import { useTranslation } from 'react-i18next' -import React, { useCallback } from 'react' -import { produce } from 'immer' -import { RiDeleteBinLine } from '@remixicon/react' -import OperationSelector from '../operation-selector' -import { AssignerNodeInputType, WriteMode } from '../../types' import type { AssignerNodeOperation } from '../../types' -import ListNoDataPlaceholder from '@/app/components/workflow/nodes/_base/components/list-no-data-placeholder' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { RiDeleteBinLine } from '@remixicon/react' +import { produce } from 'immer' +import { noop } from 'lodash-es' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { noop } from 'lodash-es' +import ListNoDataPlaceholder from '@/app/components/workflow/nodes/_base/components/list-no-data-placeholder' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' +import { VarType } from '@/app/components/workflow/types' +import { AssignerNodeInputType, WriteMode } from '../../types' +import OperationSelector from '../operation-selector' type Props = { readonly: boolean @@ -105,7 +105,8 @@ const VarList: FC<Props> = ({ const handleFilterToAssignedVar = useCallback((index: number) => { return (payload: Var) => { const { variable_selector, operation } = list[index] - if (!variable_selector || !operation || !filterToAssignedVar) return true + if (!variable_selector || !operation || !filterToAssignedVar) + return true const assignedVarType = getAssignedVarType?.(variable_selector) const isSameVariable = Array.isArray(variable_selector) && variable_selector.join('.') === `${payload.nodeId}.${payload.variable}` @@ -123,7 +124,7 @@ const VarList: FC<Props> = ({ } return ( - <div className='flex flex-col items-start gap-4 self-stretch'> + <div className="flex flex-col items-start gap-4 self-stretch"> {list.map((item, index) => { const assignedVarType = item.variable_selector ? getAssignedVarType?.(item.variable_selector) : undefined const toAssignedVarType = (assignedVarType && item.operation && getToAssignedVarType) @@ -131,9 +132,9 @@ const VarList: FC<Props> = ({ : undefined return ( - <div className='flex items-start gap-1 self-stretch' key={index}> - <div className='flex grow flex-col items-start gap-1'> - <div className='flex items-center gap-1 self-stretch'> + <div className="flex items-start gap-1 self-stretch" key={index}> + <div className="flex grow flex-col items-start gap-1"> + <div className="flex items-center gap-1 self-stretch"> <VarReferencePicker readonly={readonly} nodeId={nodeId} @@ -144,12 +145,12 @@ const VarList: FC<Props> = ({ filterVar={filterVar} placeholder={t('workflow.nodes.assigner.selectAssignedVariable') as string} minWidth={352} - popupFor='assigned' - className='w-full' + popupFor="assigned" + className="w-full" /> <OperationSelector value={item.operation} - placeholder='Operation' + placeholder="Operation" disabled={!item.variable_selector || item.variable_selector.length === 0} onSelect={handleOperationChange(index, assignedVarType!)} assignedVarType={assignedVarType} @@ -172,11 +173,10 @@ const VarList: FC<Props> = ({ valueTypePlaceHolder={toAssignedVarType} placeholder={t('workflow.nodes.assigner.setParameter') as string} minWidth={352} - popupFor='toAssigned' - className='w-full' + popupFor="toAssigned" + className="w-full" /> - ) - } + )} {item.operation === WriteMode.set && assignedVarType && ( <> {assignedVarType === 'number' && ( @@ -184,14 +184,14 @@ const VarList: FC<Props> = ({ type="number" value={item.value as number} onChange={e => handleToAssignedVarChange(index)(Number(e.target.value))} - className='w-full' + className="w-full" /> )} {assignedVarType === 'string' && ( <Textarea value={item.value as string} onChange={e => handleToAssignedVarChange(index)(e.target.value)} - className='w-full' + className="w-full" /> )} {assignedVarType === 'boolean' && ( @@ -205,28 +205,29 @@ const VarList: FC<Props> = ({ value={item.value as string} language={CodeLanguage.json} onChange={value => handleToAssignedVarChange(index)(value)} - className='w-full' + className="w-full" readOnly={readonly} /> )} </> )} {writeModeTypesNum?.includes(item.operation) - && <Input - type="number" - value={item.value as number} - onChange={e => handleToAssignedVarChange(index)(Number(e.target.value))} - placeholder="Enter number value..." - className='w-full' - /> - } + && ( + <Input + type="number" + value={item.value as number} + onChange={e => handleToAssignedVarChange(index)(Number(e.target.value))} + placeholder="Enter number value..." + className="w-full" + /> + )} </div> <ActionButton - size='l' - className='group shrink-0 hover:!bg-state-destructive-hover' + size="l" + className="group shrink-0 hover:!bg-state-destructive-hover" onClick={handleVarRemove(index)} > - <RiDeleteBinLine className='h-4 w-4 text-text-tertiary group-hover:text-text-destructive' /> + <RiDeleteBinLine className="h-4 w-4 text-text-tertiary group-hover:text-text-destructive" /> </ActionButton> </div> ) diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/use-var-list.ts b/web/app/components/workflow/nodes/assigner/components/var-list/use-var-list.ts index c97006d023..51f938d9b4 100644 --- a/web/app/components/workflow/nodes/assigner/components/var-list/use-var-list.ts +++ b/web/app/components/workflow/nodes/assigner/components/var-list/use-var-list.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { AssignerNodeOperation, AssignerNodeType } from '../../types' +import { produce } from 'immer' +import { useCallback } from 'react' import { AssignerNodeInputType, WriteMode } from '../../types' type Params = { diff --git a/web/app/components/workflow/nodes/assigner/default.ts b/web/app/components/workflow/nodes/assigner/default.ts index 049c91f6c2..dcf2a1b5ac 100644 --- a/web/app/components/workflow/nodes/assigner/default.ts +++ b/web/app/components/workflow/nodes/assigner/default.ts @@ -1,8 +1,10 @@ import type { NodeDefault } from '../../types' -import { type AssignerNodeType, WriteMode } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import type { AssignerNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { WriteMode } from './types' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/assigner/hooks.ts b/web/app/components/workflow/nodes/assigner/hooks.ts index d42fb8ee8a..d708222868 100644 --- a/web/app/components/workflow/nodes/assigner/hooks.ts +++ b/web/app/components/workflow/nodes/assigner/hooks.ts @@ -1,15 +1,15 @@ +import type { + Node, + Var, +} from '../../types' +import { uniqBy } from 'lodash-es' import { useCallback } from 'react' import { useNodes } from 'reactflow' -import { uniqBy } from 'lodash-es' import { useIsChatMode, useWorkflow, useWorkflowVariables, } from '../../hooks' -import type { - Node, - Var, -} from '../../types' import { AssignerNodeInputType, WriteMode } from './types' export const useGetAvailableVars = () => { diff --git a/web/app/components/workflow/nodes/assigner/node.tsx b/web/app/components/workflow/nodes/assigner/node.tsx index 5e5950d715..c7777c4541 100644 --- a/web/app/components/workflow/nodes/assigner/node.tsx +++ b/web/app/components/workflow/nodes/assigner/node.tsx @@ -1,14 +1,15 @@ import type { FC } from 'react' -import React from 'react' -import { useNodes } from 'reactflow' -import { useTranslation } from 'react-i18next' import type { AssignerNodeType } from './types' +import type { Node, NodeProps } from '@/app/components/workflow/types' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' +import Badge from '@/app/components/base/badge' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' import { VariableLabelInNode, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' -import Badge from '@/app/components/base/badge' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.assigner' @@ -25,17 +26,17 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({ if (validOperationItems.length === 0) { return ( - <div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'> - <div className='flex flex-col items-start gap-1 self-stretch'> - <div className='flex items-center gap-1 self-stretch rounded-md bg-workflow-block-parma-bg px-[5px] py-1'> - <div className='system-xs-medium flex-1 text-text-tertiary'>{t(`${i18nPrefix}.varNotSet`)}</div> + <div className="relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1"> + <div className="flex flex-col items-start gap-1 self-stretch"> + <div className="flex items-center gap-1 self-stretch rounded-md bg-workflow-block-parma-bg px-[5px] py-1"> + <div className="system-xs-medium flex-1 text-text-tertiary">{t(`${i18nPrefix}.varNotSet`)}</div> </div> </div> </div> ) } return ( - <div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'> + <div className="relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1"> {operationItems.map((value, index) => { const variable = value.variable_selector if (!variable || variable.length === 0) @@ -49,7 +50,7 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({ nodeType={node?.data.type} nodeTitle={node?.data.title} rightSlot={ - value.operation && <Badge className='!ml-auto shrink-0' text={t(`${i18nPrefix}.operations.${value.operation}`)} /> + value.operation && <Badge className="!ml-auto shrink-0" text={t(`${i18nPrefix}.operations.${value.operation}`)} /> } /> ) @@ -66,13 +67,13 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({ const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) return ( - <div className='relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1'> + <div className="relative flex flex-col items-start gap-0.5 self-stretch px-3 py-1"> <VariableLabelInNode variables={variable} nodeType={node?.data.type} nodeTitle={node?.data.title} rightSlot={ - writeMode && <Badge className='!ml-auto shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} /> + writeMode && <Badge className="!ml-auto shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}`)} /> } /> </div> diff --git a/web/app/components/workflow/nodes/assigner/panel.tsx b/web/app/components/workflow/nodes/assigner/panel.tsx index 430f1ae27f..04da330fd4 100644 --- a/web/app/components/workflow/nodes/assigner/panel.tsx +++ b/web/app/components/workflow/nodes/assigner/panel.tsx @@ -1,15 +1,15 @@ import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' +import type { AssignerNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import { RiAddLine, } from '@remixicon/react' -import VarList from './components/var-list' -import useConfig from './use-config' -import type { AssignerNodeType } from './types' -import type { NodePanelProps } from '@/app/components/workflow/types' -import { useHandleAddOperationItem } from './hooks' +import React from 'react' +import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' +import VarList from './components/var-list' +import { useHandleAddOperationItem } from './hooks' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.assigner' @@ -37,12 +37,12 @@ const Panel: FC<NodePanelProps<AssignerNodeType>> = ({ } return ( - <div className='flex flex-col items-start self-stretch py-2'> - <div className='flex w-full flex-col items-start justify-center gap-1 self-stretch px-4 py-2'> - <div className='flex items-start gap-2 self-stretch'> - <div className='system-sm-semibold-uppercase flex grow flex-col items-start justify-center text-text-secondary'>{t(`${i18nPrefix}.variables`)}</div> + <div className="flex flex-col items-start self-stretch py-2"> + <div className="flex w-full flex-col items-start justify-center gap-1 self-stretch px-4 py-2"> + <div className="flex items-start gap-2 self-stretch"> + <div className="system-sm-semibold-uppercase flex grow flex-col items-start justify-center text-text-secondary">{t(`${i18nPrefix}.variables`)}</div> <ActionButton onClick={handleAddOperation}> - <RiAddLine className='h-4 w-4 shrink-0 text-text-tertiary' /> + <RiAddLine className="h-4 w-4 shrink-0 text-text-tertiary" /> </ActionButton> </div> <VarList diff --git a/web/app/components/workflow/nodes/assigner/use-config.ts b/web/app/components/workflow/nodes/assigner/use-config.ts index a69ddd2464..b9f34c4a9c 100644 --- a/web/app/components/workflow/nodes/assigner/use-config.ts +++ b/web/app/components/workflow/nodes/assigner/use-config.ts @@ -1,20 +1,19 @@ -import { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' -import { VarType } from '../../types' import type { ValueSelector, Var } from '../../types' -import { WriteMode } from './types' import type { AssignerNodeOperation, AssignerNodeType } from './types' -import { writeModeTypesNum } from './types' -import { useGetAvailableVars } from './hooks' -import { convertV1ToV2 } from './utils' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback, useMemo } from 'react' +import { useStoreApi } from 'reactflow' import { useIsChatMode, useNodesReadOnly, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarType } from '../../types' +import { useGetAvailableVars } from './hooks' +import { WriteMode, writeModeTypesNum } from './types' +import { convertV1ToV2 } from './utils' const useConfig = (id: string, rawPayload: AssignerNodeType) => { const payload = useMemo(() => convertV1ToV2(rawPayload), [rawPayload]) @@ -75,8 +74,9 @@ const useConfig = (id: string, rawPayload: AssignerNodeType) => { const getToAssignedVarType = useCallback((assignedVarType: VarType, write_mode: WriteMode) => { if (write_mode === WriteMode.overwrite || write_mode === WriteMode.increment || write_mode === WriteMode.decrement - || write_mode === WriteMode.multiply || write_mode === WriteMode.divide || write_mode === WriteMode.extend) + || write_mode === WriteMode.multiply || write_mode === WriteMode.divide || write_mode === WriteMode.extend) { return assignedVarType + } if (write_mode === WriteMode.append) { if (assignedVarType === VarType.arrayString) return VarType.string diff --git a/web/app/components/workflow/nodes/assigner/use-single-run-form-params.ts b/web/app/components/workflow/nodes/assigner/use-single-run-form-params.ts index 403157b132..002fac719d 100644 --- a/web/app/components/workflow/nodes/assigner/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/assigner/use-single-run-form-params.ts @@ -1,13 +1,13 @@ import type { RefObject } from 'react' +import type { AssignerNodeType } from './types' import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' import { useMemo } from 'react' import useNodeCrud from '../_base/hooks/use-node-crud' -import { type AssignerNodeType, WriteMode } from './types' -import { writeModeTypesNum } from './types' +import { WriteMode, writeModeTypesNum } from './types' type Params = { - id: string, - payload: AssignerNodeType, + id: string + payload: AssignerNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -26,8 +26,8 @@ const useSingleRunFormParams = ({ const vars = (inputs.items ?? []).filter((item) => { return item.operation !== WriteMode.clear && item.operation !== WriteMode.set - && item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast - && !writeModeTypesNum.includes(item.operation) + && item.operation !== WriteMode.removeFirst && item.operation !== WriteMode.removeLast + && !writeModeTypesNum.includes(item.operation) }).map(item => item.value as ValueSelector) const forms = useMemo(() => { diff --git a/web/app/components/workflow/nodes/code/code-parser.spec.ts b/web/app/components/workflow/nodes/code/code-parser.spec.ts index 67f2c218e1..d7fd590f28 100644 --- a/web/app/components/workflow/nodes/code/code-parser.spec.ts +++ b/web/app/components/workflow/nodes/code/code-parser.spec.ts @@ -31,27 +31,27 @@ const SAMPLE_CODES = { describe('extractFunctionParams', () => { describe('Python3', () => { - test('handles no parameters', () => { + it('handles no parameters', () => { const result = extractFunctionParams(SAMPLE_CODES.python3.noParams, CodeLanguage.python3) expect(result).toEqual([]) }) - test('extracts single parameter', () => { + it('extracts single parameter', () => { const result = extractFunctionParams(SAMPLE_CODES.python3.singleParam, CodeLanguage.python3) expect(result).toEqual(['param1']) }) - test('extracts multiple parameters', () => { + it('extracts multiple parameters', () => { const result = extractFunctionParams(SAMPLE_CODES.python3.multipleParams, CodeLanguage.python3) expect(result).toEqual(['param1', 'param2', 'param3']) }) - test('handles type hints', () => { + it('handles type hints', () => { const result = extractFunctionParams(SAMPLE_CODES.python3.withTypes, CodeLanguage.python3) expect(result).toEqual(['param1', 'param2', 'param3']) }) - test('handles default values', () => { + it('handles default values', () => { const result = extractFunctionParams(SAMPLE_CODES.python3.withDefaults, CodeLanguage.python3) expect(result).toEqual(['param1', 'param2']) }) @@ -59,27 +59,27 @@ describe('extractFunctionParams', () => { // JavaScript のテストケース describe('JavaScript', () => { - test('handles no parameters', () => { + it('handles no parameters', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.noParams, CodeLanguage.javascript) expect(result).toEqual([]) }) - test('extracts single parameter', () => { + it('extracts single parameter', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.singleParam, CodeLanguage.javascript) expect(result).toEqual(['param1']) }) - test('extracts multiple parameters', () => { + it('extracts multiple parameters', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.multipleParams, CodeLanguage.javascript) expect(result).toEqual(['param1', 'param2', 'param3']) }) - test('handles comments in code', () => { + it('handles comments in code', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.withComments, CodeLanguage.javascript) expect(result).toEqual(['param1', 'param2']) }) - test('handles whitespace', () => { + it('handles whitespace', () => { const result = extractFunctionParams(SAMPLE_CODES.javascript.withSpaces, CodeLanguage.javascript) expect(result).toEqual(['param1', 'param2']) }) @@ -182,7 +182,7 @@ function main(name, age, city) { describe('extractReturnType', () => { // Python3 のテスト describe('Python3', () => { - test('extracts single return value', () => { + it('extracts single return value', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.singleReturn, CodeLanguage.python3) expect(result).toEqual({ result: { @@ -192,7 +192,7 @@ describe('extractReturnType', () => { }) }) - test('extracts multiple return values', () => { + it('extracts multiple return values', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.multipleReturns, CodeLanguage.python3) expect(result).toEqual({ result: { @@ -206,12 +206,12 @@ describe('extractReturnType', () => { }) }) - test('returns empty object when no return statement', () => { + it('returns empty object when no return statement', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.noReturn, CodeLanguage.python3) expect(result).toEqual({}) }) - test('handles complex return statement', () => { + it('handles complex return statement', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.complexReturn, CodeLanguage.python3) expect(result).toEqual({ result: { @@ -228,7 +228,7 @@ describe('extractReturnType', () => { }, }) }) - test('handles nested object structure', () => { + it('handles nested object structure', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.nestedObject, CodeLanguage.python3) expect(result).toEqual({ personal_info: { @@ -249,7 +249,7 @@ describe('extractReturnType', () => { // JavaScript のテスト describe('JavaScript', () => { - test('extracts single return value', () => { + it('extracts single return value', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.singleReturn, CodeLanguage.javascript) expect(result).toEqual({ result: { @@ -259,7 +259,7 @@ describe('extractReturnType', () => { }) }) - test('extracts multiple return values', () => { + it('extracts multiple return values', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.multipleReturns, CodeLanguage.javascript) expect(result).toEqual({ result: { @@ -273,7 +273,7 @@ describe('extractReturnType', () => { }) }) - test('handles return with parentheses', () => { + it('handles return with parentheses', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withParentheses, CodeLanguage.javascript) expect(result).toEqual({ result: { @@ -287,12 +287,12 @@ describe('extractReturnType', () => { }) }) - test('returns empty object when no return statement', () => { + it('returns empty object when no return statement', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.noReturn, CodeLanguage.javascript) expect(result).toEqual({}) }) - test('handles quoted keys', () => { + it('handles quoted keys', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withQuotes, CodeLanguage.javascript) expect(result).toEqual({ result: { @@ -305,7 +305,7 @@ describe('extractReturnType', () => { }, }) }) - test('handles nested object structure', () => { + it('handles nested object structure', () => { const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.nestedObject, CodeLanguage.javascript) expect(result).toEqual({ personal_info: { diff --git a/web/app/components/workflow/nodes/code/code-parser.ts b/web/app/components/workflow/nodes/code/code-parser.ts index 7550e62e96..cdd8a69a26 100644 --- a/web/app/components/workflow/nodes/code/code-parser.ts +++ b/web/app/components/workflow/nodes/code/code-parser.ts @@ -1,5 +1,5 @@ -import { VarType } from '../../types' import type { OutputVar } from './types' +import { VarType } from '../../types' import { CodeLanguage } from './types' export const extractFunctionParams = (code: string, language: CodeLanguage) => { @@ -68,7 +68,7 @@ export const extractReturnType = (code: string, language: CodeLanguage): OutputV const result: OutputVar = {} - const keyRegex = /['"]?(\w+)['"]?\s*:(?![^{]*})/g + const keyRegex = /['"]?(\w+)['"]?\s*:(?![^{]*\})/g const matches = returnContent.matchAll(keyRegex) for (const match of matches) { diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 7cf40db63f..6a0ae7861d 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -1,8 +1,9 @@ import type { NodeDefault } from '../../types' -import { CodeLanguage, type CodeNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import type { CodeNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { CodeLanguage } from './types' const i18nPrefix = 'workflow.errorMsg' diff --git a/web/app/components/workflow/nodes/code/dependency-picker.tsx b/web/app/components/workflow/nodes/code/dependency-picker.tsx index a302a21366..2e0e0c0d59 100644 --- a/web/app/components/workflow/nodes/code/dependency-picker.tsx +++ b/web/app/components/workflow/nodes/code/dependency-picker.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { t } from 'i18next' +import type { CodeDependency } from './types' import { RiArrowDownSLine, } from '@remixicon/react' -import type { CodeDependency } from './types' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import Input from '@/app/components/base/input' +import { t } from 'i18next' +import React, { useCallback, useState } from 'react' import { Check } from '@/app/components/base/icons/src/vender/line/general' +import Input from '@/app/components/base/input' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' type Props = { value: CodeDependency @@ -34,22 +34,26 @@ const DependencyPicker: FC<Props> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={4} > - <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='grow cursor-pointer'> - <div className='flex h-8 items-center justify-between rounded-lg border-0 bg-gray-100 px-2.5 text-[13px] text-gray-900'> - <div className='w-0 grow truncate' title={value.name}>{value.name}</div> - <RiArrowDownSLine className='h-3.5 w-3.5 shrink-0 text-gray-700' /> + <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className="grow cursor-pointer"> + <div className="flex h-8 items-center justify-between rounded-lg border-0 bg-gray-100 px-2.5 text-[13px] text-gray-900"> + <div className="w-0 grow truncate" title={value.name}>{value.name}</div> + <RiArrowDownSLine className="h-3.5 w-3.5 shrink-0 text-gray-700" /> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent style={{ zIndex: 100, - }}> - <div className='rounded-lg bg-white p-1 shadow-sm' style={{ - width: 350, - }}> - <div className='mx-1 mb-2'> + }} + > + <div + className="rounded-lg bg-white p-1 shadow-sm" + style={{ + width: 350, + }} + > + <div className="mx-1 mb-2"> <Input showLeftIcon showClearIcon @@ -60,7 +64,7 @@ const DependencyPicker: FC<Props> = ({ autoFocus /> </div> - <div className='max-h-[30vh] overflow-y-auto'> + <div className="max-h-[30vh] overflow-y-auto"> {available_dependencies.filter((v) => { if (!searchText) return true @@ -68,11 +72,11 @@ const DependencyPicker: FC<Props> = ({ }).map(dependency => ( <div key={dependency.name} - className='flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-gray-900 hover:bg-gray-100' + className="flex h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-gray-900 hover:bg-gray-100" onClick={handleChange(dependency)} > - <div className='w-0 grow truncate'>{dependency.name}</div> - {dependency.name === value.name && <Check className='h-4 w-4 shrink-0 text-primary-600' />} + <div className="w-0 grow truncate">{dependency.name}</div> + {dependency.name === value.name && <Check className="h-4 w-4 shrink-0 text-primary-600" />} </div> ))} </div> diff --git a/web/app/components/workflow/nodes/code/node.tsx b/web/app/components/workflow/nodes/code/node.tsx index 03e16f56e9..5fa002913d 100644 --- a/web/app/components/workflow/nodes/code/node.tsx +++ b/web/app/components/workflow/nodes/code/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' -import React from 'react' import type { CodeNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' const Node: FC<NodeProps<CodeNodeType>> = () => { return ( diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index afbf293911..261195c4c5 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -1,20 +1,21 @@ import type { FC } from 'react' +import type { CodeNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' -import useConfig from './use-config' -import type { CodeNodeType } from './types' -import { CodeLanguage } from './types' -import { extractFunctionParams, extractReturnType } from './code-parser' -import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' -import OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list' import AddButton from '@/app/components/base/button/add-button' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' -import type { NodePanelProps } from '@/app/components/workflow/types' import SyncButton from '@/app/components/base/button/sync-button' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list' +import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' +import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' +import { extractFunctionParams, extractReturnType } from './code-parser' +import { CodeLanguage } from './types' +import useConfig from './use-config' + const i18nPrefix = 'workflow.nodes.code' const codeLanguages = [ @@ -65,17 +66,19 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({ } return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.inputVars`)} operations={ - !readOnly ? ( - <div className="flex gap-2"> - <SyncButton popupContent={t(`${i18nPrefix}.syncFunctionSignature`)} onClick={handleSyncFunctionSignature} /> - <AddButton onClick={handleAddVariable} /> - </div> - ) : undefined + !readOnly + ? ( + <div className="flex gap-2"> + <SyncButton popupContent={t(`${i18nPrefix}.syncFunctionSignature`)} onClick={handleSyncFunctionSignature} /> + <AddButton onClick={handleAddVariable} /> + </div> + ) + : undefined } > <VarList @@ -92,13 +95,13 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({ nodeId={id} isInNode readOnly={readOnly} - title={ + title={( <TypeSelector options={codeLanguages} value={inputs.code_language} onChange={handleCodeLanguageChange} /> - } + )} language={inputs.code_language} value={inputs.code} onChange={handleCodeChange} @@ -107,7 +110,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({ /> </div> <Split /> - <div className='px-4 pb-2 pt-4'> + <div className="px-4 pb-2 pt-4"> <Field title={t(`${i18nPrefix}.outputVars`)} operations={ @@ -129,7 +132,7 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({ onCancel={hideRemoveVarConfirm} onConfirm={onRemoveVarConfirm} /> - </div > + </div> ) } diff --git a/web/app/components/workflow/nodes/code/types.ts b/web/app/components/workflow/nodes/code/types.ts index 265fd9d25d..ab85fccc86 100644 --- a/web/app/components/workflow/nodes/code/types.ts +++ b/web/app/components/workflow/nodes/code/types.ts @@ -1,4 +1,4 @@ -import type { CommonNodeType, VarType, Variable } from '@/app/components/workflow/types' +import type { CommonNodeType, Variable, VarType } from '@/app/components/workflow/types' export enum CodeLanguage { python3 = 'python3', diff --git a/web/app/components/workflow/nodes/code/use-config.ts b/web/app/components/workflow/nodes/code/use-config.ts index 39137ada30..fdb1c8ce51 100644 --- a/web/app/components/workflow/nodes/code/use-config.ts +++ b/web/app/components/workflow/nodes/code/use-config.ts @@ -1,20 +1,20 @@ -import { useCallback, useEffect, useState } from 'react' -import { produce } from 'immer' -import useVarList from '../_base/hooks/use-var-list' -import useOutputVarList from '../_base/hooks/use-output-var-list' -import { BlockEnum, VarType } from '../../types' import type { Var, Variable } from '../../types' -import { useStore } from '../../store' import type { CodeNodeType, OutputVar } from './types' -import { CodeLanguage } from './types' +import { produce } from 'immer' +import { useCallback, useEffect, useState } from 'react' +import { + useNodesReadOnly, +} from '@/app/components/workflow/hooks' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { fetchNodeDefault, fetchPipelineNodeDefault, } from '@/service/workflow' -import { - useNodesReadOnly, -} from '@/app/components/workflow/hooks' +import { useStore } from '../../store' +import { BlockEnum, VarType } from '../../types' +import useOutputVarList from '../_base/hooks/use-output-var-list' +import useVarList from '../_base/hooks/use-var-list' +import { CodeLanguage } from './types' const useConfig = (id: string, payload: CodeNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/code/use-single-run-form-params.ts b/web/app/components/workflow/nodes/code/use-single-run-form-params.ts index cda882ac89..18f31f19c4 100644 --- a/web/app/components/workflow/nodes/code/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/code/use-single-run-form-params.ts @@ -1,12 +1,12 @@ import type { RefObject } from 'react' +import type { CodeNodeType } from './types' import type { InputVar, Variable } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' import useNodeCrud from '../_base/hooks/use-node-crud' -import type { CodeNodeType } from './types' type Params = { - id: string, - payload: CodeNodeType, + id: string + payload: CodeNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] diff --git a/web/app/components/workflow/nodes/components.ts b/web/app/components/workflow/nodes/components.ts index d8da8b9dae..87c0066d15 100644 --- a/web/app/components/workflow/nodes/components.ts +++ b/web/app/components/workflow/nodes/components.ts @@ -1,53 +1,53 @@ import type { ComponentType } from 'react' import { BlockEnum } from '../types' -import StartNode from './start/node' -import StartPanel from './start/panel' -import EndNode from './end/node' -import EndPanel from './end/panel' -import AnswerNode from './answer/node' -import AnswerPanel from './answer/panel' -import LLMNode from './llm/node' -import LLMPanel from './llm/panel' -import KnowledgeRetrievalNode from './knowledge-retrieval/node' -import KnowledgeRetrievalPanel from './knowledge-retrieval/panel' -import QuestionClassifierNode from './question-classifier/node' -import QuestionClassifierPanel from './question-classifier/panel' -import IfElseNode from './if-else/node' -import IfElsePanel from './if-else/panel' -import CodeNode from './code/node' -import CodePanel from './code/panel' -import TemplateTransformNode from './template-transform/node' -import TemplateTransformPanel from './template-transform/panel' -import HttpNode from './http/node' -import HttpPanel from './http/panel' -import ToolNode from './tool/node' -import ToolPanel from './tool/panel' -import VariableAssignerNode from './variable-assigner/node' -import VariableAssignerPanel from './variable-assigner/panel' -import AssignerNode from './assigner/node' -import AssignerPanel from './assigner/panel' -import ParameterExtractorNode from './parameter-extractor/node' -import ParameterExtractorPanel from './parameter-extractor/panel' -import IterationNode from './iteration/node' -import IterationPanel from './iteration/panel' -import LoopNode from './loop/node' -import LoopPanel from './loop/panel' -import DocExtractorNode from './document-extractor/node' -import DocExtractorPanel from './document-extractor/panel' -import ListFilterNode from './list-operator/node' -import ListFilterPanel from './list-operator/panel' import AgentNode from './agent/node' import AgentPanel from './agent/panel' +import AnswerNode from './answer/node' +import AnswerPanel from './answer/panel' +import AssignerNode from './assigner/node' +import AssignerPanel from './assigner/panel' +import CodeNode from './code/node' +import CodePanel from './code/panel' import DataSourceNode from './data-source/node' import DataSourcePanel from './data-source/panel' +import DocExtractorNode from './document-extractor/node' +import DocExtractorPanel from './document-extractor/panel' +import EndNode from './end/node' +import EndPanel from './end/panel' +import HttpNode from './http/node' +import HttpPanel from './http/panel' +import IfElseNode from './if-else/node' +import IfElsePanel from './if-else/panel' +import IterationNode from './iteration/node' +import IterationPanel from './iteration/panel' import KnowledgeBaseNode from './knowledge-base/node' import KnowledgeBasePanel from './knowledge-base/panel' +import KnowledgeRetrievalNode from './knowledge-retrieval/node' +import KnowledgeRetrievalPanel from './knowledge-retrieval/panel' +import ListFilterNode from './list-operator/node' +import ListFilterPanel from './list-operator/panel' +import LLMNode from './llm/node' +import LLMPanel from './llm/panel' +import LoopNode from './loop/node' +import LoopPanel from './loop/panel' +import ParameterExtractorNode from './parameter-extractor/node' +import ParameterExtractorPanel from './parameter-extractor/panel' +import QuestionClassifierNode from './question-classifier/node' +import QuestionClassifierPanel from './question-classifier/panel' +import StartNode from './start/node' +import StartPanel from './start/panel' +import TemplateTransformNode from './template-transform/node' +import TemplateTransformPanel from './template-transform/panel' +import ToolNode from './tool/node' +import ToolPanel from './tool/panel' +import TriggerPluginNode from './trigger-plugin/node' +import TriggerPluginPanel from './trigger-plugin/panel' import TriggerScheduleNode from './trigger-schedule/node' import TriggerSchedulePanel from './trigger-schedule/panel' import TriggerWebhookNode from './trigger-webhook/node' import TriggerWebhookPanel from './trigger-webhook/panel' -import TriggerPluginNode from './trigger-plugin/node' -import TriggerPluginPanel from './trigger-plugin/panel' +import VariableAssignerNode from './variable-assigner/node' +import VariableAssignerPanel from './variable-assigner/panel' export const NodeComponentMap: Record<string, ComponentType<any>> = { [BlockEnum.Start]: StartNode, diff --git a/web/app/components/workflow/nodes/data-source-empty/default.ts b/web/app/components/workflow/nodes/data-source-empty/default.ts index 69d9904217..5ba1ca7543 100644 --- a/web/app/components/workflow/nodes/data-source-empty/default.ts +++ b/web/app/components/workflow/nodes/data-source-empty/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { DataSourceEmptyNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: -1, diff --git a/web/app/components/workflow/nodes/data-source-empty/hooks.ts b/web/app/components/workflow/nodes/data-source-empty/hooks.ts index a17f0b2acb..93191b0261 100644 --- a/web/app/components/workflow/nodes/data-source-empty/hooks.ts +++ b/web/app/components/workflow/nodes/data-source-empty/hooks.ts @@ -1,9 +1,9 @@ +import type { OnSelectBlock } from '@/app/components/workflow/types' +import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -import { produce } from 'immer' -import type { OnSelectBlock } from '@/app/components/workflow/types' -import { generateNewNode } from '@/app/components/workflow/utils' import { useNodesMetaData } from '@/app/components/workflow/hooks' +import { generateNewNode } from '@/app/components/workflow/utils' export const useReplaceDataSourceNode = (id: string) => { const store = useStoreApi() @@ -20,7 +20,8 @@ export const useReplaceDataSourceNode = (id: string) => { const nodes = getNodes() const emptyNodeIndex = nodes.findIndex(node => node.id === id) - if (emptyNodeIndex < 0) return + if (emptyNodeIndex < 0) + return const { defaultValue, } = nodesMetaDataMap![type] diff --git a/web/app/components/workflow/nodes/data-source-empty/index.tsx b/web/app/components/workflow/nodes/data-source-empty/index.tsx index b85cb94e95..378b9c80c4 100644 --- a/web/app/components/workflow/nodes/data-source-empty/index.tsx +++ b/web/app/components/workflow/nodes/data-source-empty/index.tsx @@ -1,13 +1,13 @@ +import type { NodeProps } from 'reactflow' +import { RiAddLine } from '@remixicon/react' import { memo, useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from 'reactflow' -import { RiAddLine } from '@remixicon/react' -import { cn } from '@/utils/classnames' import Button from '@/app/components/base/button' import BlockSelector from '@/app/components/workflow/block-selector' +import { cn } from '@/utils/classnames' import { useReplaceDataSourceNode } from './hooks' const DataSourceEmptyNode = ({ id, data }: NodeProps) => { @@ -17,10 +17,10 @@ const DataSourceEmptyNode = ({ id, data }: NodeProps) => { const renderTrigger = useCallback(() => { return ( <Button - variant='primary' - className='w-full' + variant="primary" + className="w-full" > - <RiAddLine className='mr-1 h-4 w-4' /> + <RiAddLine className="mr-1 h-4 w-4" /> {t('workflow.nodes.dataSource.add')} </Button> ) @@ -37,8 +37,8 @@ const DataSourceEmptyNode = ({ id, data }: NodeProps) => { height: data.height, }} > - <div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'> - <div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'> + <div className="absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]"> + <div className="system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary"> {t('workflow.blocks.datasource')} </div> </div> @@ -51,15 +51,16 @@ const DataSourceEmptyNode = ({ id, data }: NodeProps) => { > <div className={cn( 'flex items-center rounded-t-2xl p-3', - )}> + )} + > <BlockSelector asChild onSelect={handleReplaceNode} trigger={renderTrigger} noBlocks noTools - popupClassName='w-[320px]' - placement='bottom-start' + popupClassName="w-[320px]" + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, diff --git a/web/app/components/workflow/nodes/data-source/before-run-form.tsx b/web/app/components/workflow/nodes/data-source/before-run-form.tsx index 521fdfb087..a091211fa5 100644 --- a/web/app/components/workflow/nodes/data-source/before-run-form.tsx +++ b/web/app/components/workflow/nodes/data-source/before-run-form.tsx @@ -1,17 +1,17 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' import type { CustomRunFormProps } from './types' -import { DatasourceType } from '@/models/pipeline' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import LocalFile from '@/app/components/datasets/documents/create-from-pipeline/data-source/local-file' import OnlineDocuments from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-documents' -import WebsiteCrawl from '@/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl' import OnlineDrive from '@/app/components/datasets/documents/create-from-pipeline/data-source/online-drive' import { useDataSourceStore } from '@/app/components/datasets/documents/create-from-pipeline/data-source/store' -import { useOnlineDocument, useOnlineDrive, useWebsiteCrawl } from '@/app/components/rag-pipeline/components/panel/test-run/preparation/hooks' -import Button from '@/app/components/base/button' -import { useTranslation } from 'react-i18next' import DataSourceProvider from '@/app/components/datasets/documents/create-from-pipeline/data-source/store/provider' +import WebsiteCrawl from '@/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl' +import { useOnlineDocument, useOnlineDrive, useWebsiteCrawl } from '@/app/components/rag-pipeline/components/panel/test-run/preparation/hooks' +import { DatasourceType } from '@/models/pipeline' import PanelWrap from '../_base/components/before-run-form/panel-wrap' import useBeforeRunForm from './hooks/use-before-run-form' @@ -56,7 +56,7 @@ const BeforeRunForm: FC<CustomRunFormProps> = (props) => { nodeName={payload.title} onHide={onCancel} > - <div className='flex flex-col gap-y-5 px-4 pt-4'> + <div className="flex flex-col gap-y-5 px-4 pt-4"> {datasourceType === DatasourceType.localFile && ( <LocalFile allowedExtensions={datasourceNodeData.fileExtensions || []} @@ -90,13 +90,13 @@ const BeforeRunForm: FC<CustomRunFormProps> = (props) => { supportBatchUpload={false} /> )} - <div className='flex justify-end gap-x-2'> + <div className="flex justify-end gap-x-2"> <Button onClick={onCancel}> {t('common.operation.cancel')} </Button> <Button onClick={handleRunWithSyncDraft} - variant='primary' + variant="primary" loading={isPending} disabled={isPending || startRunBtnDisabled} > diff --git a/web/app/components/workflow/nodes/data-source/default.ts b/web/app/components/workflow/nodes/data-source/default.ts index 82e69679a2..565bfb0d24 100644 --- a/web/app/components/workflow/nodes/data-source/default.ts +++ b/web/app/components/workflow/nodes/data-source/default.ts @@ -1,14 +1,14 @@ import type { NodeDefault } from '../../types' import type { DataSourceNodeType } from './types' -import { DataSourceClassification } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' import { COMMON_OUTPUT, LOCAL_FILE_OUTPUT, } from './constants' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' +import { DataSourceClassification } from './types' const i18nPrefix = 'workflow.errorMsg' @@ -86,12 +86,14 @@ const nodeDefault: NodeDefault<DataSourceNodeType> = { type, description: output.description, schemaType, - children: output.type === 'object' ? { - schema: { - type: 'object', - properties: output.properties, - }, - } : undefined, + children: output.type === 'object' + ? { + schema: { + type: 'object', + properties: output.properties, + }, + } + : undefined, }) }) } diff --git a/web/app/components/workflow/nodes/data-source/hooks/use-before-run-form.ts b/web/app/components/workflow/nodes/data-source/hooks/use-before-run-form.ts index 1e42d032e8..004da29bf5 100644 --- a/web/app/components/workflow/nodes/data-source/hooks/use-before-run-form.ts +++ b/web/app/components/workflow/nodes/data-source/hooks/use-before-run-form.ts @@ -1,17 +1,17 @@ -import { useStoreApi } from 'reactflow' import type { CustomRunFormProps, DataSourceNodeType } from '../types' -import { useEffect, useMemo, useRef } from 'react' -import { useNodeDataUpdate, useNodesSyncDraft } from '../../../hooks' -import { NodeRunningStatus } from '../../../types' -import { useInvalidLastRun } from '@/service/use-workflow' import type { NodeRunResult } from '@/types/workflow' -import { fetchNodeInspectVars } from '@/service/workflow' -import { FlowType } from '@/types/common' -import { useDatasourceSingleRun } from '@/service/use-pipeline' +import { useEffect, useMemo, useRef } from 'react' +import { useStoreApi } from 'reactflow' +import { useShallow } from 'zustand/react/shallow' import { useDataSourceStore, useDataSourceStoreWithSelector } from '@/app/components/datasets/documents/create-from-pipeline/data-source/store' import { DatasourceType } from '@/models/pipeline' +import { useDatasourceSingleRun } from '@/service/use-pipeline' +import { useInvalidLastRun } from '@/service/use-workflow' +import { fetchNodeInspectVars } from '@/service/workflow' import { TransferMethod } from '@/types/app' -import { useShallow } from 'zustand/react/shallow' +import { FlowType } from '@/types/common' +import { useNodeDataUpdate, useNodesSyncDraft } from '../../../hooks' +import { NodeRunningStatus } from '../../../types' const useBeforeRunForm = ({ nodeId, @@ -46,7 +46,8 @@ const useBeforeRunForm = ({ }))) const startRunBtnDisabled = useMemo(() => { - if (!datasourceNodeData) return false + if (!datasourceNodeData) + return false if (datasourceType === DatasourceType.localFile) return !localFileList.length || localFileList.some(file => !file.file.id) if (datasourceType === DatasourceType.onlineDocument) diff --git a/web/app/components/workflow/nodes/data-source/hooks/use-config.ts b/web/app/components/workflow/nodes/data-source/hooks/use-config.ts index 98683e70e7..08f66e4089 100644 --- a/web/app/components/workflow/nodes/data-source/hooks/use-config.ts +++ b/web/app/components/workflow/nodes/data-source/hooks/use-config.ts @@ -1,3 +1,7 @@ +import type { + DataSourceNodeType, + ToolVarInputs, +} from '../types' import { useCallback, useEffect, @@ -5,10 +9,6 @@ import { } from 'react' import { useStoreApi } from 'reactflow' import { useNodeDataUpdate } from '@/app/components/workflow/hooks' -import type { - DataSourceNodeType, - ToolVarInputs, -} from '../types' export const useConfig = (id: string, dataSourceList?: any[]) => { const store = useStoreApi() @@ -61,7 +61,8 @@ export const useConfig = (id: string, dataSourceList?: any[]) => { const outputSchema = useMemo(() => { const nodeData = getNodeData() - if (!nodeData?.data || !dataSourceList) return [] + if (!nodeData?.data || !dataSourceList) + return [] const currentDataSource = dataSourceList.find((ds: any) => ds.plugin_id === nodeData.data.plugin_id) const currentDataSourceItem = currentDataSource?.tools?.find((tool: any) => tool.name === nodeData.data.datasource_name) @@ -95,7 +96,8 @@ export const useConfig = (id: string, dataSourceList?: any[]) => { const hasObjectOutput = useMemo(() => { const nodeData = getNodeData() - if (!nodeData?.data || !dataSourceList) return false + if (!nodeData?.data || !dataSourceList) + return false const currentDataSource = dataSourceList.find((ds: any) => ds.plugin_id === nodeData.data.plugin_id) const currentDataSourceItem = currentDataSource?.tools?.find((tool: any) => tool.name === nodeData.data.datasource_name) diff --git a/web/app/components/workflow/nodes/data-source/node.tsx b/web/app/components/workflow/nodes/data-source/node.tsx index b490aea2a9..be8a2c50b2 100644 --- a/web/app/components/workflow/nodes/data-source/node.tsx +++ b/web/app/components/workflow/nodes/data-source/node.tsx @@ -1,10 +1,10 @@ import type { FC } from 'react' -import { memo, useEffect } from 'react' -import type { NodeProps } from '@/app/components/workflow/types' -import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' -import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' -import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' import type { DataSourceNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' +import { memo, useEffect } from 'react' +import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' +import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' +import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' const Node: FC<NodeProps<DataSourceNodeType>> = ({ id, @@ -39,15 +39,15 @@ const Node: FC<NodeProps<DataSourceNodeType>> = ({ return null return ( - <div className='relative mb-1 px-3 py-1'> - <div className='pointer-events-auto absolute right-3 top-[-32px] z-40'> + <div className="relative mb-1 px-3 py-1"> + <div className="pointer-events-auto absolute right-3 top-[-32px] z-40"> <InstallPluginButton - size='small' + size="small" extraIdentifiers={[ data.plugin_id, data.provider_name, ].filter(Boolean) as string[]} - className='!font-medium !text-text-accent' + className="!font-medium !text-text-accent" uniqueIdentifier={uniqueIdentifier!} onSuccess={onInstallSuccess} /> diff --git a/web/app/components/workflow/nodes/data-source/panel.tsx b/web/app/components/workflow/nodes/data-source/panel.tsx index cbb506964b..607caa79d3 100644 --- a/web/app/components/workflow/nodes/data-source/panel.tsx +++ b/web/app/components/workflow/nodes/data-source/panel.tsx @@ -1,29 +1,29 @@ import type { FC } from 'react' +import type { DataSourceNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import { + memo, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { memo } from 'react' -import type { DataSourceNodeType } from './types' -import { DataSourceClassification } from './types' -import type { NodePanelProps } from '@/app/components/workflow/types' +import TagInput from '@/app/components/base/tag-input' +import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import { useNodesReadOnly } from '@/app/components/workflow/hooks' import { BoxGroupField, } from '@/app/components/workflow/nodes/_base/components/layout' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' -import TagInput from '@/app/components/base/tag-input' -import { useNodesReadOnly } from '@/app/components/workflow/hooks' -import { useConfig } from './hooks/use-config' +import { useStore } from '@/app/components/workflow/store' +import { wrapStructuredVarItem } from '@/app/components/workflow/utils/tool' +import useMatchSchemaType, { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' +import ToolForm from '../tool/components/tool-form' import { COMMON_OUTPUT, LOCAL_FILE_OUTPUT, } from './constants' -import { useStore } from '@/app/components/workflow/store' -import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' -import ToolForm from '../tool/components/tool-form' -import { wrapStructuredVarItem } from '@/app/components/workflow/utils/tool' -import useMatchSchemaType, { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' +import { useConfig } from './hooks/use-config' +import { DataSourceClassification } from './types' const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => { const { t } = useTranslation() @@ -52,7 +52,7 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => { const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel) const { schemaTypeDefinitions } = useMatchSchemaType() return ( - <div > + <div> { currentDataSource?.is_authorized && !isLocalFile && !!formSchemas?.length && ( <BoxGroupField @@ -94,12 +94,12 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => { }, }} > - <div className='rounded-lg bg-components-input-bg-normal p-1 pt-0'> + <div className="rounded-lg bg-components-input-bg-normal p-1 pt-0"> <TagInput items={fileExtensions} onChange={handleFileExtensionsChange} placeholder={t('workflow.nodes.dataSource.supportedFileFormatsPlaceholder')} - inputClassName='bg-transparent' + inputClassName="bg-transparent" disableAdd={nodesReadOnly} disableRemove={nodesReadOnly} /> @@ -141,7 +141,7 @@ const Panel: FC<NodePanelProps<DataSourceNodeType>> = ({ id, data }) => { <div key={outputItem.name}> {outputItem.value?.type === 'object' ? ( <StructureOutputItem - rootClassName='code-sm-semibold text-text-secondary' + rootClassName="code-sm-semibold text-text-secondary" payload={wrapStructuredVarItem(outputItem, schemaType)} /> ) : ( diff --git a/web/app/components/workflow/nodes/data-source/types.ts b/web/app/components/workflow/nodes/data-source/types.ts index d0bc034b89..19b4bada3f 100644 --- a/web/app/components/workflow/nodes/data-source/types.ts +++ b/web/app/components/workflow/nodes/data-source/types.ts @@ -1,8 +1,9 @@ +import type { Dispatch, SetStateAction } from 'react' +import type { ResourceVarInputs } from '../_base/types' import type { CommonNodeType, Node } from '@/app/components/workflow/types' import type { FlowType } from '@/types/common' import type { NodeRunResult, VarInInspect } from '@/types/workflow' -import type { Dispatch, SetStateAction } from 'react' -import type { ResourceVarInputs } from '../_base/types' + export { VarKindType as VarType } from '../_base/types' export enum DataSourceClassification { diff --git a/web/app/components/workflow/nodes/data-source/utils.ts b/web/app/components/workflow/nodes/data-source/utils.ts index fbda69f3fd..94c1f81ec8 100644 --- a/web/app/components/workflow/nodes/data-source/utils.ts +++ b/web/app/components/workflow/nodes/data-source/utils.ts @@ -1,5 +1,5 @@ -import { PipelineInputVarType } from '@/models/pipeline' import { VarType } from '@/app/components/workflow/types' +import { PipelineInputVarType } from '@/models/pipeline' export const inputVarTypeToVarType = (type: PipelineInputVarType): VarType => { return ({ diff --git a/web/app/components/workflow/nodes/document-extractor/default.ts b/web/app/components/workflow/nodes/document-extractor/default.ts index 77a847b5fd..73ed069b7d 100644 --- a/web/app/components/workflow/nodes/document-extractor/default.ts +++ b/web/app/components/workflow/nodes/document-extractor/default.ts @@ -1,8 +1,9 @@ import type { NodeDefault } from '../../types' import type { DocExtractorNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/document-extractor/node.tsx b/web/app/components/workflow/nodes/document-extractor/node.tsx index a0437a4f54..c092cd353a 100644 --- a/web/app/components/workflow/nodes/document-extractor/node.tsx +++ b/web/app/components/workflow/nodes/document-extractor/node.tsx @@ -1,13 +1,14 @@ import type { FC } from 'react' -import React from 'react' -import { useNodes } from 'reactflow' -import { useTranslation } from 'react-i18next' import type { DocExtractorNodeType } from './types' +import type { Node, NodeProps } from '@/app/components/workflow/types' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' import { VariableLabelInNode, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.docExtractor' @@ -25,8 +26,8 @@ const NodeComponent: FC<NodeProps<DocExtractorNodeType>> = ({ const isSystem = isSystemVar(variable) const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) return ( - <div className='relative mb-1 px-3 py-1'> - <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div> + <div className="relative mb-1 px-3 py-1"> + <div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t(`${i18nPrefix}.inputVar`)}</div> <VariableLabelInNode variables={variable} nodeType={node?.data.type} diff --git a/web/app/components/workflow/nodes/document-extractor/panel.tsx b/web/app/components/workflow/nodes/document-extractor/panel.tsx index 7165dc06df..b7cfddea4b 100644 --- a/web/app/components/workflow/nodes/document-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/document-extractor/panel.tsx @@ -1,18 +1,19 @@ import type { FC } from 'react' +import type { DocExtractorNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import OutputVars, { VarItem } from '../_base/components/output-vars' -import Split from '../_base/components/split' -import { useNodeHelpLink } from '../_base/hooks/use-node-help-link' -import useConfig from './use-config' -import type { DocExtractorNodeType } from './types' import Field from '@/app/components/workflow/nodes/_base/components/field' -import { BlockEnum, type NodePanelProps } from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' import I18n from '@/context/i18n' import { LanguagesSupported } from '@/i18n-config/language' import { useFileSupportTypes } from '@/service/use-common' +import OutputVars, { VarItem } from '../_base/components/output-vars' +import Split from '../_base/components/split' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' +import { useNodeHelpLink } from '../_base/hooks/use-node-help-link' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.docExtractor' @@ -48,8 +49,8 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({ } = useConfig(id, data) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.inputVar`)} required @@ -62,11 +63,11 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({ value={inputs.variable_selector || []} onChange={handleVarChanges} filterVar={filterVar} - typePlaceHolder='File | Array[File]' + typePlaceHolder="File | Array[File]" /> - <div className='body-xs-regular mt-1 py-0.5 text-text-tertiary'> + <div className="body-xs-regular mt-1 py-0.5 text-text-tertiary"> {t(`${i18nPrefix}.supportFileTypes`, { types: supportTypesShowNames })} - <a className='text-text-accent' href={link} target='_blank'>{t(`${i18nPrefix}.learnMore`)}</a> + <a className="text-text-accent" href={link} target="_blank">{t(`${i18nPrefix}.learnMore`)}</a> </div> </> </Field> @@ -75,7 +76,7 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({ <div> <OutputVars> <VarItem - name='text' + name="text" type={inputs.is_array_file ? 'array[string]' : 'string'} description={t(`${i18nPrefix}.outputVars.text`)} /> diff --git a/web/app/components/workflow/nodes/document-extractor/use-config.ts b/web/app/components/workflow/nodes/document-extractor/use-config.ts index 53393aa030..b5ff863821 100644 --- a/web/app/components/workflow/nodes/document-extractor/use-config.ts +++ b/web/app/components/workflow/nodes/document-extractor/use-config.ts @@ -1,16 +1,16 @@ -import { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' import type { ValueSelector, Var } from '../../types' -import { VarType } from '../../types' import type { DocExtractorNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback, useMemo } from 'react' +import { useStoreApi } from 'reactflow' import { useIsChatMode, useNodesReadOnly, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarType } from '../../types' const useConfig = (id: string, payload: DocExtractorNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/document-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/document-extractor/use-single-run-form-params.ts index f60f1cbd77..afd1bb17f4 100644 --- a/web/app/components/workflow/nodes/document-extractor/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/document-extractor/use-single-run-form-params.ts @@ -1,15 +1,15 @@ import type { RefObject } from 'react' +import type { DocExtractorNodeType } from './types' import type { InputVar, Variable } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' -import type { DocExtractorNodeType } from './types' import { useTranslation } from 'react-i18next' import { InputVarType } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.docExtractor' type Params = { - id: string, - payload: DocExtractorNodeType, + id: string + payload: DocExtractorNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -50,7 +50,7 @@ const useSingleRunFormParams = ({ } const getDependentVar = (variable: string) => { - if(variable === 'files') + if (variable === 'files') return payload.variable_selector } diff --git a/web/app/components/workflow/nodes/end/default.ts b/web/app/components/workflow/nodes/end/default.ts index 881c16986b..eee07cb0ff 100644 --- a/web/app/components/workflow/nodes/end/default.ts +++ b/web/app/components/workflow/nodes/end/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { EndNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: 2.1, diff --git a/web/app/components/workflow/nodes/end/node.tsx b/web/app/components/workflow/nodes/end/node.tsx index 2583e61b68..26e7b7d022 100644 --- a/web/app/components/workflow/nodes/end/node.tsx +++ b/web/app/components/workflow/nodes/end/node.tsx @@ -1,16 +1,16 @@ import type { FC } from 'react' -import React from 'react' import type { EndNodeType } from './types' import type { NodeProps, Variable } from '@/app/components/workflow/types' +import React from 'react' import { useIsChatMode, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' -import { BlockEnum } from '@/app/components/workflow/types' import { VariableLabelInNode, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { BlockEnum } from '@/app/components/workflow/types' const Node: FC<NodeProps<EndNodeType>> = ({ id, @@ -36,7 +36,7 @@ const Node: FC<NodeProps<EndNodeType>> = ({ return null return ( - <div className='mb-1 space-y-0.5 px-3 py-1'> + <div className="mb-1 space-y-0.5 px-3 py-1"> {filteredOutputs.map(({ value_selector }, index) => { const node = getNode(value_selector[0]) const varType = getCurrentVariableType({ diff --git a/web/app/components/workflow/nodes/end/panel.tsx b/web/app/components/workflow/nodes/end/panel.tsx index 420280d7c5..3970dc8efe 100644 --- a/web/app/components/workflow/nodes/end/panel.tsx +++ b/web/app/components/workflow/nodes/end/panel.tsx @@ -1,12 +1,12 @@ import type { FC } from 'react' +import type { EndNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import useConfig from './use-config' -import type { EndNodeType } from './types' -import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' -import Field from '@/app/components/workflow/nodes/_base/components/field' import AddButton from '@/app/components/base/button/add-button' -import type { NodePanelProps } from '@/app/components/workflow/types' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.end' @@ -25,8 +25,8 @@ const Panel: FC<NodePanelProps<EndNodeType>> = ({ const outputs = inputs.outputs return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.output.variable`)} diff --git a/web/app/components/workflow/nodes/end/use-config.ts b/web/app/components/workflow/nodes/end/use-config.ts index b9876f95ed..251b47c821 100644 --- a/web/app/components/workflow/nodes/end/use-config.ts +++ b/web/app/components/workflow/nodes/end/use-config.ts @@ -1,9 +1,10 @@ -import useVarList from '../_base/hooks/use-var-list' import type { EndNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import useVarList from '../_base/hooks/use-var-list' + const useConfig = (id: string, payload: EndNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() const { inputs, setInputs } = useNodeCrud<EndNodeType>(id, payload) diff --git a/web/app/components/workflow/nodes/http/components/api-input.tsx b/web/app/components/workflow/nodes/http/components/api-input.tsx index 62ce0f15c6..a72fc9fde0 100644 --- a/web/app/components/workflow/nodes/http/components/api-input.tsx +++ b/web/app/components/workflow/nodes/http/components/api-input.tsx @@ -1,15 +1,15 @@ 'use client' import type { FC } from 'react' +import type { Var } from '../../../types' +import { RiArrowDownSLine } from '@remixicon/react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { Method } from '../types' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import { cn } from '@/utils/classnames' +import { VarType } from '../../../types' import Selector from '../../_base/components/selector' import useAvailableVarList from '../../_base/hooks/use-available-var-list' -import { VarType } from '../../../types' -import type { Var } from '../../../types' -import { cn } from '@/utils/classnames' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import { Method } from '../types' const MethodOptions = [ { label: 'GET', value: Method.get }, @@ -47,24 +47,24 @@ const ApiInput: FC<Props> = ({ }) return ( - <div className='flex items-start space-x-1'> + <div className="flex items-start space-x-1"> <Selector value={method} onChange={onMethodChange} options={MethodOptions} - trigger={ - <div className={cn(readonly && 'cursor-pointer', 'flex h-8 shrink-0 items-center rounded-lg border border-components-button-secondary-border bg-components-button-secondary-bg px-2.5')} > - <div className='w-12 pl-0.5 text-xs font-medium uppercase leading-[18px] text-text-primary'>{method}</div> - {!readonly && <RiArrowDownSLine className='ml-1 h-3.5 w-3.5 text-text-secondary' />} + trigger={( + <div className={cn(readonly && 'cursor-pointer', 'flex h-8 shrink-0 items-center rounded-lg border border-components-button-secondary-border bg-components-button-secondary-bg px-2.5')}> + <div className="w-12 pl-0.5 text-xs font-medium uppercase leading-[18px] text-text-primary">{method}</div> + {!readonly && <RiArrowDownSLine className="ml-1 h-3.5 w-3.5 text-text-secondary" />} </div> - } - popupClassName='top-[34px] w-[108px]' + )} + popupClassName="top-[34px] w-[108px]" showChecked readonly={readonly} /> <Input - instanceId='http-api-url' + instanceId="http-api-url" className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')} value={url} onChange={onUrlChange} @@ -73,9 +73,9 @@ const ApiInput: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={setIsFocus} placeholder={!readonly ? t('workflow.nodes.http.apiPlaceholder')! : ''} - placeholderClassName='!leading-[21px]' + placeholderClassName="!leading-[21px]" /> - </div > + </div> ) } export default React.memo(ApiInput) diff --git a/web/app/components/workflow/nodes/http/components/authorization/index.tsx b/web/app/components/workflow/nodes/http/components/authorization/index.tsx index 7fd811dfbc..50505fd4c8 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/index.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/index.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' -import { useTranslation } from 'react-i18next' -import React, { useCallback, useState } from 'react' -import { produce } from 'immer' import type { Authorization as AuthorizationPayloadType } from '../../types' -import { APIType, AuthorizationType } from '../../types' -import RadioGroup from './radio-group' +import type { Var } from '@/app/components/workflow/types' +import { produce } from 'immer' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import BaseInput from '@/app/components/base/input' +import Modal from '@/app/components/base/modal' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import { VarType } from '@/app/components/workflow/types' -import type { Var } from '@/app/components/workflow/types' -import Modal from '@/app/components/base/modal' -import Button from '@/app/components/base/button' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' -import BaseInput from '@/app/components/base/input' import { cn } from '@/utils/classnames' +import { APIType, AuthorizationType } from '../../types' +import RadioGroup from './radio-group' const i18nPrefix = 'workflow.nodes.http.authorization' @@ -25,12 +25,12 @@ type Props = { onHide: () => void } -const Field = ({ title, isRequired, children }: { title: string; isRequired?: boolean; children: React.JSX.Element }) => { +const Field = ({ title, isRequired, children }: { title: string, isRequired?: boolean, children: React.JSX.Element }) => { return ( <div> - <div className='text-[13px] font-medium leading-8 text-text-secondary'> + <div className="text-[13px] font-medium leading-8 text-text-secondary"> {title} - {isRequired && <span className='ml-0.5 text-text-destructive'>*</span>} + {isRequired && <span className="ml-0.5 text-text-destructive">*</span>} </div> <div>{children}</div> </div> @@ -120,7 +120,7 @@ const Authorization: FC<Props> = ({ onClose={onHide} > <div> - <div className='space-y-2'> + <div className="space-y-2"> <Field title={t(`${i18nPrefix}.authorizationType`)}> <RadioGroup options={[ @@ -155,9 +155,9 @@ const Authorization: FC<Props> = ({ )} <Field title={t(`${i18nPrefix}.api-key-title`)} isRequired> - <div className='flex'> + <div className="flex"> <Input - instanceId='http-api-key' + instanceId="http-api-key" className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')} value={tempPayload.config?.api_key || ''} onChange={handleAPIKeyChange} @@ -165,16 +165,16 @@ const Authorization: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={setIsFocus} placeholder={' '} - placeholderClassName='!leading-[21px]' + placeholderClassName="!leading-[21px]" /> </div> </Field> </> )} </div> - <div className='mt-6 flex justify-end space-x-2'> + <div className="mt-6 flex justify-end space-x-2"> <Button onClick={onHide}>{t('common.operation.cancel')}</Button> - <Button variant='primary' onClick={handleConfirm}>{t('common.operation.save')}</Button> + <Button variant="primary" onClick={handleConfirm}>{t('common.operation.save')}</Button> </div> </div> </Modal> diff --git a/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx b/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx index d5d12d7f34..6edd325b18 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx @@ -47,7 +47,7 @@ const RadioGroup: FC<Props> = ({ return () => onChange(value) }, [onChange]) return ( - <div className='flex space-x-2'> + <div className="flex space-x-2"> {options.map(option => ( <Item key={option.value} diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx index 4b9ee56f85..2710fd3c5d 100644 --- a/web/app/components/workflow/nodes/http/components/curl-panel.tsx +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -1,13 +1,14 @@ 'use client' import type { FC } from 'react' +import type { HttpNodeType } from '../types' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { BodyPayloadValueType, BodyType, type HttpNodeType, Method } from '../types' -import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' +import Modal from '@/app/components/base/modal' import Textarea from '@/app/components/base/textarea' import Toast from '@/app/components/base/toast' import { useNodesInteractions } from '@/app/components/workflow/hooks' +import { BodyPayloadValueType, BodyType, Method } from '../types' type Props = { nodeId: string @@ -16,7 +17,7 @@ type Props = { handleCurlImport: (node: HttpNodeType) => void } -const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: string | null } => { +const parseCurl = (curlCommand: string): { node: HttpNodeType | null, error: string | null } => { if (!curlCommand.trim().toLowerCase().startsWith('curl')) return { node: null, error: 'Invalid cURL command. Command must start with "curl".' } @@ -29,7 +30,7 @@ const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: str params: '', body: { type: BodyType.none, data: '' }, } - const args = curlCommand.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [] + const args = curlCommand.match(/(?:[^\s"']|"[^"]*"|'[^']*')+/g) || [] let hasData = false for (let i = 1; i < args.length; i++) { @@ -145,19 +146,22 @@ const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => { title={t('workflow.nodes.http.curl.title')} isShow={isShow} onClose={onHide} - className='!w-[400px] !max-w-[400px] !p-4' + className="!w-[400px] !max-w-[400px] !p-4" > <div> <Textarea value={inputString} - className='my-3 h-40 w-full grow' + className="my-3 h-40 w-full grow" onChange={e => setInputString(e.target.value)} placeholder={t('workflow.nodes.http.curl.placeholder')!} /> </div> - <div className='mt-4 flex justify-end space-x-2'> - <Button className='!w-[95px]' onClick={onHide} >{t('common.operation.cancel')}</Button> - <Button className='!w-[95px]' variant='primary' onClick={handleSave} > {t('common.operation.save')}</Button> + <div className="mt-4 flex justify-end space-x-2"> + <Button className="!w-[95px]" onClick={onHide}>{t('common.operation.cancel')}</Button> + <Button className="!w-[95px]" variant="primary" onClick={handleSave}> + {' '} + {t('common.operation.save')} + </Button> </div> </Modal> ) diff --git a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx index 050f54f040..1770d01ef5 100644 --- a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx +++ b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx @@ -1,17 +1,17 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' +import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types' +import type { ValueSelector, Var } from '@/app/components/workflow/types' import { produce } from 'immer' import { uniqueId } from 'lodash-es' -import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types' +import React, { useCallback, useMemo } from 'react' +import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor' +import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import VarReferencePicker from '../../../_base/components/variable/var-reference-picker' +import useAvailableVarList from '../../../_base/hooks/use-available-var-list' import { BodyPayloadValueType, BodyType } from '../../types' import KeyValue from '../key-value' -import useAvailableVarList from '../../../_base/hooks/use-available-var-list' -import VarReferencePicker from '../../../_base/components/variable/var-reference-picker' -import { cn } from '@/utils/classnames' -import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' const UNIQUE_ID_PREFIX = 'key-value-' @@ -68,13 +68,13 @@ const EditBody: FC<Props> = ({ type: newType, data: hasKeyValue ? [ - { - id: uniqueId(UNIQUE_ID_PREFIX), - type: BodyPayloadValueType.text, - key: '', - value: '', - }, - ] + { + id: uniqueId(UNIQUE_ID_PREFIX), + type: BodyPayloadValueType.text, + key: '', + value: '', + }, + ] : [], }) }, [onChange]) @@ -133,9 +133,9 @@ const EditBody: FC<Props> = ({ return ( <div> {/* body type */} - <div className='flex flex-wrap'> + <div className="flex flex-wrap"> {allTypes.map(t => ( - <label key={t} htmlFor={`body-type-${t}`} className='mr-4 flex h-7 items-center space-x-2'> + <label key={t} htmlFor={`body-type-${t}`} className="mr-4 flex h-7 items-center space-x-2"> <input type="radio" id={`body-type-${t}`} @@ -144,7 +144,7 @@ const EditBody: FC<Props> = ({ onChange={handleTypeChange} disabled={readonly} /> - <div className='text-[13px] font-normal leading-[18px] text-text-secondary'>{bodyTextMap[t]}</div> + <div className="text-[13px] font-normal leading-[18px] text-text-secondary">{bodyTextMap[t]}</div> </label> ))} </div> @@ -164,8 +164,8 @@ const EditBody: FC<Props> = ({ {type === BodyType.rawText && ( <InputWithVar - instanceId={'http-body-raw'} - title={<div className='uppercase'>Raw text</div>} + instanceId="http-body-raw" + title={<div className="uppercase">Raw text</div>} onChange={handleBodyValueChange} value={stringValue} justVar @@ -177,8 +177,8 @@ const EditBody: FC<Props> = ({ {type === BodyType.json && ( <InputWithVar - instanceId={'http-body-json'} - title='JSON' + instanceId="http-body-json" + title="JSON" value={stringValue} onChange={handleBodyValueChange} justVar diff --git a/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx index 43c766c878..ea43c726e2 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import TextEditor from '@/app/components/workflow/nodes/_base/components/editor/text-editor' import { LayoutGrid02 } from '@/app/components/base/icons/src/vender/line/layout' +import TextEditor from '@/app/components/workflow/nodes/_base/components/editor/text-editor' const i18nPrefix = 'workflow.nodes.http' @@ -38,22 +38,22 @@ const BulkEdit: FC<Props> = ({ <div> <TextEditor isInNode - title={<div className='uppercase'>{t(`${i18nPrefix}.bulkEdit`)}</div>} + title={<div className="uppercase">{t(`${i18nPrefix}.bulkEdit`)}</div>} value={tempValue} onChange={handleChange} onBlur={handleBlur} - headerRight={ - <div className='flex h-[18px] items-center'> + headerRight={( + <div className="flex h-[18px] items-center"> <div - className='flex cursor-pointer items-center space-x-1' + className="flex cursor-pointer items-center space-x-1" onClick={handleSwitchToKeyValueEdit} > - <LayoutGrid02 className='h-3 w-3 text-gray-500' /> - <div className='text-xs font-normal leading-[18px] text-gray-500'>{t(`${i18nPrefix}.keyValueEdit`)}</div> + <LayoutGrid02 className="h-3 w-3 text-gray-500" /> + <div className="text-xs font-normal leading-[18px] text-gray-500">{t(`${i18nPrefix}.keyValueEdit`)}</div> </div> - <div className='ml-3 mr-1.5 h-3 w-px bg-gray-200'></div> + <div className="ml-3 mr-1.5 h-3 w-px bg-gray-200"></div> </div> - } + )} minHeight={150} /> </div> diff --git a/web/app/components/workflow/nodes/http/components/key-value/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/index.tsx index e930114f32..0191cb0c7a 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' -import React from 'react' import type { KeyValue } from '../../types' +import React from 'react' import KeyValueEdit from './key-value-edit' type Props = { @@ -44,15 +44,17 @@ const KeyValueList: FC<Props> = ({ // }).join('\n') // return res // })() - return <KeyValueEdit - readonly={readonly} - nodeId={nodeId} - list={list} - onChange={onChange} - onAdd={onAdd} - isSupportFile={isSupportFile} - // onSwitchToBulkEdit={toggleKeyValueEdit} - /> + return ( + <KeyValueEdit + readonly={readonly} + nodeId={nodeId} + list={list} + onChange={onChange} + onAdd={onAdd} + isSupportFile={isSupportFile} + // onSwitchToBulkEdit={toggleKeyValueEdit} + /> + ) // : <BulkEdit // value={bulkList} // onChange={handleBulkValueChange} diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx index d107520c75..61d6292e06 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' import type { KeyValue } from '../../../types' -import KeyValueItem from './item' +import { produce } from 'immer' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' +import KeyValueItem from './item' const i18nPrefix = 'workflow.nodes.http' @@ -56,10 +56,10 @@ const KeyValueList: FC<Props> = ({ return null return ( - <div className='overflow-hidden rounded-lg border border-divider-regular'> + <div className="overflow-hidden rounded-lg border border-divider-regular"> <div className={cn('system-xs-medium-uppercase flex h-7 items-center leading-7 text-text-tertiary')}> <div className={cn('h-full border-r border-divider-regular pl-3', isSupportFile ? 'w-[140px]' : 'w-1/2')}>{t(`${i18nPrefix}.key`)}</div> - {isSupportFile && <div className='h-full w-[70px] shrink-0 border-r border-divider-regular pl-3'>{t(`${i18nPrefix}.type`)}</div>} + {isSupportFile && <div className="h-full w-[70px] shrink-0 border-r border-divider-regular pl-3">{t(`${i18nPrefix}.type`)}</div>} <div className={cn('h-full items-center justify-between pl-3 pr-1', isSupportFile ? 'grow' : 'w-1/2')}>{t(`${i18nPrefix}.value`)}</div> </div> { diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx index 80c42209d6..2f1857f7af 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx @@ -1,13 +1,14 @@ 'use client' import type { FC } from 'react' +import type { Var } from '@/app/components/workflow/types' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import useAvailableVarList from '../../../../_base/hooks/use-available-var-list' -import { cn } from '@/utils/classnames' -import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' -import type { Var } from '@/app/components/workflow/types' +import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import useAvailableVarList from '../../../../_base/hooks/use-available-var-list' + type Props = { className?: string instanceId?: string @@ -60,29 +61,9 @@ const InputItem: FC<Props> = ({ <div className={cn(className, 'hover:cursor-text hover:bg-state-base-hover', 'relative flex h-full')}> {(!readOnly) ? ( - <Input - instanceId={instanceId} - className={cn(isFocus ? 'bg-components-input-bg-active' : 'bg-width', 'w-0 grow px-3 py-1')} - value={value} - onChange={onChange} - readOnly={readOnly} - nodesOutputVars={availableVars} - availableNodes={availableNodesWithParent} - onFocusChange={setIsFocus} - placeholder={t('workflow.nodes.http.insertVarPlaceholder')!} - placeholderClassName='!leading-[21px]' - promptMinHeightClassName='h-full' - insertVarTipToLeft={insertVarTipToLeft} - /> - ) - : <div - className="h-[18px] w-full pl-0.5 leading-[18px]" - > - {!hasValue && <div className='text-xs font-normal text-text-quaternary'>{placeholder}</div>} - {hasValue && ( <Input instanceId={instanceId} - className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')} + className={cn(isFocus ? 'bg-components-input-bg-active' : 'bg-width', 'w-0 grow px-3 py-1')} value={value} onChange={onChange} readOnly={readOnly} @@ -90,16 +71,38 @@ const InputItem: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={setIsFocus} placeholder={t('workflow.nodes.http.insertVarPlaceholder')!} - placeholderClassName='!leading-[21px]' - promptMinHeightClassName='h-full' + placeholderClassName="!leading-[21px]" + promptMinHeightClassName="h-full" insertVarTipToLeft={insertVarTipToLeft} /> - )} + ) + : ( + <div + className="h-[18px] w-full pl-0.5 leading-[18px]" + > + {!hasValue && <div className="text-xs font-normal text-text-quaternary">{placeholder}</div>} + {hasValue && ( + <Input + instanceId={instanceId} + className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')} + value={value} + onChange={onChange} + readOnly={readOnly} + nodesOutputVars={availableVars} + availableNodes={availableNodesWithParent} + onFocusChange={setIsFocus} + placeholder={t('workflow.nodes.http.insertVarPlaceholder')!} + placeholderClassName="!leading-[21px]" + promptMinHeightClassName="h-full" + insertVarTipToLeft={insertVarTipToLeft} + /> + )} - </div>} + </div> + )} {hasRemove && !isFocus && ( <RemoveButton - className='absolute right-1 top-0.5 hidden group-hover:block' + className="absolute right-1 top-0.5 hidden group-hover:block" onClick={handleRemove} /> )} diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx index 5a27d1efa1..365367fd97 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx @@ -1,15 +1,15 @@ 'use client' import type { FC } from 'react' +import type { KeyValue } from '../../../types' +import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { produce } from 'immer' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import type { KeyValue } from '../../../types' +import { PortalSelect } from '@/app/components/base/select' +import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' import VarReferencePicker from '../../../../_base/components/variable/var-reference-picker' import InputItem from './input-item' -import { cn } from '@/utils/classnames' -import { PortalSelect } from '@/app/components/base/select' -import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' // import Input from '@/app/components/base/input' const i18nPrefix = 'workflow.nodes.http' @@ -66,27 +66,27 @@ const KeyValueItem: FC<Props> = ({ <div className={cn('shrink-0 border-r border-divider-regular', isSupportFile ? 'w-[140px]' : 'w-1/2')}> {!keyNotSupportVar ? ( - <InputItem - instanceId={`http-key-${instanceId}`} - nodeId={nodeId} - value={payload.key} - onChange={handleChange('key')} - hasRemove={false} - placeholder={t(`${i18nPrefix}.key`)!} - readOnly={readonly} - insertVarTipToLeft={insertVarTipToLeft} - /> - ) + <InputItem + instanceId={`http-key-${instanceId}`} + nodeId={nodeId} + value={payload.key} + onChange={handleChange('key')} + hasRemove={false} + placeholder={t(`${i18nPrefix}.key`)!} + readOnly={readonly} + insertVarTipToLeft={insertVarTipToLeft} + /> + ) : ( - <input - className='system-sm-regular focus:bg-gray-100! appearance-none rounded-none border-none bg-transparent outline-none hover:bg-components-input-bg-hover focus:ring-0' - value={payload.key} - onChange={e => handleChange('key')(e.target.value)} - /> - )} + <input + className="system-sm-regular focus:bg-gray-100! appearance-none rounded-none border-none bg-transparent outline-none hover:bg-components-input-bg-hover focus:ring-0" + value={payload.key} + onChange={e => handleChange('key')(e.target.value)} + /> + )} </div> {isSupportFile && ( - <div className='w-[70px] shrink-0 border-r border-divider-regular'> + <div className="w-[70px] shrink-0 border-r border-divider-regular"> <PortalSelect value={payload.type!} onSelect={item => handleChange('type')(item.value as string)} @@ -95,38 +95,39 @@ const KeyValueItem: FC<Props> = ({ { name: 'file', value: 'file' }, ]} readonly={readonly} - triggerClassName='rounded-none h-7 text-text-primary' + triggerClassName="rounded-none h-7 text-text-primary" triggerClassNameFn={isOpen => isOpen ? 'bg-state-base-hover' : 'bg-transparent'} - popupClassName='w-[80px] h-7' + popupClassName="w-[80px] h-7" /> - </div>)} + </div> + )} <div className={cn(isSupportFile ? 'grow' : 'w-1/2')} onClick={() => isLastItem && onAdd()}> {(isSupportFile && payload.type === 'file') ? ( - <VarReferencePicker - nodeId={nodeId} - readonly={readonly} - value={payload.file || []} - onChange={handleChange('file')} - filterVar={filterOnlyFileVariable} - isInTable - onRemove={onRemove} - /> - ) + <VarReferencePicker + nodeId={nodeId} + readonly={readonly} + value={payload.file || []} + onChange={handleChange('file')} + filterVar={filterOnlyFileVariable} + isInTable + onRemove={onRemove} + /> + ) : ( - <InputItem - instanceId={`http-value-${instanceId}`} - nodeId={nodeId} - value={payload.value} - onChange={handleChange('value')} - hasRemove={!readonly && canRemove} - onRemove={onRemove} - placeholder={t(`${i18nPrefix}.value`)!} - readOnly={readonly} - isSupportFile={isSupportFile} - insertVarTipToLeft={insertVarTipToLeft} - /> - )} + <InputItem + instanceId={`http-value-${instanceId}`} + nodeId={nodeId} + value={payload.value} + onChange={handleChange('value')} + hasRemove={!readonly && canRemove} + onRemove={onRemove} + placeholder={t(`${i18nPrefix}.value`)!} + readOnly={readonly} + isSupportFile={isSupportFile} + insertVarTipToLeft={insertVarTipToLeft} + /> + )} </div> </div> diff --git a/web/app/components/workflow/nodes/http/components/timeout/index.tsx b/web/app/components/workflow/nodes/http/components/timeout/index.tsx index bb84091d67..11edfa8c93 100644 --- a/web/app/components/workflow/nodes/http/components/timeout/index.tsx +++ b/web/app/components/workflow/nodes/http/components/timeout/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' +import type { Timeout as TimeoutPayloadType } from '../../types' import React from 'react' import { useTranslation } from 'react-i18next' -import type { Timeout as TimeoutPayloadType } from '../../types' import Input from '@/app/components/base/input' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' import { useStore } from '@/app/components/workflow/store' @@ -34,7 +34,7 @@ const InputField: FC<{ <span className="text-xs font-normal text-text-tertiary">{description}</span> </div> <Input - type='number' + type="number" value={value} onChange={(e) => { const inputValue = e.target.value @@ -70,7 +70,7 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => { return ( <FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}> - <div className='mt-2 space-y-1'> + <div className="mt-2 space-y-1"> <div className="space-y-3"> <InputField title={t('workflow.nodes.http.timeout.connectLabel')!} diff --git a/web/app/components/workflow/nodes/http/default.ts b/web/app/components/workflow/nodes/http/default.ts index 4a08702d6a..b127f70a1f 100644 --- a/web/app/components/workflow/nodes/http/default.ts +++ b/web/app/components/workflow/nodes/http/default.ts @@ -1,9 +1,9 @@ import type { NodeDefault } from '../../types' -import { AuthorizationType, BodyType, Method } from './types' import type { BodyPayload, HttpNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { AuthorizationType, BodyType, Method } from './types' const metaData = genNodeMetaData({ classification: BlockClassificationEnum.Utilities, @@ -45,10 +45,11 @@ const nodeDefault: NodeDefault<HttpNodeType> = { errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.api') }) if (!errorMessages - && payload.body.type === BodyType.binary - && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) - ) + && payload.body.type === BodyType.binary + && ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0) + ) { errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.binaryFileVariable') }) + } return { isValid: !errorMessages, diff --git a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts index 44774074dc..b174b7e6de 100644 --- a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts +++ b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts @@ -1,7 +1,7 @@ -import { useCallback, useEffect, useState } from 'react' +import type { KeyValue } from '../types' import { useBoolean } from 'ahooks' import { uniqueId } from 'lodash-es' -import type { KeyValue } from '../types' +import { useCallback, useEffect, useState } from 'react' const UNIQUE_ID_PREFIX = 'key-value-' const strToKeyValueList = (value: string) => { diff --git a/web/app/components/workflow/nodes/http/node.tsx b/web/app/components/workflow/nodes/http/node.tsx index 6002bf737d..332a28c44c 100644 --- a/web/app/components/workflow/nodes/http/node.tsx +++ b/web/app/components/workflow/nodes/http/node.tsx @@ -1,8 +1,9 @@ import type { FC } from 'react' -import React from 'react' -import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' import type { HttpNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' +import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' + const Node: FC<NodeProps<HttpNodeType>> = ({ id, data, @@ -12,12 +13,12 @@ const Node: FC<NodeProps<HttpNodeType>> = ({ return null return ( - <div className='mb-1 px-3 py-1'> - <div className='flex items-center justify-start rounded-md bg-workflow-block-parma-bg p-1'> - <div className='flex h-4 shrink-0 items-center rounded bg-components-badge-white-to-dark px-1 text-xs font-semibold uppercase text-text-secondary'>{method}</div> - <div className='w-0 grow pl-1 pt-1'> + <div className="mb-1 px-3 py-1"> + <div className="flex items-center justify-start rounded-md bg-workflow-block-parma-bg p-1"> + <div className="flex h-4 shrink-0 items-center rounded bg-components-badge-white-to-dark px-1 text-xs font-semibold uppercase text-text-secondary">{method}</div> + <div className="w-0 grow pl-1 pt-1"> <ReadonlyInputWithSelectVar - className='text-text-secondary' + className="text-text-secondary" value={url} nodeId={id} /> diff --git a/web/app/components/workflow/nodes/http/panel.tsx b/web/app/components/workflow/nodes/http/panel.tsx index a174ff4742..b46474ab84 100644 --- a/web/app/components/workflow/nodes/http/panel.tsx +++ b/web/app/components/workflow/nodes/http/panel.tsx @@ -1,22 +1,22 @@ import type { FC } from 'react' +import type { HttpNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import useConfig from './use-config' -import ApiInput from './components/api-input' -import KeyValue from './components/key-value' -import EditBody from './components/edit-body' -import AuthorizationModal from './components/authorization' -import type { HttpNodeType } from './types' -import Timeout from './components/timeout' -import CurlPanel from './components/curl-panel' -import { cn } from '@/utils/classnames' +import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files' +import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import Switch from '@/app/components/base/switch' import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' -import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files' -import type { NodePanelProps } from '@/app/components/workflow/types' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import { cn } from '@/utils/classnames' +import ApiInput from './components/api-input' +import AuthorizationModal from './components/authorization' +import CurlPanel from './components/curl-panel' +import EditBody from './components/edit-body' +import KeyValue from './components/key-value' +import Timeout from './components/timeout' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.http' @@ -55,34 +55,34 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({ return null return ( - <div className='pt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="pt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.api`)} required - operations={ - <div className='flex'> + operations={( + <div className="flex"> <div onClick={showAuthorization} className={cn(!readOnly && 'cursor-pointer hover:bg-state-base-hover', 'flex h-6 items-center space-x-1 rounded-md px-2 ')} > - {!readOnly && <Settings01 className='h-3 w-3 text-text-tertiary' />} - <div className='text-xs font-medium text-text-tertiary'> + {!readOnly && <Settings01 className="h-3 w-3 text-text-tertiary" />} + <div className="text-xs font-medium text-text-tertiary"> {t(`${i18nPrefix}.authorization.authorization`)} - <span className='ml-1 text-text-secondary'>{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span> + <span className="ml-1 text-text-secondary">{t(`${i18nPrefix}.authorization.${inputs.authorization.type}`)}</span> </div> </div> <div onClick={showCurlPanel} className={cn(!readOnly && 'cursor-pointer hover:bg-state-base-hover', 'flex h-6 items-center space-x-1 rounded-md px-2 ')} > - {!readOnly && <FileArrow01 className='h-3 w-3 text-text-tertiary' />} - <div className='text-xs font-medium text-text-tertiary'> + {!readOnly && <FileArrow01 className="h-3 w-3 text-text-tertiary" />} + <div className="text-xs font-medium text-text-tertiary"> {t(`${i18nPrefix}.curl.title`)} </div> </div> </div> - } + )} > <ApiInput nodeId={id} @@ -129,14 +129,15 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({ <Field title={t(`${i18nPrefix}.verifySSL.title`)} tooltip={t(`${i18nPrefix}.verifySSL.warningTooltip`)} - operations={ + operations={( <Switch defaultValue={!!inputs.ssl_verify} onChange={handleSSLVerifyChange} - size='md' + size="md" disabled={readOnly} /> - }> + )} + > </Field> </div> <Split /> @@ -156,27 +157,27 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({ /> )} <Split /> - <div className=''> + <div className=""> <OutputVars> <> <VarItem - name='body' - type='string' + name="body" + type="string" description={t(`${i18nPrefix}.outputVars.body`)} /> <VarItem - name='status_code' - type='number' + name="status_code" + type="number" description={t(`${i18nPrefix}.outputVars.statusCode`)} /> <VarItem - name='headers' - type='object' + name="headers" + type="object" description={t(`${i18nPrefix}.outputVars.headers`)} /> <VarItem - name='files' - type='Array[File]' + name="files" + type="Array[File]" description={t(`${i18nPrefix}.outputVars.files`)} /> </> diff --git a/web/app/components/workflow/nodes/http/use-config.ts b/web/app/components/workflow/nodes/http/use-config.ts index 45303c3b46..fe8c8ac236 100644 --- a/web/app/components/workflow/nodes/http/use-config.ts +++ b/web/app/components/workflow/nodes/http/use-config.ts @@ -1,17 +1,18 @@ -import { useCallback, useEffect, useState } from 'react' -import { produce } from 'immer' -import { useBoolean } from 'ahooks' -import useVarList from '../_base/hooks/use-var-list' -import { VarType } from '../../types' import type { Var } from '../../types' -import { useStore } from '../../store' -import { type Authorization, type Body, BodyType, type HttpNodeType, type Method, type Timeout } from './types' -import useKeyValueList from './hooks/use-key-value-list' -import { transformToBodyPayload } from './utils' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import type { Authorization, Body, HttpNodeType, Method, Timeout } from './types' +import { useBoolean } from 'ahooks' +import { produce } from 'immer' +import { useCallback, useEffect, useState } from 'react' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { useStore } from '../../store' +import { VarType } from '../../types' +import useVarList from '../_base/hooks/use-var-list' +import useKeyValueList from './hooks/use-key-value-list' +import { BodyType } from './types' +import { transformToBodyPayload } from './utils' const useConfig = (id: string, payload: HttpNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/http/use-single-run-form-params.ts b/web/app/components/workflow/nodes/http/use-single-run-form-params.ts index 06d4ac3a27..735ed082c3 100644 --- a/web/app/components/workflow/nodes/http/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/http/use-single-run-form-params.ts @@ -1,12 +1,12 @@ import type { RefObject } from 'react' +import type { HttpNodeType } from './types' import type { InputVar, Variable } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' import useNodeCrud from '../_base/hooks/use-node-crud' -import type { HttpNodeType } from './types' type Params = { - id: string, - payload: HttpNodeType, + id: string + payload: HttpNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] diff --git a/web/app/components/workflow/nodes/http/utils.ts b/web/app/components/workflow/nodes/http/utils.ts index ffb474f45a..1d07fc5398 100644 --- a/web/app/components/workflow/nodes/http/utils.ts +++ b/web/app/components/workflow/nodes/http/utils.ts @@ -1,4 +1,5 @@ -import { type BodyPayload, BodyPayloadValueType } from './types' +import type { BodyPayload } from './types' +import { BodyPayloadValueType } from './types' export const transformToBodyPayload = (old: string, hasKey: boolean): BodyPayload => { if (!hasKey) { diff --git a/web/app/components/workflow/nodes/if-else/components/condition-add.tsx b/web/app/components/workflow/nodes/if-else/components/condition-add.tsx index 9b6ad2700b..91d7a0f03a 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-add.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-add.tsx @@ -1,10 +1,15 @@ +import type { HandleAddCondition } from '../types' +import type { + NodeOutPutVar, + ValueSelector, + Var, +} from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' import { useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' -import type { HandleAddCondition } from '../types' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -12,11 +17,6 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { - NodeOutPutVar, - ValueSelector, - Var, -} from '@/app/components/workflow/types' type ConditionAddProps = { className?: string @@ -44,7 +44,7 @@ const ConditionAdd = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, @@ -52,16 +52,16 @@ const ConditionAdd = ({ > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> <Button - size='small' + size="small" className={className} disabled={disabled} > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> + <RiAddLine className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.ifElse.addCondition')} </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <VarReferenceVars vars={variables} isSupportFileVar diff --git a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx index 1a2e3e3bc5..53df68c337 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx @@ -1,20 +1,22 @@ +import type { ValueSelector } from '../../../types' +import type { Condition } from '../types' import { memo, useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import { ComparisonOperator, type Condition } from '../types' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' +import { ComparisonOperator } from '../types' import { comparisonOperatorNotRequireValue, isComparisonOperatorNeedTranslate, isEmptyRelatedOperator, } from '../utils' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' -import type { ValueSelector } from '../../../types' -import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { - VariableLabelInNode, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' + const i18nPrefix = 'workflow.nodes.ifElse' type ConditionValueProps = { @@ -39,7 +41,7 @@ const ConditionValue = ({ return '' const value = c.value as string - return value.replace(/{{#([^#]*)#}}/g, (a, b) => { + return value.replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -57,41 +59,41 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(c.value) ? c.value[0] : c.value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => { - const arr: string[] = b.split('.') - if (isSystemVar(arr)) - return `{{${b}}}` + ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + const arr: string[] = b.split('.') + if (isSystemVar(arr)) + return `{{${b}}}` - return `{{${arr.slice(1).join('.')}}}` - }) + return `{{${arr.slice(1).join('.')}}}` + }) : '' } return '' }, [t]) return ( - <div className='rounded-md bg-workflow-block-parma-bg'> - <div className='flex h-6 items-center px-1 '> + <div className="rounded-md bg-workflow-block-parma-bg"> + <div className="flex h-6 items-center px-1 "> <VariableLabelInNode - className='w-0 grow' + className="w-0 grow" variables={variableSelector} notShowFullPath /> <div - className='mx-1 shrink-0 text-xs font-medium text-text-primary' + className="mx-1 shrink-0 text-xs font-medium text-text-primary" title={operatorName} > {operatorName} </div> </div> - <div className='ml-[10px] border-l border-divider-regular pl-[10px]'> + <div className="ml-[10px] border-l border-divider-regular pl-[10px]"> { sub_variable_condition?.conditions.map((c: Condition, index) => ( - <div className='relative flex h-6 items-center space-x-1' key={c.id}> - <div className='system-xs-medium text-text-accent'>{c.key}</div> - <div className='system-xs-medium text-text-primary'>{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> - {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className='system-xs-regular text-text-secondary'>{isSelect(c) ? selectName(c) : formatValue(c)}</div>} - {index !== sub_variable_condition.conditions.length - 1 && (<div className='absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent'>{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} + <div className="relative flex h-6 items-center space-x-1" key={c.id}> + <div className="system-xs-medium text-text-accent">{c.key}</div> + <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> + {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className="system-xs-regular text-text-secondary">{isSelect(c) ? selectName(c) : formatValue(c)}</div>} + {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} </div> )) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx index eba7a3bab7..f4961a7156 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx @@ -1,11 +1,11 @@ -import { useTranslation } from 'react-i18next' -import { useStore } from '@/app/components/workflow/store' -import PromptEditor from '@/app/components/base/prompt-editor' -import { BlockEnum } from '@/app/components/workflow/types' import type { Node, NodeOutPutVar, } from '@/app/components/workflow/types' +import { useTranslation } from 'react-i18next' +import PromptEditor from '@/app/components/base/prompt-editor' +import { useStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' type ConditionInputProps = { disabled?: boolean diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx index af87b70196..b323e65066 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx @@ -1,53 +1,54 @@ -import { - useCallback, - useMemo, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import { RiDeleteBinLine } from '@remixicon/react' -import { produce } from 'immer' import type { VarType as NumberVarType } from '../../../tool/types' import type { Condition, HandleAddSubVariableCondition, HandleRemoveCondition, + handleRemoveSubVariableCondition, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, - handleRemoveSubVariableCondition, } from '../../types' -import { - ComparisonOperator, -} from '../../types' -import { comparisonOperatorNotRequireValue, getOperators } from '../../utils' -import ConditionNumberInput from '../condition-number-input' -import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../../constants' -import ConditionWrap from '../condition-wrap' -import ConditionOperator from './condition-operator' -import ConditionInput from './condition-input' -import { useWorkflowStore } from '@/app/components/workflow/store' - -import ConditionVarSelector from './condition-var-selector' import type { Node, NodeOutPutVar, ValueSelector, Var, } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' -import { cn } from '@/utils/classnames' -import { SimpleSelect as Select } from '@/app/components/base/select' +import { RiDeleteBinLine } from '@remixicon/react' +import { produce } from 'immer' +import { + useCallback, + useMemo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' -import { getVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { SimpleSelect as Select } from '@/app/components/base/select' import { useIsChatMode } from '@/app/components/workflow/hooks/use-workflow' -import useMatchSchemaType from '../../../_base/components/variable/use-match-schema-type' +import { getVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' +import { useWorkflowStore } from '@/app/components/workflow/store' +import { VarType } from '@/app/components/workflow/types' + import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { cn } from '@/utils/classnames' +import useMatchSchemaType from '../../../_base/components/variable/use-match-schema-type' +import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from '../../../constants' +import { + ComparisonOperator, +} from '../../types' +import { comparisonOperatorNotRequireValue, getOperators } from '../../utils' +import ConditionNumberInput from '../condition-number-input' +import ConditionWrap from '../condition-wrap' +import ConditionInput from './condition-input' +import ConditionOperator from './condition-operator' +import ConditionVarSelector from './condition-var-selector' + const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' type ConditionItemProps = { @@ -249,10 +250,10 @@ const ConditionItem = ({ }, [condition, doUpdateCondition, availableNodes, isChatMode, schemaTypeDefinitions, buildInTools, customTools, mcpTools, workflowTools]) const showBooleanInput = useMemo(() => { - if(condition.varType === VarType.boolean) + if (condition.varType === VarType.boolean) return true - if(condition.varType === VarType.arrayBoolean && [ComparisonOperator.contains, ComparisonOperator.notContains].includes(condition.comparison_operator!)) + if (condition.varType === VarType.arrayBoolean && [ComparisonOperator.contains, ComparisonOperator.notContains].includes(condition.comparison_operator!)) return true return false }, [condition]) @@ -261,45 +262,48 @@ const ConditionItem = ({ <div className={cn( 'grow rounded-lg bg-components-input-bg-normal', isHovered && 'bg-state-destructive-hover', - )}> - <div className='flex items-center p-1'> - <div className='w-0 grow'> + )} + > + <div className="flex items-center p-1"> + <div className="w-0 grow"> {isSubVarSelect ? ( - <Select - wrapperClassName='h-6' - className='pl-0 text-xs' - optionWrapClassName='w-[165px] max-h-none' - defaultValue={condition.key} - items={subVarOptions} - onSelect={item => handleSubVarKeyChange(item.value as string)} - renderTrigger={item => ( - item - ? <div className='flex cursor-pointer justify-start'> - <div className='inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs'> - <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' /> - <div className='system-xs-medium ml-0.5 truncate'>{item?.name}</div> - </div> - </div> - : <div className='system-sm-regular text-left text-components-input-text-placeholder'>{t('common.placeholder.select')}</div> - )} - hideChecked - /> - ) + <Select + wrapperClassName="h-6" + className="pl-0 text-xs" + optionWrapClassName="w-[165px] max-h-none" + defaultValue={condition.key} + items={subVarOptions} + onSelect={item => handleSubVarKeyChange(item.value as string)} + renderTrigger={item => ( + item + ? ( + <div className="flex cursor-pointer justify-start"> + <div className="inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs"> + <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" /> + <div className="system-xs-medium ml-0.5 truncate">{item?.name}</div> + </div> + </div> + ) + : <div className="system-sm-regular text-left text-components-input-text-placeholder">{t('common.placeholder.select')}</div> + )} + hideChecked + /> + ) : ( - <ConditionVarSelector - open={open} - onOpenChange={setOpen} - valueSelector={condition.variable_selector || []} - varType={condition.varType} - availableNodes={availableNodes} - nodesOutputVars={nodesOutputVars} - onChange={handleVarChange} - /> - )} + <ConditionVarSelector + open={open} + onOpenChange={setOpen} + valueSelector={condition.variable_selector || []} + varType={condition.varType} + availableNodes={availableNodes} + nodesOutputVars={nodesOutputVars} + onChange={handleVarChange} + /> + )} </div> - <div className='mx-1 h-3 w-[1px] bg-divider-regular'></div> + <div className="mx-1 h-3 w-[1px] bg-divider-regular"></div> <ConditionOperator disabled={!canChooseOperator} varType={condition.varType} @@ -310,7 +314,7 @@ const ConditionItem = ({ </div> { !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && !showBooleanInput && ( - <div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'> + <div className="max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1"> <ConditionInput disabled={disabled} value={condition.value as string} @@ -323,7 +327,7 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && showBooleanInput && ( - <div className='p-1'> + <div className="p-1"> <BoolValue value={condition.value as boolean} onChange={handleUpdateConditionValue} @@ -333,7 +337,7 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && ( - <div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'> + <div className="border-t border-t-divider-subtle px-2 py-1 pt-[3px]"> <ConditionNumberInput numberVarType={condition.numberVarType} onNumberVarTypeChange={handleUpdateConditionNumberVarType} @@ -348,10 +352,10 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && isSelect && ( - <div className='border-t border-t-divider-subtle'> + <div className="border-t border-t-divider-subtle"> <Select - wrapperClassName='h-8' - className='rounded-t-none px-2 text-xs' + wrapperClassName="h-8" + className="rounded-t-none px-2 text-xs" defaultValue={isArrayValue ? (condition.value as string[])?.[0] : (condition.value as string)} items={selectOptions} onSelect={item => handleUpdateConditionValue(item.value as string)} @@ -363,7 +367,7 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && isSubVariable && ( - <div className='p-1'> + <div className="p-1"> <ConditionWrap isSubVariable caseId={caseId} @@ -384,12 +388,12 @@ const ConditionItem = ({ } </div> <div - className='ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive' + className="ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={doRemoveCondition} > - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx index 1763afdfd1..e2753ba6e7 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx @@ -1,19 +1,20 @@ +import type { ComparisonOperator } from '../../types' +import type { VarType } from '@/app/components/workflow/types' +import { RiArrowDownSLine } from '@remixicon/react' import { useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils' -import type { ComparisonOperator } from '../../types' import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { VarType } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils' + const i18nPrefix = 'workflow.nodes.ifElse' type ConditionOperatorProps = { @@ -48,7 +49,7 @@ const ConditionOperator = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 0, @@ -57,8 +58,8 @@ const ConditionOperator = ({ <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <Button className={cn('shrink-0', !selectedOption && 'opacity-50', className)} - size='small' - variant='ghost' + size="small" + variant="ghost" disabled={disabled} > { @@ -66,16 +67,16 @@ const ConditionOperator = ({ ? selectedOption.label : t(`${i18nPrefix}.select`) } - <RiArrowDownSLine className='ml-1 h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[11]'> - <div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[11]"> + <div className="rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.value} - className='flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover' + className="flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover" onClick={() => { onSelect(option.value) setOpen(false) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx index c05e733c1a..a8146c353d 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-var-selector.tsx @@ -1,7 +1,7 @@ +import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' type ConditionVarSelectorProps = { open: boolean @@ -26,7 +26,7 @@ const ConditionVarSelector = ({ <PortalToFollowElem open={open} onOpenChange={onOpenChange} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, @@ -42,8 +42,8 @@ const ConditionVarSelector = ({ /> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <VarReferenceVars vars={nodesOutputVars} isSupportFileVar diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx index 6c03dd8412..bda775e21c 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx @@ -1,23 +1,18 @@ -import { RiLoopLeftLine } from '@remixicon/react' -import { useCallback, useMemo } from 'react' -import { - type CaseItem, - type HandleAddSubVariableCondition, - type HandleRemoveCondition, - type HandleToggleConditionLogicalOperator, - type HandleToggleSubVariableConditionLogicalOperator, - type HandleUpdateCondition, - type HandleUpdateSubVariableCondition, - LogicalOperator, - type handleRemoveSubVariableCondition, -} from '../../types' -import ConditionItem from './condition-item' +import type { CaseItem, HandleAddSubVariableCondition, HandleRemoveCondition, handleRemoveSubVariableCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition } from '../../types' import type { Node, NodeOutPutVar, Var, } from '@/app/components/workflow/types' +import { RiLoopLeftLine } from '@remixicon/react' +import { useCallback, useMemo } from 'react' import { cn } from '@/utils/classnames' +import { + + LogicalOperator, + +} from '../../types' +import ConditionItem from './condition-item' type ConditionListProps = { isSubVariable?: boolean @@ -90,15 +85,16 @@ const ConditionList = ({ 'absolute bottom-0 left-0 top-0 w-[60px]', isSubVariable && logical_operator === LogicalOperator.and && 'left-[-10px]', isSubVariable && logical_operator === LogicalOperator.or && 'left-[-18px]', - )}> - <div className='absolute bottom-4 left-[46px] top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep'></div> - <div className='absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg'></div> + )} + > + <div className="absolute bottom-4 left-[46px] top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep"></div> + <div className="absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg"></div> <div - className='absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs' + className="absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs" onClick={doToggleConditionLogicalOperator} > {logical_operator.toUpperCase()} - <RiLoopLeftLine className='ml-0.5 h-3 w-3' /> + <RiLoopLeftLine className="ml-0.5 h-3 w-3" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx index a13fbef011..29419be011 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx @@ -1,29 +1,29 @@ +import type { + NodeOutPutVar, + ValueSelector, +} from '@/app/components/workflow/types' +import { RiArrowDownSLine } from '@remixicon/react' +import { useBoolean } from 'ahooks' +import { capitalize } from 'lodash-es' import { memo, useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { capitalize } from 'lodash-es' -import { useBoolean } from 'ahooks' -import { VarType as NumberVarType } from '../../tool/types' -import VariableTag from '../../_base/components/variable-tag' +import Button from '@/app/components/base/button' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import { cn } from '@/utils/classnames' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { - NodeOutPutVar, - ValueSelector, -} from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' import { variableTransformer } from '@/app/components/workflow/utils' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { cn } from '@/utils/classnames' +import VariableTag from '../../_base/components/variable-tag' +import { VarType as NumberVarType } from '../../tool/types' const options = [ NumberVarType.variable, @@ -62,25 +62,25 @@ const ConditionNumberInput = ({ }, [onValueChange]) return ( - <div className='flex cursor-pointer items-center'> + <div className="flex cursor-pointer items-center"> <PortalToFollowElem open={numberVarTypeVisible} onOpenChange={setNumberVarTypeVisible} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 2, crossAxis: 0 }} > <PortalToFollowElemTrigger onClick={() => setNumberVarTypeVisible(v => !v)}> <Button - className='shrink-0' - variant='ghost' - size='small' + className="shrink-0" + variant="ghost" + size="small" > {capitalize(numberVarType)} - <RiArrowDownSLine className='ml-[1px] h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-[1px] h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div @@ -102,19 +102,20 @@ const ConditionNumberInput = ({ </div> </PortalToFollowElemContent> </PortalToFollowElem> - <div className='mx-1 h-4 w-[1px] bg-divider-regular'></div> - <div className='ml-0.5 w-0 grow'> + <div className="mx-1 h-4 w-[1px] bg-divider-regular"></div> + <div className="ml-0.5 w-0 grow"> { numberVarType === NumberVarType.variable && ( <PortalToFollowElem open={variableSelectorVisible} onOpenChange={setVariableSelectorVisible} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 2, crossAxis: 0 }} > <PortalToFollowElemTrigger - className='w-full' - onClick={() => setVariableSelectorVisible(v => !v)}> + className="w-full" + onClick={() => setVariableSelectorVisible(v => !v)} + > { value && ( <VariableTag @@ -126,14 +127,14 @@ const ConditionNumberInput = ({ } { !value && ( - <div className='flex h-6 items-center p-1 text-[13px] text-components-input-text-placeholder'> - <Variable02 className='mr-1 h-4 w-4 shrink-0' /> - <div className='w-0 grow truncate'>{t('workflow.nodes.ifElse.selectVariable')}</div> + <div className="flex h-6 items-center p-1 text-[13px] text-components-input-text-placeholder"> + <Variable02 className="mr-1 h-4 w-4 shrink-0" /> + <div className="w-0 grow truncate">{t('workflow.nodes.ifElse.selectVariable')}</div> </div> ) } </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> + <PortalToFollowElemContent className="z-[1000]"> <div className={cn('w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pt-1 shadow-lg', isShort && 'w-[200px]')}> <VarReferenceVars vars={variables} @@ -146,17 +147,17 @@ const ConditionNumberInput = ({ } { numberVarType === NumberVarType.constant && ( - <div className=' relative'> + <div className=" relative"> <input className={cn('block w-full appearance-none bg-transparent px-2 text-[13px] text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder', unit && 'pr-6')} - type='number' + type="number" value={value} onChange={e => onValueChange(e.target.value)} placeholder={t('workflow.nodes.ifElse.enterValue') || ''} onFocus={setFocus} onBlur={setBlur} /> - {!isFocus && unit && <div className='system-sm-regular absolute right-2 top-[50%] translate-y-[-50%] text-text-tertiary'>{unit}</div>} + {!isFocus && unit && <div className="system-sm-regular absolute right-2 top-[50%] translate-y-[-50%] text-text-tertiary">{unit}</div>} </div> ) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx index 82db6d15f8..376c3a670f 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -1,24 +1,24 @@ +import type { + CommonNodeType, + Node, +} from '@/app/components/workflow/types' import { memo, useMemo, } from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInText, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { isExceptionVariable } from '@/app/components/workflow/utils' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' import { ComparisonOperator } from '../types' import { comparisonOperatorNotRequireValue, isComparisonOperatorNeedTranslate, } from '../utils' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '../../constants' -import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { isExceptionVariable } from '@/app/components/workflow/utils' -import type { - CommonNodeType, - Node, -} from '@/app/components/workflow/types' -import { - VariableLabelInText, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type ConditionValueProps = { variableSelector: string[] @@ -49,7 +49,7 @@ const ConditionValue = ({ if (value === true || value === false) return value ? 'True' : 'False' - return value.replace(/{{#([^#]*)#}}/g, (a, b) => { + return value.replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -63,22 +63,22 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(value) ? value[0] : value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => { - const arr: string[] = b.split('.') - if (isSystemVar(arr)) - return `{{${b}}}` + ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + const arr: string[] = b.split('.') + if (isSystemVar(arr)) + return `{{${b}}}` - return `{{${arr.slice(1).join('.')}}}` - }) + return `{{${arr.slice(1).join('.')}}}` + }) : '' } return '' }, [isSelect, t, value]) return ( - <div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1'> + <div className="flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1"> <VariableLabelInText - className='w-0 grow' + className="w-0 grow" variables={variableSelector} nodeTitle={node?.data.title} nodeType={node?.data.type} @@ -86,14 +86,14 @@ const ConditionValue = ({ notShowFullPath /> <div - className='mx-1 shrink-0 text-xs font-medium text-text-primary' + className="mx-1 shrink-0 text-xs font-medium text-text-primary" title={operatorName} > {operatorName} </div> { !notHasValue && ( - <div className='shrink-[3] truncate text-xs text-text-secondary' title={formatValue}>{isSelect ? selectName : formatValue}</div> + <div className="shrink-[3] truncate text-xs text-text-secondary" title={formatValue}>{isSelect ? selectName : formatValue}</div> ) } </div> diff --git a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx index e9352d154e..b9f80cb9f0 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx @@ -1,24 +1,24 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { ReactSortable } from 'react-sortablejs' +import type { Node, NodeOutPutVar, Var } from '../../../types' +import type { CaseItem, HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, handleRemoveSubVariableCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition } from '../types' import { RiAddLine, RiDeleteBinLine, RiDraggable, } from '@remixicon/react' -import type { CaseItem, HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, handleRemoveSubVariableCondition } from '../types' -import type { Node, NodeOutPutVar, Var } from '../../../types' -import { VarType } from '../../../types' -import { useGetAvailableVars } from '../../variable-assigner/hooks' -import { SUB_VARIABLES } from '../../constants' -import ConditionList from './condition-list' -import ConditionAdd from './condition-add' -import { cn } from '@/utils/classnames' +import { noop } from 'lodash-es' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ReactSortable } from 'react-sortablejs' import Button from '@/app/components/base/button' import { PortalSelect as Select } from '@/app/components/base/select' -import { noop } from 'lodash-es' +import { cn } from '@/utils/classnames' +import { VarType } from '../../../types' +import { SUB_VARIABLES } from '../../constants' +import { useGetAvailableVars } from '../../variable-assigner/hooks' +import ConditionAdd from './condition-add' +import ConditionList from './condition-list' type Props = { isSubVariable?: boolean @@ -86,8 +86,8 @@ const ConditionWrap: FC<Props> = ({ <ReactSortable list={cases.map(caseItem => ({ ...caseItem, id: caseItem.case_id }))} setList={handleSortCase} - handle='.handle' - ghostClass='bg-components-panel-bg' + handle=".handle" + ghostClass="bg-components-panel-bg" animation={150} disabled={readOnly || isSubVariable} > @@ -107,17 +107,22 @@ const ConditionWrap: FC<Props> = ({ <RiDraggable className={cn( 'handle absolute left-1 top-2 hidden h-3 w-3 cursor-pointer text-text-quaternary', casesLength > 1 && 'group-hover:block', - )} /> + )} + /> <div className={cn( 'absolute left-4 text-[13px] font-semibold leading-4 text-text-secondary', casesLength === 1 ? 'top-2.5' : 'top-1', - )}> + )} + > { index === 0 ? 'IF' : 'ELIF' } { casesLength > 1 && ( - <div className='text-[10px] font-medium text-text-tertiary'>CASE {index + 1}</div> + <div className="text-[10px] font-medium text-text-tertiary"> + CASE + {index + 1} + </div> ) } </div> @@ -126,7 +131,7 @@ const ConditionWrap: FC<Props> = ({ { !!item.conditions.length && ( - <div className='mb-2'> + <div className="mb-2"> <ConditionList disabled={readOnly} caseItem={item} @@ -156,47 +161,48 @@ const ConditionWrap: FC<Props> = ({ !item.conditions.length && !isSubVariable && 'mt-1', !item.conditions.length && isSubVariable && 'mt-2', !isSubVariable && ' pl-[60px]', - )}> + )} + > {isSubVariable ? ( - <Select - popupInnerClassName='w-[165px] max-h-none' - onSelect={value => handleAddSubVariableCondition?.(caseId!, conditionId!, value.value as string)} - items={subVarOptions} - value='' - renderTrigger={() => ( - <Button - size='small' - disabled={readOnly} - > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> - {t('workflow.nodes.ifElse.addSubVariable')} - </Button> - )} - hideChecked - /> - ) + <Select + popupInnerClassName="w-[165px] max-h-none" + onSelect={value => handleAddSubVariableCondition?.(caseId!, conditionId!, value.value as string)} + items={subVarOptions} + value="" + renderTrigger={() => ( + <Button + size="small" + disabled={readOnly} + > + <RiAddLine className="mr-1 h-3.5 w-3.5" /> + {t('workflow.nodes.ifElse.addSubVariable')} + </Button> + )} + hideChecked + /> + ) : ( - <ConditionAdd - disabled={readOnly} - caseId={item.case_id} - variables={getAvailableVars(id, '', filterVar)} - onSelectVariable={handleAddCondition!} - /> - )} + <ConditionAdd + disabled={readOnly} + caseId={item.case_id} + variables={getAvailableVars(id, '', filterVar)} + onSelectVariable={handleAddCondition!} + /> + )} { ((index === 0 && casesLength > 1) || (index > 0)) && ( <Button - className='hover:bg-components-button-destructive-ghost-bg-hover hover:text-components-button-destructive-ghost-text' - size='small' - variant='ghost' + className="hover:bg-components-button-destructive-ghost-bg-hover hover:text-components-button-destructive-ghost-text" + size="small" + variant="ghost" disabled={readOnly} onClick={() => handleRemoveCase?.(item.case_id)} onMouseEnter={() => setWillDeleteCaseId(item.case_id)} onMouseLeave={() => setWillDeleteCaseId('')} > - <RiDeleteBinLine className='mr-1 h-3.5 w-3.5' /> + <RiDeleteBinLine className="mr-1 h-3.5 w-3.5" /> {t('common.operation.remove')} </Button> ) @@ -204,7 +210,7 @@ const ConditionWrap: FC<Props> = ({ </div> </div> {!isSubVariable && ( - <div className='mx-3 my-2 h-px bg-divider-subtle'></div> + <div className="mx-3 my-2 h-px bg-divider-subtle"></div> )} </div> )) @@ -212,11 +218,11 @@ const ConditionWrap: FC<Props> = ({ </ReactSortable> {(cases.length === 0) && ( <Button - size='small' + size="small" disabled={readOnly} onClick={() => handleAddSubVariableCondition?.(caseId!, conditionId!)} > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> + <RiAddLine className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.ifElse.addSubVariable')} </Button> )} diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index ca3aa0cd2b..155971b454 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -1,9 +1,12 @@ -import { type NodeDefault, VarType } from '../../types' -import { type IfElseNodeType, LogicalOperator } from './types' -import { isEmptyRelatedOperator } from './utils' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import type { NodeDefault } from '../../types' +import type { IfElseNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { VarType } from '../../types' +import { LogicalOperator } from './types' +import { isEmptyRelatedOperator } from './utils' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/if-else/node.tsx b/web/app/components/workflow/nodes/if-else/node.tsx index e423e95a3f..41a7ec5d46 100644 --- a/web/app/components/workflow/nodes/if-else/node.tsx +++ b/web/app/components/workflow/nodes/if-else/node.tsx @@ -1,13 +1,14 @@ import type { FC } from 'react' +import type { NodeProps } from 'reactflow' +import type { Condition, IfElseNodeType } from './types' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from 'reactflow' -import { NodeSourceHandle } from '../_base/components/node-handle' -import { isEmptyRelatedOperator } from './utils' -import type { Condition, IfElseNodeType } from './types' -import ConditionValue from './components/condition-value' -import ConditionFilesListValue from './components/condition-files-list-value' import { VarType } from '../../types' +import { NodeSourceHandle } from '../_base/components/node-handle' +import ConditionFilesListValue from './components/condition-files-list-value' +import ConditionValue from './components/condition-value' +import { isEmptyRelatedOperator } from './utils' + const i18nPrefix = 'workflow.nodes.ifElse' const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { @@ -34,50 +35,53 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { return (condition.varType === VarType.boolean || condition.varType === VarType.arrayBoolean) ? true : !!condition.value } }, []) - const conditionNotSet = (<div className='flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'> - {t(`${i18nPrefix}.conditionNotSetup`)} - </div>) + const conditionNotSet = ( + <div className="flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"> + {t(`${i18nPrefix}.conditionNotSetup`)} + </div> + ) return ( - <div className='px-3'> + <div className="px-3"> { cases.map((caseItem, index) => ( <div key={caseItem.case_id}> - <div className='relative flex h-6 items-center px-1'> - <div className='flex w-full items-center justify-between'> - <div className='text-[10px] font-semibold text-text-tertiary'> + <div className="relative flex h-6 items-center px-1"> + <div className="flex w-full items-center justify-between"> + <div className="text-[10px] font-semibold text-text-tertiary"> {casesLength > 1 && `CASE ${index + 1}`} </div> - <div className='text-[12px] font-semibold text-text-secondary'>{index === 0 ? 'IF' : 'ELIF'}</div> + <div className="text-[12px] font-semibold text-text-secondary">{index === 0 ? 'IF' : 'ELIF'}</div> </div> <NodeSourceHandle {...props} handleId={caseItem.case_id} - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2' + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2" /> </div> - <div className='space-y-0.5'> + <div className="space-y-0.5"> {caseItem.conditions.map((condition, i) => ( - <div key={condition.id} className='relative'> + <div key={condition.id} className="relative"> { checkIsConditionSet(condition) ? ( - (!isEmptyRelatedOperator(condition.comparison_operator!) && condition.sub_variable_condition) - ? ( - <ConditionFilesListValue condition={condition} /> - ) - : ( - <ConditionValue - variableSelector={condition.variable_selector!} - operator={condition.comparison_operator!} - value={condition.varType === VarType.boolean ? (!condition.value ? 'False' : condition.value) : condition.value} - /> - ) + (!isEmptyRelatedOperator(condition.comparison_operator!) && condition.sub_variable_condition) + ? ( + <ConditionFilesListValue condition={condition} /> + ) + : ( + <ConditionValue + variableSelector={condition.variable_selector!} + operator={condition.comparison_operator!} + value={condition.varType === VarType.boolean ? (!condition.value ? 'False' : condition.value) : condition.value} + /> + ) - ) - : conditionNotSet} + ) + : conditionNotSet + } {i !== caseItem.conditions.length - 1 && ( - <div className='absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent'>{t(`${i18nPrefix}.${caseItem.logical_operator}`)}</div> + <div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${caseItem.logical_operator}`)}</div> )} </div> ))} @@ -85,12 +89,12 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { </div> )) } - <div className='relative flex h-6 items-center px-1'> - <div className='w-full text-right text-xs font-semibold text-text-secondary'>ELSE</div> + <div className="relative flex h-6 items-center px-1"> + <div className="w-full text-right text-xs font-semibold text-text-secondary">ELSE</div> <NodeSourceHandle {...props} - handleId='false' - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2' + handleId="false" + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2" /> </div> </div> diff --git a/web/app/components/workflow/nodes/if-else/panel.tsx b/web/app/components/workflow/nodes/if-else/panel.tsx index 4b74132b04..78e2406805 100644 --- a/web/app/components/workflow/nodes/if-else/panel.tsx +++ b/web/app/components/workflow/nodes/if-else/panel.tsx @@ -1,17 +1,17 @@ import type { FC } from 'react' +import type { IfElseNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' +import { + RiAddLine, +} from '@remixicon/react' import { memo, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiAddLine, -} from '@remixicon/react' -import useConfig from './use-config' -import type { IfElseNodeType } from './types' -import ConditionWrap from './components/condition-wrap' import Button from '@/app/components/base/button' -import type { NodePanelProps } from '@/app/components/workflow/types' import Field from '@/app/components/workflow/nodes/_base/components/field' +import ConditionWrap from './components/condition-wrap' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.ifElse' @@ -42,7 +42,7 @@ const Panel: FC<NodePanelProps<IfElseNodeType>> = ({ const cases = inputs.cases || [] return ( - <div className='p-1'> + <div className="p-1"> <ConditionWrap nodeId={id} cases={cases} @@ -62,23 +62,23 @@ const Panel: FC<NodePanelProps<IfElseNodeType>> = ({ varsIsVarFileAttribute={varsIsVarFileAttribute} filterVar={filterVar} /> - <div className='px-4 py-2'> + <div className="px-4 py-2"> <Button - className='w-full' - variant='tertiary' + className="w-full" + variant="tertiary" onClick={() => handleAddCase()} disabled={readOnly} > - <RiAddLine className='mr-1 h-4 w-4' /> + <RiAddLine className="mr-1 h-4 w-4" /> ELIF </Button> </div> - <div className='mx-3 my-2 h-px bg-divider-subtle'></div> + <div className="mx-3 my-2 h-px bg-divider-subtle"></div> <Field title={t(`${i18nPrefix}.else`)} - className='px-4 py-2' + className="px-4 py-2" > - <div className='text-xs font-normal leading-[18px] text-text-tertiary'>{t(`${i18nPrefix}.elseDescription`)}</div> + <div className="text-xs font-normal leading-[18px] text-text-tertiary">{t(`${i18nPrefix}.elseDescription`)}</div> </Field> </div> ) diff --git a/web/app/components/workflow/nodes/if-else/use-config.ts b/web/app/components/workflow/nodes/if-else/use-config.ts index 205715b898..f7384e1d67 100644 --- a/web/app/components/workflow/nodes/if-else/use-config.ts +++ b/web/app/components/workflow/nodes/if-else/use-config.ts @@ -1,12 +1,6 @@ -import { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { v4 as uuid4 } from 'uuid' -import { useUpdateNodeInternals } from 'reactflow' import type { Var, } from '../../types' -import { VarType } from '../../types' -import { LogicalOperator } from './types' import type { CaseItem, HandleAddCondition, @@ -18,17 +12,23 @@ import type { HandleUpdateSubVariableCondition, IfElseNodeType, } from './types' -import { - branchNameCorrect, - getOperators, -} from './utils' -import useIsVarFileAttribute from './use-is-var-file-attribute' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback, useMemo } from 'react' +import { useUpdateNodeInternals } from 'reactflow' +import { v4 as uuid4 } from 'uuid' import { useEdgesInteractions, useNodesReadOnly, } from '@/app/components/workflow/hooks' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarType } from '../../types' +import { LogicalOperator } from './types' +import useIsVarFileAttribute from './use-is-var-file-attribute' +import { + branchNameCorrect, + getOperators, +} from './utils' const useConfig = (id: string, payload: IfElseNodeType) => { const updateNodeInternals = useUpdateNodeInternals() diff --git a/web/app/components/workflow/nodes/if-else/use-is-var-file-attribute.ts b/web/app/components/workflow/nodes/if-else/use-is-var-file-attribute.ts index c0cf8cfefe..8bb2b602b8 100644 --- a/web/app/components/workflow/nodes/if-else/use-is-var-file-attribute.ts +++ b/web/app/components/workflow/nodes/if-else/use-is-var-file-attribute.ts @@ -1,7 +1,7 @@ -import { useStoreApi } from 'reactflow' -import { useMemo } from 'react' -import { useIsChatMode, useWorkflow, useWorkflowVariables } from '../../hooks' import type { ValueSelector } from '../../types' +import { useMemo } from 'react' +import { useStoreApi } from 'reactflow' +import { useIsChatMode, useWorkflow, useWorkflowVariables } from '../../hooks' import { VarType } from '../../types' type Params = { diff --git a/web/app/components/workflow/nodes/if-else/use-single-run-form-params.ts b/web/app/components/workflow/nodes/if-else/use-single-run-form-params.ts index 4f07f072cc..8e3d712ded 100644 --- a/web/app/components/workflow/nodes/if-else/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/if-else/use-single-run-form-params.ts @@ -1,11 +1,11 @@ import type { RefObject } from 'react' +import type { CaseItem, Condition, IfElseNodeType } from './types' import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' import { useCallback } from 'react' -import type { CaseItem, Condition, IfElseNodeType } from './types' type Params = { - id: string, - payload: IfElseNodeType, + id: string + payload: IfElseNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -94,7 +94,7 @@ const useSingleRunFormParams = ({ const existVarsKey: Record<string, boolean> = {} const uniqueVarInputs: InputVar[] = [] varInputs.forEach((input) => { - if(!input) + if (!input) return if (!existVarsKey[input.variable]) { existVarsKey[input.variable] = true @@ -126,7 +126,7 @@ const useSingleRunFormParams = ({ if (condition.variable_selector) vars.push(condition.variable_selector) - if(condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length) + if (condition.sub_variable_condition && condition.sub_variable_condition.conditions?.length) vars.push(...getVarFromCaseItem(condition.sub_variable_condition)) return vars } diff --git a/web/app/components/workflow/nodes/if-else/utils.ts b/web/app/components/workflow/nodes/if-else/utils.ts index 37481c092c..167f26c913 100644 --- a/web/app/components/workflow/nodes/if-else/utils.ts +++ b/web/app/components/workflow/nodes/if-else/utils.ts @@ -1,15 +1,18 @@ -import { ComparisonOperator } from './types' -import { VarType } from '@/app/components/workflow/types' import type { Branch } from '@/app/components/workflow/types' +import { VarType } from '@/app/components/workflow/types' +import { ComparisonOperator } from './types' export const isEmptyRelatedOperator = (operator: ComparisonOperator) => { return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull, ComparisonOperator.exists, ComparisonOperator.notExists].includes(operator) } const notTranslateKey = [ - ComparisonOperator.equal, ComparisonOperator.notEqual, - ComparisonOperator.largerThan, ComparisonOperator.largerThanOrEqual, - ComparisonOperator.lessThan, ComparisonOperator.lessThanOrEqual, + ComparisonOperator.equal, + ComparisonOperator.notEqual, + ComparisonOperator.largerThan, + ComparisonOperator.largerThanOrEqual, + ComparisonOperator.lessThan, + ComparisonOperator.lessThanOrEqual, ] export const isComparisonOperatorNeedTranslate = (operator?: ComparisonOperator) => { diff --git a/web/app/components/workflow/nodes/index.tsx b/web/app/components/workflow/nodes/index.tsx index ba880b398b..809c4af284 100644 --- a/web/app/components/workflow/nodes/index.tsx +++ b/web/app/components/workflow/nodes/index.tsx @@ -1,16 +1,16 @@ +import type { NodeProps } from 'reactflow' +import type { Node } from '../types' import { memo, useMemo, } from 'react' -import type { NodeProps } from 'reactflow' -import type { Node } from '../types' import { CUSTOM_NODE } from '../constants' +import BasePanel from './_base/components/workflow-panel' +import BaseNode from './_base/node' import { NodeComponentMap, PanelComponentMap, } from './components' -import BaseNode from './_base/node' -import BasePanel from './_base/components/workflow-panel' const CustomNode = (props: NodeProps) => { const nodeData = props.data diff --git a/web/app/components/workflow/nodes/iteration-start/default.ts b/web/app/components/workflow/nodes/iteration-start/default.ts index 5229229648..70b8b4c45e 100644 --- a/web/app/components/workflow/nodes/iteration-start/default.ts +++ b/web/app/components/workflow/nodes/iteration-start/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { IterationStartNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: -1, diff --git a/web/app/components/workflow/nodes/iteration-start/index.tsx b/web/app/components/workflow/nodes/iteration-start/index.tsx index 6f880c95e1..36deac04e1 100644 --- a/web/app/components/workflow/nodes/iteration-start/index.tsx +++ b/web/app/components/workflow/nodes/iteration-start/index.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' import type { NodeProps } from 'reactflow' import { RiHome5Fill } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { NodeSourceHandle } from '@/app/components/workflow/nodes/_base/components/node-handle' @@ -9,17 +9,17 @@ const IterationStartNode = ({ id, data }: NodeProps) => { const { t } = useTranslation() return ( - <div className='nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs'> + <div className="nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs"> <Tooltip popupContent={t('workflow.blocks.iteration-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> <NodeSourceHandle id={id} data={data} - handleClassName='!top-1/2 !-right-[9px] !-translate-y-1/2' - handleId='source' + handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2" + handleId="source" /> </div> ) @@ -29,10 +29,10 @@ export const IterationStartNodeDumb = () => { const { t } = useTranslation() return ( - <div className='nodrag relative left-[17px] top-[21px] z-[11] flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg'> + <div className="nodrag relative left-[17px] top-[21px] z-[11] flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg"> <Tooltip popupContent={t('workflow.blocks.iteration-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> </div> diff --git a/web/app/components/workflow/nodes/iteration/add-block.tsx b/web/app/components/workflow/nodes/iteration/add-block.tsx index e838fe560b..fdd183da1e 100644 --- a/web/app/components/workflow/nodes/iteration/add-block.tsx +++ b/web/app/components/workflow/nodes/iteration/add-block.tsx @@ -1,25 +1,25 @@ +import type { IterationNodeType } from './types' +import type { + OnSelectBlock, +} from '@/app/components/workflow/types' +import { + RiAddLine, +} from '@remixicon/react' import { memo, useCallback, } from 'react' -import { - RiAddLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' +import BlockSelector from '@/app/components/workflow/block-selector' +import { + BlockEnum, +} from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' import { useAvailableBlocks, useNodesInteractions, useNodesReadOnly, } from '../../hooks' -import type { IterationNodeType } from './types' -import { cn } from '@/utils/classnames' -import BlockSelector from '@/app/components/workflow/block-selector' -import type { - OnSelectBlock, -} from '@/app/components/workflow/types' -import { - BlockEnum, -} from '@/app/components/workflow/types' type AddBlockProps = { iterationNodeId: string @@ -52,24 +52,25 @@ const AddBlock = ({ 'system-sm-medium relative inline-flex h-8 cursor-pointer items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 text-components-button-secondary-text shadow-xs backdrop-blur-[5px] hover:bg-components-button-secondary-bg-hover', `${nodesReadOnly && '!cursor-not-allowed bg-components-button-secondary-bg-disabled'}`, open && 'bg-components-button-secondary-bg-hover', - )}> - <RiAddLine className='mr-1 h-4 w-4' /> + )} + > + <RiAddLine className="mr-1 h-4 w-4" /> {t('workflow.common.addBlock')} </div> ) }, [nodesReadOnly, t]) return ( - <div className='absolute left-14 top-7 z-10 flex h-8 items-center'> - <div className='group/insert relative h-0.5 w-16 bg-gray-300'> - <div className='absolute right-0 top-1/2 h-2 w-0.5 -translate-y-1/2 bg-primary-500'></div> + <div className="absolute left-14 top-7 z-10 flex h-8 items-center"> + <div className="group/insert relative h-0.5 w-16 bg-gray-300"> + <div className="absolute right-0 top-1/2 h-2 w-0.5 -translate-y-1/2 bg-primary-500"></div> </div> <BlockSelector disabled={nodesReadOnly} onSelect={handleSelect} trigger={renderTriggerElement} - triggerInnerClassName='inline-flex' - popupClassName='!min-w-[256px]' + triggerInnerClassName="inline-flex" + popupClassName="!min-w-[256px]" availableBlocksTypes={availableNextBlocks} /> </div> diff --git a/web/app/components/workflow/nodes/iteration/default.ts b/web/app/components/workflow/nodes/iteration/default.ts index c375dbdcbf..b708926ba8 100644 --- a/web/app/components/workflow/nodes/iteration/default.ts +++ b/web/app/components/workflow/nodes/iteration/default.ts @@ -1,8 +1,9 @@ -import { BlockEnum, ErrorHandleMode } from '../../types' import type { NodeDefault } from '../../types' import type { IterationNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum, ErrorHandleMode } from '../../types' + const i18nPrefix = 'workflow' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/iteration/node.tsx b/web/app/components/workflow/nodes/iteration/node.tsx index 98ef98e7c4..2d40417df5 100644 --- a/web/app/components/workflow/nodes/iteration/node.tsx +++ b/web/app/components/workflow/nodes/iteration/node.tsx @@ -1,22 +1,22 @@ import type { FC } from 'react' +import type { IterationNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import { memo, useEffect, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { Background, useNodesInitialized, useViewport, } from 'reactflow' -import { useTranslation } from 'react-i18next' -import { IterationStartNodeDumb } from '../iteration-start' -import { useNodeIterationInteractions } from './use-interactions' -import type { IterationNodeType } from './types' -import AddBlock from './add-block' -import { cn } from '@/utils/classnames' -import type { NodeProps } from '@/app/components/workflow/types' import Toast from '@/app/components/base/toast' +import { cn } from '@/utils/classnames' +import { IterationStartNodeDumb } from '../iteration-start' +import AddBlock from './add-block' +import { useNodeIterationInteractions } from './use-interactions' const i18nPrefix = 'workflow.nodes.iteration' @@ -46,13 +46,14 @@ const Node: FC<NodeProps<IterationNodeType>> = ({ return ( <div className={cn( 'relative h-full min-h-[90px] w-full min-w-[240px] rounded-2xl bg-workflow-canvas-workflow-bg', - )}> + )} + > <Background id={`iteration-background-${id}`} - className='!z-0 rounded-2xl' + className="!z-0 rounded-2xl" gap={[14 / zoom, 14 / zoom]} size={2 / zoom} - color='var(--color-workflow-canvas-workflow-dot-color)' + color="var(--color-workflow-canvas-workflow-dot-color)" /> { data._isCandidate && ( diff --git a/web/app/components/workflow/nodes/iteration/panel.tsx b/web/app/components/workflow/nodes/iteration/panel.tsx index 63e0d5f8cd..5cffb356a2 100644 --- a/web/app/components/workflow/nodes/iteration/panel.tsx +++ b/web/app/components/workflow/nodes/iteration/panel.tsx @@ -1,18 +1,19 @@ import type { FC } from 'react' +import type { IterationNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import Split from '../_base/components/split' -import { MIN_ITERATION_PARALLEL_NUM } from '../../constants' -import type { IterationNodeType } from './types' -import useConfig from './use-config' -import { ErrorHandleMode, type NodePanelProps } from '@/app/components/workflow/types' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Switch from '@/app/components/base/switch' +import Input from '@/app/components/base/input' import Select from '@/app/components/base/select' import Slider from '@/app/components/base/slider' -import Input from '@/app/components/base/input' +import Switch from '@/app/components/base/switch' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { ErrorHandleMode } from '@/app/components/workflow/types' import { MAX_PARALLEL_LIMIT } from '@/config' +import { MIN_ITERATION_PARALLEL_NUM } from '../../constants' +import Split from '../_base/components/split' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.iteration' @@ -50,13 +51,13 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({ } = useConfig(id, data) return ( - <div className='pb-2 pt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="pb-2 pt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.input`)} required operations={( - <div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div> + <div className="system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary">Array</div> )} > <VarReferencePicker @@ -70,12 +71,12 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({ </Field> </div> <Split /> - <div className='mt-2 space-y-4 px-4 pb-4'> + <div className="mt-2 space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.output`)} required operations={( - <div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary'>Array</div> + <div className="system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 capitalize text-text-tertiary">Array</div> )} > <VarReferencePicker @@ -89,42 +90,44 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({ /> </Field> </div> - <div className='px-4 pb-2'> - <Field title={t(`${i18nPrefix}.parallelMode`)} tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.parallelPanelDesc`)}</div>} inline> + <div className="px-4 pb-2"> + <Field title={t(`${i18nPrefix}.parallelMode`)} tooltip={<div className="w-[230px]">{t(`${i18nPrefix}.parallelPanelDesc`)}</div>} inline> <Switch defaultValue={inputs.is_parallel} onChange={changeParallel} /> </Field> </div> { - inputs.is_parallel && (<div className='px-4 pb-2'> - <Field title={t(`${i18nPrefix}.MaxParallelismTitle`)} isSubTitle tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.MaxParallelismDesc`)}</div>}> - <div className='row flex'> - <Input type='number' wrapperClassName='w-18 mr-4 ' max={MAX_PARALLEL_LIMIT} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} /> - <Slider - value={inputs.parallel_nums} - onChange={changeParallelNums} - max={MAX_PARALLEL_LIMIT} - min={MIN_ITERATION_PARALLEL_NUM} - className=' mt-4 flex-1 shrink-0' - /> - </div> + inputs.is_parallel && ( + <div className="px-4 pb-2"> + <Field title={t(`${i18nPrefix}.MaxParallelismTitle`)} isSubTitle tooltip={<div className="w-[230px]">{t(`${i18nPrefix}.MaxParallelismDesc`)}</div>}> + <div className="row flex"> + <Input type="number" wrapperClassName="w-18 mr-4 " max={MAX_PARALLEL_LIMIT} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} /> + <Slider + value={inputs.parallel_nums} + onChange={changeParallelNums} + max={MAX_PARALLEL_LIMIT} + min={MIN_ITERATION_PARALLEL_NUM} + className=" mt-4 flex-1 shrink-0" + /> + </div> - </Field> - </div>) + </Field> + </div> + ) } <Split /> - <div className='px-4 py-2'> - <Field title={t(`${i18nPrefix}.errorResponseMethod`)} > + <div className="px-4 py-2"> + <Field title={t(`${i18nPrefix}.errorResponseMethod`)}> <Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false} /> </Field> </div> <Split /> - <div className='px-4 py-2'> + <div className="px-4 py-2"> <Field title={t(`${i18nPrefix}.flattenOutput`)} - tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.flattenOutputDesc`)}</div>} + tooltip={<div className="w-[230px]">{t(`${i18nPrefix}.flattenOutputDesc`)}</div>} inline > <Switch defaultValue={inputs.flatten_output} onChange={changeFlattenOutput} /> diff --git a/web/app/components/workflow/nodes/iteration/use-config.ts b/web/app/components/workflow/nodes/iteration/use-config.ts index 2e47bb3740..50cee67f81 100644 --- a/web/app/components/workflow/nodes/iteration/use-config.ts +++ b/web/app/components/workflow/nodes/iteration/use-config.ts @@ -1,26 +1,26 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { - useIsChatMode, - useNodesReadOnly, - useWorkflow, -} from '../../hooks' -import { VarType } from '../../types' import type { ErrorHandleMode, ValueSelector, Var } from '../../types' -import useNodeCrud from '../_base/hooks/use-node-crud' import type { IterationNodeType } from './types' -import { toNodeOutputVars } from '../_base/components/variable/utils' -import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import type { Item } from '@/app/components/base/select' -import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' +import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { produce } from 'immer' import { isEqual } from 'lodash-es' -import { useStore } from '../../store' +import { useCallback } from 'react' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { + useIsChatMode, + useNodesReadOnly, + useWorkflow, +} from '../../hooks' +import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' +import { useStore } from '../../store' +import { VarType } from '../../types' +import { toNodeOutputVars } from '../_base/components/variable/utils' +import useNodeCrud from '../_base/hooks/use-node-crud' const useConfig = (id: string, payload: IterationNodeType) => { const { diff --git a/web/app/components/workflow/nodes/iteration/use-interactions.ts b/web/app/components/workflow/nodes/iteration/use-interactions.ts index 3bbbaf252f..c6fddff5ad 100644 --- a/web/app/components/workflow/nodes/iteration/use-interactions.ts +++ b/web/app/components/workflow/nodes/iteration/use-interactions.ts @@ -1,21 +1,21 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' -import { useStoreApi } from 'reactflow' import type { BlockEnum, ChildNodeTypeCount, Node, } from '../../types' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { useStoreApi } from 'reactflow' +import { useNodesMetaData } from '@/app/components/workflow/hooks' +import { + ITERATION_PADDING, +} from '../../constants' import { generateNewNode, getNodeCustomTypeByNodeDataType, } from '../../utils' -import { - ITERATION_PADDING, -} from '../../constants' import { CUSTOM_ITERATION_START_NODE } from '../iteration-start/constants' -import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeIterationInteractions = () => { const { t } = useTranslation() @@ -78,7 +78,7 @@ export const useNodeIterationInteractions = () => { const { getNodes } = store.getState() const nodes = getNodes() - const restrictPosition: { x?: number; y?: number } = { x: undefined, y: undefined } + const restrictPosition: { x?: number, y?: number } = { x: undefined, y: undefined } if (node.data.isInIteration) { const parentNode = nodes.find(n => n.id === node.parentId) @@ -121,7 +121,7 @@ export const useNodeIterationInteractions = () => { const childNodeType = child.data.type as BlockEnum const nodesWithSameType = nodes.filter(node => node.data.type === childNodeType) - if(!childNodeTypeCount[childNodeType]) + if (!childNodeTypeCount[childNodeType]) childNodeTypeCount[childNodeType] = nodesWithSameType.length + 1 else childNodeTypeCount[childNodeType] = childNodeTypeCount[childNodeType] + 1 diff --git a/web/app/components/workflow/nodes/iteration/use-single-run-form-params.ts b/web/app/components/workflow/nodes/iteration/use-single-run-form-params.ts index ba840a472d..fec99d023f 100644 --- a/web/app/components/workflow/nodes/iteration/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/iteration/use-single-run-form-params.ts @@ -1,20 +1,20 @@ import type { RefObject } from 'react' -import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' -import { useCallback, useMemo } from 'react' import type { IterationNodeType } from './types' +import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' +import type { NodeTracing } from '@/types/workflow' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' +import formatTracing from '@/app/components/workflow/run/utils/format-log' +import { InputVarType, VarType } from '@/app/components/workflow/types' +import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config' import { useIsNodeInIteration, useWorkflow } from '../../hooks' import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils' -import { InputVarType, VarType } from '@/app/components/workflow/types' -import formatTracing from '@/app/components/workflow/run/utils/format-log' -import type { NodeTracing } from '@/types/workflow' -import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config' const i18nPrefix = 'workflow.nodes.iteration' type Params = { - id: string, - payload: IterationNodeType, + id: string + payload: IterationNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -138,7 +138,7 @@ const useSingleRunFormParams = ({ return [payload.iterator_selector] } const getDependentVar = (variable: string) => { - if(variable === iteratorInputKey) + if (variable === iteratorInputKey) return payload.iterator_selector } diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/hooks.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/hooks.tsx index 876c91862f..4c6ff96d7d 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/hooks.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/hooks.tsx @@ -1,3 +1,4 @@ +import type { Option } from './type' import { useTranslation } from 'react-i18next' import { GeneralChunk, @@ -6,7 +7,6 @@ import { } from '@/app/components/base/icons/src/vender/knowledge' import { cn } from '@/utils/classnames' import { ChunkStructureEnum } from '../../types' -import type { Option } from './type' export const useChunkStructure = () => { const { t } = useTranslation() @@ -17,7 +17,8 @@ export const useChunkStructure = () => { className={cn( 'h-[18px] w-[18px] text-text-tertiary group-hover:text-util-colors-indigo-indigo-600', isActive && 'text-util-colors-indigo-indigo-600', - )} /> + )} + /> ), title: t('datasetCreation.stepTwo.general'), description: t('datasetCreation.stepTwo.generalTip'), diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx index 60aa3d5590..b9d105ed6e 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/index.tsx @@ -1,13 +1,13 @@ +import type { ChunkStructureEnum } from '../../types' +import { RiAddLine } from '@remixicon/react' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' -import { Field } from '@/app/components/workflow/nodes/_base/components/layout' -import type { ChunkStructureEnum } from '../../types' -import OptionCard from '../option-card' -import Selector from './selector' -import { useChunkStructure } from './hooks' import Button from '@/app/components/base/button' +import { Field } from '@/app/components/workflow/nodes/_base/components/layout' +import OptionCard from '../option-card' +import { useChunkStructure } from './hooks' import Instruction from './instruction' +import Selector from './selector' type ChunkStructureProps = { chunkStructure?: ChunkStructureEnum @@ -59,15 +59,15 @@ const ChunkStructure = ({ readonly={readonly} trigger={( <Button - className='w-full' - variant='secondary-accent' + className="w-full" + variant="secondary-accent" > - <RiAddLine className='mr-1 h-4 w-4' /> + <RiAddLine className="mr-1 h-4 w-4" /> {t('workflow.nodes.knowledgeBase.chooseChunkStructure')} </Button> )} /> - <Instruction className='mt-2' /> + <Instruction className="mt-2" /> </> ) } diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx index b621eaf04d..82af5f8d2f 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx @@ -1,9 +1,9 @@ import React from 'react' -import { AddChunks } from '@/app/components/base/icons/src/vender/knowledge' -import Line from './line' -import { cn } from '@/utils/classnames' import { useTranslation } from 'react-i18next' +import { AddChunks } from '@/app/components/base/icons/src/vender/knowledge' import { useDocLink } from '@/context/i18n' +import { cn } from '@/utils/classnames' +import Line from './line' type InstructionProps = { className?: string @@ -17,24 +17,24 @@ const Instruction = ({ return ( <div className={cn('flex flex-col gap-y-2 overflow-hidden rounded-[10px] bg-workflow-process-bg p-4', className)}> - <div className='relative flex size-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur-[5px]'> - <AddChunks className='size-5 text-text-accent' /> - <Line className='absolute -left-px bottom-[-76px]' type='vertical' /> - <Line className='absolute -right-px bottom-[-76px]' type='vertical' /> - <Line className='absolute -top-px right-[-184px]' type='horizontal' /> - <Line className='absolute -bottom-px right-[-184px]' type='horizontal' /> + <div className="relative flex size-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur-[5px]"> + <AddChunks className="size-5 text-text-accent" /> + <Line className="absolute -left-px bottom-[-76px]" type="vertical" /> + <Line className="absolute -right-px bottom-[-76px]" type="vertical" /> + <Line className="absolute -top-px right-[-184px]" type="horizontal" /> + <Line className="absolute -bottom-px right-[-184px]" type="horizontal" /> </div> - <div className='flex flex-col gap-y-1'> - <div className='system-sm-medium text-text-secondary'> + <div className="flex flex-col gap-y-1"> + <div className="system-sm-medium text-text-secondary"> {t('workflow.nodes.knowledgeBase.chunkStructureTip.title')} </div> - <div className='system-xs-regular'> - <p className='text-text-tertiary'>{t('workflow.nodes.knowledgeBase.chunkStructureTip.message')}</p> + <div className="system-xs-regular"> + <p className="text-text-tertiary">{t('workflow.nodes.knowledgeBase.chunkStructureTip.message')}</p> <a href={docLink('/guides/knowledge-base/create-knowledge-and-upload-documents/chunking-and-cleaning-text')} - target='_blank' - rel='noopener noreferrer' - className='text-text-accent' + target="_blank" + rel="noopener noreferrer" + className="text-text-accent" > {t('workflow.nodes.knowledgeBase.chunkStructureTip.learnMore')} </a> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx index 1177130d2b..a2a3835be6 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx @@ -11,13 +11,13 @@ const Line = ({ }: LineProps) => { if (type === 'vertical') { return ( - <svg xmlns='http://www.w3.org/2000/svg' width='2' height='132' viewBox='0 0 2 132' fill='none' className={className}> - <path d='M1 0L1 132' stroke='url(#paint0_linear_10882_18766)' /> + <svg xmlns="http://www.w3.org/2000/svg" width="2" height="132" viewBox="0 0 2 132" fill="none" className={className}> + <path d="M1 0L1 132" stroke="url(#paint0_linear_10882_18766)" /> <defs> - <linearGradient id='paint0_linear_10882_18766' x1='-7.99584' y1='132' x2='-7.96108' y2='6.4974e-07' gradientUnits='userSpaceOnUse'> - <stop stopColor='var(--color-background-gradient-mask-transparent)' /> - <stop offset='0.877606' stopColor='var(--color-divider-subtle)' /> - <stop offset='1' stopColor='var(--color-background-gradient-mask-transparent)' /> + <linearGradient id="paint0_linear_10882_18766" x1="-7.99584" y1="132" x2="-7.96108" y2="6.4974e-07" gradientUnits="userSpaceOnUse"> + <stop stopColor="var(--color-background-gradient-mask-transparent)" /> + <stop offset="0.877606" stopColor="var(--color-divider-subtle)" /> + <stop offset="1" stopColor="var(--color-background-gradient-mask-transparent)" /> </linearGradient> </defs> </svg> @@ -25,13 +25,13 @@ const Line = ({ } return ( - <svg xmlns='http://www.w3.org/2000/svg' width='240' height='2' viewBox='0 0 240 2' fill='none' className={className}> - <path d='M0 1H240' stroke='url(#paint0_linear_10882_18763)' /> + <svg xmlns="http://www.w3.org/2000/svg" width="240" height="2" viewBox="0 0 240 2" fill="none" className={className}> + <path d="M0 1H240" stroke="url(#paint0_linear_10882_18763)" /> <defs> - <linearGradient id='paint0_linear_10882_18763' x1='240' y1='9.99584' x2='3.95539e-05' y2='9.88094' gradientUnits='userSpaceOnUse'> - <stop stopColor='var(--color-background-gradient-mask-transparent)' /> - <stop offset='0.9031' stopColor='var(--color-divider-subtle)' /> - <stop offset='1' stopColor='var(--color-background-gradient-mask-transparent)' /> + <linearGradient id="paint0_linear_10882_18763" x1="240" y1="9.99584" x2="3.95539e-05" y2="9.88094" gradientUnits="userSpaceOnUse"> + <stop stopColor="var(--color-background-gradient-mask-transparent)" /> + <stop offset="0.9031" stopColor="var(--color-divider-subtle)" /> + <stop offset="1" stopColor="var(--color-background-gradient-mask-transparent)" /> </linearGradient> </defs> </svg> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/selector.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/selector.tsx index 97a5740fce..a349e16aea 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/selector.tsx @@ -1,15 +1,15 @@ import type { ReactNode } from 'react' +import type { ChunkStructureEnum } from '../../types' +import type { Option } from './type' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import type { ChunkStructureEnum } from '../../types' import OptionCard from '../option-card' -import type { Option } from './type' type SelectorProps = { options: Option[] @@ -35,7 +35,7 @@ const Selector = ({ return ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 0, crossAxis: -8, @@ -54,20 +54,20 @@ const Selector = ({ { trigger || ( <Button - size='small' - variant='ghost-accent' + size="small" + variant="ghost-accent" > {t('workflow.panel.change')} </Button> ) } </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[404px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]'> - <div className='system-sm-semibold px-3 pt-3.5 text-text-primary'> + <PortalToFollowElemContent className="z-10"> + <div className="w-[404px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-[5px]"> + <div className="system-sm-semibold px-3 pt-3.5 text-text-primary"> {t('workflow.nodes.knowledgeBase.changeChunkStructure')} </div> - <div className='space-y-1 p-3 pt-2'> + <div className="space-y-1 p-3 pt-2"> { options.map(option => ( <OptionCard @@ -80,7 +80,8 @@ const Selector = ({ readonly={readonly} onClick={handleSelect} effectColor={option.effectColor} - ></OptionCard> + > + </OptionCard> )) } </div> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/embedding-model.tsx b/web/app/components/workflow/nodes/knowledge-base/components/embedding-model.tsx index 7709fb49d7..1625f9d332 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/embedding-model.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/embedding-model.tsx @@ -1,14 +1,14 @@ +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { memo, useCallback, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { Field } from '@/app/components/workflow/nodes/_base/components/layout' -import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' -import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' +import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' +import { Field } from '@/app/components/workflow/nodes/_base/components/layout' type EmbeddingModelProps = { embeddingModel?: string diff --git a/web/app/components/workflow/nodes/knowledge-base/components/index-method.tsx b/web/app/components/workflow/nodes/knowledge-base/components/index-method.tsx index cf93c4191d..916d25afcb 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/index-method.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/index-method.tsx @@ -1,23 +1,23 @@ +import { RiQuestionLine } from '@remixicon/react' import { memo, useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import { RiQuestionLine } from '@remixicon/react' import { Economic, HighQuality, } from '@/app/components/base/icons/src/vender/knowledge' -import Tooltip from '@/app/components/base/tooltip' -import Slider from '@/app/components/base/slider' import Input from '@/app/components/base/input' +import Slider from '@/app/components/base/slider' +import Tooltip from '@/app/components/base/tooltip' import { Field } from '@/app/components/workflow/nodes/_base/components/layout' -import OptionCard from './option-card' import { cn } from '@/utils/classnames' import { ChunkStructureEnum, IndexMethodEnum, } from '../types' +import OptionCard from './option-card' type IndexMethodProps = { chunkStructure: ChunkStructureEnum @@ -55,64 +55,65 @@ const IndexMethod = ({ title: t('datasetCreation.stepTwo.indexMode'), }} > - <div className='space-y-1'> + <div className="space-y-1"> <OptionCard<IndexMethodEnum> id={IndexMethodEnum.QUALIFIED} selectedId={indexMethod} - icon={ + icon={( <HighQuality className={cn( 'h-[15px] w-[15px] text-text-tertiary group-hover:text-util-colors-orange-orange-500', isHighQuality && 'text-util-colors-orange-orange-500', )} /> - } + )} title={t('datasetCreation.stepTwo.qualified')} description={t('datasetSettings.form.indexMethodHighQualityTip')} onClick={handleIndexMethodChange} isRecommended - effectColor='orange' - ></OptionCard> + effectColor="orange" + > + </OptionCard> { chunkStructure === ChunkStructureEnum.general && ( <OptionCard id={IndexMethodEnum.ECONOMICAL} selectedId={indexMethod} - icon={ + icon={( <Economic className={cn( 'h-[15px] w-[15px] text-text-tertiary group-hover:text-util-colors-indigo-indigo-500', isEconomy && 'text-util-colors-indigo-indigo-500', )} /> - } + )} title={t('datasetSettings.form.indexMethodEconomy')} description={t('datasetSettings.form.indexMethodEconomyTip', { count: keywordNumber })} onClick={handleIndexMethodChange} - effectColor='blue' + effectColor="blue" > - <div className='flex items-center'> - <div className='flex grow items-center'> - <div className='system-xs-medium truncate text-text-secondary'> + <div className="flex items-center"> + <div className="flex grow items-center"> + <div className="system-xs-medium truncate text-text-secondary"> {t('datasetSettings.form.numberOfKeywords')} </div> <Tooltip - popupContent='number of keywords' + popupContent="number of keywords" > - <RiQuestionLine className='ml-0.5 h-3.5 w-3.5 text-text-quaternary' /> + <RiQuestionLine className="ml-0.5 h-3.5 w-3.5 text-text-quaternary" /> </Tooltip> </div> <Slider disabled={readonly} - className='mr-3 w-24 shrink-0' + className="mr-3 w-24 shrink-0" value={keywordNumber} onChange={onKeywordNumberChange} /> <Input disabled={readonly} - className='shrink-0' - wrapperClassName='shrink-0 w-[72px]' - type='number' + className="shrink-0" + wrapperClassName="shrink-0 w-[72px]" + type="number" value={keywordNumber} onChange={handleInputChange} /> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/option-card.tsx b/web/app/components/workflow/nodes/knowledge-base/components/option-card.tsx index fed53f798a..0244d1ebc6 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/option-card.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/option-card.tsx @@ -4,7 +4,6 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' import Badge from '@/app/components/base/badge' import { OptionCardEffectBlue, @@ -14,6 +13,7 @@ import { OptionCardEffectTeal, } from '@/app/components/base/icons/src/public/knowledge' import { ArrowShape } from '@/app/components/base/icons/src/vender/knowledge' +import { cn } from '@/utils/classnames' const HEADER_EFFECT_MAP: Record<string, ReactNode> = { 'blue': <OptionCardEffectBlue />, @@ -68,7 +68,8 @@ const OptionCard = memo(({ 'absolute left-[-2px] top-[-2px] hidden h-14 w-14 rounded-full', 'group-hover:block', isActive && 'block', - )}> + )} + > {HEADER_EFFECT_MAP[effectColor]} </div> ) @@ -95,22 +96,23 @@ const OptionCard = memo(({ <div className={cn( 'relative flex rounded-t-xl p-2', className && (typeof className === 'function' ? className(isActive) : className), - )}> + )} + > {effectElement} { icon && ( - <div className='mr-1 flex h-[18px] w-[18px] shrink-0 items-center justify-center'> + <div className="mr-1 flex h-[18px] w-[18px] shrink-0 items-center justify-center"> {typeof icon === 'function' ? icon(isActive) : icon} </div> ) } - <div className='grow py-1 pt-[1px]'> - <div className='flex items-center'> - <div className='system-sm-medium flex grow items-center text-text-secondary'> + <div className="grow py-1 pt-[1px]"> + <div className="flex items-center"> + <div className="system-sm-medium flex grow items-center text-text-secondary"> {title} { isRecommended && ( - <Badge className='ml-1 h-4 border-text-accent-secondary text-text-accent-secondary'> + <Badge className="ml-1 h-4 border-text-accent-secondary text-text-accent-secondary"> {t('datasetCreation.stepTwo.recommend')} </Badge> ) @@ -121,14 +123,15 @@ const OptionCard = memo(({ <div className={cn( 'ml-2 h-4 w-4 shrink-0 rounded-full border border-components-radio-border bg-components-radio-bg', isActive && 'border-[5px] border-components-radio-border-checked', - )}> + )} + > </div> ) } </div> { description && ( - <div className='system-xs-regular mt-1 text-text-tertiary'> + <div className="system-xs-regular mt-1 text-text-tertiary"> {description} </div> ) @@ -137,8 +140,8 @@ const OptionCard = memo(({ </div> { children && isActive && ( - <div className='relative rounded-b-xl bg-components-panel-bg p-3'> - <ArrowShape className='absolute left-[14px] top-[-11px] h-4 w-4 text-components-panel-bg' /> + <div className="relative rounded-b-xl bg-components-panel-bg p-3"> + <ArrowShape className="absolute left-[14px] top-[-11px] h-4 w-4 text-components-panel-bg" /> {children} </div> ) diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/hooks.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/hooks.tsx index 4bcfc4effd..482df34059 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/hooks.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/hooks.tsx @@ -1,3 +1,7 @@ +import type { + HybridSearchModeOption, + Option, +} from './type' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,10 +14,6 @@ import { IndexMethodEnum, RetrievalSearchMethodEnum, } from '../../types' -import type { - HybridSearchModeOption, - Option, -} from './type' export const useRetrievalSetting = (indexMethod?: IndexMethodEnum) => { const { t } = useTranslation() @@ -70,13 +70,15 @@ export const useRetrievalSetting = (indexMethod?: IndexMethodEnum) => { }, [t]) return useMemo(() => ({ - options: indexMethod === IndexMethodEnum.ECONOMICAL ? [ - InvertedIndexOption, - ] : [ - VectorSearchOption, - FullTextSearchOption, - HybridSearchOption, - ], + options: indexMethod === IndexMethodEnum.ECONOMICAL + ? [ + InvertedIndexOption, + ] + : [ + VectorSearchOption, + FullTextSearchOption, + HybridSearchOption, + ], hybridSearchModeOptions: [ WeightedScoreModeOption, RerankModelModeOption, diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/index.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/index.tsx index 5d6b8b8c19..74ef1f8e58 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/index.tsx @@ -1,19 +1,17 @@ +import type { + HybridSearchModeEnum, + IndexMethodEnum, + RetrievalSearchMethodEnum, + WeightedScore, +} from '../../types' +import type { RerankingModelSelectorProps } from './reranking-model-selector' +import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold' import { memo, } from 'react' import { useTranslation } from 'react-i18next' import { Field } from '@/app/components/workflow/nodes/_base/components/layout' -import type { - HybridSearchModeEnum, - RetrievalSearchMethodEnum, -} from '../../types' -import type { - IndexMethodEnum, - WeightedScore, -} from '../../types' import { useRetrievalSetting } from './hooks' -import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold' -import type { RerankingModelSelectorProps } from './reranking-model-selector' import SearchMethodOption from './search-method-option' type RetrievalSettingProps = { @@ -62,14 +60,15 @@ const RetrievalSetting = ({ fieldTitleProps={{ title: t('datasetSettings.form.retrievalSetting.title'), subTitle: ( - <div className='body-xs-regular flex items-center text-text-tertiary'> - <a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-text-accent'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a> -  {t('workflow.nodes.knowledgeBase.aboutRetrieval')} + <div className="body-xs-regular flex items-center text-text-tertiary"> + <a target="_blank" rel="noopener noreferrer" href="https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings" className="text-text-accent">{t('datasetSettings.form.retrievalSetting.learnMore')}</a> +   + {t('workflow.nodes.knowledgeBase.aboutRetrieval')} </div> ), }} > - <div className='space-y-1'> + <div className="space-y-1"> { options.map(option => ( <SearchMethodOption diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/reranking-model-selector.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/reranking-model-selector.tsx index 19566362a1..3e0bea2b28 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/reranking-model-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/reranking-model-selector.tsx @@ -1,12 +1,12 @@ +import type { RerankingModel } from '../../types' +import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { memo, useMemo, } from 'react' -import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' -import { useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' -import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { RerankingModel } from '../../types' +import { useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' export type RerankingModelSelectorProps = { rerankingModel?: RerankingModel diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx index 70996ddd57..8b85bb576e 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx @@ -1,31 +1,31 @@ +import type { + WeightedScore, +} from '../../types' +import type { RerankingModelSelectorProps } from './reranking-model-selector' +import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold' +import type { + HybridSearchModeOption, + Option, +} from './type' import { memo, useCallback, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' import WeightedScoreComponent from '@/app/components/app/configuration/dataset-config/params-config/weighted-score' -import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets' +import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' +import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets' +import { cn } from '@/utils/classnames' import { HybridSearchModeEnum, RetrievalSearchMethodEnum, } from '../../types' -import type { - WeightedScore, -} from '../../types' import OptionCard from '../option-card' -import type { - HybridSearchModeOption, - Option, -} from './type' -import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold' -import TopKAndScoreThreshold from './top-k-and-score-threshold' -import type { RerankingModelSelectorProps } from './reranking-model-selector' import RerankingModelSelector from './reranking-model-selector' -import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' +import TopKAndScoreThreshold from './top-k-and-score-threshold' type SearchMethodOptionProps = { readonly?: boolean @@ -128,10 +128,10 @@ const SearchMethodOption = ({ onClick={onRetrievalSearchMethodChange} readonly={readonly} > - <div className='space-y-3'> + <div className="space-y-3"> { isHybridSearch && ( - <div className='space-y-1'> + <div className="space-y-1"> { hybridSearchModeOptions.map(hybridOption => ( <OptionCard @@ -141,7 +141,7 @@ const SearchMethodOption = ({ enableHighlightBorder={false} enableRadio wrapperClassName={hybridSearchModeWrapperClassName} - className='p-3' + className="p-3" title={hybridOption.title} description={hybridOption.description} onClick={onHybridSearchModeChange} @@ -166,16 +166,16 @@ const SearchMethodOption = ({ <div> { showRerankModelSelectorSwitch && ( - <div className='system-sm-semibold mb-1 flex items-center text-text-secondary'> + <div className="system-sm-semibold mb-1 flex items-center text-text-secondary"> <Switch - className='mr-1' + className="mr-1" defaultValue={rerankingModelEnabled} onChange={onRerankingModelEnabledChange} disabled={readonly} /> {t('common.modelProvider.rerankModel.key')} <Tooltip - triggerClassName='ml-0.5 shrink-0 w-3.5 h-3.5' + triggerClassName="ml-0.5 shrink-0 w-3.5 h-3.5" popupContent={t('common.modelProvider.rerankModel.tip')} /> </div> @@ -187,12 +187,12 @@ const SearchMethodOption = ({ readonly={readonly} /> {showMultiModalTip && ( - <div className='mt-2 flex h-10 items-center gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs backdrop-blur-[5px]'> - <div className='absolute bottom-0 left-0 right-0 top-0 bg-dataset-warning-message-bg opacity-40' /> - <div className='p-1'> - <AlertTriangle className='size-4 text-text-warning-secondary' /> + <div className="mt-2 flex h-10 items-center gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs backdrop-blur-[5px]"> + <div className="absolute bottom-0 left-0 right-0 top-0 bg-dataset-warning-message-bg opacity-40" /> + <div className="p-1"> + <AlertTriangle className="size-4 text-text-warning-secondary" /> </div> - <span className='system-xs-medium text-text-primary'> + <span className="system-xs-medium text-text-primary"> {t('datasetSettings.form.retrievalSetting.multiModalTip')} </span> </div> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx index 9eb1cb39c9..04dff1723f 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx @@ -1,8 +1,8 @@ import { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' -import Switch from '@/app/components/base/switch' import { InputNumber } from '@/app/components/base/input-number' +import Switch from '@/app/components/base/switch' +import Tooltip from '@/app/components/base/tooltip' export type TopKAndScoreThresholdProps = { topK: number @@ -58,20 +58,20 @@ const TopKAndScoreThreshold = ({ } return ( - <div className='grid grid-cols-2 gap-4'> + <div className="grid grid-cols-2 gap-4"> <div> - <div className='system-xs-medium mb-0.5 flex h-6 items-center text-text-secondary'> + <div className="system-xs-medium mb-0.5 flex h-6 items-center text-text-secondary"> {t('appDebug.datasetConfig.top_k')} <Tooltip - triggerClassName='ml-0.5 shrink-0 w-3.5 h-3.5' + triggerClassName="ml-0.5 shrink-0 w-3.5 h-3.5" popupContent={t('appDebug.datasetConfig.top_kTip')} /> </div> <InputNumber disabled={readonly} - type='number' + type="number" {...TOP_K_VALUE_LIMIT} - size='regular' + size="regular" value={topK} onChange={handleTopKChange} /> @@ -79,26 +79,26 @@ const TopKAndScoreThreshold = ({ { !hiddenScoreThreshold && ( <div> - <div className='mb-0.5 flex h-6 items-center'> + <div className="mb-0.5 flex h-6 items-center"> <Switch - className='mr-2' + className="mr-2" defaultValue={isScoreThresholdEnabled} onChange={onScoreThresholdEnabledChange} disabled={readonly} /> - <div className='system-sm-medium grow truncate text-text-secondary'> + <div className="system-sm-medium grow truncate text-text-secondary"> {t('appDebug.datasetConfig.score_threshold')} </div> <Tooltip - triggerClassName='shrink-0 ml-0.5 w-3.5 h-3.5' + triggerClassName="shrink-0 ml-0.5 w-3.5 h-3.5" popupContent={t('appDebug.datasetConfig.score_thresholdTip')} /> </div> <InputNumber disabled={readonly || !isScoreThresholdEnabled} - type='number' + type="number" {...SCORE_THRESHOLD_VALUE_LIMIT} - size='regular' + size="regular" value={scoreThreshold} onChange={handleScoreThresholdChange} /> diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/type.ts b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/type.ts index b72a830d4b..73e15fd9d9 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/type.ts +++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/type.ts @@ -10,7 +10,7 @@ export type Option = { title: any description: string effectColor?: string - showEffectColor?: boolean, + showEffectColor?: boolean } export type HybridSearchModeOption = { diff --git a/web/app/components/workflow/nodes/knowledge-base/default.ts b/web/app/components/workflow/nodes/knowledge-base/default.ts index 952eb10fa0..fe9f6ba78e 100644 --- a/web/app/components/workflow/nodes/knowledge-base/default.ts +++ b/web/app/components/workflow/nodes/knowledge-base/default.ts @@ -1,8 +1,8 @@ import type { NodeDefault } from '../../types' import type { KnowledgeBaseNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { IndexingType } from '@/app/components/datasets/create/step-two' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: 3.1, diff --git a/web/app/components/workflow/nodes/knowledge-base/hooks/use-config.ts b/web/app/components/workflow/nodes/knowledge-base/hooks/use-config.ts index 8b22704c5a..f2a27d338e 100644 --- a/web/app/components/workflow/nodes/knowledge-base/hooks/use-config.ts +++ b/web/app/components/workflow/nodes/knowledge-base/hooks/use-config.ts @@ -1,25 +1,23 @@ -import { - useCallback, -} from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' -import { useNodeDataUpdate } from '@/app/components/workflow/hooks' -import type { ValueSelector } from '@/app/components/workflow/types' -import { - ChunkStructureEnum, - IndexMethodEnum, - RetrievalSearchMethodEnum, - WeightedScoreEnum, -} from '../types' import type { KnowledgeBaseNodeType, RerankingModel, } from '../types' +import type { ValueSelector } from '@/app/components/workflow/types' +import { produce } from 'immer' import { + useCallback, +} from 'react' +import { useStoreApi } from 'reactflow' +import { useNodeDataUpdate } from '@/app/components/workflow/hooks' +import { DEFAULT_WEIGHTED_SCORE, RerankingModeEnum } from '@/models/datasets' +import { + ChunkStructureEnum, HybridSearchModeEnum, + IndexMethodEnum, + RetrievalSearchMethodEnum, + WeightedScoreEnum, } from '../types' import { isHighQualitySearchMethod } from '../utils' -import { DEFAULT_WEIGHTED_SCORE, RerankingModeEnum } from '@/models/datasets' export const useConfig = (id: string) => { const store = useStoreApi() diff --git a/web/app/components/workflow/nodes/knowledge-base/node.tsx b/web/app/components/workflow/nodes/knowledge-base/node.tsx index 29de1bce9e..e69ae56be7 100644 --- a/web/app/components/workflow/nodes/knowledge-base/node.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/node.tsx @@ -1,33 +1,33 @@ import type { FC } from 'react' +import type { KnowledgeBaseNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import type { KnowledgeBaseNodeType } from './types' import { useSettingsDisplay } from './hooks/use-settings-display' -import type { NodeProps } from '@/app/components/workflow/types' const Node: FC<NodeProps<KnowledgeBaseNodeType>> = ({ data }) => { const { t } = useTranslation() const settingsDisplay = useSettingsDisplay() return ( - <div className='mb-1 space-y-0.5 px-3 py-1'> - <div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5'> - <div className='system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary'> + <div className="mb-1 space-y-0.5 px-3 py-1"> + <div className="flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5"> + <div className="system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary"> {t('datasetCreation.stepTwo.indexMode')} </div> <div - className='system-xs-medium grow truncate text-right text-text-secondary' + className="system-xs-medium grow truncate text-right text-text-secondary" title={data.indexing_technique} > {settingsDisplay[data.indexing_technique as keyof typeof settingsDisplay]} </div> </div> - <div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5'> - <div className='system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary'> + <div className="flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5"> + <div className="system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary"> {t('datasetSettings.form.retrievalSetting.title')} </div> <div - className='system-xs-medium grow truncate text-right text-text-secondary' + className="system-xs-medium grow truncate text-right text-text-secondary" title={data.retrieval_model?.search_method} > {settingsDisplay[data.retrieval_model?.search_method as keyof typeof settingsDisplay]} diff --git a/web/app/components/workflow/nodes/knowledge-base/panel.tsx b/web/app/components/workflow/nodes/knowledge-base/panel.tsx index f6448d6fff..0e4bd29def 100644 --- a/web/app/components/workflow/nodes/knowledge-base/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/panel.tsx @@ -1,33 +1,32 @@ import type { FC } from 'react' +import type { KnowledgeBaseNodeType } from './types' +import type { NodePanelProps, Var } from '@/app/components/workflow/types' import { memo, useCallback, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import type { KnowledgeBaseNodeType } from './types' -import { - ChunkStructureEnum, - IndexMethodEnum, -} from './types' -import ChunkStructure from './components/chunk-structure' -import IndexMethod from './components/index-method' -import RetrievalSetting from './components/retrieval-setting' -import EmbeddingModel from './components/embedding-model' -import { useConfig } from './hooks/use-config' -import type { NodePanelProps } from '@/app/components/workflow/types' +import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useNodesReadOnly } from '@/app/components/workflow/hooks' import { BoxGroup, BoxGroupField, Group, } from '@/app/components/workflow/nodes/_base/components/layout' -import Split from '../_base/components/split' -import { useNodesReadOnly } from '@/app/components/workflow/hooks' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import type { Var } from '@/app/components/workflow/types' -import { checkShowMultiModalTip } from '@/app/components/datasets/settings/utils' -import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import Split from '../_base/components/split' +import ChunkStructure from './components/chunk-structure' +import EmbeddingModel from './components/embedding-model' +import IndexMethod from './components/index-method' +import RetrievalSetting from './components/retrieval-setting' +import { useConfig } from './hooks/use-config' +import { + ChunkStructureEnum, + IndexMethodEnum, +} from './types' const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ id, @@ -55,7 +54,8 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ } = useConfig(id) const filterVar = useCallback((variable: Var) => { - if (!data.chunk_structure) return false + if (!data.chunk_structure) + return false switch (data.chunk_structure) { case ChunkStructureEnum.general: return variable.schemaType === 'general_structure' || variable.schemaType === 'multimodal_general_structure' @@ -69,7 +69,8 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ }, [data.chunk_structure]) const chunkTypePlaceHolder = useMemo(() => { - if (!data.chunk_structure) return '' + if (!data.chunk_structure) + return '' let placeholder = '' switch (data.chunk_structure) { case ChunkStructureEnum.general: @@ -107,7 +108,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ return ( <div> <Group - className='py-3' + className="py-3" withBorderBottom={!!data.chunk_structure} > <ChunkStructure @@ -144,7 +145,7 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ /> </BoxGroupField> <BoxGroup> - <div className='space-y-3'> + <div className="space-y-3"> <IndexMethod chunkStructure={data.chunk_structure} indexMethod={data.indexing_technique} @@ -163,8 +164,8 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({ /> ) } - <div className='pt-1'> - <Split className='h-[1px]' /> + <div className="pt-1"> + <Split className="h-[1px]" /> </div> <RetrievalSetting indexMethod={data.indexing_technique} diff --git a/web/app/components/workflow/nodes/knowledge-base/types.ts b/web/app/components/workflow/nodes/knowledge-base/types.ts index 1f484a5c55..b54e8e2b2f 100644 --- a/web/app/components/workflow/nodes/knowledge-base/types.ts +++ b/web/app/components/workflow/nodes/knowledge-base/types.ts @@ -1,13 +1,13 @@ -import type { CommonNodeType } from '@/app/components/workflow/types' import type { IndexingType } from '@/app/components/datasets/create/step-two' -import type { RETRIEVE_METHOD } from '@/types/app' -import type { WeightedScoreEnum } from '@/models/datasets' -import type { RerankingModeEnum } from '@/models/datasets' import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations' -export { WeightedScoreEnum } from '@/models/datasets' +import type { CommonNodeType } from '@/app/components/workflow/types' +import type { RerankingModeEnum, WeightedScoreEnum } from '@/models/datasets' +import type { RETRIEVE_METHOD } from '@/types/app' + export { IndexingType as IndexMethodEnum } from '@/app/components/datasets/create/step-two' -export { RETRIEVE_METHOD as RetrievalSearchMethodEnum } from '@/types/app' +export { WeightedScoreEnum } from '@/models/datasets' export { RerankingModeEnum as HybridSearchModeEnum } from '@/models/datasets' +export { RETRIEVE_METHOD as RetrievalSearchMethodEnum } from '@/types/app' export enum ChunkStructureEnum { general = 'text_model', diff --git a/web/app/components/workflow/nodes/knowledge-base/use-single-run-form-params.ts b/web/app/components/workflow/nodes/knowledge-base/use-single-run-form-params.ts index 707407a589..f653d7fdc3 100644 --- a/web/app/components/workflow/nodes/knowledge-base/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/knowledge-base/use-single-run-form-params.ts @@ -1,11 +1,11 @@ -import { useTranslation } from 'react-i18next' -import type { InputVar, Variable } from '@/app/components/workflow/types' -import { InputVarType } from '@/app/components/workflow/types' -import { useCallback, useMemo } from 'react' import type { KnowledgeBaseNodeType } from './types' +import type { InputVar, Variable } from '@/app/components/workflow/types' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { InputVarType } from '@/app/components/workflow/types' type Params = { - id: string, + id: string payload: KnowledgeBaseNodeType runInputData: Record<string, any> getInputVars: (textList: string[]) => InputVar[] @@ -45,7 +45,7 @@ const useSingleRunFormParams = ({ return [payload.index_chunk_variable_selector] } const getDependentVar = (variable: string) => { - if(variable === 'query') + if (variable === 'query') return payload.index_chunk_variable_selector } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx index 010ecbb966..b51d085113 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx @@ -1,10 +1,10 @@ 'use client' -import { useBoolean } from 'ahooks' import type { FC } from 'react' -import React, { useCallback } from 'react' -import AddButton from '@/app/components/base/button/add-button' -import SelectDataset from '@/app/components/app/configuration/dataset-config/select-dataset' import type { DataSet } from '@/models/datasets' +import { useBoolean } from 'ahooks' +import React, { useCallback } from 'react' +import SelectDataset from '@/app/components/app/configuration/dataset-config/select-dataset' +import AddButton from '@/app/components/base/button/add-button' type Props = { selectedIds: string[] diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx index e164e4f320..440aa9189a 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useBoolean } from 'ahooks' +import type { DataSet } from '@/models/datasets' import { RiDeleteBinLine, RiEditLine, } from '@remixicon/react' +import { useBoolean } from 'ahooks' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import type { DataSet } from '@/models/datasets' -import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' -import Drawer from '@/app/components/base/drawer' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import Badge from '@/app/components/base/badge' -import { useKnowledge } from '@/hooks/use-knowledge' +import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' -import FeatureIcon from '@/app/components/header/account-setting/model-provider-page/model-selector/feature-icon' +import Badge from '@/app/components/base/badge' +import Drawer from '@/app/components/base/drawer' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import FeatureIcon from '@/app/components/header/account-setting/model-provider-page/model-selector/feature-icon' +import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import { useKnowledge } from '@/hooks/use-knowledge' type Props = { payload: DataSet @@ -67,28 +67,31 @@ const DatasetItem: FC<Props> = ({ ${isDeleteHovered ? 'border-state-destructive-border bg-state-destructive-hover' : 'bg-components-panel-on-panel-item-bg hover:bg-components-panel-on-panel-item-bg-hover' - }`}> - <div className='flex w-0 grow items-center space-x-1.5'> + }`} + > + <div className="flex w-0 grow items-center space-x-1.5"> <AppIcon - size='tiny' + size="tiny" iconType={iconInfo.icon_type} icon={iconInfo.icon} background={iconInfo.icon_type === 'image' ? undefined : iconInfo.icon_background} imageUrl={iconInfo.icon_type === 'image' ? iconInfo.icon_url : undefined} /> - <div className='system-sm-medium w-0 grow truncate text-text-secondary'>{payload.name}</div> + <div className="system-sm-medium w-0 grow truncate text-text-secondary">{payload.name}</div> </div> {!readonly && ( - <div className='ml-2 hidden shrink-0 items-center space-x-1 group-hover/dataset-item:flex'> + <div className="ml-2 hidden shrink-0 items-center space-x-1 group-hover/dataset-item:flex"> { - editable && <ActionButton - onClick={(e) => { - e.stopPropagation() - showSettingsModal() - }} - > - <RiEditLine className='h-4 w-4 shrink-0 text-text-tertiary' /> - </ActionButton> + editable && ( + <ActionButton + onClick={(e) => { + e.stopPropagation() + showSettingsModal() + }} + > + <RiEditLine className="h-4 w-4 shrink-0 text-text-tertiary" /> + </ActionButton> + ) } <ActionButton onClick={handleRemove} @@ -101,25 +104,29 @@ const DatasetItem: FC<Props> = ({ </div> )} {payload.is_multimodal && ( - <div className='mr-1 shrink-0 group-hover/dataset-item:hidden'> + <div className="mr-1 shrink-0 group-hover/dataset-item:hidden"> <FeatureIcon feature={ModelFeatureEnum.vision} /> </div> )} { - payload.indexing_technique && <Badge - className='shrink-0 group-hover/dataset-item:hidden' - text={formatIndexingTechniqueAndMethod(payload.indexing_technique, payload.retrieval_model_dict?.search_method)} - /> + payload.indexing_technique && ( + <Badge + className="shrink-0 group-hover/dataset-item:hidden" + text={formatIndexingTechniqueAndMethod(payload.indexing_technique, payload.retrieval_model_dict?.search_method)} + /> + ) } { - payload.provider === 'external' && <Badge - className='shrink-0 group-hover/dataset-item:hidden' - text={t('dataset.externalTag') as string} - /> + payload.provider === 'external' && ( + <Badge + className="shrink-0 group-hover/dataset-item:hidden" + text={t('dataset.externalTag') as string} + /> + ) } {isShowSettingsModal && ( - <Drawer isOpen={isShowSettingsModal} onClose={hideSettingsModal} footer={null} mask={isMobile} panelClassName='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> + <Drawer isOpen={isShowSettingsModal} onClose={hideSettingsModal} footer={null} mask={isMobile} panelClassName="mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl"> <SettingsModal currentDataset={payload} onCancel={hideSettingsModal} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx index 35fd225d82..a804ae8953 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' -import Item from './dataset-item' import type { DataSet } from '@/models/datasets' +import { produce } from 'immer' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { useSelector as useAppContextSelector } from '@/context/app-context' import { hasEditPermissionForDataset } from '@/utils/permission' +import Item from './dataset-item' type Props = { list: DataSet[] @@ -55,26 +55,25 @@ const DatasetList: FC<Props> = ({ }, [list, userProfile?.id]) return ( - <div className='space-y-1'> + <div className="space-y-1"> {formattedList.length ? formattedList.map((item, index) => { - return ( - <Item - key={index} - payload={item} - onRemove={handleRemove(index)} - onChange={handleChange(index)} - readonly={readonly} - editable={item.editable} - /> - ) - }) + return ( + <Item + key={index} + payload={item} + onRemove={handleRemove(index)} + onChange={handleChange(index)} + readonly={readonly} + editable={item.editable} + /> + ) + }) : ( - <div className='cursor-default select-none rounded-lg bg-background-section p-3 text-center text-xs text-text-tertiary'> - {t('appDebug.datasetConfig.knowledgeTip')} - </div> - ) - } + <div className="cursor-default select-none rounded-lg bg-background-section p-3 text-center text-xs text-text-tertiary"> + {t('appDebug.datasetConfig.knowledgeTip')} + </div> + )} </div> ) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx index ed6b4c6c78..8044604207 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/add-condition.tsx @@ -1,22 +1,22 @@ +import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import type { MetadataInDoc } from '@/models/datasets' +import { + RiAddLine, +} from '@remixicon/react' import { useCallback, useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { - RiAddLine, -} from '@remixicon/react' -import MetadataIcon from './metadata-icon' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import Input from '@/app/components/base/input' -import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' -import type { MetadataInDoc } from '@/models/datasets' +import MetadataIcon from './metadata-icon' const AddCondition = ({ metadataList, @@ -39,7 +39,7 @@ const AddCondition = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 3, crossAxis: 0, @@ -47,16 +47,16 @@ const AddCondition = ({ > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> <Button - size='small' - variant='secondary' + size="small" + variant="secondary" > - <RiAddLine className='h-3.5 w-3.5' /> + <RiAddLine className="h-3.5 w-3.5" /> {t('workflow.nodes.knowledgeRetrieval.metadata.panel.add')} </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> - <div className='p-2 pb-1'> + <PortalToFollowElemContent className="z-10"> + <div className="w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> + <div className="p-2 pb-1"> <Input showLeftIcon placeholder={t('workflow.nodes.knowledgeRetrieval.metadata.panel.search')} @@ -64,24 +64,24 @@ const AddCondition = ({ onChange={e => setSearchText(e.target.value)} /> </div> - <div className='p-1'> + <div className="p-1"> { filteredMetadataList?.map(metadata => ( <div key={metadata.name} - className='system-sm-medium flex h-6 cursor-pointer items-center rounded-md px-3 text-text-secondary hover:bg-state-base-hover' + className="system-sm-medium flex h-6 cursor-pointer items-center rounded-md px-3 text-text-secondary hover:bg-state-base-hover" > - <div className='mr-1 p-[1px]'> + <div className="mr-1 p-[1px]"> <MetadataIcon type={metadata.type} /> </div> <div - className='grow truncate' + className="grow truncate" title={metadata.name} onClick={() => handleAddConditionWrapped(metadata)} > {metadata.name} </div> - <div className='system-xs-regular shrink-0 text-text-tertiary'>{metadata.type}</div> + <div className="system-xs-regular shrink-0 text-text-tertiary">{metadata.type}</div> </div> )) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx index 0d81d808db..48581fd1d1 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-common-variable-selector.tsx @@ -1,15 +1,15 @@ +import type { VarType } from '@/app/components/workflow/types' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { VarType } from '@/app/components/workflow/types' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' type ConditionCommonVariableSelectorProps = { - variables?: { name: string; type: string; value: string }[] + variables?: { name: string, type: string, value: string }[] value?: string | number varType?: VarType onChange: (v: string) => void @@ -34,21 +34,25 @@ const ConditionCommonVariableSelector = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, }} > - <PortalToFollowElemTrigger asChild onClick={() => { - if (!variables.length) return - setOpen(!open) - }}> + <PortalToFollowElemTrigger + asChild + onClick={() => { + if (!variables.length) + return + setOpen(!open) + }} + > <div className="flex h-6 grow cursor-pointer items-center"> { selected && ( - <div className='system-xs-medium inline-flex h-6 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-[5px] pr-1.5 text-text-secondary shadow-xs'> - <Variable02 className='mr-1 h-3.5 w-3.5 text-text-accent' /> + <div className="system-xs-medium inline-flex h-6 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-[5px] pr-1.5 text-text-secondary shadow-xs"> + <Variable02 className="mr-1 h-3.5 w-3.5 text-text-accent" /> {selected.value} </div> ) @@ -56,11 +60,11 @@ const ConditionCommonVariableSelector = ({ { !selected && ( <> - <div className='system-sm-regular flex grow items-center text-components-input-text-placeholder'> - <Variable02 className='mr-1 h-4 w-4' /> + <div className="system-sm-regular flex grow items-center text-components-input-text-placeholder"> + <Variable02 className="mr-1 h-4 w-4" /> {t('workflow.nodes.knowledgeRetrieval.metadata.panel.select')} </div> - <div className='system-2xs-medium flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] text-text-tertiary'> + <div className="system-2xs-medium flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] text-text-tertiary"> {varType} </div> </> @@ -68,16 +72,16 @@ const ConditionCommonVariableSelector = ({ } </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { variables.map(v => ( <div key={v.value} - className='system-xs-medium flex h-6 cursor-pointer items-center rounded-md px-2 text-text-secondary hover:bg-state-base-hover' + className="system-xs-medium flex h-6 cursor-pointer items-center rounded-md px-2 text-text-secondary hover:bg-state-base-hover" onClick={() => handleChange(v.value)} > - <Variable02 className='mr-1 h-4 w-4 text-text-accent' /> + <Variable02 className="mr-1 h-4 w-4 text-text-accent" /> {v.value} </div> )) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-date.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-date.tsx index ed8192239a..f89992a2e4 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-date.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-date.tsx @@ -1,14 +1,14 @@ -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import dayjs from 'dayjs' +import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types' import { RiCalendarLine, RiCloseCircleFill, } from '@remixicon/react' +import dayjs from 'dayjs' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import DatePicker from '@/app/components/base/date-and-time-picker/date-picker' -import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types' -import { cn } from '@/utils/classnames' import { useAppContext } from '@/context/app-context' +import { cn } from '@/utils/classnames' type ConditionDateProps = { value?: number @@ -32,7 +32,7 @@ const ConditionDate = ({ handleClickTrigger, }: TriggerProps) => { return ( - <div className='group flex items-center' onClick={handleClickTrigger}> + <div className="group flex items-center" onClick={handleClickTrigger}> <div className={cn( 'system-sm-regular mr-0.5 flex h-6 grow cursor-pointer items-center px-1', @@ -71,7 +71,7 @@ const ConditionDate = ({ }, [value, handleDateChange, timezone, t]) return ( - <div className='h-8 px-2 py-1'> + <div className="h-8 px-2 py-1"> <DatePicker timezone={timezone} value={value ? dayjs(value * 1000) : undefined} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx index 42f3a085bc..815844d434 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx @@ -1,21 +1,3 @@ -import { - useCallback, - useMemo, - useState, -} from 'react' -import { - RiDeleteBinLine, -} from '@remixicon/react' -import MetadataIcon from '../metadata-icon' -import { - COMMON_VARIABLE_REGEX, - VARIABLE_REGEX, - comparisonOperatorNotRequireValue, -} from './utils' -import ConditionOperator from './condition-operator' -import ConditionString from './condition-string' -import ConditionNumber from './condition-number' -import ConditionDate from './condition-date' import type { ComparisonOperator, HandleRemoveCondition, @@ -23,8 +5,26 @@ import type { MetadataFilteringCondition, MetadataShape, } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { + RiDeleteBinLine, +} from '@remixicon/react' +import { + useCallback, + useMemo, + useState, +} from 'react' import { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import { cn } from '@/utils/classnames' +import MetadataIcon from '../metadata-icon' +import ConditionDate from './condition-date' +import ConditionNumber from './condition-number' +import ConditionOperator from './condition-operator' +import ConditionString from './condition-string' +import { + COMMON_VARIABLE_REGEX, + comparisonOperatorNotRequireValue, + VARIABLE_REGEX, +} from './utils' type ConditionItemProps = { className?: string @@ -72,14 +72,15 @@ const ConditionItem = ({ ...condition, value: comparisonOperatorNotRequireValue(condition.comparison_operator) ? undefined : condition.value, comparison_operator: operator, - }) + }, + ) }, [onUpdateCondition, condition]) const valueAndValueMethod = useMemo(() => { if ( (currentMetadata?.type === MetadataFilteringVariableType.string - || currentMetadata?.type === MetadataFilteringVariableType.number - || currentMetadata?.type === MetadataFilteringVariableType.select) + || currentMetadata?.type === MetadataFilteringVariableType.number + || currentMetadata?.type === MetadataFilteringVariableType.select) && typeof condition.value === 'string' ) { const regex = isCommonVariable ? COMMON_VARIABLE_REGEX : VARIABLE_REGEX @@ -121,18 +122,19 @@ const ConditionItem = ({ <div className={cn( 'grow rounded-lg bg-components-input-bg-normal', isHovered && 'bg-state-destructive-hover', - )}> - <div className='flex items-center p-1'> - <div className='w-0 grow'> - <div className='flex h-6 min-w-0 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-1 pr-1.5 shadow-xs'> - <div className='mr-0.5 p-[1px]'> - <MetadataIcon type={currentMetadata?.type} className='h-3 w-3' /> + )} + > + <div className="flex items-center p-1"> + <div className="w-0 grow"> + <div className="flex h-6 min-w-0 items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark pl-1 pr-1.5 shadow-xs"> + <div className="mr-0.5 p-[1px]"> + <MetadataIcon type={currentMetadata?.type} className="h-3 w-3" /> </div> - <div className='system-xs-medium mr-0.5 min-w-0 flex-1 truncate text-text-secondary'>{currentMetadata?.name}</div> - <div className='system-xs-regular text-text-tertiary'>{currentMetadata?.type}</div> + <div className="system-xs-medium mr-0.5 min-w-0 flex-1 truncate text-text-secondary">{currentMetadata?.name}</div> + <div className="system-xs-regular text-text-tertiary">{currentMetadata?.type}</div> </div> </div> - <div className='mx-1 h-3 w-[1px] bg-divider-regular'></div> + <div className="mx-1 h-3 w-[1px] bg-divider-regular"></div> <ConditionOperator disabled={!canChooseOperator} variableType={currentMetadata?.type || MetadataFilteringVariableType.string} @@ -140,11 +142,11 @@ const ConditionItem = ({ onSelect={handleConditionOperatorChange} /> </div> - <div className='border-t border-t-divider-subtle'> + <div className="border-t border-t-divider-subtle"> { !comparisonOperatorNotRequireValue(condition.comparison_operator) && (currentMetadata?.type === MetadataFilteringVariableType.string - || currentMetadata?.type === MetadataFilteringVariableType.select) && ( + || currentMetadata?.type === MetadataFilteringVariableType.select) && ( <ConditionString valueMethod={localValueMethod} onValueMethodChange={handleValueMethodChange} @@ -182,12 +184,12 @@ const ConditionItem = ({ </div> </div> <div - className='ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive' + className="ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={doRemoveCondition} > - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx index 6421401a2a..3b5587c442 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-number.tsx @@ -1,16 +1,16 @@ -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import ConditionValueMethod from './condition-value-method' import type { ConditionValueMethodProps } from './condition-value-method' -import ConditionVariableSelector from './condition-variable-selector' -import ConditionCommonVariableSelector from './condition-common-variable-selector' import type { Node, NodeOutPutVar, ValueSelector, } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' +import { VarType } from '@/app/components/workflow/types' +import ConditionCommonVariableSelector from './condition-common-variable-selector' +import ConditionValueMethod from './condition-value-method' +import ConditionVariableSelector from './condition-variable-selector' type ConditionNumberProps = { value?: string | number @@ -18,7 +18,7 @@ type ConditionNumberProps = { nodesOutputVars: NodeOutPutVar[] availableNodes: Node[] isCommonVariable?: boolean - commonVariables: { name: string; type: string; value: string }[] + commonVariables: { name: string, type: string, value: string }[] } & ConditionValueMethodProps const ConditionNumber = ({ value, @@ -40,12 +40,12 @@ const ConditionNumber = ({ }, [onChange]) return ( - <div className='flex h-8 items-center pl-1 pr-2'> + <div className="flex h-8 items-center pl-1 pr-2"> <ConditionValueMethod valueMethod={valueMethod} onValueMethodChange={onValueMethodChange} /> - <div className='ml-1 mr-1.5 h-4 w-[1px] bg-divider-regular'></div> + <div className="ml-1 mr-1.5 h-4 w-[1px] bg-divider-regular"></div> { valueMethod === 'variable' && !isCommonVariable && ( <ConditionVariableSelector @@ -70,14 +70,14 @@ const ConditionNumber = ({ { valueMethod === 'constant' && ( <Input - className='border-none bg-transparent outline-none hover:bg-transparent focus:bg-transparent focus:shadow-none' + className="border-none bg-transparent outline-none hover:bg-transparent focus:bg-transparent focus:shadow-none" value={value} onChange={(e) => { const v = e.target.value onChange(v ? Number(e.target.value) : undefined) }} placeholder={t('workflow.nodes.knowledgeRetrieval.metadata.panel.placeholder')} - type='number' + type="number" /> ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx index 93a078ff5d..8f0430b655 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx @@ -1,13 +1,13 @@ +import type { + ComparisonOperator, + MetadataFilteringVariableType, +} from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { RiArrowDownSLine } from '@remixicon/react' import { useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { - getOperators, - isComparisonOperatorNeedTranslate, -} from './utils' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -15,10 +15,10 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' -import type { - ComparisonOperator, - MetadataFilteringVariableType, -} from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { + getOperators, + isComparisonOperatorNeedTranslate, +} from './utils' const i18nPrefix = 'workflow.nodes.ifElse' @@ -52,7 +52,7 @@ const ConditionOperator = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 0, @@ -61,8 +61,8 @@ const ConditionOperator = ({ <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <Button className={cn('shrink-0', !selectedOption && 'opacity-50', className)} - size='small' - variant='ghost' + size="small" + variant="ghost" disabled={disabled} > { @@ -70,16 +70,16 @@ const ConditionOperator = ({ ? selectedOption.label : t(`${i18nPrefix}.select`) } - <RiArrowDownSLine className='ml-1 h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-10"> + <div className="rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.value} - className='flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover' + className="flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover" onClick={() => { onSelect(option.value) setOpen(false) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx index d5cb06e690..d2592d7a57 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-string.tsx @@ -1,16 +1,16 @@ -import { useCallback } from 'react' -import { useTranslation } from 'react-i18next' -import ConditionValueMethod from './condition-value-method' import type { ConditionValueMethodProps } from './condition-value-method' -import ConditionVariableSelector from './condition-variable-selector' -import ConditionCommonVariableSelector from './condition-common-variable-selector' import type { Node, NodeOutPutVar, ValueSelector, } from '@/app/components/workflow/types' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import { VarType } from '@/app/components/workflow/types' +import ConditionCommonVariableSelector from './condition-common-variable-selector' +import ConditionValueMethod from './condition-value-method' +import ConditionVariableSelector from './condition-variable-selector' type ConditionStringProps = { value?: string @@ -18,7 +18,7 @@ type ConditionStringProps = { nodesOutputVars: NodeOutPutVar[] availableNodes: Node[] isCommonVariable?: boolean - commonVariables: { name: string; type: string; value: string }[] + commonVariables: { name: string, type: string, value: string }[] } & ConditionValueMethodProps const ConditionString = ({ value, @@ -40,12 +40,12 @@ const ConditionString = ({ }, [onChange]) return ( - <div className='flex h-8 items-center pl-1 pr-2'> + <div className="flex h-8 items-center pl-1 pr-2"> <ConditionValueMethod valueMethod={valueMethod} onValueMethodChange={onValueMethodChange} /> - <div className='ml-1 mr-1.5 h-4 w-[1px] bg-divider-regular'></div> + <div className="ml-1 mr-1.5 h-4 w-[1px] bg-divider-regular"></div> { valueMethod === 'variable' && !isCommonVariable && ( <ConditionVariableSelector @@ -70,7 +70,7 @@ const ConditionString = ({ { valueMethod === 'constant' && ( <Input - className='border-none bg-transparent outline-none hover:bg-transparent focus:bg-transparent focus:shadow-none' + className="border-none bg-transparent outline-none hover:bg-transparent focus:bg-transparent focus:shadow-none" value={value} onChange={e => onChange(e.target.value)} placeholder={t('workflow.nodes.knowledgeRetrieval.metadata.panel.placeholder')} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx index 562cda76e4..c930387c82 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx @@ -1,12 +1,12 @@ -import { useState } from 'react' -import { capitalize } from 'lodash-es' import { RiArrowDownSLine } from '@remixicon/react' +import { capitalize } from 'lodash-es' +import { useState } from 'react' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' import { cn } from '@/utils/classnames' export type ConditionValueMethodProps = { @@ -27,21 +27,21 @@ const ConditionValueMethod = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0 }} > <PortalToFollowElemTrigger asChild onClick={() => setOpen(v => !v)}> <Button - className='shrink-0' - variant='ghost' - size='small' + className="shrink-0" + variant="ghost" + size="small" > {capitalize(valueMethod)} - <RiArrowDownSLine className='ml-[1px] h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-[1px] h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx index 7908f6a98a..bbd5d9f227 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-variable-selector.tsx @@ -1,5 +1,12 @@ +import type { + Node, + NodeOutPutVar, + ValueSelector, + Var, +} from '@/app/components/workflow/types' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { PortalToFollowElem, PortalToFollowElemContent, @@ -7,14 +14,7 @@ import { } from '@/app/components/base/portal-to-follow-elem' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { - Node, - NodeOutPutVar, - ValueSelector, - Var, -} from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' type ConditionVariableSelectorProps = { valueSelector?: ValueSelector @@ -43,7 +43,7 @@ const ConditionVariableSelector = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, @@ -64,11 +64,11 @@ const ConditionVariableSelector = ({ { !valueSelector.length && ( <> - <div className='system-sm-regular flex grow items-center text-components-input-text-placeholder'> - <Variable02 className='mr-1 h-4 w-4' /> + <div className="system-sm-regular flex grow items-center text-components-input-text-placeholder"> + <Variable02 className="mr-1 h-4 w-4" /> {t('workflow.nodes.knowledgeRetrieval.metadata.panel.select')} </div> - <div className='system-2xs-medium flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] text-text-tertiary'> + <div className="system-2xs-medium flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] text-text-tertiary"> {varType} </div> </> @@ -76,8 +76,8 @@ const ConditionVariableSelector = ({ } </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <VarReferenceVars vars={nodesOutputVars} isSupportFileVar diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx index 49da29ce7b..163d1084d2 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/index.tsx @@ -1,8 +1,8 @@ -import { RiLoopLeftLine } from '@remixicon/react' -import ConditionItem from './condition-item' -import { cn } from '@/utils/classnames' import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { RiLoopLeftLine } from '@remixicon/react' import { LogicalOperator } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { cn } from '@/utils/classnames' +import ConditionItem from './condition-item' type ConditionListProps = { disabled?: boolean @@ -34,15 +34,16 @@ const ConditionList = ({ conditions.length > 1 && ( <div className={cn( 'absolute bottom-0 left-0 top-0 w-[44px]', - )}> - <div className='absolute bottom-4 right-1 top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep'></div> - <div className='absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg'></div> + )} + > + <div className="absolute bottom-4 right-1 top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep"></div> + <div className="absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg"></div> <div - className='absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs' + className="absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs" onClick={() => handleToggleConditionLogicalOperator()} > {logical_operator.toUpperCase()} - <RiLoopLeftLine className='ml-0.5 h-3 w-3' /> + <RiLoopLeftLine className="ml-0.5 h-3 w-3" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts index 10ee1aff1f..ab68275b8d 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils.ts @@ -8,9 +8,12 @@ export const isEmptyRelatedOperator = (operator: ComparisonOperator) => { } const notTranslateKey = [ - ComparisonOperator.equal, ComparisonOperator.notEqual, - ComparisonOperator.largerThan, ComparisonOperator.largerThanOrEqual, - ComparisonOperator.lessThan, ComparisonOperator.lessThanOrEqual, + ComparisonOperator.equal, + ComparisonOperator.notEqual, + ComparisonOperator.largerThan, + ComparisonOperator.largerThanOrEqual, + ComparisonOperator.lessThan, + ComparisonOperator.lessThanOrEqual, ] export const isComparisonOperatorNeedTranslate = (operator?: ComparisonOperator) => { @@ -64,5 +67,5 @@ export const comparisonOperatorNotRequireValue = (operator?: ComparisonOperator) return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull, ComparisonOperator.exists, ComparisonOperator.notExists].includes(operator) } -export const VARIABLE_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi -export const COMMON_VARIABLE_REGEX = /\{\{([a-zA-Z0-9_-]{1,50})\}\}/gi +export const VARIABLE_REGEX = /\{\{(#[\w-]{1,50}(\.[a-z_]\w{0,29}){1,10}#)\}\}/gi +export const COMMON_VARIABLE_REGEX = /\{\{([\w-]{1,50})\}\}/g diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx index 1c6158a60e..9b541b9ea6 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx @@ -1,16 +1,16 @@ +import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { noop } from 'lodash-es' import { useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' +import Collapse from '@/app/components/workflow/nodes/_base/components/collapse' +import { MetadataFilteringModeEnum } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import MetadataTrigger from '../metadata-trigger' import MetadataFilterSelector from './metadata-filter-selector' -import Collapse from '@/app/components/workflow/nodes/_base/components/collapse' -import Tooltip from '@/app/components/base/tooltip' -import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' -import { MetadataFilteringModeEnum } from '@/app/components/workflow/nodes/knowledge-retrieval/types' -import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import { noop } from 'lodash-es' type MetadataFilterProps = { metadataFilterMode?: MetadataFilteringModeEnum @@ -41,28 +41,28 @@ const MetadataFilter = ({ onCollapse={setCollapsed} hideCollapseIcon trigger={collapseIcon => ( - <div className='flex grow items-center justify-between pr-4'> - <div className='flex items-center'> - <div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'> + <div className="flex grow items-center justify-between pr-4"> + <div className="flex items-center"> + <div className="system-sm-semibold-uppercase mr-0.5 text-text-secondary"> {t('workflow.nodes.knowledgeRetrieval.metadata.title')} </div> <Tooltip popupContent={( - <div className='w-[200px]'> + <div className="w-[200px]"> {t('workflow.nodes.knowledgeRetrieval.metadata.tip')} </div> )} /> {collapseIcon} </div> - <div className='flex items-center'> + <div className="flex items-center"> <MetadataFilterSelector value={metadataFilterMode} onSelect={handleMetadataFilterModeChangeWrapped} /> { metadataFilterMode === MetadataFilteringModeEnum.manual && ( - <div className='ml-1'> + <div className="ml-1"> <MetadataTrigger {...restProps} /> </div> ) @@ -75,13 +75,13 @@ const MetadataFilter = ({ { metadataFilterMode === MetadataFilteringModeEnum.automatic && ( <> - <div className='body-xs-regular px-4 text-text-tertiary'> + <div className="body-xs-regular px-4 text-text-tertiary"> {t('workflow.nodes.knowledgeRetrieval.metadata.options.automatic.desc')} </div> - <div className='mt-1 px-4'> + <div className="mt-1 px-4"> <ModelParameterModal - portalToFollowElemContentClassName='z-[50]' - popupClassName='!w-[387px]' + portalToFollowElemContentClassName="z-[50]" + popupClassName="!w-[387px]" isInWorkflow isAdvancedMode={true} provider={metadataModelConfig?.provider || ''} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/metadata-filter-selector.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/metadata-filter-selector.tsx index 7183e685f4..4bf52d7b34 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/metadata-filter-selector.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/metadata-filter-selector.tsx @@ -1,15 +1,15 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' import { RiArrowDownSLine, RiCheckLine, } from '@remixicon/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' import { MetadataFilteringModeEnum } from '@/app/components/workflow/nodes/knowledge-retrieval/types' type MetadataFilterSelectorProps = { @@ -44,7 +44,7 @@ const MetadataFilterSelector = ({ return ( <PortalToFollowElem - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 0, @@ -60,37 +60,37 @@ const MetadataFilterSelector = ({ asChild > <Button - variant='secondary' - size='small' + variant="secondary" + size="small" > {selectedOption.value} - <RiArrowDownSLine className='h-3.5 w-3.5' /> + <RiArrowDownSLine className="h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-10"> + <div className="w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.key} - className='flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover' + className="flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover" onClick={() => { onSelect(option.key) setOpen(false) }} > - <div className='w-4 shrink-0'> + <div className="w-4 shrink-0"> { option.key === value && ( - <RiCheckLine className='h-4 w-4 text-text-accent' /> + <RiCheckLine className="h-4 w-4 text-text-accent" /> ) } </div> - <div className='grow'> - <div className='system-sm-semibold text-text-secondary'> + <div className="grow"> + <div className="system-sm-semibold text-text-secondary"> {option.value} </div> - <div className='system-xs-regular text-text-tertiary'> + <div className="system-xs-regular text-text-tertiary"> {option.desc} </div> </div> diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx index 4c327ce882..803a836142 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-icon.tsx @@ -1,9 +1,9 @@ -import { memo } from 'react' import { RiHashtag, RiTextSnippet, RiTimeLine, } from '@remixicon/react' +import { memo } from 'react' import { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-panel.tsx index fd390abe6b..9e0444ac29 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-panel.tsx @@ -1,8 +1,8 @@ -import { useTranslation } from 'react-i18next' +import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import { RiCloseLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import AddCondition from './add-condition' import ConditionList from './condition-list' -import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' type MetadataPanelProps = { onCancel: () => void @@ -17,21 +17,21 @@ const MetadataPanel = ({ const { t } = useTranslation() return ( - <div className='w-[420px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl'> - <div className='relative px-3 pt-3.5'> - <div className='system-xl-semibold text-text-primary'> + <div className="w-[420px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl"> + <div className="relative px-3 pt-3.5"> + <div className="system-xl-semibold text-text-primary"> {t('workflow.nodes.knowledgeRetrieval.metadata.panel.title')} </div> <div - className='absolute bottom-0 right-2.5 flex h-8 w-8 cursor-pointer items-center justify-center' + className="absolute bottom-0 right-2.5 flex h-8 w-8 cursor-pointer items-center justify-center" onClick={onCancel} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> - <div className='px-1 py-2'> - <div className='px-3 py-1'> - <div className='pb-2'> + <div className="px-1 py-2"> + <div className="px-3 py-1"> + <div className="pb-2"> <ConditionList metadataList={metadataList} metadataFilteringConditions={metadataFilteringConditions} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx index 5d11bc7c6c..3a8d96f8f2 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-trigger.tsx @@ -1,17 +1,17 @@ +import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import { RiFilter3Line } from '@remixicon/react' import { useEffect, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiFilter3Line } from '@remixicon/react' -import MetadataPanel from './metadata-panel' import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import MetadataPanel from './metadata-panel' const MetadataTrigger = ({ metadataFilteringConditions, @@ -35,24 +35,24 @@ const MetadataTrigger = ({ return ( <PortalToFollowElem - placement='left' + placement="left" offset={4} open={open} onOpenChange={setOpen} > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> <Button - variant='secondary-accent' - size='small' + variant="secondary-accent" + size="small" > - <RiFilter3Line className='mr-1 h-3.5 w-3.5' /> + <RiFilter3Line className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.knowledgeRetrieval.metadata.panel.conditions')} - <div className='system-2xs-medium-uppercase ml-1 flex items-center rounded-[5px] border border-divider-deep px-1 text-text-tertiary'> + <div className="system-2xs-medium-uppercase ml-1 flex items-center rounded-[5px] border border-divider-deep px-1 text-text-tertiary"> {metadataFilteringConditions?.conditions.length || 0} </div> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> + <PortalToFollowElemContent className="z-10"> <MetadataPanel metadataFilteringConditions={metadataFilteringConditions} onCancel={() => setOpen(false)} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx index 80587b864f..ced1bfcdae 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' -import { RiEqualizer2Line } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' import type { ModelConfig } from '../../../types' -import { cn } from '@/utils/classnames' +import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' +import type { DataSet } from '@/models/datasets' +import type { DatasetConfigs } from '@/models/debug' +import { RiEqualizer2Line } from '@remixicon/react' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import ConfigRetrievalContent from '@/app/components/app/configuration/dataset-config/params-config/config-content' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import ConfigRetrievalContent from '@/app/components/app/configuration/dataset-config/params-config/config-content' -import { RETRIEVE_TYPE } from '@/types/app' import { DATASET_DEFAULT } from '@/config' -import Button from '@/app/components/base/button' -import type { DatasetConfigs } from '@/models/debug' -import type { DataSet } from '@/models/datasets' +import { RETRIEVE_TYPE } from '@/types/app' +import { cn } from '@/utils/classnames' type Props = { payload: { @@ -68,13 +68,13 @@ const RetrievalConfig: FC<Props> = ({ retrieval_model: retrieval_mode, reranking_model: (reranking_model?.provider && reranking_model?.model) ? { - reranking_provider_name: reranking_model?.provider, - reranking_model_name: reranking_model?.model, - } + reranking_provider_name: reranking_model?.provider, + reranking_model_name: reranking_model?.model, + } : { - reranking_provider_name: '', - reranking_model_name: '', - }, + reranking_provider_name: '', + reranking_model_name: '', + }, top_k: top_k || DATASET_DEFAULT.top_k, score_threshold_enabled: !(score_threshold === undefined || score_threshold === null), score_threshold, @@ -100,11 +100,11 @@ const RetrievalConfig: FC<Props> = ({ ? undefined // eslint-disable-next-line sonarjs/no-nested-conditional : (!configs.reranking_model?.reranking_provider_name - ? undefined - : { - provider: configs.reranking_model?.reranking_provider_name, - model: configs.reranking_model?.reranking_model_name, - }), + ? undefined + : { + provider: configs.reranking_model?.reranking_provider_name, + model: configs.reranking_model?.reranking_model_name, + }), reranking_mode: configs.reranking_mode, weights: configs.weights, reranking_enable: configs.reranking_enable, @@ -115,7 +115,7 @@ const RetrievalConfig: FC<Props> = ({ <PortalToFollowElem open={rerankModalOpen} onOpenChange={handleOpen} - placement='bottom-end' + placement="bottom-end" offset={{ crossAxis: -2, }} @@ -128,17 +128,17 @@ const RetrievalConfig: FC<Props> = ({ }} > <Button - variant='ghost' - size='small' + variant="ghost" + size="small" disabled={readonly} className={cn(rerankModalOpen && 'bg-components-button-ghost-bg-hover')} > - <RiEqualizer2Line className='mr-1 h-3.5 w-3.5' /> + <RiEqualizer2Line className="mr-1 h-3.5 w-3.5" /> {t('dataset.retrievalSettings')} </Button> </PortalToFollowElemTrigger> <PortalToFollowElemContent style={{ zIndex: 1001 }}> - <div className='w-[404px] rounded-2xl border border-components-panel-border bg-components-panel-bg px-4 pb-4 pt-3 shadow-xl'> + <div className="w-[404px] rounded-2xl border border-components-panel-border bg-components-panel-bg px-4 pb-4 pt-3 shadow-xl"> <ConfigRetrievalContent datasetConfigs={datasetConfigs} onChange={handleChange} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts index 72d67a1a4f..5e7798f1c6 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts @@ -1,10 +1,11 @@ import type { NodeDefault } from '../../types' import type { KnowledgeRetrievalNodeType } from './types' -import { checkoutRerankModelConfiguredInRetrievalSettings } from './utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' import { DATASET_DEFAULT } from '@/config' import { RETRIEVE_TYPE } from '@/types/app' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import { checkoutRerankModelConfiguredInRetrievalSettings } from './utils' + const i18nPrefix = 'workflow' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/hooks.ts b/web/app/components/workflow/nodes/knowledge-retrieval/hooks.ts index 139ac87382..00dcb4939b 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/hooks.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/hooks.ts @@ -1,9 +1,9 @@ -import { useMemo } from 'react' -import { getSelectedDatasetsMode } from './utils' import type { DataSet, SelectedDatasetsMode, } from '@/models/datasets' +import { useMemo } from 'react' +import { getSelectedDatasetsMode } from './utils' export const useSelectedDatasetsMode = (datasets: DataSet[]) => { const selectedDatasetsMode: SelectedDatasetsMode = useMemo(() => { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx index da84baf201..55715f2fb0 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx @@ -1,10 +1,10 @@ -import { type FC, useEffect, useState } from 'react' -import React from 'react' +import type { FC } from 'react' import type { KnowledgeRetrievalNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' import type { DataSet } from '@/models/datasets' -import { useDatasetsDetailStore } from '../../datasets-detail-store/store' +import React, { useEffect, useState } from 'react' import AppIcon from '@/app/components/base/app-icon' +import { useDatasetsDetailStore } from '../../datasets-detail-store/store' const Node: FC<NodeProps<KnowledgeRetrievalNodeType>> = ({ data, @@ -30,19 +30,19 @@ const Node: FC<NodeProps<KnowledgeRetrievalNodeType>> = ({ return null return ( - <div className='mb-1 px-3 py-1'> - <div className='space-y-0.5'> + <div className="mb-1 px-3 py-1"> + <div className="space-y-0.5"> {selectedDatasets.map(({ id, name, icon_info }) => ( - <div key={id} className='flex h-[26px] items-center gap-x-1 rounded-md bg-workflow-block-parma-bg px-1'> + <div key={id} className="flex h-[26px] items-center gap-x-1 rounded-md bg-workflow-block-parma-bg px-1"> <AppIcon - size='xs' + size="xs" iconType={icon_info.icon_type} icon={icon_info.icon} background={icon_info.icon_type === 'image' ? undefined : icon_info.icon_background} imageUrl={icon_info.icon_type === 'image' ? icon_info.icon_url : undefined} - className='shrink-0' + className="shrink-0" /> - <div className='system-xs-regular w-0 grow truncate text-text-secondary'> + <div className="system-xs-regular w-0 grow truncate text-text-secondary"> {name} </div> </div> diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index 0d46e2ebac..ff5a9e2292 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -1,21 +1,21 @@ import type { FC } from 'react' +import type { KnowledgeRetrievalNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' +import { intersectionBy } from 'lodash-es' import { memo, useMemo, } from 'react' -import { intersectionBy } from 'lodash-es' import { useTranslation } from 'react-i18next' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import useConfig from './use-config' -import RetrievalConfig from './components/retrieval-config' import AddKnowledge from './components/add-dataset' import DatasetList from './components/dataset-list' import MetadataFilter from './components/metadata/metadata-filter' -import type { KnowledgeRetrievalNodeType } from './types' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import type { NodePanelProps } from '@/app/components/workflow/types' +import RetrievalConfig from './components/retrieval-config' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.knowledgeRetrieval' @@ -64,8 +64,8 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ }, [selectedDatasets]) return ( - <div className='pt-2'> - <div className='space-y-4 px-4 pb-2'> + <div className="pt-2"> + <div className="space-y-4 px-4 pb-2"> <Field title={t(`${i18nPrefix}.queryText`)}> <VarReferencePicker nodeId={id} @@ -93,8 +93,8 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ <Field title={t(`${i18nPrefix}.knowledge`)} required - operations={ - <div className='flex items-center space-x-1'> + operations={( + <div className="flex items-center space-x-1"> <RetrievalConfig payload={{ retrieval_mode: inputs.retrieval_mode, @@ -111,7 +111,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ onRerankModelOpenChange={setRerankModelOpen} selectedDatasets={selectedDatasets} /> - {!readOnly && (<div className='h-3 w-px bg-divider-regular'></div>)} + {!readOnly && (<div className="h-3 w-px bg-divider-regular"></div>)} {!readOnly && ( <AddKnowledge selectedIds={inputs.dataset_ids} @@ -119,7 +119,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ /> )} </div> - } + )} > <DatasetList list={selectedDatasets} @@ -128,7 +128,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ /> </Field> </div> - <div className='mb-2 py-2'> + <div className="mb-2 py-2"> <MetadataFilter metadataList={metadataList} selectedDatasetsLoaded={selectedDatasetsLoaded} @@ -153,8 +153,8 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ <OutputVars> <> <VarItem - name='result' - type='Array[Object]' + name="result" + type="Array[Object]" description={t(`${i18nPrefix}.outputVars.output`)} subItems={[ { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts index 4e9b19dbe2..3b62a1e83f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/types.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/types.ts @@ -5,13 +5,13 @@ import type { NodeOutPutVar, ValueSelector, } from '@/app/components/workflow/types' -import type { RETRIEVE_TYPE } from '@/types/app' import type { DataSet, MetadataInDoc, RerankingModeEnum, WeightedScoreEnum, } from '@/models/datasets' +import type { RETRIEVE_TYPE } from '@/types/app' export type MultipleRetrievalConfig = { top_k: number @@ -122,13 +122,13 @@ export type MetadataShape = { handleToggleConditionLogicalOperator: HandleToggleConditionLogicalOperator handleUpdateCondition: HandleUpdateCondition metadataModelConfig?: ModelConfig - handleMetadataModelChange?: (model: { modelId: string; provider: string; mode?: string; features?: string[] }) => void + handleMetadataModelChange?: (model: { modelId: string, provider: string, mode?: string, features?: string[] }) => void handleMetadataCompletionParamsChange?: (params: Record<string, any>) => void availableStringVars?: NodeOutPutVar[] availableStringNodesWithParent?: Node[] availableNumberVars?: NodeOutPutVar[] availableNumberNodesWithParent?: Node[] isCommonVariable?: boolean - availableCommonStringVars?: { name: string; type: string; value: string }[] - availableCommonNumberVars?: { name: string; type: string; value: string }[] + availableCommonStringVars?: { name: string, type: string, value: string }[] + availableCommonNumberVars?: { name: string, type: string, value: string }[] } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts index 94c28f680b..d0846b3a34 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts @@ -1,20 +1,4 @@ -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { produce } from 'immer' -import { isEqual } from 'lodash-es' -import { v4 as uuid4 } from 'uuid' import type { ValueSelector, Var } from '../../types' -import { BlockEnum, VarType } from '../../types' -import { - useIsChatMode, - useNodesReadOnly, - useWorkflow, -} from '../../hooks' import type { HandleAddCondition, HandleRemoveCondition, @@ -24,6 +8,31 @@ import type { MetadataFilteringModeEnum, MultipleRetrievalConfig, } from './types' +import type { DataSet } from '@/models/datasets' +import { produce } from 'immer' +import { isEqual } from 'lodash-es' +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { v4 as uuid4 } from 'uuid' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { DATASET_DEFAULT } from '@/config' +import { fetchDatasets } from '@/service/datasets' +import { AppModeEnum, RETRIEVE_TYPE } from '@/types/app' +import { useDatasetsDetailStore } from '../../datasets-detail-store/store' +import { + useIsChatMode, + useNodesReadOnly, + useWorkflow, +} from '../../hooks' +import { BlockEnum, VarType } from '../../types' import { ComparisonOperator, LogicalOperator, @@ -33,15 +42,6 @@ import { getMultipleRetrievalConfig, getSelectedDatasetsMode, } from './utils' -import { AppModeEnum, RETRIEVE_TYPE } from '@/types/app' -import { DATASET_DEFAULT } from '@/config' -import type { DataSet } from '@/models/datasets' -import { fetchDatasets } from '@/service/datasets' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import { useDatasetsDetailStore } from '../../datasets-detail-store/store' const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() @@ -97,13 +97,13 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { rerankModelList, rerankDefaultModel ? { - ...rerankDefaultModel, - provider: rerankDefaultModel.provider.provider, - } + ...rerankDefaultModel, + provider: rerankDefaultModel.provider.provider, + } : undefined, ) - const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => { + const handleModelChanged = useCallback((model: { provider: string, modelId: string, mode?: string }) => { const newInputs = produce(inputRef.current, (draft) => { if (!draft.single_retrieval_config) { draft.single_retrieval_config = { @@ -282,8 +282,9 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { (allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel)) || mixtureInternalAndExternal || allExternal - ) + ) { setRerankModelOpen(true) + } }, [inputs, setInputs, payload.retrieval_mode, selectedDatasets, currentRerankModel, currentRerankProvider, updateDatasetsDetail]) const filterStringVar = useCallback((varPayload: Var) => { @@ -359,7 +360,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { setInputs(newInputs) }, [setInputs]) - const handleMetadataModelChange = useCallback((model: { provider: string; modelId: string; mode?: string }) => { + const handleMetadataModelChange = useCallback((model: { provider: string, modelId: string, mode?: string }) => { const newInputs = produce(inputRef.current, (draft) => { draft.metadata_model_config = { provider: model.provider, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts index 0f079bcee8..3ffd3c87d6 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-single-run-form-params.ts @@ -1,19 +1,19 @@ import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' -import type { InputVar, Var, Variable } from '@/app/components/workflow/types' -import { InputVarType, VarType } from '@/app/components/workflow/types' -import { useCallback, useMemo } from 'react' import type { KnowledgeRetrievalNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import { useDatasetsDetailStore } from '../../datasets-detail-store/store' +import type { InputVar, Var, Variable } from '@/app/components/workflow/types' import type { DataSet } from '@/models/datasets' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { InputVarType, VarType } from '@/app/components/workflow/types' +import { useDatasetsDetailStore } from '../../datasets-detail-store/store' import useAvailableVarList from '../_base/hooks/use-available-var-list' import { findVariableWhenOnLLMVision } from '../utils' const i18nPrefix = 'workflow.nodes.knowledgeRetrieval' type Params = { - id: string, + id: string payload: KnowledgeRetrievalNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts index 719aa57f2f..12cf8c053c 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts @@ -1,19 +1,19 @@ -import { - uniq, - xorBy, -} from 'lodash-es' import type { MultipleRetrievalConfig } from './types' import type { DataSet, SelectedDatasetsMode, } from '@/models/datasets' +import { + uniq, + xorBy, +} from 'lodash-es' +import { DATASET_DEFAULT } from '@/config' import { DEFAULT_WEIGHTED_SCORE, RerankingModeEnum, WeightedScoreEnum, } from '@/models/datasets' import { RETRIEVE_METHOD } from '@/types/app' -import { DATASET_DEFAULT } from '@/config' export const checkNodeValid = () => { return true @@ -94,7 +94,7 @@ export const getMultipleRetrievalConfig = ( multipleRetrievalConfig: MultipleRetrievalConfig, selectedDatasets: DataSet[], originalDatasets: DataSet[], - fallbackRerankModel?: { provider?: string; model?: string }, // fallback rerank model + fallbackRerankModel?: { provider?: string, model?: string }, // fallback rerank model ) => { // Check if the selected datasets are different from the original datasets const isDatasetsChanged = xorBy(selectedDatasets, originalDatasets, 'id').length > 0 diff --git a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx index 66f23829e8..7d4b472fd3 100644 --- a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import type { Var } from '../../../types' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import { VarType } from '../../../types' -import type { Var } from '../../../types' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import { cn } from '@/utils/classnames' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import { VarType } from '../../../types' type Props = { nodeId: string @@ -32,9 +32,9 @@ const ExtractInput: FC<Props> = ({ }) return ( - <div className='flex items-start space-x-1'> + <div className="flex items-start space-x-1"> <Input - instanceId='http-extract-number' + instanceId="http-extract-number" className={cn(isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' : 'border-components-input-border-hover bg-components-input-bg-normal', 'w-0 grow rounded-lg border px-3 py-[6px]')} value={value} onChange={onChange} @@ -43,9 +43,9 @@ const ExtractInput: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={setIsFocus} placeholder={!readOnly ? t('workflow.nodes.http.extractListPlaceholder')! : ''} - placeholderClassName='!leading-[21px]' + placeholderClassName="!leading-[21px]" /> - </div > + </div> ) } export default React.memo(ExtractInput) diff --git a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx index a51aefe9c6..d77a0c3eb3 100644 --- a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' +import type { Condition } from '../types' import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { SimpleSelect as Select } from '@/app/components/base/select' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants' +import { getConditionValueAsString } from '@/app/components/workflow/nodes/utils' +import { cn } from '@/utils/classnames' +import BoolValue from '../../../panel/chat-variable-panel/components/bool-value' +import { VarType } from '../../../types' import ConditionOperator from '../../if-else/components/condition-list/condition-operator' -import type { Condition } from '../types' import { ComparisonOperator } from '../../if-else/types' import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils' import SubVariablePicker from './sub-variable-picker' -import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants' -import { SimpleSelect as Select } from '@/app/components/base/select' -import BoolValue from '../../../panel/chat-variable-panel/components/bool-value' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import { cn } from '@/utils/classnames' -import { VarType } from '../../../types' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' -import { getConditionValueAsString } from '@/app/components/workflow/nodes/utils' const VAR_INPUT_SUPPORTED_KEYS: Record<string, VarType> = { name: VarType.string, @@ -108,22 +108,24 @@ const FilterCondition: FC<Props> = ({ items={selectOptions} defaultValue={isArrayValue ? (condition.value as string[])[0] : condition.value as string} onSelect={item => handleChange('value')(item.value)} - className='!text-[13px]' - wrapperClassName='grow h-8' - placeholder='Select value' + className="!text-[13px]" + wrapperClassName="grow h-8" + placeholder="Select value" /> ) } else if (isBoolean) { - inputElement = (<BoolValue - value={condition.value as boolean} - onChange={handleChange('value')} - />) + inputElement = ( + <BoolValue + value={condition.value as boolean} + onChange={handleChange('value')} + /> + ) } else if (supportVariableInput) { inputElement = ( <Input - instanceId='filter-condition-input' + instanceId="filter-condition-input" className={cn( isFocus ? 'border-components-input-border-active bg-components-input-bg-active shadow-xs' @@ -139,7 +141,7 @@ const FilterCondition: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={setIsFocus} placeholder={!readOnly ? t('workflow.nodes.http.insertVarPlaceholder')! : ''} - placeholderClassName='!leading-[21px]' + placeholderClassName="!leading-[21px]" /> ) } @@ -147,7 +149,7 @@ const FilterCondition: FC<Props> = ({ inputElement = ( <input type={((hasSubVariable && condition.key === 'size') || (!hasSubVariable && varType === VarType.number)) ? 'number' : 'text'} - className='grow rounded-lg border border-components-input-border-hover bg-components-input-bg-normal px-3 py-[6px]' + className="grow rounded-lg border border-components-input-border-hover bg-components-input-bg-normal px-3 py-[6px]" value={ getConditionValueAsString(condition) } @@ -167,9 +169,9 @@ const FilterCondition: FC<Props> = ({ onChange={handleSubVariableChange} /> )} - <div className='flex space-x-1'> + <div className="flex space-x-1"> <ConditionOperator - className='h-8 bg-components-input-bg-normal' + className="h-8 bg-components-input-bg-normal" varType={expectedVarType ?? varType ?? VarType.string} value={condition.comparison_operator} onSelect={handleChange('comparison_operator')} diff --git a/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx b/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx index 6a54eac8ab..f7356b58aa 100644 --- a/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import type { Limit } from '../types' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { Limit } from '../types' -import InputNumberWithSlider from '../../_base/components/input-number-with-slider' -import { cn } from '@/utils/classnames' -import Field from '@/app/components/workflow/nodes/_base/components/field' import Switch from '@/app/components/base/switch' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { cn } from '@/utils/classnames' +import InputNumberWithSlider from '../../_base/components/input-number-with-slider' const i18nPrefix = 'workflow.nodes.listFilter' const LIMIT_SIZE_MIN = 1 @@ -53,25 +53,25 @@ const LimitConfig: FC<Props> = ({ <div className={cn(className)}> <Field title={t(`${i18nPrefix}.limit`)} - operations={ + operations={( <Switch defaultValue={payload.enabled} onChange={handleLimitEnabledChange} - size='md' + size="md" disabled={readonly} /> - } + )} > {payload?.enabled ? ( - <InputNumberWithSlider - value={payload?.size || LIMIT_SIZE_DEFAULT} - min={LIMIT_SIZE_MIN} - max={LIMIT_SIZE_MAX} - onChange={handleLimitSizeChange} - readonly={readonly || !payload?.enabled} - /> - ) + <InputNumberWithSlider + value={payload?.size || LIMIT_SIZE_DEFAULT} + min={LIMIT_SIZE_MIN} + max={LIMIT_SIZE_MAX} + onChange={handleLimitSizeChange} + readonly={readonly || !payload?.enabled} + /> + ) : null} </Field> </div> diff --git a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx index 9d835436d9..ee8703ca6c 100644 --- a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import type { Item } from '@/app/components/base/select' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { SUB_VARIABLES } from '../../constants' -import type { Item } from '@/app/components/base/select' -import { SimpleSelect as Select } from '@/app/components/base/select' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { SimpleSelect as Select } from '@/app/components/base/select' import { cn } from '@/utils/classnames' +import { SUB_VARIABLES } from '../../constants' type Props = { value: string @@ -27,12 +27,12 @@ const SubVariablePicker: FC<Props> = ({ const renderOption = ({ item }: { item: Record<string, any> }) => { return ( - <div className='flex h-6 items-center justify-between'> - <div className='flex h-full items-center'> - <Variable02 className='mr-[5px] h-3.5 w-3.5 text-text-accent' /> - <span className='system-sm-medium text-text-secondary'>{item.name}</span> + <div className="flex h-6 items-center justify-between"> + <div className="flex h-full items-center"> + <Variable02 className="mr-[5px] h-3.5 w-3.5 text-text-accent" /> + <span className="system-sm-medium text-text-secondary">{item.name}</span> </div> - <span className='system-xs-regular text-text-tertiary'>{item.type}</span> + <span className="system-xs-regular text-text-tertiary">{item.type}</span> </div> ) } @@ -47,23 +47,27 @@ const SubVariablePicker: FC<Props> = ({ items={subVarOptions} defaultValue={value} onSelect={handleChange} - className='!text-[13px]' + className="!text-[13px]" placeholder={t('workflow.nodes.listFilter.selectVariableKeyPlaceholder')!} - optionClassName='pl-1 pr-5 py-0' + optionClassName="pl-1 pr-5 py-0" renderOption={renderOption} renderTrigger={item => ( - <div className='group/sub-variable-picker flex h-8 items-center rounded-lg bg-components-input-bg-normal pl-1 hover:bg-state-base-hover-alt'> + <div className="group/sub-variable-picker flex h-8 items-center rounded-lg bg-components-input-bg-normal pl-1 hover:bg-state-base-hover-alt"> {item - ? <div className='flex cursor-pointer justify-start'> - <div className='inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs'> - <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' /> - <div className='system-xs-medium ml-0.5 truncate'>{item?.name}</div> - </div> - </div> - : <div className='system-sm-regular flex pl-1 text-components-input-text-placeholder group-hover/sub-variable-picker:text-text-tertiary'> - <Variable02 className='mr-1 h-4 w-4 shrink-0' /> - <span>{t('common.placeholder.select')}</span> - </div>} + ? ( + <div className="flex cursor-pointer justify-start"> + <div className="inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs"> + <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" /> + <div className="system-xs-medium ml-0.5 truncate">{item?.name}</div> + </div> + </div> + ) + : ( + <div className="system-sm-regular flex pl-1 text-components-input-text-placeholder group-hover/sub-variable-picker:text-text-tertiary"> + <Variable02 className="mr-1 h-4 w-4 shrink-0" /> + <span>{t('common.placeholder.select')}</span> + </div> + )} </div> )} /> diff --git a/web/app/components/workflow/nodes/list-operator/default.ts b/web/app/components/workflow/nodes/list-operator/default.ts index 816e225046..3ab7ce0d31 100644 --- a/web/app/components/workflow/nodes/list-operator/default.ts +++ b/web/app/components/workflow/nodes/list-operator/default.ts @@ -1,9 +1,11 @@ -import { BlockEnum, VarType } from '../../types' import type { NodeDefault } from '../../types' -import { comparisonOperatorNotRequireValue } from '../if-else/utils' -import { type ListFilterNodeType, OrderBy } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' +import type { ListFilterNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum, VarType } from '../../types' +import { comparisonOperatorNotRequireValue } from '../if-else/utils' +import { OrderBy } from './types' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/list-operator/node.tsx b/web/app/components/workflow/nodes/list-operator/node.tsx index 3c59f36587..4d02595fcf 100644 --- a/web/app/components/workflow/nodes/list-operator/node.tsx +++ b/web/app/components/workflow/nodes/list-operator/node.tsx @@ -1,13 +1,14 @@ import type { FC } from 'react' -import React from 'react' -import { useNodes } from 'reactflow' -import { useTranslation } from 'react-i18next' import type { ListFilterNodeType } from './types' +import type { Node, NodeProps } from '@/app/components/workflow/types' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { BlockEnum, type Node, type NodeProps } from '@/app/components/workflow/types' import { VariableLabelInNode, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.listFilter' @@ -25,8 +26,8 @@ const NodeComponent: FC<NodeProps<ListFilterNodeType>> = ({ const isSystem = isSystemVar(variable) const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0]) return ( - <div className='relative px-3'> - <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div> + <div className="relative px-3"> + <div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t(`${i18nPrefix}.inputVar`)}</div> <VariableLabelInNode variables={variable} nodeType={node?.data.type} diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index e76befcac0..be1d79dcad 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -1,19 +1,20 @@ import type { FC } from 'react' +import type { ListFilterNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import OutputVars, { VarItem } from '../_base/components/output-vars' -import OptionCard from '../_base/components/option-card' -import Split from '../_base/components/split' -import useConfig from './use-config' -import SubVariablePicker from './components/sub-variable-picker' -import { type ListFilterNodeType, OrderBy } from './types' -import LimitConfig from './components/limit-config' -import FilterCondition from './components/filter-condition' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import type { NodePanelProps } from '@/app/components/workflow/types' import Switch from '@/app/components/base/switch' +import Field from '@/app/components/workflow/nodes/_base/components/field' import ExtractInput from '@/app/components/workflow/nodes/list-operator/components/extract-input' +import OptionCard from '../_base/components/option-card' +import OutputVars, { VarItem } from '../_base/components/output-vars' +import Split from '../_base/components/split' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' +import FilterCondition from './components/filter-condition' +import LimitConfig from './components/limit-config' +import SubVariablePicker from './components/sub-variable-picker' +import { OrderBy } from './types' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.listFilter' @@ -42,8 +43,8 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ } = useConfig(id, data) return ( - <div className='pt-2'> - <div className='space-y-4 px-4'> + <div className="pt-2"> + <div className="space-y-4 px-4"> <Field title={t(`${i18nPrefix}.inputVar`)} required @@ -56,59 +57,59 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ onChange={handleVarChanges} filterVar={filterVar} isSupportFileVar={false} - typePlaceHolder='Array' + typePlaceHolder="Array" /> </Field> <Field title={t(`${i18nPrefix}.filterCondition`)} - operations={ + operations={( <Switch defaultValue={inputs.filter_by?.enabled} onChange={handleFilterEnabledChange} - size='md' + size="md" disabled={readOnly} /> - } + )} > {inputs.filter_by?.enabled ? ( - <FilterCondition - condition={inputs.filter_by.conditions[0]} - onChange={handleFilterChange} - varType={itemVarType} - hasSubVariable={hasSubVariable} - readOnly={readOnly} - nodeId={id} - /> - ) + <FilterCondition + condition={inputs.filter_by.conditions[0]} + onChange={handleFilterChange} + varType={itemVarType} + hasSubVariable={hasSubVariable} + readOnly={readOnly} + nodeId={id} + /> + ) : null} </Field> <Split /> <Field title={t(`${i18nPrefix}.extractsCondition`)} - operations={ + operations={( <Switch defaultValue={inputs.extract_by?.enabled} onChange={handleExtractsEnabledChange} - size='md' + size="md" disabled={readOnly} /> - } + )} > {inputs.extract_by?.enabled ? ( - <div className='flex items-center justify-between'> - <div className='mr-2 grow'> - <ExtractInput - value={inputs.extract_by.serial as string} - onChange={handleExtractsChange} - readOnly={readOnly} - nodeId={id} - /> + <div className="flex items-center justify-between"> + <div className="mr-2 grow"> + <ExtractInput + value={inputs.extract_by.serial as string} + onChange={handleExtractsChange} + readOnly={readOnly} + nodeId={id} + /> + </div> </div> - </div> - ) + ) : null} </Field> <Split /> @@ -120,40 +121,40 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ <Split /> <Field title={t(`${i18nPrefix}.orderBy`)} - operations={ + operations={( <Switch defaultValue={inputs.order_by?.enabled} onChange={handleOrderByEnabledChange} - size='md' + size="md" disabled={readOnly} /> - } + )} > {inputs.order_by?.enabled ? ( - <div className='flex items-center justify-between'> - {hasSubVariable && ( - <div className='mr-2 grow'> - <SubVariablePicker - value={inputs.order_by.key as string} - onChange={handleOrderByKeyChange} + <div className="flex items-center justify-between"> + {hasSubVariable && ( + <div className="mr-2 grow"> + <SubVariablePicker + value={inputs.order_by.key as string} + onChange={handleOrderByKeyChange} + /> + </div> + )} + <div className={!hasSubVariable ? 'grid w-full grid-cols-2 gap-1' : 'flex shrink-0 space-x-1'}> + <OptionCard + title={t(`${i18nPrefix}.asc`)} + onSelect={handleOrderByTypeChange(OrderBy.ASC)} + selected={inputs.order_by.value === OrderBy.ASC} + /> + <OptionCard + title={t(`${i18nPrefix}.desc`)} + onSelect={handleOrderByTypeChange(OrderBy.DESC)} + selected={inputs.order_by.value === OrderBy.DESC} /> </div> - )} - <div className={!hasSubVariable ? 'grid w-full grid-cols-2 gap-1' : 'flex shrink-0 space-x-1'}> - <OptionCard - title={t(`${i18nPrefix}.asc`)} - onSelect={handleOrderByTypeChange(OrderBy.ASC)} - selected={inputs.order_by.value === OrderBy.ASC} - /> - <OptionCard - title={t(`${i18nPrefix}.desc`)} - onSelect={handleOrderByTypeChange(OrderBy.DESC)} - selected={inputs.order_by.value === OrderBy.DESC} - /> </div> - </div> - ) + ) : null} </Field> <Split /> @@ -162,17 +163,17 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({ <OutputVars> <> <VarItem - name='result' + name="result" type={`Array[${itemVarTypeShowName}]`} description={t(`${i18nPrefix}.outputVars.result`)} /> <VarItem - name='first_record' + name="first_record" type={itemVarTypeShowName} description={t(`${i18nPrefix}.outputVars.first_record`)} /> <VarItem - name='last_record' + name="last_record" type={itemVarTypeShowName} description={t(`${i18nPrefix}.outputVars.last_record`)} /> diff --git a/web/app/components/workflow/nodes/list-operator/use-config.ts b/web/app/components/workflow/nodes/list-operator/use-config.ts index eff249b717..72f92bfea4 100644 --- a/web/app/components/workflow/nodes/list-operator/use-config.ts +++ b/web/app/components/workflow/nodes/list-operator/use-config.ts @@ -1,18 +1,18 @@ -import { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' import type { ValueSelector, Var } from '../../types' -import { VarType } from '../../types' -import { getOperators } from '../if-else/utils' -import { OrderBy } from './types' import type { Condition, Limit, ListFilterNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback, useMemo } from 'react' +import { useStoreApi } from 'reactflow' import { useIsChatMode, useNodesReadOnly, useWorkflow, useWorkflowVariables, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { VarType } from '../../types' +import { getOperators } from '../if-else/utils' +import { OrderBy } from './types' const useConfig = (id: string, payload: ListFilterNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index 88ba95f76d..a712d6e408 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' +import type { ModelConfig, PromptItem, Variable } from '../../../types' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { ModelConfig, PromptItem, Variable } from '../../../types' -import { EditionType } from '../../../types' -import { useWorkflowStore } from '../../../store' +import Tooltip from '@/app/components/base/tooltip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' -import Tooltip from '@/app/components/base/tooltip' import { PromptRole } from '@/models/debug' +import { useWorkflowStore } from '../../../store' +import { EditionType } from '../../../types' const i18nPrefix = 'workflow.nodes.llm' @@ -99,31 +99,33 @@ const ConfigPromptItem: FC<Props> = ({ headerClassName={headerClassName} instanceId={instanceId} key={instanceId} - title={ - <div className='relative left-1 flex items-center'> + title={( + <div className="relative left-1 flex items-center"> {payload.role === PromptRole.system - ? (<div className='relative left-[-4px] text-xs font-semibold uppercase text-text-secondary'> - SYSTEM - </div>) + ? ( + <div className="relative left-[-4px] text-xs font-semibold uppercase text-text-secondary"> + SYSTEM + </div> + ) : ( - <TypeSelector - value={payload.role as string} - allOptions={roleOptions} - options={canNotChooseSystemRole ? roleOptionsWithoutSystemRole : roleOptions} - onChange={handleChatModeMessageRoleChange} - triggerClassName='text-xs font-semibold text-text-secondary uppercase' - itemClassName='text-[13px] font-medium text-text-secondary' - /> - )} + <TypeSelector + value={payload.role as string} + allOptions={roleOptions} + options={canNotChooseSystemRole ? roleOptionsWithoutSystemRole : roleOptions} + onChange={handleChatModeMessageRoleChange} + triggerClassName="text-xs font-semibold text-text-secondary uppercase" + itemClassName="text-[13px] font-medium text-text-secondary" + /> + )} <Tooltip popupContent={ - <div className='max-w-[180px]'>{t(`${i18nPrefix}.roleDescription.${payload.role}`)}</div> + <div className="max-w-[180px]">{t(`${i18nPrefix}.roleDescription.${payload.role}`)}</div> } - triggerClassName='w-4 h-4' + triggerClassName="w-4 h-4" /> </div> - } + )} value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text} onChange={onPromptChange} readOnly={readOnly} diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx index d44d299bc4..856b88ac00 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' +import type { ModelConfig, PromptItem, ValueSelector, Var, Variable } from '../../../types' +import { produce } from 'immer' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' -import type { ModelConfig, PromptItem, ValueSelector, Var, Variable } from '../../../types' +import { DragHandle } from '@/app/components/base/icons/src/vender/line/others' +import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' +import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' +import { cn } from '@/utils/classnames' +import { useWorkflowStore } from '../../../store' import { EditionType, PromptRole } from '../../../types' import useAvailableVarList from '../../_base/hooks/use-available-var-list' -import { useWorkflowStore } from '../../../store' import ConfigPromptItem from './config-prompt-item' -import { cn } from '@/utils/classnames' -import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' -import { DragHandle } from '@/app/components/base/icons/src/vender/line/others' const i18nPrefix = 'workflow.nodes.llm' @@ -57,15 +57,15 @@ const ConfigPrompt: FC<Props> = ({ } = workflowStore.getState() const payloadWithIds = (isChatModel && Array.isArray(payload)) ? payload.map((item) => { - const id = uuid4() - return { - id: item.id || id, - p: { - ...item, + const id = uuid4() + return { id: item.id || id, - }, - } - }) + p: { + ...item, + id: item.id || id, + }, + } + }) : [] const { availableVars, @@ -153,96 +153,97 @@ const ConfigPrompt: FC<Props> = ({ <div> {(isChatModel && Array.isArray(payload)) ? ( - <div> - <div className='space-y-2'> - <ReactSortable className="space-y-1" - list={payloadWithIds} - setList={(list) => { - if ((payload as PromptItem[])?.[0]?.role === PromptRole.system && list[0].p?.role !== PromptRole.system) - return + <div> + <div className="space-y-2"> + <ReactSortable + className="space-y-1" + list={payloadWithIds} + setList={(list) => { + if ((payload as PromptItem[])?.[0]?.role === PromptRole.system && list[0].p?.role !== PromptRole.system) + return - onChange(list.map(item => item.p)) - }} - handle='.handle' - ghostClass="opacity-50" - animation={150} - > - { - (payload as PromptItem[]).map((item, index) => { - const canDrag = (() => { - if (readOnly) - return false + onChange(list.map(item => item.p)) + }} + handle=".handle" + ghostClass="opacity-50" + animation={150} + > + { + (payload as PromptItem[]).map((item, index) => { + const canDrag = (() => { + if (readOnly) + return false - if (index === 0 && item.role === PromptRole.system) - return false + if (index === 0 && item.role === PromptRole.system) + return false - return true - })() - return ( - <div key={item.id || index} className='group relative'> - {canDrag && <DragHandle className='absolute left-[-14px] top-2 hidden h-3.5 w-3.5 text-text-quaternary group-hover:block' />} - <ConfigPromptItem - instanceId={item.role === PromptRole.system ? `${nodeId}-chat-workflow-llm-prompt-editor` : `${nodeId}-chat-workflow-llm-prompt-editor-${index}`} - className={cn(canDrag && 'handle')} - headerClassName={cn(canDrag && 'cursor-grab')} - canNotChooseSystemRole={!canChooseSystemRole} - canRemove={payload.length > 1 && !(index === 0 && item.role === PromptRole.system)} - readOnly={readOnly} - id={item.id!} - nodeId={nodeId} - handleChatModeMessageRoleChange={handleChatModeMessageRoleChange(index)} - isChatModel={isChatModel} - isChatApp={isChatApp} - payload={item} - onPromptChange={handleChatModePromptChange(index)} - onEditionTypeChange={handleChatModeEditionTypeChange(index)} - onRemove={handleRemove(index)} - isShowContext={isShowContext} - hasSetBlockStatus={hasSetBlockStatus} - availableVars={availableVars} - availableNodes={availableNodesWithParent} - varList={varList} - handleAddVariable={handleAddVariable} - modelConfig={modelConfig} - /> - </div> - ) - }) - } - </ReactSortable> + return true + })() + return ( + <div key={item.id || index} className="group relative"> + {canDrag && <DragHandle className="absolute left-[-14px] top-2 hidden h-3.5 w-3.5 text-text-quaternary group-hover:block" />} + <ConfigPromptItem + instanceId={item.role === PromptRole.system ? `${nodeId}-chat-workflow-llm-prompt-editor` : `${nodeId}-chat-workflow-llm-prompt-editor-${index}`} + className={cn(canDrag && 'handle')} + headerClassName={cn(canDrag && 'cursor-grab')} + canNotChooseSystemRole={!canChooseSystemRole} + canRemove={payload.length > 1 && !(index === 0 && item.role === PromptRole.system)} + readOnly={readOnly} + id={item.id!} + nodeId={nodeId} + handleChatModeMessageRoleChange={handleChatModeMessageRoleChange(index)} + isChatModel={isChatModel} + isChatApp={isChatApp} + payload={item} + onPromptChange={handleChatModePromptChange(index)} + onEditionTypeChange={handleChatModeEditionTypeChange(index)} + onRemove={handleRemove(index)} + isShowContext={isShowContext} + hasSetBlockStatus={hasSetBlockStatus} + availableVars={availableVars} + availableNodes={availableNodesWithParent} + varList={varList} + handleAddVariable={handleAddVariable} + modelConfig={modelConfig} + /> + </div> + ) + }) + } + </ReactSortable> + </div> + <AddButton + className="mt-2" + text={t(`${i18nPrefix}.addMessage`)} + onClick={handleAddPrompt} + /> </div> - <AddButton - className='mt-2' - text={t(`${i18nPrefix}.addMessage`)} - onClick={handleAddPrompt} - /> - </div> - ) + ) : ( - <div> - <Editor - instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`} - title={<span className='capitalize'>{t(`${i18nPrefix}.prompt`)}</span>} - value={((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')} - onChange={handleCompletionPromptChange} - readOnly={readOnly} - isChatModel={isChatModel} - isChatApp={isChatApp} - isShowContext={isShowContext} - hasSetBlockStatus={hasSetBlockStatus} - nodesOutputVars={availableVars} - availableNodes={availableNodesWithParent} - isSupportPromptGenerator - isSupportJinja - editionType={(payload as PromptItem).edition_type} - varList={varList} - onEditionTypeChange={handleCompletionEditionTypeChange} - handleAddVariable={handleAddVariable} - onGenerated={handleGenerated} - modelConfig={modelConfig} - /> - </div> - )} + <div> + <Editor + instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`} + title={<span className="capitalize">{t(`${i18nPrefix}.prompt`)}</span>} + value={((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')} + onChange={handleCompletionPromptChange} + readOnly={readOnly} + isChatModel={isChatModel} + isChatApp={isChatApp} + isShowContext={isShowContext} + hasSetBlockStatus={hasSetBlockStatus} + nodesOutputVars={availableVars} + availableNodes={availableNodesWithParent} + isSupportPromptGenerator + isSupportJinja + editionType={(payload as PromptItem).edition_type} + varList={varList} + onEditionTypeChange={handleCompletionEditionTypeChange} + handleAddVariable={handleAddVariable} + onGenerated={handleGenerated} + modelConfig={modelConfig} + /> + </div> + )} </div> ) } diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx index 3ca2162206..72620ee233 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx @@ -1,12 +1,13 @@ -import React, { type FC, useCallback, useEffect, useMemo, useRef } from 'react' -import useTheme from '@/hooks/use-theme' -import { Theme } from '@/types/app' -import { cn } from '@/utils/classnames' +import type { FC } from 'react' import { Editor } from '@monaco-editor/react' import { RiClipboardLine, RiIndentIncrease } from '@remixicon/react' import copy from 'copy-to-clipboard' -import Tooltip from '@/app/components/base/tooltip' +import React, { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import useTheme from '@/hooks/use-theme' +import { Theme } from '@/types/app' +import { cn } from '@/utils/classnames' type CodeEditorProps = { value: string @@ -114,28 +115,29 @@ const CodeEditor: FC<CodeEditorProps> = ({ return ( <div className={cn('flex h-full flex-col overflow-hidden bg-components-input-bg-normal', hideTopMenu && 'pt-2', className)}> {!hideTopMenu && ( - <div className='flex items-center justify-between pl-2 pr-1 pt-1'> - <div className='system-xs-semibold-uppercase py-0.5 text-text-secondary'> - <span className='px-1 py-0.5'>JSON</span> + <div className="flex items-center justify-between pl-2 pr-1 pt-1"> + <div className="system-xs-semibold-uppercase py-0.5 text-text-secondary"> + <span className="px-1 py-0.5">JSON</span> </div> - <div className='flex items-center gap-x-0.5'> + <div className="flex items-center gap-x-0.5"> {showFormatButton && ( <Tooltip popupContent={t('common.operation.format')}> <button - type='button' - className='flex h-6 w-6 items-center justify-center' + type="button" + className="flex h-6 w-6 items-center justify-center" onClick={formatJsonContent} > - <RiIndentIncrease className='h-4 w-4 text-text-tertiary' /> + <RiIndentIncrease className="h-4 w-4 text-text-tertiary" /> </button> </Tooltip> )} <Tooltip popupContent={t('common.operation.copy')}> <button - type='button' - className='flex h-6 w-6 items-center justify-center' - onClick={() => copy(value)}> - <RiClipboardLine className='h-4 w-4 text-text-tertiary' /> + type="button" + className="flex h-6 w-6 items-center justify-center" + onClick={() => copy(value)} + > + <RiClipboardLine className="h-4 w-4 text-text-tertiary" /> </button> </Tooltip> </div> @@ -144,7 +146,7 @@ const CodeEditor: FC<CodeEditorProps> = ({ {topContent} <div className={cn('relative overflow-hidden', editorWrapperClassName)}> <Editor - defaultLanguage='json' + defaultLanguage="json" theme={isMounted ? editorTheme : 'default-theme'} // sometimes not load the default theme value={value} onChange={handleEditorChange} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx index 937165df1c..5cb2a421d5 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx @@ -1,6 +1,6 @@ -import React from 'react' import type { FC } from 'react' import { RiErrorWarningFill } from '@remixicon/react' +import React from 'react' import { cn } from '@/utils/classnames' type ErrorMessageProps = { @@ -12,10 +12,9 @@ const ErrorMessage: FC<ErrorMessageProps> = ({ className, }) => { return ( - <div className={cn('mt-1 flex gap-x-1 rounded-lg border-[0.5px] border-components-panel-border bg-toast-error-bg p-2', - className)}> - <RiErrorWarningFill className='h-4 w-4 shrink-0 text-text-destructive' /> - <div className='system-xs-medium max-h-12 grow overflow-y-auto whitespace-pre-line break-words text-text-primary'> + <div className={cn('mt-1 flex gap-x-1 rounded-lg border-[0.5px] border-components-panel-border bg-toast-error-bg p-2', className)}> + <RiErrorWarningFill className="h-4 w-4 shrink-0 text-text-destructive" /> + <div className="system-xs-medium max-h-12 grow overflow-y-auto whitespace-pre-line break-words text-text-primary"> {message} </div> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx index d34836d5b2..b7a0a40f32 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx @@ -1,6 +1,7 @@ -import React, { type FC } from 'react' -import Modal from '../../../../../base/modal' +import type { FC } from 'react' import type { SchemaRoot } from '../../types' +import React from 'react' +import Modal from '../../../../../base/modal' import JsonSchemaConfig from './json-schema-config' type JsonSchemaConfigModalProps = { @@ -20,7 +21,7 @@ const JsonSchemaConfigModal: FC<JsonSchemaConfigModalProps> = ({ <Modal isShow={isShow} onClose={onClose} - className='h-[800px] max-w-[960px] p-0' + className="h-[800px] max-w-[960px] p-0" > <JsonSchemaConfig defaultSchema={defaultSchema} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx index 41539ec605..0110756b47 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx @@ -1,15 +1,16 @@ -import React, { type FC, useCallback, useEffect, useRef, useState } from 'react' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import { cn } from '@/utils/classnames' -import { useTranslation } from 'react-i18next' +import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import { checkJsonDepth } from '../../utils' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import { cn } from '@/utils/classnames' +import { checkJsonDepth } from '../../utils' import CodeEditor from './code-editor' import ErrorMessage from './error-message' -import { useVisualEditorStore } from './visual-editor/store' import { useMittContext } from './visual-editor/context' +import { useVisualEditorStore } from './visual-editor/store' type JsonImporterProps = { onSubmit: (schema: any) => void @@ -78,7 +79,7 @@ const JsonImporter: FC<JsonImporterProps> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 16, @@ -86,31 +87,31 @@ const JsonImporter: FC<JsonImporterProps> = ({ > <PortalToFollowElemTrigger ref={importBtnRef} onClick={handleTrigger}> <button - type='button' + type="button" className={cn( 'system-xs-medium flex shrink-0 rounded-md px-1.5 py-1 text-text-tertiary hover:bg-components-button-ghost-bg-hover', open && 'bg-components-button-ghost-bg-hover', )} > - <span className='px-0.5'>{t('workflow.nodes.llm.jsonSchema.import')}</span> + <span className="px-0.5">{t('workflow.nodes.llm.jsonSchema.import')}</span> </button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[100]'> - <div className='flex w-[400px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9'> + <PortalToFollowElemContent className="z-[100]"> + <div className="flex w-[400px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9"> {/* Title */} - <div className='relative px-3 pb-1 pt-3.5'> - <div className='absolute bottom-0 right-2.5 flex h-8 w-8 items-center justify-center' onClick={onClose}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="relative px-3 pb-1 pt-3.5"> + <div className="absolute bottom-0 right-2.5 flex h-8 w-8 items-center justify-center" onClick={onClose}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> - <div className='system-xl-semibold flex pl-1 pr-8 text-text-primary'> + <div className="system-xl-semibold flex pl-1 pr-8 text-text-primary"> {t('workflow.nodes.llm.jsonSchema.import')} </div> </div> {/* Content */} - <div className='px-4 py-2'> + <div className="px-4 py-2"> <CodeEditor - className='rounded-lg' - editorWrapperClassName='h-[340px]' + className="rounded-lg" + editorWrapperClassName="h-[340px]" value={json} onUpdate={setJson} showFormatButton={false} @@ -118,11 +119,11 @@ const JsonImporter: FC<JsonImporterProps> = ({ {parseError && <ErrorMessage message={parseError.message} />} </div> {/* Footer */} - <div className='flex items-center justify-end gap-x-2 p-4 pt-2'> - <Button variant='secondary' onClick={onClose}> + <div className="flex items-center justify-end gap-x-2 p-4 pt-2"> + <Button variant="secondary" onClick={onClose}> {t('common.operation.cancel')} </Button> - <Button variant='primary' onClick={handleSubmit}> + <Button variant="primary" onClick={handleSubmit}> {t('common.operation.submit')} </Button> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx index be80a8aac7..c5eaea6efd 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx @@ -1,14 +1,15 @@ -import React, { type FC, useCallback, useState } from 'react' -import { type SchemaRoot, Type } from '../../types' +import type { FC } from 'react' +import type { SchemaRoot } from '../../types' import { RiBracesLine, RiCloseLine, RiExternalLinkLine, RiTimelineView } from '@remixicon/react' -import { SegmentedControl } from '../../../../../base/segmented-control' -import JsonSchemaGenerator from './json-schema-generator' -import Divider from '@/app/components/base/divider' -import JsonImporter from './json-importer' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import VisualEditor from './visual-editor' -import SchemaEditor from './schema-editor' +import Divider from '@/app/components/base/divider' +import Toast from '@/app/components/base/toast' +import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import { useDocLink } from '@/context/i18n' +import { SegmentedControl } from '../../../../../base/segmented-control' +import { Type } from '../../types' import { checkJsonSchemaDepth, getValidationErrorMessage, @@ -16,12 +17,13 @@ import { preValidateSchema, validateSchemaAgainstDraft7, } from '../../utils' -import { MittProvider, VisualEditorContextProvider, useMittContext } from './visual-editor/context' import ErrorMessage from './error-message' +import JsonImporter from './json-importer' +import JsonSchemaGenerator from './json-schema-generator' +import SchemaEditor from './schema-editor' +import VisualEditor from './visual-editor' +import { MittProvider, useMittContext, VisualEditorContextProvider } from './visual-editor/context' import { useVisualEditorStore } from './visual-editor/store' -import Toast from '@/app/components/base/toast' -import { JSON_SCHEMA_MAX_DEPTH } from '@/config' -import { useDocLink } from '@/context/i18n' type JsonSchemaConfigProps = { defaultSchema?: SchemaRoot @@ -71,7 +73,8 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ }, []) const handleTabChange = useCallback((value: SchemaView) => { - if (currentTab === value) return + if (currentTab === value) + return if (currentTab === SchemaView.JsonSchema) { try { const schema = JSON.parse(json) @@ -199,31 +202,31 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ }, [currentTab, jsonSchema, json, onSave, onClose, advancedEditing, isAddingNewField, t]) return ( - <div className='flex h-full flex-col'> + <div className="flex h-full flex-col"> {/* Header */} - <div className='relative flex p-6 pb-3 pr-14'> - <div className='title-2xl-semi-bold grow truncate text-text-primary'> + <div className="relative flex p-6 pb-3 pr-14"> + <div className="title-2xl-semi-bold grow truncate text-text-primary"> {t('workflow.nodes.llm.jsonSchema.title')} </div> - <div className='absolute right-5 top-5 flex h-8 w-8 items-center justify-center p-1.5' onClick={onClose}> - <RiCloseLine className='h-[18px] w-[18px] text-text-tertiary' /> + <div className="absolute right-5 top-5 flex h-8 w-8 items-center justify-center p-1.5" onClick={onClose}> + <RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" /> </div> </div> {/* Content */} - <div className='flex items-center justify-between px-6 py-2'> + <div className="flex items-center justify-between px-6 py-2"> {/* Tab */} <SegmentedControl<SchemaView> options={VIEW_TABS} value={currentTab} onChange={handleTabChange} /> - <div className='flex items-center gap-x-0.5'> + <div className="flex items-center gap-x-0.5"> {/* JSON Schema Generator */} <JsonSchemaGenerator crossAxisOffset={btnWidth} onApply={handleApplySchema} /> - <Divider type='vertical' className='h-3' /> + <Divider type="vertical" className="h-3" /> {/* JSON Schema Importer */} <JsonImporter updateBtnWidth={updateBtnWidth} @@ -231,7 +234,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ /> </div> </div> - <div className='flex grow flex-col gap-y-1 overflow-hidden px-6'> + <div className="flex grow flex-col gap-y-1 overflow-hidden px-6"> {currentTab === SchemaView.VisualEditor && ( <VisualEditor schema={jsonSchema} @@ -248,28 +251,28 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({ {validationError && <ErrorMessage message={validationError} />} </div> {/* Footer */} - <div className='flex items-center gap-x-2 p-6 pt-5'> + <div className="flex items-center gap-x-2 p-6 pt-5"> <a - className='flex grow items-center gap-x-1 text-text-accent' + className="flex grow items-center gap-x-1 text-text-accent" href={docLink('/guides/workflow/structured-outputs')} - target='_blank' - rel='noopener noreferrer' + target="_blank" + rel="noopener noreferrer" > - <span className='system-xs-regular'>{t('workflow.nodes.llm.jsonSchema.doc')}</span> - <RiExternalLinkLine className='h-3 w-3' /> + <span className="system-xs-regular">{t('workflow.nodes.llm.jsonSchema.doc')}</span> + <RiExternalLinkLine className="h-3 w-3" /> </a> - <div className='flex items-center gap-x-3'> - <div className='flex items-center gap-x-2'> - <Button variant='secondary' onClick={handleResetDefaults}> + <div className="flex items-center gap-x-3"> + <div className="flex items-center gap-x-2"> + <Button variant="secondary" onClick={handleResetDefaults}> {t('workflow.nodes.llm.jsonSchema.resetDefaults')} </Button> - <Divider type='vertical' className='ml-1 mr-0 h-4' /> + <Divider type="vertical" className="ml-1 mr-0 h-4" /> </div> - <div className='flex items-center gap-x-2'> - <Button variant='secondary' onClick={handleCancel}> + <div className="flex items-center gap-x-2"> + <Button variant="secondary" onClick={handleCancel}> {t('common.operation.cancel')} </Button> - <Button variant='primary' onClick={handleSave}> + <Button variant="primary" onClick={handleSave}> {t('common.operation.save')} </Button> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/assets/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/assets/index.tsx index 5f1f117086..91dc3dfdd5 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/assets/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/assets/index.tsx @@ -1,7 +1,7 @@ -import SchemaGeneratorLight from './schema-generator-light' import SchemaGeneratorDark from './schema-generator-dark' +import SchemaGeneratorLight from './schema-generator-light' export { - SchemaGeneratorLight, SchemaGeneratorDark, + SchemaGeneratorLight, } diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx index 00f57237e5..0e7d8c8d0c 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx @@ -1,12 +1,13 @@ -import React, { type FC, useCallback, useMemo, useState } from 'react' +import type { FC } from 'react' import type { SchemaRoot } from '../../../types' import { RiArrowLeftLine, RiCloseLine, RiSparklingLine } from '@remixicon/react' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' +import Loading from '@/app/components/base/loading' +import { getValidationErrorMessage, validateSchemaAgainstDraft7 } from '../../../utils' import CodeEditor from '../code-editor' import ErrorMessage from '../error-message' -import { getValidationErrorMessage, validateSchemaAgainstDraft7 } from '../../../utils' -import Loading from '@/app/components/base/loading' type GeneratedResultProps = { schema: SchemaRoot @@ -57,32 +58,32 @@ const GeneratedResult: FC<GeneratedResultProps> = ({ }, [schema, onApply]) return ( - <div className='flex w-[480px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9'> + <div className="flex w-[480px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9"> { isGenerating ? ( - <div className='flex h-[600px] flex-col items-center justify-center gap-y-3'> - <Loading type='area' /> - <div className='system-xs-regular text-text-tertiary'>{t('workflow.nodes.llm.jsonSchema.generating')}</div> + <div className="flex h-[600px] flex-col items-center justify-center gap-y-3"> + <Loading type="area" /> + <div className="system-xs-regular text-text-tertiary">{t('workflow.nodes.llm.jsonSchema.generating')}</div> </div> ) : ( <> - <div className='absolute right-2.5 top-2.5 flex h-8 w-8 items-center justify-center' onClick={onClose}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="absolute right-2.5 top-2.5 flex h-8 w-8 items-center justify-center" onClick={onClose}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> {/* Title */} - <div className='flex flex-col gap-y-[0.5px] px-3 pb-1 pt-3.5'> - <div className='system-xl-semibold flex pl-1 pr-8 text-text-primary'> + <div className="flex flex-col gap-y-[0.5px] px-3 pb-1 pt-3.5"> + <div className="system-xl-semibold flex pl-1 pr-8 text-text-primary"> {t('workflow.nodes.llm.jsonSchema.generatedResult')} </div> - <div className='system-xs-regular flex px-1 text-text-tertiary'> + <div className="system-xs-regular flex px-1 text-text-tertiary"> {t('workflow.nodes.llm.jsonSchema.resultTip')} </div> </div> {/* Content */} - <div className='px-4 py-2'> + <div className="px-4 py-2"> <CodeEditor - className='rounded-lg' - editorWrapperClassName='h-[424px]' + className="rounded-lg" + editorWrapperClassName="h-[424px]" value={jsonSchema} readOnly showFormatButton={false} @@ -91,21 +92,21 @@ const GeneratedResult: FC<GeneratedResultProps> = ({ {validationError && <ErrorMessage message={validationError} />} </div> {/* Footer */} - <div className='flex items-center justify-between p-4 pt-2'> - <Button variant='secondary' className='flex items-center gap-x-0.5' onClick={onBack}> - <RiArrowLeftLine className='h-4 w-4' /> + <div className="flex items-center justify-between p-4 pt-2"> + <Button variant="secondary" className="flex items-center gap-x-0.5" onClick={onBack}> + <RiArrowLeftLine className="h-4 w-4" /> <span>{t('workflow.nodes.llm.jsonSchema.back')}</span> </Button> - <div className='flex items-center gap-x-2'> + <div className="flex items-center gap-x-2"> <Button - variant='secondary' - className='flex items-center gap-x-0.5' + variant="secondary" + className="flex items-center gap-x-0.5" onClick={onRegenerate} > - <RiSparklingLine className='h-4 w-4' /> + <RiSparklingLine className="h-4 w-4" /> <span>{t('workflow.nodes.llm.jsonSchema.regenerate')}</span> </Button> - <Button variant='primary' onClick={handleApply}> + <Button variant="primary" onClick={handleApply}> {t('workflow.nodes.llm.jsonSchema.apply')} </Button> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx index 2671858628..a4da5b69e3 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx @@ -1,24 +1,25 @@ -import React, { type FC, useCallback, useEffect, useState } from 'react' +import type { FC } from 'react' import type { SchemaRoot } from '../../../types' +import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { CompletionParams, Model } from '@/types/app' +import React, { useCallback, useEffect, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import useTheme from '@/hooks/use-theme' -import type { CompletionParams, Model } from '@/types/app' -import { ModelModeType } from '@/types/app' -import { Theme } from '@/types/app' -import { SchemaGeneratorDark, SchemaGeneratorLight } from './assets' -import { cn } from '@/utils/classnames' -import PromptEditor from './prompt-editor' -import GeneratedResult from './generated-result' -import { useGenerateStructuredOutputRules } from '@/service/use-common' import Toast from '@/app/components/base/toast' -import { type FormValue, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { useVisualEditorStore } from '../visual-editor/store' +import useTheme from '@/hooks/use-theme' +import { useGenerateStructuredOutputRules } from '@/service/use-common' +import { ModelModeType, Theme } from '@/types/app' +import { cn } from '@/utils/classnames' import { useMittContext } from '../visual-editor/context' +import { useVisualEditorStore } from '../visual-editor/store' +import { SchemaGeneratorDark, SchemaGeneratorLight } from './assets' +import GeneratedResult from './generated-result' +import PromptEditor from './prompt-editor' type JsonSchemaGeneratorProps = { onApply: (schema: SchemaRoot) => void @@ -85,7 +86,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ setOpen(false) }, []) - const handleModelChange = useCallback((newValue: { modelId: string; provider: string; mode?: string; features?: string[] }) => { + const handleModelChange = useCallback((newValue: { modelId: string, provider: string, mode?: string, features?: string[] }) => { const newModel = { ...model, provider: newValue.provider, @@ -124,7 +125,8 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ const handleGenerate = useCallback(async () => { setView(GeneratorView.result) const output = await generateSchema() - if (output === undefined) return + if (output === undefined) + return setSchema(JSON.parse(output)) }, [generateSchema]) @@ -134,7 +136,8 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ const handleRegenerate = useCallback(async () => { const output = await generateSchema() - if (output === undefined) return + if (output === undefined) + return setSchema(JSON.parse(output)) }, [generateSchema]) @@ -147,7 +150,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: crossAxisOffset ?? 0, @@ -155,7 +158,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ > <PortalToFollowElemTrigger onClick={handleTrigger}> <button - type='button' + type="button" className={cn( 'flex h-6 w-6 items-center justify-center rounded-md p-0.5 hover:bg-state-accent-hover', open && 'bg-state-accent-active', @@ -164,7 +167,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({ <SchemaGenerator /> </button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[100]'> + <PortalToFollowElemContent className="z-[100]"> {view === GeneratorView.promptEditor && ( <PromptEditor instruction={instruction} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx index 62d156253a..17641fdf37 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx @@ -1,13 +1,13 @@ -import React, { useCallback } from 'react' import type { FC } from 'react' +import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Model } from '@/types/app' import { RiCloseLine, RiSparklingFill } from '@remixicon/react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import Textarea from '@/app/components/base/textarea' import Tooltip from '@/app/components/base/tooltip' -import Button from '@/app/components/base/button' -import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import type { Model } from '@/types/app' export type ModelInfo = { modelId: string @@ -42,27 +42,27 @@ const PromptEditor: FC<PromptEditorProps> = ({ }, [onInstructionChange]) return ( - <div className='relative flex w-[480px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9'> - <div className='absolute right-2.5 top-2.5 flex h-8 w-8 items-center justify-center' onClick={onClose}> - <RiCloseLine className='h-4 w-4 text-text-tertiary'/> + <div className="relative flex w-[480px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9"> + <div className="absolute right-2.5 top-2.5 flex h-8 w-8 items-center justify-center" onClick={onClose}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> {/* Title */} - <div className='flex flex-col gap-y-[0.5px] px-3 pb-1 pt-3.5'> - <div className='system-xl-semibold flex pl-1 pr-8 text-text-primary'> + <div className="flex flex-col gap-y-[0.5px] px-3 pb-1 pt-3.5"> + <div className="system-xl-semibold flex pl-1 pr-8 text-text-primary"> {t('workflow.nodes.llm.jsonSchema.generateJsonSchema')} </div> - <div className='system-xs-regular flex px-1 text-text-tertiary'> + <div className="system-xs-regular flex px-1 text-text-tertiary"> {t('workflow.nodes.llm.jsonSchema.generationTip')} </div> </div> {/* Content */} - <div className='flex flex-col gap-y-1 px-4 py-2'> - <div className='system-sm-semibold-uppercase flex h-6 items-center text-text-secondary'> + <div className="flex flex-col gap-y-1 px-4 py-2"> + <div className="system-sm-semibold-uppercase flex h-6 items-center text-text-secondary"> {t('common.modelProvider.model')} </div> <ModelParameterModal - popupClassName='!w-[448px]' - portalToFollowElemContentClassName='z-[1000]' + popupClassName="!w-[448px]" + portalToFollowElemContentClassName="z-[1000]" isAdvancedMode={true} provider={model.provider} completionParams={model.completion_params} @@ -72,14 +72,14 @@ const PromptEditor: FC<PromptEditorProps> = ({ hideDebugWithMultipleModel /> </div> - <div className='flex flex-col gap-y-1 px-4 py-2'> - <div className='system-sm-semibold-uppercase flex h-6 items-center text-text-secondary'> + <div className="flex flex-col gap-y-1 px-4 py-2"> + <div className="system-sm-semibold-uppercase flex h-6 items-center text-text-secondary"> <span>{t('workflow.nodes.llm.jsonSchema.instruction')}</span> <Tooltip popupContent={t('workflow.nodes.llm.jsonSchema.promptTooltip')} /> </div> - <div className='flex items-center'> + <div className="flex items-center"> <Textarea - className='h-[364px] resize-none px-2 py-1' + className="h-[364px] resize-none px-2 py-1" value={instruction} placeholder={t('workflow.nodes.llm.jsonSchema.promptPlaceholder')} onChange={handleInstructionChange} @@ -87,16 +87,16 @@ const PromptEditor: FC<PromptEditorProps> = ({ </div> </div> {/* Footer */} - <div className='flex justify-end gap-x-2 p-4 pt-2'> - <Button variant='secondary' onClick={onClose}> + <div className="flex justify-end gap-x-2 p-4 pt-2"> + <Button variant="secondary" onClick={onClose}> {t('common.operation.cancel')} </Button> <Button - variant='primary' - className='flex items-center gap-x-0.5' + variant="primary" + className="flex items-center gap-x-0.5" onClick={onGenerate} > - <RiSparklingFill className='h-4 w-4' /> + <RiSparklingFill className="h-4 w-4" /> <span>{t('workflow.nodes.llm.jsonSchema.generate')}</span> </Button> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx index 6ba59320b7..54753f08b4 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx @@ -1,7 +1,8 @@ -import React, { type FC } from 'react' -import CodeEditor from './code-editor' -import { cn } from '@/utils/classnames' +import type { FC } from 'react' +import React from 'react' import LargeDataAlert from '@/app/components/workflow/variable-inspect/large-data-alert' +import { cn } from '@/utils/classnames' +import CodeEditor from './code-editor' type SchemaEditorProps = { schema: string @@ -28,13 +29,13 @@ const SchemaEditor: FC<SchemaEditorProps> = ({ <CodeEditor readOnly={readonly} className={cn('grow rounded-xl', className)} - editorWrapperClassName='grow' + editorWrapperClassName="grow" value={schema} onUpdate={onUpdate} hideTopMenu={hideTopMenu} onFocus={onFocus} onBlur={onBlur} - topContent={isTruncated && <LargeDataAlert className='mx-1 mb-3 mt-[-4px]' />} + topContent={isTruncated && <LargeDataAlert className="mx-1 mb-3 mt-[-4px]" />} /> ) } diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx index eba94e85dd..54a3b6bb85 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx @@ -1,9 +1,9 @@ -import React, { useCallback } from 'react' -import Button from '@/app/components/base/button' import { RiAddCircleFill } from '@remixicon/react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useVisualEditorStore } from './store' +import Button from '@/app/components/base/button' import { useMittContext } from './context' +import { useVisualEditorStore } from './store' const AddField = () => { const { t } = useTranslation() @@ -19,15 +19,15 @@ const AddField = () => { }, [setIsAddingNewField, emit]) return ( - <div className='py-2 pl-5'> + <div className="py-2 pl-5"> <Button - size='small' - variant='secondary-accent' - className='flex items-center gap-x-[1px]' + size="small" + variant="secondary-accent" + className="flex items-center gap-x-[1px]" onClick={handleAddField} > - <RiAddCircleFill className='h-3.5 w-3.5'/> - <span className='px-[3px]'>{t('workflow.nodes.llm.jsonSchema.addField')}</span> + <RiAddCircleFill className="h-3.5 w-3.5" /> + <span className="px-[3px]">{t('workflow.nodes.llm.jsonSchema.addField')}</span> </Button> </div> ) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx index 4f53f6b163..3510498835 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx @@ -1,4 +1,5 @@ -import React, { type FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' type CardProps = { @@ -17,17 +18,17 @@ const Card: FC<CardProps> = ({ const { t } = useTranslation() return ( - <div className='flex flex-col py-0.5'> - <div className='flex h-6 items-center gap-x-1 pl-1 pr-0.5'> - <div className='system-sm-semibold truncate border border-transparent px-1 py-px text-text-primary'> + <div className="flex flex-col py-0.5"> + <div className="flex h-6 items-center gap-x-1 pl-1 pr-0.5"> + <div className="system-sm-semibold truncate border border-transparent px-1 py-px text-text-primary"> {name} </div> - <div className='system-xs-medium px-1 py-0.5 text-text-tertiary'> + <div className="system-xs-medium px-1 py-0.5 text-text-tertiary"> {type} </div> { required && ( - <div className='system-2xs-medium-uppercase px-1 py-0.5 text-text-warning'> + <div className="system-2xs-medium-uppercase px-1 py-0.5 text-text-warning"> {t('workflow.nodes.llm.jsonSchema.required')} </div> ) @@ -35,7 +36,7 @@ const Card: FC<CardProps> = ({ </div> {description && ( - <div className='system-xs-regular truncate px-2 pb-1 text-text-tertiary'> + <div className="system-xs-regular truncate px-2 pb-1 text-text-tertiary"> {description} </div> )} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx index 268683aec3..cfe63159d3 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx @@ -1,11 +1,11 @@ +import { noop } from 'lodash-es' import { createContext, useContext, useRef, } from 'react' -import { createVisualEditorStore } from './store' import { useMitt } from '@/hooks/use-mitt' -import { noop } from 'lodash-es' +import { createVisualEditorStore } from './store' type VisualEditorStore = ReturnType<typeof createVisualEditorStore> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx index 3f693c23c7..a612701adc 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' -import React from 'react' -import Tooltip from '@/app/components/base/tooltip' import { RiAddCircleLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' +import React from 'react' import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' type ActionsProps = { disableAddBtn: boolean @@ -20,33 +20,33 @@ const Actions: FC<ActionsProps> = ({ const { t } = useTranslation() return ( - <div className='flex items-center gap-x-0.5'> + <div className="flex items-center gap-x-0.5"> <Tooltip popupContent={t('workflow.nodes.llm.jsonSchema.addChildField')}> <button - type='button' - className='flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled' + type="button" + className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled" onClick={onAddChildField} disabled={disableAddBtn} > - <RiAddCircleLine className='h-4 w-4'/> + <RiAddCircleLine className="h-4 w-4" /> </button> </Tooltip> <Tooltip popupContent={t('common.operation.edit')}> <button - type='button' - className='flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' + type="button" + className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary" onClick={onEdit} > - <RiEditLine className='h-4 w-4' /> + <RiEditLine className="h-4 w-4" /> </button> </Tooltip> <Tooltip popupContent={t('common.operation.remove')}> <button - type='button' - className='flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive' + type="button" + className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive" onClick={onDelete} > - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </button> </Tooltip> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx index e065406bde..ee60195fdb 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx @@ -1,8 +1,9 @@ -import React, { type FC } from 'react' -import Button from '@/app/components/base/button' -import { useTranslation } from 'react-i18next' -import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' +import type { FC } from 'react' import { useKeyPress } from 'ahooks' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' type AdvancedActionsProps = { isConfirmDisabled: boolean @@ -13,7 +14,7 @@ type AdvancedActionsProps = { const Key = (props: { keyName: string }) => { const { keyName } = props return ( - <kbd className='system-kbd flex h-4 min-w-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-white px-px text-text-primary-on-surface'> + <kbd className="system-kbd flex h-4 min-w-4 items-center justify-center rounded-[4px] bg-components-kbd-bg-white px-px text-text-primary-on-surface"> {keyName} </kbd> ) @@ -35,21 +36,21 @@ const AdvancedActions: FC<AdvancedActionsProps> = ({ }) return ( - <div className='flex items-center gap-x-1'> - <Button size='small' variant='secondary' onClick={onCancel}> + <div className="flex items-center gap-x-1"> + <Button size="small" variant="secondary" onClick={onCancel}> {t('common.operation.cancel')} </Button> <Button - className='flex items-center gap-x-1' + className="flex items-center gap-x-1" disabled={isConfirmDisabled} - size='small' - variant='primary' + size="small" + variant="primary" onClick={onConfirm} > <span>{t('common.operation.confirm')}</span> - <div className='flex items-center gap-x-0.5'> + <div className="flex items-center gap-x-0.5"> <Key keyName={getKeyboardKeyNameBySystem('ctrl')} /> - <Key keyName='⏎' /> + <Key keyName="⏎" /> </div> </Button> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx index cd06fc8244..28ea12d9a3 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx @@ -1,4 +1,5 @@ -import React, { type FC, useCallback, useState } from 'react' +import type { FC } from 'react' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Textarea from '@/app/components/base/textarea' @@ -33,28 +34,28 @@ const AdvancedOptions: FC<AdvancedOptionsProps> = ({ // }, []) return ( - <div className='border-t border-divider-subtle'> + <div className="border-t border-divider-subtle"> {/* {showAdvancedOptions ? ( */} - <div className='flex flex-col gap-y-1 px-2 py-1.5'> - <div className='flex w-full items-center gap-x-2'> - <span className='system-2xs-medium-uppercase text-text-tertiary'> + <div className="flex flex-col gap-y-1 px-2 py-1.5"> + <div className="flex w-full items-center gap-x-2"> + <span className="system-2xs-medium-uppercase text-text-tertiary"> {t('workflow.nodes.llm.jsonSchema.stringValidations')} </span> - <div className='grow'> - <Divider type='horizontal' className='my-0 h-px bg-line-divider-bg' /> + <div className="grow"> + <Divider type="horizontal" className="my-0 h-px bg-line-divider-bg" /> </div> </div> - <div className='flex flex-col'> - <div className='system-xs-medium flex h-6 items-center text-text-secondary'> + <div className="flex flex-col"> + <div className="system-xs-medium flex h-6 items-center text-text-secondary"> Enum </div> <Textarea - size='small' - className='min-h-6' + size="small" + className="min-h-6" value={enumValue} onChange={handleEnumChange} onBlur={handleEnumBlur} - placeholder={'abcd, 1, 1.5, etc.'} + placeholder="abcd, 1, 1.5, etc." /> </div> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx index 19dc478e83..2dfaa88260 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react' import type { FC } from 'react' +import React, { useEffect, useState } from 'react' import { cn } from '@/utils/classnames' type AutoWidthInputProps = { @@ -43,11 +43,11 @@ const AutoWidthInput: FC<AutoWidthInputProps> = ({ } return ( - <div className='relative inline-flex items-center'> + <div className="relative inline-flex items-center"> {/* Hidden measurement span */} <span ref={textRef} - className='system-sm-semibold invisible absolute left-0 top-0 -z-10 whitespace-pre px-1' + className="system-sm-semibold invisible absolute left-0 top-0 -z-10 whitespace-pre px-1" aria-hidden="true" > {value || placeholder} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx index 643bc2ef13..e3ae0d16ab 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx @@ -1,20 +1,22 @@ -import React, { type FC, useCallback, useMemo, useRef, useState } from 'react' +import type { FC } from 'react' import type { SchemaEnumType } from '../../../../types' -import { ArrayType, Type } from '../../../../types' +import type { AdvancedOptionsType } from './advanced-options' import type { TypeItem } from './type-selector' -import TypeSelector from './type-selector' -import RequiredSwitch from './required-switch' +import { useUnmount } from 'ahooks' +import React, { useCallback, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' +import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import { cn } from '@/utils/classnames' +import { ArrayType, Type } from '../../../../types' +import { useMittContext } from '../context' +import { useVisualEditorStore } from '../store' import Actions from './actions' import AdvancedActions from './advanced-actions' -import AdvancedOptions, { type AdvancedOptionsType } from './advanced-options' -import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' -import { useVisualEditorStore } from '../store' -import { useMittContext } from '../context' -import { useUnmount } from 'ahooks' -import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import AdvancedOptions from './advanced-options' import AutoWidthInput from './auto-width-input' +import RequiredSwitch from './required-switch' +import TypeSelector from './type-selector' export type EditData = { name: string @@ -127,19 +129,22 @@ const EditCard: FC<EditCardProps> = ({ }, []) const handlePropertyNameBlur = useCallback(() => { - if (isAdvancedEditing) return + if (isAdvancedEditing) + return emitPropertyNameChange() }, [isAdvancedEditing, emitPropertyNameChange]) const handleTypeChange = useCallback((item: TypeItem) => { setCurrentFields(prev => ({ ...prev, type: item.value })) - if (isAdvancedEditing) return + if (isAdvancedEditing) + return emitPropertyTypeChange(item.value) }, [isAdvancedEditing, emitPropertyTypeChange]) const toggleRequired = useCallback(() => { setCurrentFields(prev => ({ ...prev, required: !prev.required })) - if (isAdvancedEditing) return + if (isAdvancedEditing) + return emitPropertyRequiredToggle() }, [isAdvancedEditing, emitPropertyRequiredToggle]) @@ -148,7 +153,8 @@ const EditCard: FC<EditCardProps> = ({ }, []) const handleDescriptionBlur = useCallback(() => { - if (isAdvancedEditing) return + if (isAdvancedEditing) + return emitPropertyOptionsChange({ description: currentFields.description, enum: currentFields.enum }) }, [isAdvancedEditing, emitPropertyOptionsChange, currentFields]) @@ -165,7 +171,8 @@ const EditCard: FC<EditCardProps> = ({ enumValue = stringArray } setCurrentFields(prev => ({ ...prev, enum: enumValue })) - if (isAdvancedEditing) return + if (isAdvancedEditing) + return emitPropertyOptionsChange({ description: currentFields.description, enum: enumValue }) }, [isAdvancedEditing, emitPropertyOptionsChange, currentFields]) @@ -203,14 +210,15 @@ const EditCard: FC<EditCardProps> = ({ }, [isAddingNewField, emit, setIsAddingNewField, setAdvancedEditing, backupFields]) useUnmount(() => { - if (isAdvancedEditing || blurWithActions.current) return + if (isAdvancedEditing || blurWithActions.current) + return emitFieldChange() }) return ( - <div className='flex flex-col rounded-lg bg-components-panel-bg py-0.5 shadow-sm shadow-shadow-shadow-4'> - <div className='flex h-6 items-center pl-1 pr-0.5'> - <div className='flex grow items-center gap-x-1'> + <div className="flex flex-col rounded-lg bg-components-panel-bg py-0.5 shadow-sm shadow-shadow-shadow-4"> + <div className="flex h-6 items-center pl-1 pr-0.5"> + <div className="flex grow items-center gap-x-1"> <AutoWidthInput value={currentFields.name} placeholder={t('workflow.nodes.llm.jsonSchema.fieldNamePlaceholder')} @@ -223,11 +231,11 @@ const EditCard: FC<EditCardProps> = ({ currentValue={currentFields.type} items={maximumDepthReached ? MAXIMUM_DEPTH_TYPE_OPTIONS : TYPE_OPTIONS} onSelect={handleTypeChange} - popupClassName={'z-[1000]'} + popupClassName="z-[1000]" /> { currentFields.required && ( - <div className='system-2xs-medium-uppercase px-1 py-0.5 text-text-warning'> + <div className="system-2xs-medium-uppercase px-1 py-0.5 text-text-warning"> {t('workflow.nodes.llm.jsonSchema.required')} </div> ) @@ -237,28 +245,30 @@ const EditCard: FC<EditCardProps> = ({ defaultValue={currentFields.required} toggleRequired={toggleRequired} /> - <Divider type='vertical' className='h-3' /> - {isAdvancedEditing ? ( - <AdvancedActions - isConfirmDisabled={currentFields.name === ''} - onCancel={handleCancel} - onConfirm={handleConfirm} - /> - ) : ( - <Actions - disableAddBtn={disableAddBtn} - onAddChildField={handleAddChildField} - onDelete={handleDelete} - onEdit={handleAdvancedEdit} - /> - )} + <Divider type="vertical" className="h-3" /> + {isAdvancedEditing + ? ( + <AdvancedActions + isConfirmDisabled={currentFields.name === ''} + onCancel={handleCancel} + onConfirm={handleConfirm} + /> + ) + : ( + <Actions + disableAddBtn={disableAddBtn} + onAddChildField={handleAddChildField} + onDelete={handleDelete} + onEdit={handleAdvancedEdit} + /> + )} </div> {(fields.description || isAdvancedEditing) && ( <div className={cn('flex', isAdvancedEditing ? 'p-2 pt-1' : 'px-2 pb-1')}> <input value={currentFields.description} - className='system-xs-regular placeholder:system-xs-regular h-4 w-full p-0 text-text-tertiary caret-[#295EFF] outline-none placeholder:text-text-placeholder' + className="system-xs-regular placeholder:system-xs-regular h-4 w-full p-0 text-text-tertiary caret-[#295EFF] outline-none placeholder:text-text-placeholder" placeholder={t('workflow.nodes.llm.jsonSchema.descriptionPlaceholder')} onChange={handleDescriptionChange} onBlur={handleDescriptionBlur} diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx index c7179408cf..b84bdd0775 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx @@ -1,7 +1,7 @@ -import React from 'react' import type { FC } from 'react' -import Switch from '@/app/components/base/switch' +import React from 'react' import { useTranslation } from 'react-i18next' +import Switch from '@/app/components/base/switch' type RequiredSwitchProps = { defaultValue: boolean @@ -15,9 +15,9 @@ const RequiredSwitch: FC<RequiredSwitchProps> = ({ const { t } = useTranslation() return ( - <div className='flex items-center gap-x-1 rounded-[5px] border border-divider-subtle bg-background-default-lighter px-1.5 py-1'> - <span className='system-2xs-medium-uppercase text-text-secondary'>{t('workflow.nodes.llm.jsonSchema.required')}</span> - <Switch size='xs' defaultValue={defaultValue} onChange={toggleRequired} /> + <div className="flex items-center gap-x-1 rounded-[5px] border border-divider-subtle bg-background-default-lighter px-1.5 py-1"> + <span className="system-2xs-medium-uppercase text-text-secondary">{t('workflow.nodes.llm.jsonSchema.required')}</span> + <Switch size="xs" defaultValue={defaultValue} onChange={toggleRequired} /> </div> ) } diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/type-selector.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/type-selector.tsx index c4d381613d..375cdca09c 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/type-selector.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/type-selector.tsx @@ -1,8 +1,8 @@ -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' -import type { ArrayType, Type } from '../../../../types' import type { FC } from 'react' -import { useState } from 'react' +import type { ArrayType, Type } from '../../../../types' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' +import { useState } from 'react' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' export type TypeItem = { @@ -29,7 +29,7 @@ const TypeSelector: FC<TypeSelectorProps> = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, }} @@ -38,26 +38,28 @@ const TypeSelector: FC<TypeSelectorProps> = ({ <div className={cn( 'flex items-center rounded-[5px] p-0.5 pl-1 hover:bg-state-base-hover', open && 'bg-state-base-hover', - )}> - <span className='system-xs-medium text-text-tertiary'>{currentValue}</span> - <RiArrowDownSLine className='h-4 w-4 text-text-tertiary' /> + )} + > + <span className="system-xs-medium text-text-tertiary">{currentValue}</span> + <RiArrowDownSLine className="h-4 w-4 text-text-tertiary" /> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent className={popupClassName}> - <div className='w-40 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg shadow-shadow-shadow-5'> + <div className="w-40 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg shadow-shadow-shadow-5"> {items.map((item) => { const isSelected = item.value === currentValue - return (<div - key={item.value} - className={'flex items-center gap-x-1 rounded-lg px-2 py-1 hover:bg-state-base-hover'} - onClick={() => { - onSelect(item) - setOpen(false) - }} - > - <span className='system-sm-medium px-1 text-text-secondary'>{item.text}</span> - {isSelected && <RiCheckLine className='h-4 w-4 text-text-accent' />} - </div> + return ( + <div + key={item.value} + className="flex items-center gap-x-1 rounded-lg px-2 py-1 hover:bg-state-base-hover" + onClick={() => { + onSelect(item) + setOpen(false) + }} + > + <span className="system-sm-medium px-1 text-text-secondary">{item.text}</span> + {isSelected && <RiCheckLine className="h-4 w-4 text-text-accent" />} + </div> ) })} </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts index 202bb44638..84c28b236e 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts @@ -1,18 +1,19 @@ -import { produce } from 'immer' import type { VisualEditorProps } from '.' +import type { Field } from '../../../types' +import type { EditData } from './edit-card' +import { produce } from 'immer' +import { noop } from 'lodash-es' +import Toast from '@/app/components/base/toast' +import { ArrayType, Type } from '../../../types' +import { findPropertyWithPath } from '../../../utils' import { useMittContext } from './context' import { useVisualEditorStore } from './store' -import type { EditData } from './edit-card' -import { ArrayType, type Field, Type } from '../../../types' -import Toast from '@/app/components/base/toast' -import { findPropertyWithPath } from '../../../utils' -import { noop } from 'lodash-es' type ChangeEventParams = { - path: string[], - parentPath: string[], - oldFields: EditData, - fields: EditData, + path: string[] + parentPath: string[] + oldFields: EditData + fields: EditData } type AddEventParams = { @@ -57,7 +58,8 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { const { name: oldName } = oldFields const { name: newName } = fields const newSchema = produce(jsonSchema, (draft) => { - if (oldName === newName) return + if (oldName === newName) + return const schema = findPropertyWithPath(draft, parentPath) as Field if (schema.type === Type.object) { @@ -120,7 +122,8 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { const { path, oldFields, fields } = params as ChangeEventParams const { type: oldType } = oldFields const { type: newType } = fields - if (oldType === newType) return + if (oldType === newType) + return const newSchema = produce(jsonSchema, (draft) => { const schema = findPropertyWithPath(draft, path) as Field @@ -439,7 +442,8 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => { schema.enum = fields.enum } }) - if (samePropertyNameError) return + if (samePropertyNameError) + return onChange(newSchema) emit('fieldChangeSuccess') }) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx index f0f8bb2093..fc4d0ec106 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import type { SchemaRoot } from '../../../types' -import SchemaNode from './schema-node' -import { useSchemaNodeOperations } from './hooks' import { cn } from '@/utils/classnames' +import { useSchemaNodeOperations } from './hooks' +import SchemaNode from './schema-node' export type VisualEditorProps = { className?: string diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx index ec7b355085..23cd1ee477 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx @@ -1,16 +1,17 @@ import type { FC } from 'react' -import React, { useMemo, useState } from 'react' -import { type Field, Type } from '../../../types' -import { cn } from '@/utils/classnames' +import type { Field } from '../../../types' import { RiArrowDropDownLine, RiArrowDropRightLine } from '@remixicon/react' -import { getFieldType, getHasChildren } from '../../../utils' -import Divider from '@/app/components/base/divider' -import EditCard from './edit-card' -import Card from './card' -import { useVisualEditorStore } from './store' import { useDebounceFn } from 'ahooks' -import AddField from './add-field' +import React, { useMemo, useState } from 'react' +import Divider from '@/app/components/base/divider' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' +import { cn } from '@/utils/classnames' +import { Type } from '../../../types' +import { getFieldType, getHasChildren } from '../../../utils' +import AddField from './add-field' +import Card from './card' +import EditCard from './edit-card' +import { useVisualEditorStore } from './store' type SchemaNodeProps = { name: string @@ -79,32 +80,35 @@ const SchemaNode: FC<SchemaNodeProps> = ({ } const handleMouseEnter = () => { - if(readOnly) return - if (advancedEditing || isAddingNewField) return + if (readOnly) + return + if (advancedEditing || isAddingNewField) + return setHoveringPropertyDebounced(path.join('.')) } const handleMouseLeave = () => { - if(readOnly) return - if (advancedEditing || isAddingNewField) return + if (readOnly) + return + if (advancedEditing || isAddingNewField) + return setHoveringPropertyDebounced(null) } return ( - <div className='relative'> + <div className="relative"> <div className={cn('relative z-10', indentPadding[depth])}> {depth > 0 && hasChildren && ( - <div className={cn('absolute top-0 z-10 flex h-7 w-5 items-center bg-background-section-burn px-0.5', - indentLeft[depth - 1])}> + <div className={cn('absolute top-0 z-10 flex h-7 w-5 items-center bg-background-section-burn px-0.5', indentLeft[depth - 1])}> <button type="button" onClick={handleExpand} - className='py-0.5 text-text-tertiary hover:text-text-accent' + className="py-0.5 text-text-tertiary hover:text-text-accent" > { isExpanded - ? <RiArrowDropDownLine className='h-4 w-4' /> - : <RiArrowDropRightLine className='h-4 w-4' /> + ? <RiArrowDropDownLine className="h-4 w-4" /> + : <RiArrowDropRightLine className="h-4 w-4" /> } </button> </div> @@ -114,35 +118,35 @@ const SchemaNode: FC<SchemaNodeProps> = ({ onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > - {(isHovering && depth > 0) ? ( - <EditCard - fields={{ - name, - type, - required, - description: schema.description, - enum: schema.enum, - }} - path={path} - parentPath={parentPath!} - depth={depth} - /> - ) : ( - <Card - name={name} - type={type} - required={required} - description={schema.description} - /> - )} + {(isHovering && depth > 0) + ? ( + <EditCard + fields={{ + name, + type, + required, + description: schema.description, + enum: schema.enum, + }} + path={path} + parentPath={parentPath!} + depth={depth} + /> + ) + : ( + <Card + name={name} + type={type} + required={required} + description={schema.description} + /> + )} </div> </div> - <div className={cn('absolute z-0 flex w-5 justify-center', - schema.description ? 'top-12 h-[calc(100%-3rem)]' : 'top-7 h-[calc(100%-1.75rem)]', - indentLeft[depth])}> + <div className={cn('absolute z-0 flex w-5 justify-center', schema.description ? 'top-12 h-[calc(100%-3rem)]' : 'top-7 h-[calc(100%-1.75rem)]', indentLeft[depth])}> <Divider - type='vertical' + type="vertical" className={cn('mx-0', isHovering ? 'bg-divider-deep' : 'bg-divider-subtle')} /> </div> diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/store.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/store.ts index 3dbd6676dc..b756c6fea6 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/store.ts +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/store.ts @@ -1,6 +1,6 @@ +import type { SchemaRoot } from '../../../types' import { useContext } from 'react' import { createStore, useStore } from 'zustand' -import type { SchemaRoot } from '../../../types' import { VisualEditorContext } from './context' type VisualEditorStore = { diff --git a/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx b/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx index 60b910dc3e..eb285ee389 100644 --- a/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx +++ b/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { useBoolean } from 'ahooks' -import { cn } from '@/utils/classnames' -import { Generator } from '@/app/components/base/icons/src/vender/other' -import { ActionButton } from '@/app/components/base/action-button' -import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' -import { AppModeEnum } from '@/types/app' -import type { GenRes } from '@/service/debug' import type { ModelConfig } from '@/app/components/workflow/types' +import type { GenRes } from '@/service/debug' +import { useBoolean } from 'ahooks' +import React, { useCallback } from 'react' +import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' +import { ActionButton } from '@/app/components/base/action-button' +import { Generator } from '@/app/components/base/icons/src/vender/other' +import { AppModeEnum } from '@/types/app' +import { cn } from '@/utils/classnames' import { useHooksStore } from '../../../hooks-store' type Props = { @@ -36,9 +36,10 @@ const PromptGeneratorBtn: FC<Props> = ({ return ( <div className={cn(className)}> <ActionButton - className='hover:bg-[#155EFF]/8' - onClick={showAutomaticTrue}> - <Generator className='h-4 w-4 text-primary-600' /> + className="hover:bg-[#155EFF]/8" + onClick={showAutomaticTrue} + > + <Generator className="h-4 w-4 text-primary-600" /> </ActionButton> {showAutomatic && ( <GetAutomaticResModal diff --git a/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx b/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx index 49425ff64c..147981e398 100644 --- a/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import Field from '@/app/components/workflow/nodes/_base/components/field' import Switch from '@/app/components/base/switch' +import Field from '@/app/components/workflow/nodes/_base/components/field' type ReasoningFormatConfigProps = { value?: 'tagged' | 'separated' @@ -21,16 +21,16 @@ const ReasoningFormatConfig: FC<ReasoningFormatConfigProps> = ({ <Field title={t('workflow.nodes.llm.reasoningFormat.title')} tooltip={t('workflow.nodes.llm.reasoningFormat.tooltip')} - operations={ + operations={( // ON = separated, OFF = tagged <Switch defaultValue={value === 'separated'} onChange={enabled => onChange(enabled ? 'separated' : 'tagged')} - size='md' + size="md" disabled={readonly} key={value} /> - } + )} > <div /> </Field> diff --git a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx index 191b9bfa4a..fe078ba6fa 100644 --- a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx +++ b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx @@ -24,9 +24,9 @@ const ResolutionPicker: FC<Props> = ({ } }, [onChange]) return ( - <div className='flex items-center justify-between'> - <div className='mr-2 text-xs font-medium uppercase text-text-secondary'>{t(`${i18nPrefix}.resolution.name`)}</div> - <div className='flex items-center space-x-1'> + <div className="flex items-center justify-between"> + <div className="mr-2 text-xs font-medium uppercase text-text-secondary">{t(`${i18nPrefix}.resolution.name`)}</div> + <div className="flex items-center space-x-1"> <OptionCard title={t(`${i18nPrefix}.resolution.high`)} onSelect={handleOnChange(Resolution.high)} diff --git a/web/app/components/workflow/nodes/llm/components/structure-output.tsx b/web/app/components/workflow/nodes/llm/components/structure-output.tsx index ee31a9e5ad..b97d5e20b7 100644 --- a/web/app/components/workflow/nodes/llm/components/structure-output.tsx +++ b/web/app/components/workflow/nodes/llm/components/structure-output.tsx @@ -1,19 +1,20 @@ 'use client' -import Button from '@/app/components/base/button' -import { RiEditLine } from '@remixicon/react' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { type SchemaRoot, type StructuredOutput, Type } from '../types' -import ShowPanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' +import type { SchemaRoot, StructuredOutput } from '../types' +import { RiEditLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import JsonSchemaConfigModal from './json-schema-config-modal' -import { cn } from '@/utils/classnames' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import ShowPanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' +import { cn } from '@/utils/classnames' +import { Type } from '../types' +import JsonSchemaConfigModal from './json-schema-config-modal' type Props = { className?: string value?: StructuredOutput - onChange: (value: StructuredOutput) => void, + onChange: (value: StructuredOutput) => void } const StructureOutput: FC<Props> = ({ @@ -34,27 +35,30 @@ const StructureOutput: FC<Props> = ({ }, [onChange]) return ( <div className={cn(className)}> - <div className='flex justify-between'> - <div className='flex items-center leading-[18px]'> - <div className='code-sm-semibold text-text-secondary'>structured_output</div> - <div className='system-xs-regular ml-2 text-text-tertiary'>object</div> + <div className="flex justify-between"> + <div className="flex items-center leading-[18px]"> + <div className="code-sm-semibold text-text-secondary">structured_output</div> + <div className="system-xs-regular ml-2 text-text-tertiary">object</div> </div> <Button - size='small' - variant='secondary' - className='flex' + size="small" + variant="secondary" + className="flex" onClick={showConfigModal} > - <RiEditLine className='mr-1 size-3.5' /> - <div className='system-xs-medium text-components-button-secondary-text'>{t('app.structOutput.configure')}</div> + <RiEditLine className="mr-1 size-3.5" /> + <div className="system-xs-medium text-components-button-secondary-text">{t('app.structOutput.configure')}</div> </Button> </div> - {(value?.schema && value.schema.properties && Object.keys(value.schema.properties).length > 0) ? ( - <ShowPanel - payload={value} - />) : ( - <div className='system-xs-regular mt-1.5 flex h-10 cursor-pointer items-center justify-center rounded-[10px] bg-background-section text-text-tertiary' onClick={showConfigModal}>{t('app.structOutput.notConfiguredTip')}</div> - )} + {(value?.schema && value.schema.properties && Object.keys(value.schema.properties).length > 0) + ? ( + <ShowPanel + payload={value} + /> + ) + : ( + <div className="system-xs-regular mt-1.5 flex h-10 cursor-pointer items-center justify-center rounded-[10px] bg-background-section text-text-tertiary" onClick={showConfigModal}>{t('app.structOutput.notConfiguredTip')}</div> + )} {showConfig && ( <JsonSchemaConfigModal diff --git a/web/app/components/workflow/nodes/llm/default.ts b/web/app/components/workflow/nodes/llm/default.ts index 57033d26a1..2bc49c7f1b 100644 --- a/web/app/components/workflow/nodes/llm/default.ts +++ b/web/app/components/workflow/nodes/llm/default.ts @@ -1,9 +1,9 @@ -// import { RETRIEVAL_OUTPUT_STRUCT } from '../../constants' -import { AppModeEnum } from '@/types/app' -import { BlockEnum, EditionType } from '../../types' -import { type NodeDefault, type PromptItem, PromptRole } from '../../types' +import type { NodeDefault, PromptItem } from '../../types' import type { LLMNodeType } from './types' import { genNodeMetaData } from '@/app/components/workflow/utils' +// import { RETRIEVAL_OUTPUT_STRUCT } from '../../constants' +import { AppModeEnum } from '@/types/app' +import { BlockEnum, EditionType, PromptRole } from '../../types' const RETRIEVAL_OUTPUT_STRUCT = `{ "content": "", @@ -67,11 +67,11 @@ const nodeDefault: NodeDefault<LLMNodeType> = { const isChatModel = payload.model.mode === AppModeEnum.CHAT const isPromptEmpty = isChatModel ? !(payload.prompt_template as PromptItem[]).some((t) => { - if (t.edition_type === EditionType.jinja2) - return t.jinja2_text !== '' + if (t.edition_type === EditionType.jinja2) + return t.jinja2_text !== '' - return t.text !== '' - }) + return t.text !== '' + }) : ((payload.prompt_template as PromptItem).edition_type === EditionType.jinja2 ? (payload.prompt_template as PromptItem).jinja2_text === '' : (payload.prompt_template as PromptItem).text === '') if (isPromptEmpty) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.llm.prompt') }) diff --git a/web/app/components/workflow/nodes/llm/node.tsx b/web/app/components/workflow/nodes/llm/node.tsx index ce676ba984..9d44d49475 100644 --- a/web/app/components/workflow/nodes/llm/node.tsx +++ b/web/app/components/workflow/nodes/llm/node.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react' -import React from 'react' import type { LLMNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' -import type { NodeProps } from '@/app/components/workflow/types' const Node: FC<NodeProps<LLMNodeType>> = ({ data, @@ -20,12 +20,12 @@ const Node: FC<NodeProps<LLMNodeType>> = ({ return null return ( - <div className='mb-1 px-3 py-1'> + <div className="mb-1 px-3 py-1"> {hasSetModel && ( <ModelSelector defaultModel={{ provider, model: modelId }} modelList={textGenerationModelList} - triggerClassName='!h-6 !rounded-md' + triggerClassName="!h-6 !rounded-md" readonly /> )} diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index bb893b0da7..4044989a07 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -1,27 +1,27 @@ import type { FC } from 'react' +import type { LLMNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' +import { RiAlertFill, RiQuestionLine } from '@remixicon/react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import AddButton2 from '@/app/components/base/button/add-button' +import Switch from '@/app/components/base/switch' +import Toast from '@/app/components/base/toast' +import Tooltip from '@/app/components/base/tooltip' +import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' +import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' +import ConfigVision from '../_base/components/config-vision' import MemoryConfig from '../_base/components/memory-config' import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import ConfigVision from '../_base/components/config-vision' -import useConfig from './use-config' -import type { LLMNodeType } from './types' import ConfigPrompt from './components/config-prompt' -import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' -import AddButton2 from '@/app/components/base/button/add-button' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import type { NodePanelProps } from '@/app/components/workflow/types' -import Tooltip from '@/app/components/base/tooltip' -import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import StructureOutput from './components/structure-output' import ReasoningFormatConfig from './components/reasoning-format-config' -import Switch from '@/app/components/base/switch' -import { RiAlertFill, RiQuestionLine } from '@remixicon/react' -import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' -import Toast from '@/app/components/base/toast' +import StructureOutput from './components/structure-output' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.llm' @@ -95,14 +95,14 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ })() }, [inputs.model.completion_params]) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.model`)} required > <ModelParameterModal - popupClassName='!w-[387px]' + popupClassName="!w-[387px]" isInWorkflow isAdvancedMode={true} provider={model?.provider} @@ -131,7 +131,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ filterVar={filterVar} /> {shouldShowContextTip && ( - <div className='text-xs font-normal leading-[18px] text-[#DC6803]'>{t(`${i18nPrefix}.notSetContextInPromptTip`)}</div> + <div className="text-xs font-normal leading-[18px] text-[#DC6803]">{t(`${i18nPrefix}.notSetContextInPromptTip`)}</div> )} </> </Field> @@ -175,29 +175,31 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ {/* Memory put place examples. */} {isChatMode && isChatModel && !!inputs.memory && ( - <div className='mt-4'> - <div className='flex h-8 items-center justify-between rounded-lg bg-components-input-bg-normal pl-3 pr-2'> - <div className='flex items-center space-x-1'> - <div className='text-xs font-semibold uppercase text-text-secondary'>{t('workflow.nodes.common.memories.title')}</div> + <div className="mt-4"> + <div className="flex h-8 items-center justify-between rounded-lg bg-components-input-bg-normal pl-3 pr-2"> + <div className="flex items-center space-x-1"> + <div className="text-xs font-semibold uppercase text-text-secondary">{t('workflow.nodes.common.memories.title')}</div> <Tooltip popupContent={t('workflow.nodes.common.memories.tip')} - triggerClassName='w-4 h-4' + triggerClassName="w-4 h-4" /> </div> - <div className='flex h-[18px] items-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 text-xs font-semibold uppercase text-text-tertiary'>{t('workflow.nodes.common.memories.builtIn')}</div> + <div className="flex h-[18px] items-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 text-xs font-semibold uppercase text-text-tertiary">{t('workflow.nodes.common.memories.builtIn')}</div> </div> {/* Readonly User Query */} - <div className='mt-4'> + <div className="mt-4"> <Editor - title={<div className='flex items-center space-x-1'> - <div className='text-xs font-semibold uppercase text-text-secondary'>user</div> - <Tooltip - popupContent={ - <div className='max-w-[180px]'>{t('workflow.nodes.llm.roleDescription.user')}</div> - } - triggerClassName='w-4 h-4' - /> - </div>} + title={( + <div className="flex items-center space-x-1"> + <div className="text-xs font-semibold uppercase text-text-secondary">user</div> + <Tooltip + popupContent={ + <div className="max-w-[180px]">{t('workflow.nodes.llm.roleDescription.user')}</div> + } + triggerClassName="w-4 h-4" + /> + </div> + )} value={inputs.memory.query_prompt_template || '{{#sys.query#}}'} onChange={handleSyeQueryChange} readOnly={readOnly} @@ -211,7 +213,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ /> {inputs.memory.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && ( - <div className='text-xs font-normal leading-[18px] text-[#DC6803]'>{t(`${i18nPrefix}.sysQueryInUser`)}</div> + <div className="text-xs font-normal leading-[18px] text-[#DC6803]">{t(`${i18nPrefix}.sysQueryInUser`)}</div> )} </div> </div> @@ -253,59 +255,63 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ <OutputVars collapsed={structuredOutputCollapsed} onCollapse={setStructuredOutputCollapsed} - operations={ - <div className='mr-4 flex shrink-0 items-center'> + operations={( + <div className="mr-4 flex shrink-0 items-center"> {(!isModelSupportStructuredOutput && !!inputs.structured_output_enabled) && ( - <Tooltip noDecoration popupContent={ - <div className='w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]'> - <div className='title-xs-semi-bold text-text-primary'>{t('app.structOutput.modelNotSupported')}</div> - <div className='body-xs-regular mt-1 text-text-secondary'>{t('app.structOutput.modelNotSupportedTip')}</div> - </div> - }> + <Tooltip + noDecoration + popupContent={( + <div className="w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]"> + <div className="title-xs-semi-bold text-text-primary">{t('app.structOutput.modelNotSupported')}</div> + <div className="body-xs-regular mt-1 text-text-secondary">{t('app.structOutput.modelNotSupportedTip')}</div> + </div> + )} + > <div> - <RiAlertFill className='mr-1 size-4 text-text-warning-secondary' /> + <RiAlertFill className="mr-1 size-4 text-text-warning-secondary" /> </div> </Tooltip> )} - <div className='system-xs-medium-uppercase mr-0.5 text-text-tertiary'>{t('app.structOutput.structured')}</div> + <div className="system-xs-medium-uppercase mr-0.5 text-text-tertiary">{t('app.structOutput.structured')}</div> <Tooltip popupContent={ - <div className='max-w-[150px]'>{t('app.structOutput.structuredTip')}</div> - }> + <div className="max-w-[150px]">{t('app.structOutput.structuredTip')}</div> + } + > <div> - <RiQuestionLine className='size-3.5 text-text-quaternary' /> + <RiQuestionLine className="size-3.5 text-text-quaternary" /> </div> </Tooltip> <Switch - className='ml-2' + className="ml-2" defaultValue={!!inputs.structured_output_enabled} onChange={handleStructureOutputEnableChange} - size='md' + size="md" disabled={readOnly} /> </div> - } + )} > <> <VarItem - name='text' - type='string' + name="text" + type="string" description={t(`${i18nPrefix}.outputVars.output`)} /> <VarItem - name='reasoning_content' - type='string' + name="reasoning_content" + type="string" description={t(`${i18nPrefix}.outputVars.reasoning_content`)} /> <VarItem - name='usage' - type='object' + name="usage" + type="object" description={t(`${i18nPrefix}.outputVars.usage`)} /> {inputs.structured_output_enabled && ( <> - <Split className='mt-3' /> + <Split className="mt-3" /> <StructureOutput - className='mt-4' + className="mt-4" value={inputs.structured_output} onChange={handleStructureOutputChange} /> diff --git a/web/app/components/workflow/nodes/llm/use-config.ts b/web/app/components/workflow/nodes/llm/use-config.ts index d9b811bb85..e885f108bb 100644 --- a/web/app/components/workflow/nodes/llm/use-config.ts +++ b/web/app/components/workflow/nodes/llm/use-config.ts @@ -1,31 +1,31 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { produce } from 'immer' -import { EditionType, VarType } from '../../types' import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types' -import { useStore } from '../../store' -import { - useIsChatMode, - useNodesReadOnly, -} from '../../hooks' -import useAvailableVarList from '../_base/hooks/use-available-var-list' -import useConfigVision from '../../hooks/use-config-vision' import type { LLMNodeType, StructuredOutput } from './types' -import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { produce } from 'immer' +import { useCallback, useEffect, useRef, useState } from 'react' +import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' import { ModelFeatureEnum, ModelTypeEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' +import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { AppModeEnum } from '@/types/app' +import { + useIsChatMode, + useNodesReadOnly, +} from '../../hooks' +import useConfigVision from '../../hooks/use-config-vision' +import { useStore } from '../../store' +import { EditionType, VarType } from '../../types' +import useAvailableVarList from '../_base/hooks/use-available-var-list' const useConfig = (id: string, payload: LLMNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() const isChatMode = useIsChatMode() const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type] - const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' }) + const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string, assistant: string }>({ user: '', assistant: '' }) const { inputs, setInputs: doSetInputs } = useNodeCrud<LLMNodeType>(id, payload) const inputRef = useRef(inputs) useEffect(() => { @@ -128,7 +128,7 @@ const useConfig = (id: string, payload: LLMNodeType) => { }, }) - const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => { + const handleModelChanged = useCallback((model: { provider: string, modelId: string, mode?: string }) => { const newInputs = produce(inputRef.current, (draft) => { draft.model.provider = model.provider draft.model.name = model.modelId @@ -285,8 +285,10 @@ const useConfig = (id: string, payload: LLMNodeType) => { const { data: modelList } = useModelList(ModelTypeEnum.textGeneration) const isModelSupportStructuredOutput = modelList ?.find(provideItem => provideItem.provider === model?.provider) - ?.models.find(modelItem => modelItem.model === model?.name) - ?.features?.includes(ModelFeatureEnum.StructuredOutput) + ?.models + .find(modelItem => modelItem.model === model?.name) + ?.features + ?.includes(ModelFeatureEnum.StructuredOutput) const [structuredOutputCollapsed, setStructuredOutputCollapsed] = useState(true) const handleStructureOutputEnableChange = useCallback((enabled: boolean) => { diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts index 8d539dfc15..ae500074ff 100644 --- a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts @@ -1,23 +1,23 @@ import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' +import type { LLMNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, PromptItem, Var, Variable } from '@/app/components/workflow/types' -import { InputVarType, VarType } from '@/app/components/workflow/types' -import type { LLMNodeType } from './types' -import { EditionType } from '../../types' -import useNodeCrud from '../_base/hooks/use-node-crud' -import { useIsChatMode } from '../../hooks' -import { useCallback } from 'react' -import useConfigVision from '../../hooks/use-config-vision' import { noop } from 'lodash-es' -import { findVariableWhenOnLLMVision } from '../utils' -import useAvailableVarList from '../_base/hooks/use-available-var-list' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { InputVarType, VarType } from '@/app/components/workflow/types' import { AppModeEnum } from '@/types/app' +import { useIsChatMode } from '../../hooks' +import useConfigVision from '../../hooks/use-config-vision' +import { EditionType } from '../../types' +import useAvailableVarList from '../_base/hooks/use-available-var-list' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { findVariableWhenOnLLMVision } from '../utils' const i18nPrefix = 'workflow.nodes.llm' type Params = { - id: string, - payload: LLMNodeType, + id: string + payload: LLMNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -186,10 +186,10 @@ const useSingleRunFormParams = ({ } const getDependentVar = (variable: string) => { - if(variable === '#context#') + if (variable === '#context#') return payload.context.variable_selector - if(variable === '#files#') + if (variable === '#files#') return payload.vision.configs?.variable_selector return false diff --git a/web/app/components/workflow/nodes/llm/utils.ts b/web/app/components/workflow/nodes/llm/utils.ts index 1652d511d0..604a1f8408 100644 --- a/web/app/components/workflow/nodes/llm/utils.ts +++ b/web/app/components/workflow/nodes/llm/utils.ts @@ -1,8 +1,8 @@ -import { z } from 'zod' -import { ArrayType, Type } from './types' -import type { ArrayItems, Field, LLMNodeType } from './types' -import { draft07Validator, forbidBooleanProperties } from '@/utils/validators' import type { ValidationError } from 'jsonschema' +import type { ArrayItems, Field, LLMNodeType } from './types' +import { z } from 'zod' +import { draft07Validator, forbidBooleanProperties } from '@/utils/validators' +import { ArrayType, Type } from './types' export const checkNodeValid = (_payload: LLMNodeType) => { return true @@ -10,8 +10,10 @@ export const checkNodeValid = (_payload: LLMNodeType) => { export const getFieldType = (field: Field) => { const { type, items, enum: enums } = field - if (field.schemaType === 'file') return Type.file - if (enums && enums.length > 0) return Type.enumType + if (field.schemaType === 'file') + return Type.file + if (enums && enums.length > 0) + return Type.enumType if (type !== Type.array || !items) return type @@ -29,7 +31,8 @@ export const getHasChildren = (schema: Field) => { } export const getTypeOf = (target: any) => { - if (target === null) return 'null' + if (target === null) + return 'null' if (typeof target !== 'object') { return typeof target } @@ -43,12 +46,17 @@ export const getTypeOf = (target: any) => { export const inferType = (value: any): Type => { const type = getTypeOf(value) - if (type === 'array') return Type.array + if (type === 'array') + return Type.array // type boolean will be treated as string - if (type === 'boolean') return Type.string - if (type === 'number') return Type.number - if (type === 'string') return Type.string - if (type === 'object') return Type.object + if (type === 'boolean') + return Type.string + if (type === 'number') + return Type.number + if (type === 'string') + return Type.string + if (type === 'object') + return Type.object return Type.string } diff --git a/web/app/components/workflow/nodes/loop-end/default.ts b/web/app/components/workflow/nodes/loop-end/default.ts index bb46ff1166..7a63b0d27f 100644 --- a/web/app/components/workflow/nodes/loop-end/default.ts +++ b/web/app/components/workflow/nodes/loop-end/default.ts @@ -2,9 +2,9 @@ import type { NodeDefault } from '../../types' import type { SimpleNodeType, } from '@/app/components/workflow/simple-node/types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ classification: BlockClassificationEnum.Logic, diff --git a/web/app/components/workflow/nodes/loop-start/default.ts b/web/app/components/workflow/nodes/loop-start/default.ts index 86dc5b44bf..7b39eff078 100644 --- a/web/app/components/workflow/nodes/loop-start/default.ts +++ b/web/app/components/workflow/nodes/loop-start/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { LoopStartNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: -1, diff --git a/web/app/components/workflow/nodes/loop-start/index.tsx b/web/app/components/workflow/nodes/loop-start/index.tsx index a48cc5fc27..34097e2030 100644 --- a/web/app/components/workflow/nodes/loop-start/index.tsx +++ b/web/app/components/workflow/nodes/loop-start/index.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' import type { NodeProps } from 'reactflow' import { RiHome5Fill } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { NodeSourceHandle } from '@/app/components/workflow/nodes/_base/components/node-handle' @@ -9,17 +9,17 @@ const LoopStartNode = ({ id, data }: NodeProps) => { const { t } = useTranslation() return ( - <div className='nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg'> + <div className="nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg"> <Tooltip popupContent={t('workflow.blocks.loop-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> <NodeSourceHandle id={id} data={data} - handleClassName='!top-1/2 !-right-[9px] !-translate-y-1/2' - handleId='source' + handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2" + handleId="source" /> </div> ) @@ -29,10 +29,10 @@ export const LoopStartNodeDumb = () => { const { t } = useTranslation() return ( - <div className='nodrag relative left-[17px] top-[21px] z-[11] flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg'> + <div className="nodrag relative left-[17px] top-[21px] z-[11] flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg"> <Tooltip popupContent={t('workflow.blocks.loop-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> </div> diff --git a/web/app/components/workflow/nodes/loop/add-block.tsx b/web/app/components/workflow/nodes/loop/add-block.tsx index d86d9f83cb..9602f7700a 100644 --- a/web/app/components/workflow/nodes/loop/add-block.tsx +++ b/web/app/components/workflow/nodes/loop/add-block.tsx @@ -1,26 +1,26 @@ +import type { LoopNodeType } from './types' +import type { + OnSelectBlock, +} from '@/app/components/workflow/types' +import { + RiAddLine, +} from '@remixicon/react' import { memo, useCallback, } from 'react' -import { - RiAddLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' +import BlockSelector from '@/app/components/workflow/block-selector' +import { + BlockEnum, +} from '@/app/components/workflow/types' + +import { cn } from '@/utils/classnames' import { useAvailableBlocks, useNodesInteractions, useNodesReadOnly, } from '../../hooks' -import type { LoopNodeType } from './types' -import { cn } from '@/utils/classnames' -import BlockSelector from '@/app/components/workflow/block-selector' - -import type { - OnSelectBlock, -} from '@/app/components/workflow/types' -import { - BlockEnum, -} from '@/app/components/workflow/types' type AddBlockProps = { loopNodeId: string @@ -53,24 +53,25 @@ const AddBlock = ({ 'system-sm-medium relative inline-flex h-8 cursor-pointer items-center rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3 text-components-button-secondary-text shadow-xs backdrop-blur-[5px] hover:bg-components-button-secondary-bg-hover', `${nodesReadOnly && '!cursor-not-allowed bg-components-button-secondary-bg-disabled'}`, open && 'bg-components-button-secondary-bg-hover', - )}> - <RiAddLine className='mr-1 h-4 w-4' /> + )} + > + <RiAddLine className="mr-1 h-4 w-4" /> {t('workflow.common.addBlock')} </div> ) }, [nodesReadOnly, t]) return ( - <div className='absolute left-14 top-7 z-10 flex h-8 items-center'> - <div className='group/insert relative h-0.5 w-16 bg-gray-300'> - <div className='absolute right-0 top-1/2 h-2 w-0.5 -translate-y-1/2 bg-primary-500'></div> + <div className="absolute left-14 top-7 z-10 flex h-8 items-center"> + <div className="group/insert relative h-0.5 w-16 bg-gray-300"> + <div className="absolute right-0 top-1/2 h-2 w-0.5 -translate-y-1/2 bg-primary-500"></div> </div> <BlockSelector disabled={nodesReadOnly} onSelect={handleSelect} trigger={renderTriggerElement} - triggerInnerClassName='inline-flex' - popupClassName='!min-w-[256px]' + triggerInnerClassName="inline-flex" + popupClassName="!min-w-[256px]" availableBlocksTypes={availableNextBlocks} /> </div> diff --git a/web/app/components/workflow/nodes/loop/components/condition-add.tsx b/web/app/components/workflow/nodes/loop/components/condition-add.tsx index cd5be853b6..06af8d3ec8 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-add.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-add.tsx @@ -1,10 +1,15 @@ +import type { HandleAddCondition } from '../types' +import type { + NodeOutPutVar, + ValueSelector, + Var, +} from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' import { useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' -import type { HandleAddCondition } from '../types' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -12,11 +17,6 @@ import { PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { - NodeOutPutVar, - ValueSelector, - Var, -} from '@/app/components/workflow/types' type ConditionAddProps = { className?: string @@ -42,7 +42,7 @@ const ConditionAdd = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, @@ -50,16 +50,16 @@ const ConditionAdd = ({ > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> <Button - size='small' + size="small" className={className} disabled={disabled} > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> + <RiAddLine className="mr-1 h-3.5 w-3.5" /> {t('workflow.nodes.ifElse.addCondition')} </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <VarReferenceVars vars={variables} isSupportFileVar diff --git a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx index 00eec93de3..e13832ed46 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx @@ -1,20 +1,22 @@ +import type { ValueSelector } from '../../../types' +import type { Condition } from '../types' import { memo, useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import { ComparisonOperator, type Condition } from '../types' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { ComparisonOperator } from '../types' import { comparisonOperatorNotRequireValue, isComparisonOperatorNeedTranslate, isEmptyRelatedOperator, } from '../utils' -import type { ValueSelector } from '../../../types' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from './../default' -import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { - VariableLabelInNode, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' + const i18nPrefix = 'workflow.nodes.ifElse' type ConditionValueProps = { @@ -39,7 +41,7 @@ const ConditionValue = ({ return '' const value = c.value as string - return value.replace(/{{#([^#]*)#}}/g, (a, b) => { + return value.replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -57,41 +59,41 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(c.value) ? c.value[0] : c.value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => { - const arr: string[] = b.split('.') - if (isSystemVar(arr)) - return `{{${b}}}` + ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + const arr: string[] = b.split('.') + if (isSystemVar(arr)) + return `{{${b}}}` - return `{{${arr.slice(1).join('.')}}}` - }) + return `{{${arr.slice(1).join('.')}}}` + }) : '' } return '' }, [t]) return ( - <div className='rounded-md bg-workflow-block-parma-bg'> - <div className='flex h-6 items-center px-1 '> + <div className="rounded-md bg-workflow-block-parma-bg"> + <div className="flex h-6 items-center px-1 "> <VariableLabelInNode - className='w-0 grow' + className="w-0 grow" variables={variableSelector} notShowFullPath /> <div - className='mx-1 shrink-0 text-xs font-medium text-text-primary' + className="mx-1 shrink-0 text-xs font-medium text-text-primary" title={operatorName} > {operatorName} </div> </div> - <div className='ml-[10px] border-l border-divider-regular pl-[10px]'> + <div className="ml-[10px] border-l border-divider-regular pl-[10px]"> { sub_variable_condition?.conditions.map((c: Condition, index) => ( - <div className='relative flex h-6 items-center space-x-1' key={c.id}> - <div className='system-xs-medium text-text-accent'>{c.key}</div> - <div className='system-xs-medium text-text-primary'>{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> - {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className='system-xs-regular text-text-secondary'>{isSelect(c) ? selectName(c) : formatValue(c)}</div>} - {index !== sub_variable_condition.conditions.length - 1 && (<div className='absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent'>{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} + <div className="relative flex h-6 items-center space-x-1" key={c.id}> + <div className="system-xs-medium text-text-accent">{c.key}</div> + <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> + {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className="system-xs-regular text-text-secondary">{isSelect(c) ? selectName(c) : formatValue(c)}</div>} + {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} </div> )) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx index 683e2884cf..c6560a8a0e 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx @@ -1,10 +1,10 @@ -import { useTranslation } from 'react-i18next' -import { useStore } from '@/app/components/workflow/store' -import PromptEditor from '@/app/components/base/prompt-editor' -import { BlockEnum } from '@/app/components/workflow/types' import type { Node, } from '@/app/components/workflow/types' +import { useTranslation } from 'react-i18next' +import PromptEditor from '@/app/components/base/prompt-editor' +import { useStore } from '@/app/components/workflow/store' +import { BlockEnum } from '@/app/components/workflow/types' type ConditionInputProps = { disabled?: boolean diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx index 0ecae8f052..ea3e2ef5be 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx @@ -1,42 +1,42 @@ -import { - useCallback, - useMemo, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import { RiDeleteBinLine } from '@remixicon/react' -import { produce } from 'immer' import type { VarType as NumberVarType } from '../../../tool/types' import type { Condition, HandleAddSubVariableCondition, HandleRemoveCondition, + handleRemoveSubVariableCondition, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, - handleRemoveSubVariableCondition, } from '../../types' -import { - ComparisonOperator, -} from '../../types' -import ConditionNumberInput from '../condition-number-input' -import ConditionWrap from '../condition-wrap' -import { comparisonOperatorNotRequireValue, getOperators } from './../../utils' -import ConditionOperator from './condition-operator' -import ConditionInput from './condition-input' -import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from './../../default' import type { Node, NodeOutPutVar, ValueSelector, Var, } from '@/app/components/workflow/types' +import { RiDeleteBinLine } from '@remixicon/react' +import { produce } from 'immer' +import { + useCallback, + useMemo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { SimpleSelect as Select } from '@/app/components/base/select' +import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' import { VarType } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' -import { SimpleSelect as Select } from '@/app/components/base/select' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { + ComparisonOperator, +} from '../../types' +import ConditionNumberInput from '../condition-number-input' +import ConditionWrap from '../condition-wrap' +import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from './../../default' +import { comparisonOperatorNotRequireValue, getOperators } from './../../utils' +import ConditionInput from './condition-input' +import ConditionOperator from './condition-operator' import ConditionVarSelector from './condition-var-selector' -import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' @@ -209,45 +209,48 @@ const ConditionItem = ({ <div className={cn( 'grow rounded-lg bg-components-input-bg-normal', isHovered && 'bg-state-destructive-hover', - )}> - <div className='flex items-center p-1'> - <div className='w-0 grow'> + )} + > + <div className="flex items-center p-1"> + <div className="w-0 grow"> {isSubVarSelect ? ( - <Select - wrapperClassName='h-6' - className='pl-0 text-xs' - optionWrapClassName='w-[165px] max-h-none' - defaultValue={condition.key} - items={subVarOptions} - onSelect={item => handleSubVarKeyChange(item.value as string)} - renderTrigger={item => ( - item - ? <div className='flex cursor-pointer justify-start'> - <div className='inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs'> - <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' /> - <div className='system-xs-medium ml-0.5 truncate'>{item?.name}</div> - </div> - </div> - : <div className='system-sm-regular text-left text-components-input-text-placeholder'>{t('common.placeholder.select')}</div> - )} - hideChecked - /> - ) + <Select + wrapperClassName="h-6" + className="pl-0 text-xs" + optionWrapClassName="w-[165px] max-h-none" + defaultValue={condition.key} + items={subVarOptions} + onSelect={item => handleSubVarKeyChange(item.value as string)} + renderTrigger={item => ( + item + ? ( + <div className="flex cursor-pointer justify-start"> + <div className="inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs"> + <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" /> + <div className="system-xs-medium ml-0.5 truncate">{item?.name}</div> + </div> + </div> + ) + : <div className="system-sm-regular text-left text-components-input-text-placeholder">{t('common.placeholder.select')}</div> + )} + hideChecked + /> + ) : ( - <ConditionVarSelector - open={open} - onOpenChange={setOpen} - valueSelector={condition.variable_selector || []} - varType={condition.varType} - availableNodes={availableNodes} - nodesOutputVars={availableVars} - onChange={handleVarChange} - /> - )} + <ConditionVarSelector + open={open} + onOpenChange={setOpen} + valueSelector={condition.variable_selector || []} + varType={condition.varType} + availableNodes={availableNodes} + nodesOutputVars={availableVars} + onChange={handleVarChange} + /> + )} </div> - <div className='mx-1 h-3 w-[1px] bg-divider-regular'></div> + <div className="mx-1 h-3 w-[1px] bg-divider-regular"></div> <ConditionOperator disabled={!canChooseOperator} varType={condition.varType} @@ -258,7 +261,7 @@ const ConditionItem = ({ </div> { !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && condition.varType !== VarType.boolean && ( - <div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'> + <div className="max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1"> <ConditionInput disabled={disabled} value={condition.value as string} @@ -269,16 +272,17 @@ const ConditionItem = ({ ) } {!comparisonOperatorNotRequireValue(condition.comparison_operator) && condition.varType === VarType.boolean - && <div className='p-1'> - <BoolValue - value={condition.value as boolean} - onChange={handleUpdateConditionValue} - /> - </div> - } + && ( + <div className="p-1"> + <BoolValue + value={condition.value as boolean} + onChange={handleUpdateConditionValue} + /> + </div> + )} { !comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && ( - <div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'> + <div className="border-t border-t-divider-subtle px-2 py-1 pt-[3px]"> <ConditionNumberInput numberVarType={condition.numberVarType} onNumberVarTypeChange={handleUpdateConditionNumberVarType} @@ -293,10 +297,10 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && isSelect && ( - <div className='border-t border-t-divider-subtle'> + <div className="border-t border-t-divider-subtle"> <Select - wrapperClassName='h-8' - className='rounded-t-none px-2 text-xs' + wrapperClassName="h-8" + className="rounded-t-none px-2 text-xs" defaultValue={isArrayValue ? (condition.value as string[])?.[0] : (condition.value as string)} items={selectOptions} onSelect={item => handleUpdateConditionValue(item.value as string)} @@ -308,7 +312,7 @@ const ConditionItem = ({ } { !comparisonOperatorNotRequireValue(condition.comparison_operator) && isSubVariable && ( - <div className='p-1'> + <div className="p-1"> <ConditionWrap isSubVariable conditions={condition.sub_variable_condition?.conditions || []} @@ -328,12 +332,12 @@ const ConditionItem = ({ } </div> <div - className='ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive' + className="ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={doRemoveCondition} > - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx index 49c1d6e64f..a33b2b7727 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx @@ -1,19 +1,20 @@ +import type { ComparisonOperator } from '../../types' +import type { VarType } from '@/app/components/workflow/types' +import { RiArrowDownSLine } from '@remixicon/react' import { useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils' -import type { ComparisonOperator } from '../../types' import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { VarType } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils' + const i18nPrefix = 'workflow.nodes.ifElse' type ConditionOperatorProps = { @@ -48,7 +49,7 @@ const ConditionOperator = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 0, @@ -57,8 +58,8 @@ const ConditionOperator = ({ <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <Button className={cn('shrink-0', !selectedOption && 'opacity-50', className)} - size='small' - variant='ghost' + size="small" + variant="ghost" disabled={disabled} > { @@ -66,16 +67,16 @@ const ConditionOperator = ({ ? selectedOption.label : t(`${i18nPrefix}.select`) } - <RiArrowDownSLine className='ml-1 h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-10"> + <div className="rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.value} - className='flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover' + className="flex h-7 cursor-pointer items-center rounded-lg px-3 py-1.5 text-[13px] font-medium text-text-secondary hover:bg-state-base-hover" onClick={() => { onSelect(option.value) setOpen(false) diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx index 4cb82b4ce9..0c219413ee 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-var-selector.tsx @@ -1,7 +1,7 @@ +import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { Node, NodeOutPutVar, ValueSelector, Var, VarType } from '@/app/components/workflow/types' type ConditionVarSelectorProps = { open: boolean @@ -26,7 +26,7 @@ const ConditionVarSelector = ({ <PortalToFollowElem open={open} onOpenChange={onOpenChange} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 4, crossAxis: 0, @@ -42,8 +42,8 @@ const ConditionVarSelector = ({ /> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <VarReferenceVars vars={nodesOutputVars} isSupportFileVar diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/index.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/index.tsx index 8cfcc5d811..4c70d9073c 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/index.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/index.tsx @@ -1,22 +1,17 @@ -import { RiLoopLeftLine } from '@remixicon/react' -import { useCallback, useMemo } from 'react' -import { - type Condition, - type HandleAddSubVariableCondition, - type HandleRemoveCondition, - type HandleToggleConditionLogicalOperator, - type HandleToggleSubVariableConditionLogicalOperator, - type HandleUpdateCondition, - type HandleUpdateSubVariableCondition, - LogicalOperator, - type handleRemoveSubVariableCondition, -} from '../../types' -import ConditionItem from './condition-item' +import type { Condition, HandleAddSubVariableCondition, HandleRemoveCondition, handleRemoveSubVariableCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition } from '../../types' import type { Node, NodeOutPutVar, } from '@/app/components/workflow/types' +import { RiLoopLeftLine } from '@remixicon/react' +import { useCallback, useMemo } from 'react' import { cn } from '@/utils/classnames' +import { + + LogicalOperator, + +} from '../../types' +import ConditionItem from './condition-item' type ConditionListProps = { isSubVariable?: boolean @@ -83,15 +78,16 @@ const ConditionList = ({ 'absolute bottom-0 left-0 top-0 w-[60px]', isSubVariable && logicalOperator === LogicalOperator.and && 'left-[-10px]', isSubVariable && logicalOperator === LogicalOperator.or && 'left-[-18px]', - )}> - <div className='absolute bottom-4 left-[46px] top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep'></div> - <div className='absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg'></div> + )} + > + <div className="absolute bottom-4 left-[46px] top-4 w-2.5 rounded-l-[8px] border border-r-0 border-divider-deep"></div> + <div className="absolute right-0 top-1/2 h-[29px] w-4 -translate-y-1/2 bg-components-panel-bg"></div> <div - className='absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs' + className="absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-1 text-[10px] font-semibold text-text-accent-secondary shadow-xs" onClick={() => doToggleConditionLogicalOperator(conditionId)} > {logicalOperator && logicalOperator.toUpperCase()} - <RiLoopLeftLine className='ml-0.5 h-3 w-3' /> + <RiLoopLeftLine className="ml-0.5 h-3 w-3" /> </div> </div> ) diff --git a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx index a13fbef011..29419be011 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx @@ -1,29 +1,29 @@ +import type { + NodeOutPutVar, + ValueSelector, +} from '@/app/components/workflow/types' +import { RiArrowDownSLine } from '@remixicon/react' +import { useBoolean } from 'ahooks' +import { capitalize } from 'lodash-es' import { memo, useCallback, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' -import { capitalize } from 'lodash-es' -import { useBoolean } from 'ahooks' -import { VarType as NumberVarType } from '../../tool/types' -import VariableTag from '../../_base/components/variable-tag' +import Button from '@/app/components/base/button' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import { cn } from '@/utils/classnames' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' -import type { - NodeOutPutVar, - ValueSelector, -} from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' import { variableTransformer } from '@/app/components/workflow/utils' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { cn } from '@/utils/classnames' +import VariableTag from '../../_base/components/variable-tag' +import { VarType as NumberVarType } from '../../tool/types' const options = [ NumberVarType.variable, @@ -62,25 +62,25 @@ const ConditionNumberInput = ({ }, [onValueChange]) return ( - <div className='flex cursor-pointer items-center'> + <div className="flex cursor-pointer items-center"> <PortalToFollowElem open={numberVarTypeVisible} onOpenChange={setNumberVarTypeVisible} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 2, crossAxis: 0 }} > <PortalToFollowElemTrigger onClick={() => setNumberVarTypeVisible(v => !v)}> <Button - className='shrink-0' - variant='ghost' - size='small' + className="shrink-0" + variant="ghost" + size="small" > {capitalize(numberVarType)} - <RiArrowDownSLine className='ml-[1px] h-3.5 w-3.5' /> + <RiArrowDownSLine className="ml-[1px] h-3.5 w-3.5" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> - <div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <PortalToFollowElemContent className="z-[1000]"> + <div className="w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div @@ -102,19 +102,20 @@ const ConditionNumberInput = ({ </div> </PortalToFollowElemContent> </PortalToFollowElem> - <div className='mx-1 h-4 w-[1px] bg-divider-regular'></div> - <div className='ml-0.5 w-0 grow'> + <div className="mx-1 h-4 w-[1px] bg-divider-regular"></div> + <div className="ml-0.5 w-0 grow"> { numberVarType === NumberVarType.variable && ( <PortalToFollowElem open={variableSelectorVisible} onOpenChange={setVariableSelectorVisible} - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 2, crossAxis: 0 }} > <PortalToFollowElemTrigger - className='w-full' - onClick={() => setVariableSelectorVisible(v => !v)}> + className="w-full" + onClick={() => setVariableSelectorVisible(v => !v)} + > { value && ( <VariableTag @@ -126,14 +127,14 @@ const ConditionNumberInput = ({ } { !value && ( - <div className='flex h-6 items-center p-1 text-[13px] text-components-input-text-placeholder'> - <Variable02 className='mr-1 h-4 w-4 shrink-0' /> - <div className='w-0 grow truncate'>{t('workflow.nodes.ifElse.selectVariable')}</div> + <div className="flex h-6 items-center p-1 text-[13px] text-components-input-text-placeholder"> + <Variable02 className="mr-1 h-4 w-4 shrink-0" /> + <div className="w-0 grow truncate">{t('workflow.nodes.ifElse.selectVariable')}</div> </div> ) } </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> + <PortalToFollowElemContent className="z-[1000]"> <div className={cn('w-[296px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur pt-1 shadow-lg', isShort && 'w-[200px]')}> <VarReferenceVars vars={variables} @@ -146,17 +147,17 @@ const ConditionNumberInput = ({ } { numberVarType === NumberVarType.constant && ( - <div className=' relative'> + <div className=" relative"> <input className={cn('block w-full appearance-none bg-transparent px-2 text-[13px] text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder', unit && 'pr-6')} - type='number' + type="number" value={value} onChange={e => onValueChange(e.target.value)} placeholder={t('workflow.nodes.ifElse.enterValue') || ''} onFocus={setFocus} onBlur={setBlur} /> - {!isFocus && unit && <div className='system-sm-regular absolute right-2 top-[50%] translate-y-[-50%] text-text-tertiary'>{unit}</div>} + {!isFocus && unit && <div className="system-sm-regular absolute right-2 top-[50%] translate-y-[-50%] text-text-tertiary">{unit}</div>} </div> ) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-value.tsx b/web/app/components/workflow/nodes/loop/components/condition-value.tsx index 2f011f870a..c24a1a18a6 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-value.tsx @@ -3,16 +3,16 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { ComparisonOperator } from '../types' import { comparisonOperatorNotRequireValue, isComparisonOperatorNeedTranslate, } from '../utils' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from './../default' -import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { - VariableLabelInNode, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type ConditionValueProps = { variableSelector: string[] @@ -36,7 +36,7 @@ const ConditionValue = ({ if (Array.isArray(value)) // transfer method return value[0] - return value.replace(/{{#([^#]*)#}}/g, (a, b) => { + return value.replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -50,34 +50,34 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(value) ? value[0] : value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/{{#([^#]*)#}}/g, (a, b) => { - const arr: string[] = b.split('.') - if (isSystemVar(arr)) - return `{{${b}}}` + ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + const arr: string[] = b.split('.') + if (isSystemVar(arr)) + return `{{${b}}}` - return `{{${arr.slice(1).join('.')}}}` - }) + return `{{${arr.slice(1).join('.')}}}` + }) : '' } return '' }, [isSelect, t, value]) return ( - <div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1'> + <div className="flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1"> <VariableLabelInNode - className='w-0 grow' + className="w-0 grow" variables={variableSelector} notShowFullPath /> <div - className='mx-1 shrink-0 text-xs font-medium text-text-primary' + className="mx-1 shrink-0 text-xs font-medium text-text-primary" title={operatorName} > {operatorName} </div> { !notHasValue && ( - <div className='truncate text-xs text-text-secondary' title={formatValue}>{isSelect ? selectName : formatValue}</div> + <div className="truncate text-xs text-text-secondary" title={formatValue}>{isSelect ? selectName : formatValue}</div> ) } </div> diff --git a/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx b/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx index 6812554767..00f44ab244 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx @@ -1,20 +1,20 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { useTranslation } from 'react-i18next' +import type { Node, NodeOutPutVar, Var } from '../../../types' +import type { Condition, HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, handleRemoveSubVariableCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LogicalOperator } from '../types' import { RiAddLine, } from '@remixicon/react' -import type { Condition, HandleAddCondition, HandleAddSubVariableCondition, HandleRemoveCondition, HandleToggleConditionLogicalOperator, HandleToggleSubVariableConditionLogicalOperator, HandleUpdateCondition, HandleUpdateSubVariableCondition, LogicalOperator, handleRemoveSubVariableCondition } from '../types' -import type { Node, NodeOutPutVar, Var } from '../../../types' -import { VarType } from '../../../types' -import { useGetAvailableVars } from '../../variable-assigner/hooks' -import ConditionList from './condition-list' -import ConditionAdd from './condition-add' -import { SUB_VARIABLES } from './../default' -import { cn } from '@/utils/classnames' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalSelect as Select } from '@/app/components/base/select' +import { cn } from '@/utils/classnames' +import { VarType } from '../../../types' +import { useGetAvailableVars } from '../../variable-assigner/hooks' +import { SUB_VARIABLES } from './../default' +import ConditionAdd from './condition-add' +import ConditionList from './condition-list' type Props = { isSubVariable?: boolean @@ -81,7 +81,7 @@ const ConditionWrap: FC<Props> = ({ > { conditions && !!conditions.length && ( - <div className='mb-2'> + <div className="mb-2"> <ConditionList disabled={readOnly} conditionId={conditionId} @@ -109,33 +109,34 @@ const ConditionWrap: FC<Props> = ({ !conditions.length && !isSubVariable && 'mt-1', !conditions.length && isSubVariable && 'mt-2', conditions.length > 1 && !isSubVariable && 'ml-[60px]', - )}> + )} + > {isSubVariable ? ( - <Select - popupInnerClassName='w-[165px] max-h-none' - onSelect={value => handleAddSubVariableCondition?.(conditionId!, value.value as string)} - items={subVarOptions} - value='' - renderTrigger={() => ( - <Button - size='small' - disabled={readOnly} - > - <RiAddLine className='mr-1 h-3.5 w-3.5' /> - {t('workflow.nodes.ifElse.addSubVariable')} - </Button> - )} - hideChecked - /> - ) + <Select + popupInnerClassName="w-[165px] max-h-none" + onSelect={value => handleAddSubVariableCondition?.(conditionId!, value.value as string)} + items={subVarOptions} + value="" + renderTrigger={() => ( + <Button + size="small" + disabled={readOnly} + > + <RiAddLine className="mr-1 h-3.5 w-3.5" /> + {t('workflow.nodes.ifElse.addSubVariable')} + </Button> + )} + hideChecked + /> + ) : ( - <ConditionAdd - disabled={readOnly} - variables={availableVars} - onSelectVariable={handleAddCondition!} - /> - )} + <ConditionAdd + disabled={readOnly} + variables={availableVars} + onSelectVariable={handleAddCondition!} + /> + )} </div> </div> </div> diff --git a/web/app/components/workflow/nodes/loop/components/loop-variables/empty.tsx b/web/app/components/workflow/nodes/loop/components/loop-variables/empty.tsx index 6fe4aa0a77..13b5a0ca67 100644 --- a/web/app/components/workflow/nodes/loop/components/loop-variables/empty.tsx +++ b/web/app/components/workflow/nodes/loop/components/loop-variables/empty.tsx @@ -4,7 +4,7 @@ const Empty = () => { const { t } = useTranslation() return ( - <div className='system-xs-regular flex h-10 items-center justify-center rounded-[10px] bg-background-section text-text-tertiary'> + <div className="system-xs-regular flex h-10 items-center justify-center rounded-[10px] bg-background-section text-text-tertiary"> {t('workflow.nodes.loop.setLoopVariables')} </div> ) diff --git a/web/app/components/workflow/nodes/loop/components/loop-variables/form-item.tsx b/web/app/components/workflow/nodes/loop/components/loop-variables/form-item.tsx index e4cc13835f..911b122925 100644 --- a/web/app/components/workflow/nodes/loop/components/loop-variables/form-item.tsx +++ b/web/app/components/workflow/nodes/loop/components/loop-variables/form-item.tsx @@ -1,13 +1,3 @@ -import { - useCallback, - useMemo, -} from 'react' -import { useTranslation } from 'react-i18next' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import Input from '@/app/components/base/input' -import Textarea from '@/app/components/base/textarea' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import type { LoopVariable, } from '@/app/components/workflow/nodes/loop/types' @@ -15,9 +5,16 @@ import type { Var, } from '@/app/components/workflow/types' import { - ValueType, - VarType, -} from '@/app/components/workflow/types' + useCallback, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import Input from '@/app/components/base/input' +import Textarea from '@/app/components/base/textarea' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import ArrayBoolList from '@/app/components/workflow/panel/chat-variable-panel/components/array-bool-list' import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value' import { @@ -27,7 +24,10 @@ import { arrayStringPlaceholder, objectPlaceholder, } from '@/app/components/workflow/panel/chat-variable-panel/utils' -import ArrayBoolList from '@/app/components/workflow/panel/chat-variable-panel/components/array-bool-list' +import { + ValueType, + VarType, +} from '@/app/components/workflow/types' type FormItemProps = { nodeId: string @@ -91,7 +91,7 @@ const FormItem = ({ <Textarea value={value} onChange={handleInputChange} - className='min-h-12 w-full' + className="min-h-12 w-full" /> ) } @@ -101,7 +101,7 @@ const FormItem = ({ type="number" value={value} onChange={handleInputChange} - className='w-full' + className="w-full" /> ) } @@ -117,15 +117,15 @@ const FormItem = ({ value_type === ValueType.constant && (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject) && ( - <div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}> + <div className="w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1" style={{ height: editorMinHeight }}> <CodeEditor value={value} isExpand noWrapper language={CodeLanguage.json} onChange={handleChange} - className='w-full' - placeholder={<div className='whitespace-pre'>{placeholder}</div>} + className="w-full" + placeholder={<div className="whitespace-pre">{placeholder}</div>} /> </div> ) @@ -133,7 +133,7 @@ const FormItem = ({ { value_type === ValueType.constant && var_type === VarType.arrayBoolean && ( <ArrayBoolList - className='mt-2' + className="mt-2" list={value || [false]} onChange={handleChange} /> diff --git a/web/app/components/workflow/nodes/loop/components/loop-variables/index.tsx b/web/app/components/workflow/nodes/loop/components/loop-variables/index.tsx index 26251e394c..ddd34baeae 100644 --- a/web/app/components/workflow/nodes/loop/components/loop-variables/index.tsx +++ b/web/app/components/workflow/nodes/loop/components/loop-variables/index.tsx @@ -1,9 +1,9 @@ -import Empty from './empty' -import Item from './item' import type { LoopVariable, LoopVariablesComponentShape, } from '@/app/components/workflow/nodes/loop/types' +import Empty from './empty' +import Item from './item' type LoopVariableProps = { variables?: LoopVariable[] diff --git a/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx b/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx index 7084389be8..973e78ae73 100644 --- a/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx +++ b/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx @@ -1,18 +1,18 @@ -import { useCallback } from 'react' -import { RiDeleteBinLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import InputModeSelect from './input-mode-selec' -import VariableTypeSelect from './variable-type-select' -import FormItem from './form-item' -import ActionButton from '@/app/components/base/action-button' -import Input from '@/app/components/base/input' import type { LoopVariable, LoopVariablesComponentShape, } from '@/app/components/workflow/nodes/loop/types' -import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' +import { RiDeleteBinLine } from '@remixicon/react' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import ActionButton from '@/app/components/base/action-button' +import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' import { ValueType, VarType } from '@/app/components/workflow/types' +import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' +import FormItem from './form-item' +import InputModeSelect from './input-mode-selec' +import VariableTypeSelect from './variable-type-select' type ItemProps = { item: LoopVariable @@ -44,7 +44,7 @@ const Item = ({ }, [item.id, handleUpdateLoopVariable]) const getDefaultValue = useCallback((varType: VarType, valueType: ValueType) => { - if(valueType === ValueType.variable) + if (valueType === ValueType.variable) return undefined switch (varType) { case VarType.boolean: @@ -69,9 +69,9 @@ const Item = ({ }, [item.id, handleUpdateLoopVariable]) return ( - <div className='mb-4 flex last-of-type:mb-0'> - <div className='w-0 grow'> - <div className='mb-1 grid grid-cols-3 gap-1'> + <div className="mb-4 flex last-of-type:mb-0"> + <div className="w-0 grow"> + <div className="mb-1 grid grid-cols-3 gap-1"> <Input value={item.label} onChange={handleUpdateItemLabel} @@ -97,11 +97,11 @@ const Item = ({ </div> </div> <ActionButton - className='shrink-0' - size='l' + className="shrink-0" + size="l" onClick={() => handleRemoveLoopVariable(item.id)} > - <RiDeleteBinLine className='h-4 w-4 text-text-tertiary' /> + <RiDeleteBinLine className="h-4 w-4 text-text-tertiary" /> </ActionButton> </div> ) diff --git a/web/app/components/workflow/nodes/loop/default.ts b/web/app/components/workflow/nodes/loop/default.ts index 390545d0e2..cbc79bd54f 100644 --- a/web/app/components/workflow/nodes/loop/default.ts +++ b/web/app/components/workflow/nodes/loop/default.ts @@ -1,12 +1,14 @@ -import { VarType } from '../../types' import type { NodeDefault } from '../../types' -import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types' -import { isEmptyRelatedOperator } from './utils' -import { TransferMethod } from '@/types/app' -import { LOOP_NODE_MAX_COUNT } from '@/config' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import type { LoopNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { LOOP_NODE_MAX_COUNT } from '@/config' +import { TransferMethod } from '@/types/app' +import { VarType } from '../../types' +import { ComparisonOperator, LogicalOperator } from './types' +import { isEmptyRelatedOperator } from './utils' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ @@ -66,8 +68,9 @@ const nodeDefault: NodeDefault<LoopNodeType> = { || !Number.isInteger(Number(payload.loop_count)) || payload.loop_count < 1 || payload.loop_count > LOOP_NODE_MAX_COUNT - )) + )) { errorMessages = t('workflow.nodes.loop.loopMaxCountError', { maxCount: LOOP_NODE_MAX_COUNT }) + } return { isValid: !errorMessages, diff --git a/web/app/components/workflow/nodes/loop/insert-block.tsx b/web/app/components/workflow/nodes/loop/insert-block.tsx index 57eef5d17a..7f24ac5078 100644 --- a/web/app/components/workflow/nodes/loop/insert-block.tsx +++ b/web/app/components/workflow/nodes/loop/insert-block.tsx @@ -1,15 +1,15 @@ +import type { + BlockEnum, + OnSelectBlock, +} from '../../types' import { memo, useCallback, useState, } from 'react' import { cn } from '@/utils/classnames' -import { useNodesInteractions } from '../../hooks' -import type { - BlockEnum, - OnSelectBlock, -} from '../../types' import BlockSelector from '../../block-selector' +import { useNodesInteractions } from '../../hooks' type InsertBlockProps = { startNodeId: string diff --git a/web/app/components/workflow/nodes/loop/node.tsx b/web/app/components/workflow/nodes/loop/node.tsx index feacb2ec4d..c8d3843a75 100644 --- a/web/app/components/workflow/nodes/loop/node.tsx +++ b/web/app/components/workflow/nodes/loop/node.tsx @@ -1,4 +1,6 @@ import type { FC } from 'react' +import type { LoopNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import { memo, useEffect, @@ -8,13 +10,11 @@ import { useNodesInitialized, useViewport, } from 'reactflow' -import { LoopStartNodeDumb } from '../loop-start' -import { useNodeLoopInteractions } from './use-interactions' -import type { LoopNodeType } from './types' -import AddBlock from './add-block' import { cn } from '@/utils/classnames' +import { LoopStartNodeDumb } from '../loop-start' +import AddBlock from './add-block' -import type { NodeProps } from '@/app/components/workflow/types' +import { useNodeLoopInteractions } from './use-interactions' const Node: FC<NodeProps<LoopNodeType>> = ({ id, @@ -32,13 +32,14 @@ const Node: FC<NodeProps<LoopNodeType>> = ({ return ( <div className={cn( 'relative h-full min-h-[90px] w-full min-w-[240px] rounded-2xl bg-workflow-canvas-workflow-bg', - )}> + )} + > <Background id={`loop-background-${id}`} - className='!z-0 rounded-2xl' + className="!z-0 rounded-2xl" gap={[14 / zoom, 14 / zoom]} size={2 / zoom} - color='var(--color-workflow-canvas-workflow-dot-color)' + color="var(--color-workflow-canvas-workflow-dot-color)" /> { data._isCandidate && ( diff --git a/web/app/components/workflow/nodes/loop/panel.tsx b/web/app/components/workflow/nodes/loop/panel.tsx index d3f06482c2..45fec4030f 100644 --- a/web/app/components/workflow/nodes/loop/panel.tsx +++ b/web/app/components/workflow/nodes/loop/panel.tsx @@ -1,17 +1,17 @@ import type { FC } from 'react' +import type { LoopNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' -import Split from '../_base/components/split' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { LOOP_NODE_MAX_COUNT } from '@/config' import InputNumberWithSlider from '../_base/components/input-number-with-slider' -import type { LoopNodeType } from './types' -import useConfig from './use-config' +import Split from '../_base/components/split' import ConditionWrap from './components/condition-wrap' import LoopVariable from './components/loop-variables' -import type { NodePanelProps } from '@/app/components/workflow/types' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import { LOOP_NODE_MAX_COUNT } from '@/config' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.loop' @@ -41,20 +41,20 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({ } = useConfig(id, data) return ( - <div className='mt-2'> + <div className="mt-2"> <div> <Field - title={<div className='pl-3'>{t('workflow.nodes.loop.loopVariables')}</div>} - operations={ + title={<div className="pl-3">{t('workflow.nodes.loop.loopVariables')}</div>} + operations={( <div - className='mr-4 flex h-5 w-5 cursor-pointer items-center justify-center' + className="mr-4 flex h-5 w-5 cursor-pointer items-center justify-center" onClick={handleAddLoopVariable} > - <RiAddLine className='h-4 w-4 text-text-tertiary' /> + <RiAddLine className="h-4 w-4 text-text-tertiary" /> </div> - } + )} > - <div className='px-4'> + <div className="px-4"> <LoopVariable variables={inputs.loop_variables} nodeId={id} @@ -63,9 +63,9 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({ /> </div> </Field> - <Split className='my-2' /> + <Split className="my-2" /> <Field - title={<div className='pl-3'>{t(`${i18nPrefix}.breakCondition`)}</div>} + title={<div className="pl-3">{t(`${i18nPrefix}.breakCondition`)}</div>} tooltip={t(`${i18nPrefix}.breakConditionTip`)} > <ConditionWrap @@ -85,12 +85,12 @@ const Panel: FC<NodePanelProps<LoopNodeType>> = ({ logicalOperator={inputs.logical_operator!} /> </Field> - <Split className='mt-2' /> - <div className='mt-2'> + <Split className="mt-2" /> + <div className="mt-2"> <Field - title={<div className='pl-3'>{t(`${i18nPrefix}.loopMaxCount`)}</div>} + title={<div className="pl-3">{t(`${i18nPrefix}.loopMaxCount`)}</div>} > - <div className='px-3 py-2'> + <div className="px-3 py-2"> <InputNumberWithSlider min={1} max={LOOP_NODE_MAX_COUNT} diff --git a/web/app/components/workflow/nodes/loop/use-config.ts b/web/app/components/workflow/nodes/loop/use-config.ts index e8504fb5e9..d64cd8492e 100644 --- a/web/app/components/workflow/nodes/loop/use-config.ts +++ b/web/app/components/workflow/nodes/loop/use-config.ts @@ -1,20 +1,4 @@ -import { - useCallback, - useRef, -} from 'react' -import { produce } from 'immer' -import { v4 as uuid4 } from 'uuid' -import { - useIsChatMode, - useNodesReadOnly, - useWorkflow, -} from '../../hooks' -import { ValueType, VarType } from '../../types' import type { ErrorHandleMode, Var } from '../../types' -import useNodeCrud from '../_base/hooks/use-node-crud' -import { toNodeOutputVars } from '../_base/components/variable/utils' -import { getOperators } from './utils' -import { LogicalOperator } from './types' import type { HandleAddCondition, HandleAddSubVariableCondition, @@ -25,7 +9,12 @@ import type { HandleUpdateSubVariableCondition, LoopNodeType, } from './types' -import useIsVarFileAttribute from './use-is-var-file-attribute' +import { produce } from 'immer' +import { + useCallback, + useRef, +} from 'react' +import { v4 as uuid4 } from 'uuid' import { useStore } from '@/app/components/workflow/store' import { useAllBuiltInTools, @@ -33,6 +22,17 @@ import { useAllMCPTools, useAllWorkflowTools, } from '@/service/use-tools' +import { + useIsChatMode, + useNodesReadOnly, + useWorkflow, +} from '../../hooks' +import { ValueType, VarType } from '../../types' +import { toNodeOutputVars } from '../_base/components/variable/utils' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { LogicalOperator } from './types' +import useIsVarFileAttribute from './use-is-var-file-attribute' +import { getOperators } from './utils' const useConfig = (id: string, payload: LoopNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/loop/use-interactions.ts b/web/app/components/workflow/nodes/loop/use-interactions.ts index 737da404a0..006d8f963b 100644 --- a/web/app/components/workflow/nodes/loop/use-interactions.ts +++ b/web/app/components/workflow/nodes/loop/use-interactions.ts @@ -1,19 +1,19 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' import type { BlockEnum, Node, } from '../../types' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' +import { useNodesMetaData } from '@/app/components/workflow/hooks' +import { + LOOP_PADDING, +} from '../../constants' import { generateNewNode, getNodeCustomTypeByNodeDataType, } from '../../utils' -import { - LOOP_PADDING, -} from '../../constants' import { CUSTOM_LOOP_START_NODE } from '../loop-start/constants' -import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeLoopInteractions = () => { const store = useStoreApi() @@ -75,7 +75,7 @@ export const useNodeLoopInteractions = () => { const { getNodes } = store.getState() const nodes = getNodes() - const restrictPosition: { x?: number; y?: number } = { x: undefined, y: undefined } + const restrictPosition: { x?: number, y?: number } = { x: undefined, y: undefined } if (node.data.isInLoop) { const parentNode = nodes.find(n => n.id === node.parentId) diff --git a/web/app/components/workflow/nodes/loop/use-is-var-file-attribute.ts b/web/app/components/workflow/nodes/loop/use-is-var-file-attribute.ts index b354d3149e..455d58e605 100644 --- a/web/app/components/workflow/nodes/loop/use-is-var-file-attribute.ts +++ b/web/app/components/workflow/nodes/loop/use-is-var-file-attribute.ts @@ -1,6 +1,6 @@ +import type { ValueSelector } from '../../types' import { useMemo } from 'react' import { useIsChatMode, useWorkflow, useWorkflowVariables } from '../../hooks' -import type { ValueSelector } from '../../types' import { VarType } from '../../types' type Params = { diff --git a/web/app/components/workflow/nodes/loop/use-single-run-form-params.ts b/web/app/components/workflow/nodes/loop/use-single-run-form-params.ts index 6a1b6b20f0..c4123b0e30 100644 --- a/web/app/components/workflow/nodes/loop/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/loop/use-single-run-form-params.ts @@ -1,13 +1,13 @@ -import type { NodeTracing } from '@/types/workflow' -import { useCallback, useMemo } from 'react' -import formatTracing from '@/app/components/workflow/run/utils/format-log' -import { useTranslation } from 'react-i18next' -import { useIsNodeInLoop, useWorkflow } from '../../hooks' -import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils' import type { InputVar, ValueSelector, Variable } from '../../types' import type { CaseItem, Condition, LoopNodeType } from './types' +import type { NodeTracing } from '@/types/workflow' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import formatTracing from '@/app/components/workflow/run/utils/format-log' import { ValueType } from '@/app/components/workflow/types' import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config' +import { useIsNodeInLoop, useWorkflow } from '../../hooks' +import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar } from '../_base/components/variable/utils' type Params = { id: string diff --git a/web/app/components/workflow/nodes/loop/utils.ts b/web/app/components/workflow/nodes/loop/utils.ts index bc5e6481ca..dba5460824 100644 --- a/web/app/components/workflow/nodes/loop/utils.ts +++ b/web/app/components/workflow/nodes/loop/utils.ts @@ -1,15 +1,18 @@ -import { ComparisonOperator } from './types' -import { VarType } from '@/app/components/workflow/types' import type { Branch } from '@/app/components/workflow/types' +import { VarType } from '@/app/components/workflow/types' +import { ComparisonOperator } from './types' export const isEmptyRelatedOperator = (operator: ComparisonOperator) => { return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull, ComparisonOperator.exists, ComparisonOperator.notExists].includes(operator) } const notTranslateKey = [ - ComparisonOperator.equal, ComparisonOperator.notEqual, - ComparisonOperator.largerThan, ComparisonOperator.largerThanOrEqual, - ComparisonOperator.lessThan, ComparisonOperator.lessThanOrEqual, + ComparisonOperator.equal, + ComparisonOperator.notEqual, + ComparisonOperator.largerThan, + ComparisonOperator.largerThanOrEqual, + ComparisonOperator.lessThan, + ComparisonOperator.lessThanOrEqual, ] export const isComparisonOperatorNeedTranslate = (operator?: ComparisonOperator) => { diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx index af2efad075..e8a4491173 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx @@ -1,27 +1,27 @@ 'use client' import type { FC } from 'react' +import type { Param, ParamType } from '../../types' +import type { ToolParameter } from '@/app/components/tools/types' +import type { + PluginDefaultValue, + ToolDefaultValue, +} from '@/app/components/workflow/block-selector/types' +import type { BlockEnum } from '@/app/components/workflow/types' import { memo, useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import BlockSelector from '../../../../block-selector' -import type { Param, ParamType } from '../../types' -import { cn } from '@/utils/classnames' -import type { - PluginDefaultValue, - ToolDefaultValue, -} from '@/app/components/workflow/block-selector/types' -import type { ToolParameter } from '@/app/components/tools/types' -import { CollectionType } from '@/app/components/tools/types' -import type { BlockEnum } from '@/app/components/workflow/types' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { canFindTool } from '@/utils' +import { CollectionType } from '@/app/components/tools/types' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools, } from '@/service/use-tools' +import { canFindTool } from '@/utils' +import { cn } from '@/utils/classnames' +import BlockSelector from '../../../../block-selector' const i18nPrefix = 'workflow.nodes.parameterExtractor' @@ -80,7 +80,8 @@ const ImportFromTool: FC<Props> = ({ <div className={cn( 'flex h-6 cursor-pointer items-center rounded-md px-2 text-xs font-medium text-text-tertiary hover:bg-state-base-hover', open && 'bg-state-base-hover', - )}> + )} + > {t(`${i18nPrefix}.importFromTool`)} </div> </div> @@ -89,7 +90,7 @@ const ImportFromTool: FC<Props> = ({ return ( <BlockSelector - placement='bottom-end' + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 52, diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx index dae43227b0..6382b33154 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx @@ -1,13 +1,14 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' +import type { Param } from '../../types' import { RiDeleteBinLine, RiEditLine, } from '@remixicon/react' -import type { Param } from '../../types' +import React from 'react' +import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' + const i18nPrefix = 'workflow.nodes.parameterExtractor' type Props = { @@ -24,33 +25,33 @@ const Item: FC<Props> = ({ const { t } = useTranslation() return ( - <div className='group relative rounded-lg bg-components-input-bg-normal px-2.5 py-2 hover:shadow-xs'> - <div className='flex justify-between'> - <div className='flex items-center'> - <Variable02 className='h-3.5 w-3.5 text-text-accent-secondary' /> - <div className='ml-1 text-[13px] font-medium text-text-primary'>{payload.name}</div> - <div className='ml-2 text-xs font-normal capitalize text-text-tertiary'>{payload.type}</div> + <div className="group relative rounded-lg bg-components-input-bg-normal px-2.5 py-2 hover:shadow-xs"> + <div className="flex justify-between"> + <div className="flex items-center"> + <Variable02 className="h-3.5 w-3.5 text-text-accent-secondary" /> + <div className="ml-1 text-[13px] font-medium text-text-primary">{payload.name}</div> + <div className="ml-2 text-xs font-normal capitalize text-text-tertiary">{payload.type}</div> </div> {payload.required && ( - <div className='text-xs font-normal uppercase leading-4 text-text-tertiary'>{t(`${i18nPrefix}.addExtractParameterContent.required`)}</div> + <div className="text-xs font-normal uppercase leading-4 text-text-tertiary">{t(`${i18nPrefix}.addExtractParameterContent.required`)}</div> )} </div> - <div className='mt-0.5 text-xs font-normal leading-[18px] text-text-tertiary'>{payload.description}</div> + <div className="mt-0.5 text-xs font-normal leading-[18px] text-text-tertiary">{payload.description}</div> <div - className='absolute right-0 top-0 hidden h-full w-[119px] items-center justify-end space-x-1 rounded-lg bg-gradient-to-l from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent pr-1 group-hover:flex' + className="absolute right-0 top-0 hidden h-full w-[119px] items-center justify-end space-x-1 rounded-lg bg-gradient-to-l from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent pr-1 group-hover:flex" > <div - className='cursor-pointer rounded-md p-1 hover:bg-state-base-hover' + className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover" onClick={onEdit} > - <RiEditLine className='h-4 w-4 text-text-tertiary' /> + <RiEditLine className="h-4 w-4 text-text-tertiary" /> </div> <div - className='group shrink-0 cursor-pointer rounded-md p-1 hover:!bg-state-destructive-hover' + className="group shrink-0 cursor-pointer rounded-md p-1 hover:!bg-state-destructive-hover" onClick={onDelete} > - <RiDeleteBinLine className='h-4 w-4 text-text-tertiary group-hover:text-text-destructive' /> + <RiDeleteBinLine className="h-4 w-4 text-text-tertiary group-hover:text-text-destructive" /> </div> </div> </div> diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx index eacb78e7f4..1cd1232983 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' +import type { Param } from '../../types' +import type { MoreInfo } from '@/app/components/workflow/types' +import { useBoolean } from 'ahooks' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import type { Param } from '../../types' import ListNoDataPlaceholder from '../../../_base/components/list-no-data-placeholder' import Item from './item' import EditParam from './update' -import type { MoreInfo } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.parameterExtractor' @@ -58,11 +58,11 @@ const List: FC<Props> = ({ if (list.length === 0) { return ( - <ListNoDataPlaceholder >{t(`${i18nPrefix}.extractParametersNotSet`)}</ListNoDataPlaceholder> + <ListNoDataPlaceholder>{t(`${i18nPrefix}.extractParametersNotSet`)}</ListNoDataPlaceholder> ) } return ( - <div className='space-y-1'> + <div className="space-y-1"> {list.map((item, index) => ( <Item key={index} @@ -73,7 +73,7 @@ const List: FC<Props> = ({ ))} {isShowEditModal && ( <EditParam - type='edit' + type="edit" payload={list[currEditItemIndex]} onSave={handleItemChange(currEditItemIndex)} onCancel={hideEditModal} diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx index 165ace458f..3048a78118 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx @@ -1,22 +1,23 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useBoolean } from 'ahooks' -import { useTranslation } from 'react-i18next' import type { Param } from '../../types' -import { ParamType } from '../../types' -import AddButton from '@/app/components/base/button/add-button' -import Modal from '@/app/components/base/modal' -import Button from '@/app/components/base/button' +import type { MoreInfo } from '@/app/components/workflow/types' +import { useBoolean } from 'ahooks' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import Field from '@/app/components/app/configuration/config-var/config-modal/field' +import ConfigSelect from '@/app/components/app/configuration/config-var/config-select' +import Button from '@/app/components/base/button' +import AddButton from '@/app/components/base/button/add-button' import Input from '@/app/components/base/input' -import Textarea from '@/app/components/base/textarea' +import Modal from '@/app/components/base/modal' import Select from '@/app/components/base/select' import Switch from '@/app/components/base/switch' +import Textarea from '@/app/components/base/textarea' import Toast from '@/app/components/base/toast' -import ConfigSelect from '@/app/components/app/configuration/config-var/config-select' -import { ChangeType, type MoreInfo } from '@/app/components/workflow/types' +import { ChangeType } from '@/app/components/workflow/types' import { checkKeys } from '@/utils/var' +import { ParamType } from '../../types' const i18nPrefix = 'workflow.nodes.parameterExtractor' const errorI18nPrefix = 'workflow.errorMsg' @@ -61,12 +62,12 @@ const AddExtractParameter: FC<Props> = ({ } setRenameInfo(key === 'name' ? { - type: ChangeType.changeVarName, - payload: { - beforeKey: param.name, - afterKey: value, - }, - } + type: ChangeType.changeVarName, + payload: { + beforeKey: param.name, + afterKey: value, + }, + } : undefined) setParam((prev) => { return { @@ -124,17 +125,17 @@ const AddExtractParameter: FC<Props> = ({ return ( <div> {isAdd && ( - <AddButton className='mx-1' onClick={showAddModal} /> + <AddButton className="mx-1" onClick={showAddModal} /> )} {isShowModal && ( <Modal title={t(`${i18nPrefix}.addExtractParameter`)} isShow onClose={hideModal} - className='!w-[400px] !max-w-[400px] !p-4' + className="!w-[400px] !max-w-[400px] !p-4" > <div> - <div className='space-y-2'> + <div className="space-y-2"> <Field title={t(`${i18nPrefix}.addExtractParameterContent.name`)}> <Input value={param.name} @@ -148,7 +149,7 @@ const AddExtractParameter: FC<Props> = ({ allowSearch={false} // bgClassName='bg-gray-100' onSelect={v => handleParamChange('type')(v.value)} - optionClassName='capitalize' + optionClassName="capitalize" items={ TYPES.map(type => ({ value: type, @@ -171,14 +172,14 @@ const AddExtractParameter: FC<Props> = ({ </Field> <Field title={t(`${i18nPrefix}.addExtractParameterContent.required`)}> <> - <div className='mb-1.5 text-xs font-normal leading-[18px] text-text-tertiary'>{t(`${i18nPrefix}.addExtractParameterContent.requiredContent`)}</div> - <Switch size='l' defaultValue={param.required} onChange={handleParamChange('required')} /> + <div className="mb-1.5 text-xs font-normal leading-[18px] text-text-tertiary">{t(`${i18nPrefix}.addExtractParameterContent.requiredContent`)}</div> + <Switch size="l" defaultValue={param.required} onChange={handleParamChange('required')} /> </> </Field> </div> - <div className='mt-4 flex justify-end space-x-2'> - <Button className='!w-[95px]' onClick={hideModal} >{t('common.operation.cancel')}</Button> - <Button className='!w-[95px]' variant='primary' onClick={handleSave} >{isAdd ? t('common.operation.add') : t('common.operation.save')}</Button> + <div className="mt-4 flex justify-end space-x-2"> + <Button className="!w-[95px]" onClick={hideModal}>{t('common.operation.cancel')}</Button> + <Button className="!w-[95px]" variant="primary" onClick={handleSave}>{isAdd ? t('common.operation.add') : t('common.operation.save')}</Button> </div> </div> </Modal> diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx index f4fd6e85a6..dc5354a21a 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { ReasoningModeType } from '../types' import Field from '../../_base/components/field' import OptionCard from '../../_base/components/option-card' +import { ReasoningModeType } from '../types' const i18nPrefix = 'workflow.nodes.parameterExtractor' @@ -30,14 +30,14 @@ const ReasoningModePicker: FC<Props> = ({ title={t(`${i18nPrefix}.reasoningMode`)} tooltip={t(`${i18nPrefix}.reasoningModeTip`)!} > - <div className='grid grid-cols-2 gap-x-1'> + <div className="grid grid-cols-2 gap-x-1"> <OptionCard - title='Function/Tool Calling' + title="Function/Tool Calling" onSelect={handleChange(ReasoningModeType.functionCall)} selected={type === ReasoningModeType.functionCall} /> <OptionCard - title='Prompt' + title="Prompt" selected={type === ReasoningModeType.prompt} onSelect={handleChange(ReasoningModeType.prompt)} /> diff --git a/web/app/components/workflow/nodes/parameter-extractor/default.ts b/web/app/components/workflow/nodes/parameter-extractor/default.ts index 5d2010122d..cfb317487e 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/default.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/default.ts @@ -1,9 +1,11 @@ import type { NodeDefault } from '../../types' -import { type ParameterExtractorNodeType, ReasoningModeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' +import type { ParameterExtractorNodeType } from './types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' import { AppModeEnum } from '@/types/app' +import { ReasoningModeType } from './types' + const i18nPrefix = 'workflow' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/parameter-extractor/node.tsx b/web/app/components/workflow/nodes/parameter-extractor/node.tsx index d79ae717d9..014706810f 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/node.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/node.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react' -import React from 'react' import type { ParameterExtractorNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' -import type { NodeProps } from '@/app/components/workflow/types' const Node: FC<NodeProps<ParameterExtractorNodeType>> = ({ data, @@ -16,12 +16,12 @@ const Node: FC<NodeProps<ParameterExtractorNodeType>> = ({ } = useTextGenerationCurrentProviderAndModelAndModelList() const hasSetModel = provider && modelId return ( - <div className='mb-1 px-3 py-1'> + <div className="mb-1 px-3 py-1"> {hasSetModel && ( <ModelSelector defaultModel={{ provider, model: modelId }} modelList={textGenerationModelList} - triggerClassName='!h-6 !rounded-md' + triggerClassName="!h-6 !rounded-md" readonly /> )} diff --git a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx index 8faebfa547..563c124102 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx @@ -1,24 +1,24 @@ import type { FC } from 'react' +import type { ParameterExtractorNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import MemoryConfig from '../_base/components/memory-config' -import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import Editor from '../_base/components/prompt/editor' +import Tooltip from '@/app/components/base/tooltip' +import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' +import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import { VarType } from '@/app/components/workflow/types' import ConfigVision from '../_base/components/config-vision' -import useConfig from './use-config' -import type { ParameterExtractorNodeType } from './types' -import ExtractParameter from './components/extract-parameter/list' +import MemoryConfig from '../_base/components/memory-config' +import Editor from '../_base/components/prompt/editor' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' import ImportFromTool from './components/extract-parameter/import-from-tool' +import ExtractParameter from './components/extract-parameter/list' import AddExtractParameter from './components/extract-parameter/update' import ReasoningModePicker from './components/reasoning-mode-picker' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import type { NodePanelProps } from '@/app/components/workflow/types' -import Tooltip from '@/app/components/base/tooltip' -import { VarType } from '@/app/components/workflow/types' -import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.parameterExtractor' const i18nCommonPrefix = 'workflow.common' @@ -57,14 +57,14 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ const model = inputs.model return ( - <div className='pt-2'> - <div className='space-y-4 px-4'> + <div className="pt-2"> + <div className="space-y-4 px-4"> <Field title={t(`${i18nCommonPrefix}.model`)} required > <ModelParameterModal - popupClassName='!w-[387px]' + popupClassName="!w-[387px]" isInWorkflow isAdvancedMode={true} provider={model?.provider} @@ -108,14 +108,14 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ operations={ !readOnly ? ( - <div className='flex items-center space-x-1'> - {!readOnly && ( - <ImportFromTool onImport={handleImportFromTool} /> - )} - {!readOnly && (<div className='h-3 w-px bg-divider-regular'></div>)} - <AddExtractParameter type='add' onSave={addExtractParameter} /> - </div> - ) + <div className="flex items-center space-x-1"> + {!readOnly && ( + <ImportFromTool onImport={handleImportFromTool} /> + )} + {!readOnly && (<div className="h-3 w-px bg-divider-regular"></div>)} + <AddExtractParameter type="add" onSave={addExtractParameter} /> + </div> + ) : undefined } > @@ -126,19 +126,19 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ /> </Field> <Editor - title={ - <div className='flex items-center space-x-1'> - <span className='uppercase'>{t(`${i18nPrefix}.instruction`)}</span> + title={( + <div className="flex items-center space-x-1"> + <span className="uppercase">{t(`${i18nPrefix}.instruction`)}</span> <Tooltip - popupContent={ - <div className='w-[120px]'> + popupContent={( + <div className="w-[120px]"> {t(`${i18nPrefix}.instructionTip`)} </div> - } - triggerClassName='w-3.5 h-3.5 ml-0.5' + )} + triggerClassName="w-3.5 h-3.5 ml-0.5" /> </div> - } + )} value={inputs.instruction} onChange={handleInstructionChange} readOnly={readOnly} @@ -154,7 +154,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ <> {/* Memory */} {isChatMode && ( - <div className='mt-4'> + <div className="mt-4"> <MemoryConfig readonly={readOnly} config={{ data: inputs.memory }} @@ -164,7 +164,7 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ </div> )} {isSupportFunctionCall && ( - <div className='mt-2'> + <div className="mt-2"> <ReasoningModePicker type={inputs.reasoning_mode} onChange={handleReasoningModeChange} @@ -173,38 +173,40 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ )} </> </FieldCollapse> - {inputs.parameters?.length > 0 && (<> - <Split /> - <div> - <OutputVars> - <> - {inputs.parameters.map((param, index) => ( + {inputs.parameters?.length > 0 && ( + <> + <Split /> + <div> + <OutputVars> + <> + {inputs.parameters.map((param, index) => ( + <VarItem + key={index} + name={param.name} + type={param.type} + description={param.description} + /> + ))} <VarItem - key={index} - name={param.name} - type={param.type} - description={param.description} + name="__is_success" + type={VarType.number} + description={t(`${i18nPrefix}.outputVars.isSuccess`)} /> - ))} - <VarItem - name='__is_success' - type={VarType.number} - description={t(`${i18nPrefix}.outputVars.isSuccess`)} - /> - <VarItem - name='__reason' - type={VarType.string} - description={t(`${i18nPrefix}.outputVars.errorReason`)} - /> - <VarItem - name='__usage' - type='object' - description={t(`${i18nPrefix}.outputVars.usage`)} - /> - </> - </OutputVars> - </div> - </>)} + <VarItem + name="__reason" + type={VarType.string} + description={t(`${i18nPrefix}.outputVars.errorReason`)} + /> + <VarItem + name="__usage" + type="object" + description={t(`${i18nPrefix}.outputVars.usage`)} + /> + </> + </OutputVars> + </div> + </> + )} </div> ) } diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-config.ts b/web/app/components/workflow/nodes/parameter-extractor/use-config.ts index 676d631a8a..71e94e95db 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-config.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-config.ts @@ -1,23 +1,23 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { produce } from 'immer' import type { Memory, MoreInfo, ValueSelector, Var } from '../../types' -import { ChangeType, VarType } from '../../types' -import { useStore } from '../../store' +import type { Param, ParameterExtractorNodeType, ReasoningModeType } from './types' +import { produce } from 'immer' +import { useCallback, useEffect, useRef, useState } from 'react' +import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelListAndDefaultModelAndCurrentProviderAndModel, useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { AppModeEnum } from '@/types/app' +import { supportFunctionCall } from '@/utils/tool-call' import { useIsChatMode, useNodesReadOnly, useWorkflow, } from '../../hooks' import useConfigVision from '../../hooks/use-config-vision' -import type { Param, ParameterExtractorNodeType, ReasoningModeType } from './types' -import { useModelListAndDefaultModelAndCurrentProviderAndModel, useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import { supportFunctionCall } from '@/utils/tool-call' import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' -import { AppModeEnum } from '@/types/app' +import { useStore } from '../../store' +import { ChangeType, VarType } from '../../types' const useConfig = (id: string, payload: ParameterExtractorNodeType) => { const { @@ -30,7 +30,7 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => { const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type] - const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' }) + const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string, assistant: string }>({ user: '', assistant: '' }) const { inputs, setInputs: doSetInputs } = useNodeCrud<ParameterExtractorNodeType>(id, payload) const inputRef = useRef(inputs) @@ -127,7 +127,7 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => { currentModel, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration) - const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => { + const handleModelChanged = useCallback((model: { provider: string, modelId: string, mode?: string }) => { const newInputs = produce(inputRef.current, (draft) => { draft.model.provider = model.provider draft.model.name = model.modelId diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts index 68a6f4992b..abf187d6e5 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts @@ -1,21 +1,21 @@ import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' +import type { ParameterExtractorNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, Var, Variable } from '@/app/components/workflow/types' -import { InputVarType, VarType } from '@/app/components/workflow/types' -import type { ParameterExtractorNodeType } from './types' -import useNodeCrud from '../_base/hooks/use-node-crud' -import { useCallback } from 'react' -import useConfigVision from '../../hooks/use-config-vision' import { noop } from 'lodash-es' -import { findVariableWhenOnLLMVision } from '../utils' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { InputVarType, VarType } from '@/app/components/workflow/types' +import useConfigVision from '../../hooks/use-config-vision' import useAvailableVarList from '../_base/hooks/use-available-var-list' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { findVariableWhenOnLLMVision } from '../utils' const i18nPrefix = 'workflow.nodes.parameterExtractor' type Params = { - id: string, - payload: ParameterExtractorNodeType, + id: string + payload: ParameterExtractorNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -136,9 +136,9 @@ const useSingleRunFormParams = ({ } const getDependentVar = (variable: string) => { - if(variable === 'query') + if (variable === 'query') return payload.query - if(variable === '#files#') + if (variable === '#files#') return payload.vision.configs?.variable_selector return false diff --git a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx index dc654607f7..336bd3463a 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx @@ -1,11 +1,12 @@ 'use client' import type { FC } from 'react' +import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import MemoryConfig from '../../_base/components/memory-config' -import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types' import Tooltip from '@/app/components/base/tooltip' +import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' +import MemoryConfig from '../../_base/components/memory-config' + const i18nPrefix = 'workflow.nodes.questionClassifiers' type Props = { @@ -44,19 +45,19 @@ const AdvancedSetting: FC<Props> = ({ return ( <> <Editor - title={ - <div className='flex items-center space-x-1'> - <span className='uppercase'>{t(`${i18nPrefix}.instruction`)}</span> + title={( + <div className="flex items-center space-x-1"> + <span className="uppercase">{t(`${i18nPrefix}.instruction`)}</span> <Tooltip - popupContent={ - <div className='w-[120px]'> + popupContent={( + <div className="w-[120px]"> {t(`${i18nPrefix}.instructionTip`)} </div> - } - triggerClassName='w-3.5 h-3.5 ml-0.5' + )} + triggerClassName="w-3.5 h-3.5 ml-0.5" /> </div> - } + )} value={instruction} onChange={onInstructionChange} readOnly={readonly} @@ -69,7 +70,7 @@ const AdvancedSetting: FC<Props> = ({ /> {!hideMemorySetting && ( <MemoryConfig - className='mt-4' + className="mt-4" readonly={false} config={{ data: memory }} onChange={onMemoryChange} diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx index 8e6865f557..eb629a857c 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' import type { Topic } from '../types' -import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { uniqueId } from 'lodash-es' +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' const i18nPrefix = 'workflow.nodes.questionClassifiers' diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx index e4300008ec..387d60d671 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx @@ -1,18 +1,18 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types' +import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { RiDraggable } from '@remixicon/react' import { produce } from 'immer' +import { noop } from 'lodash-es' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { ReactSortable } from 'react-sortablejs' +import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' +import { cn } from '@/utils/classnames' import { useEdgesInteractions } from '../../../hooks' import AddButton from '../../_base/components/add-button' import Item from './class-item' -import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types' -import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { ReactSortable } from 'react-sortablejs' -import { noop } from 'lodash-es' -import { cn } from '@/utils/classnames' -import { RiDraggable } from '@remixicon/react' -import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' const i18nPrefix = 'workflow.nodes.questionClassifiers' @@ -87,9 +87,11 @@ const ClassList: FC<Props> = ({ return ( <> - <div className='mb-2 flex items-center justify-between' onClick={handleCollapse}> - <div className='flex cursor-pointer items-center text-xs font-semibold uppercase text-text-secondary'> - {t(`${i18nPrefix}.class`)} <span className='text-text-destructive'>*</span> + <div className="mb-2 flex items-center justify-between" onClick={handleCollapse}> + <div className="flex cursor-pointer items-center text-xs font-semibold uppercase text-text-secondary"> + {t(`${i18nPrefix}.class`)} + {' '} + <span className="text-text-destructive">*</span> {list.length > 0 && ( <ArrowDownRoundFill className={cn( @@ -109,11 +111,11 @@ const ClassList: FC<Props> = ({ <ReactSortable list={list.map(item => ({ ...item }))} setList={handleSortTopic} - handle='.handle' - ghostClass='bg-components-panel-bg' + handle=".handle" + ghostClass="bg-components-panel-bg" animation={150} disabled={readonly} - className='space-y-2' + className="space-y-2" > { list.map((item, index) => { @@ -136,10 +138,13 @@ const ClassList: FC<Props> = ({ }} > <div> - {canDrag && <RiDraggable className={cn( - 'handle absolute left-2 top-3 hidden h-3 w-3 cursor-pointer text-text-tertiary', - 'group-hover:block', - )} />} + {canDrag && ( + <RiDraggable className={cn( + 'handle absolute left-2 top-3 hidden h-3 w-3 cursor-pointer text-text-tertiary', + 'group-hover:block', + )} + /> + )} <Item className={cn(canDrag && 'handle')} headerClassName={cn(canDrag && 'cursor-grab group-hover:pl-5')} @@ -161,7 +166,7 @@ const ClassList: FC<Props> = ({ </div> )} {!readonly && !collapsed && ( - <div className='mt-2'> + <div className="mt-2"> <AddButton onClick={handleAddClass} text={t(`${i18nPrefix}.addClass`)} diff --git a/web/app/components/workflow/nodes/question-classifier/default.ts b/web/app/components/workflow/nodes/question-classifier/default.ts index 90ae3fd586..88a4b32d95 100644 --- a/web/app/components/workflow/nodes/question-classifier/default.ts +++ b/web/app/components/workflow/nodes/question-classifier/default.ts @@ -1,8 +1,8 @@ import type { NodeDefault } from '../../types' import type { QuestionClassifierNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' import { AppModeEnum } from '@/types/app' const i18nPrefix = 'workflow' diff --git a/web/app/components/workflow/nodes/question-classifier/node.tsx b/web/app/components/workflow/nodes/question-classifier/node.tsx index 2da37929c8..e0a330e110 100644 --- a/web/app/components/workflow/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/nodes/question-classifier/node.tsx @@ -1,23 +1,23 @@ +import type { TFunction } from 'i18next' import type { FC } from 'react' +import type { NodeProps } from 'reactflow' +import type { QuestionClassifierNodeType } from './types' import React from 'react' import { useTranslation } from 'react-i18next' -import type { TFunction } from 'i18next' -import type { NodeProps } from 'reactflow' -import { NodeSourceHandle } from '../_base/components/node-handle' -import type { QuestionClassifierNodeType } from './types' +import Tooltip from '@/app/components/base/tooltip' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' +import { NodeSourceHandle } from '../_base/components/node-handle' import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' -import Tooltip from '@/app/components/base/tooltip' const i18nPrefix = 'workflow.nodes.questionClassifiers' const MAX_CLASS_TEXT_LENGTH = 50 type TruncatedClassItemProps = { - topic: { id: string; name: string } + topic: { id: string, name: string } index: number nodeId: string t: TFunction @@ -31,31 +31,32 @@ const TruncatedClassItem: FC<TruncatedClassItemProps> = ({ topic, index, nodeId, const shouldShowTooltip = topic.name.length > MAX_CLASS_TEXT_LENGTH const content = ( - <div className='system-xs-regular truncate text-text-tertiary'> + <div className="system-xs-regular truncate text-text-tertiary"> <ReadonlyInputWithSelectVar value={truncatedText} nodeId={nodeId} - className='truncate' + className="truncate" /> </div> ) return ( - <div className='flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]'> - <div className='system-2xs-semibold-uppercase uppercase text-text-secondary'> + <div className="flex flex-col gap-y-0.5 rounded-md bg-workflow-block-parma-bg px-[5px] py-[3px]"> + <div className="system-2xs-semibold-uppercase uppercase text-text-secondary"> {`${t(`${i18nPrefix}.class`)} ${index + 1}`} </div> {shouldShowTooltip - ? (<Tooltip - popupContent={ - <div className='max-w-[300px] break-words'> - <ReadonlyInputWithSelectVar value={topic.name} nodeId={nodeId}/> - </div> - } - > - {content} - </Tooltip> - ) + ? ( + <Tooltip + popupContent={( + <div className="max-w-[300px] break-words"> + <ReadonlyInputWithSelectVar value={topic.name} nodeId={nodeId} /> + </div> + )} + > + {content} + </Tooltip> + ) : content} </div> ) @@ -77,23 +78,23 @@ const Node: FC<NodeProps<QuestionClassifierNodeType>> = (props) => { return null return ( - <div className='mb-1 px-3 py-1'> + <div className="mb-1 px-3 py-1"> {hasSetModel && ( <ModelSelector defaultModel={{ provider, model: modelId }} - triggerClassName='!h-6 !rounded-md' + triggerClassName="!h-6 !rounded-md" modelList={textGenerationModelList} readonly /> )} { !!topics.length && ( - <div className='mt-2 space-y-0.5'> - <div className='space-y-0.5'> + <div className="mt-2 space-y-0.5"> + <div className="space-y-0.5"> {topics.map((topic, index) => ( <div key={topic.id} - className='relative' + className="relative" > <TruncatedClassItem topic={topic} @@ -104,7 +105,7 @@ const Node: FC<NodeProps<QuestionClassifierNodeType>> = (props) => { <NodeSourceHandle {...props} handleId={topic.id} - handleClassName='!top-1/2 !-translate-y-1/2 !-right-[21px]' + handleClassName="!top-1/2 !-translate-y-1/2 !-right-[21px]" /> </div> ))} diff --git a/web/app/components/workflow/nodes/question-classifier/panel.tsx b/web/app/components/workflow/nodes/question-classifier/panel.tsx index 0e54d2712b..9496f90915 100644 --- a/web/app/components/workflow/nodes/question-classifier/panel.tsx +++ b/web/app/components/workflow/nodes/question-classifier/panel.tsx @@ -1,18 +1,18 @@ import type { FC } from 'react' +import type { QuestionClassifierNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import VarReferencePicker from '../_base/components/variable/var-reference-picker' -import ConfigVision from '../_base/components/config-vision' -import useConfig from './use-config' -import ClassList from './components/class-list' -import AdvancedSetting from './components/advanced-setting' -import type { QuestionClassifierNodeType } from './types' -import Field from '@/app/components/workflow/nodes/_base/components/field' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' -import type { NodePanelProps } from '@/app/components/workflow/types' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import ConfigVision from '../_base/components/config-vision' +import VarReferencePicker from '../_base/components/variable/var-reference-picker' +import AdvancedSetting from './components/advanced-setting' +import ClassList from './components/class-list' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.questionClassifiers' @@ -46,14 +46,14 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({ const model = inputs.model return ( - <div className='pt-2'> - <div className='space-y-4 px-4'> + <div className="pt-2"> + <div className="space-y-4 px-4"> <Field title={t(`${i18nPrefix}.model`)} required > <ModelParameterModal - popupClassName='!w-[387px]' + popupClassName="!w-[387px]" isInWorkflow isAdvancedMode={true} provider={model?.provider} @@ -121,13 +121,13 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({ <OutputVars> <> <VarItem - name='class_name' - type='string' + name="class_name" + type="string" description={t(`${i18nPrefix}.outputVars.className`)} /> <VarItem - name='usage' - type='object' + name="usage" + type="object" description={t(`${i18nPrefix}.outputVars.usage`)} /> </> diff --git a/web/app/components/workflow/nodes/question-classifier/use-config.ts b/web/app/components/workflow/nodes/question-classifier/use-config.ts index 28a6fa0314..5a46897de5 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-config.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-config.ts @@ -1,21 +1,22 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { produce } from 'immer' -import { BlockEnum, VarType } from '../../types' import type { Memory, ValueSelector, Var } from '../../types' +import type { QuestionClassifierNodeType, Topic } from './types' +import { produce } from 'immer' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useUpdateNodeInternals } from 'reactflow' +import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { AppModeEnum } from '@/types/app' import { - useIsChatMode, useNodesReadOnly, + useIsChatMode, + useNodesReadOnly, useWorkflow, } from '../../hooks' -import { useStore } from '../../store' -import useAvailableVarList from '../_base/hooks/use-available-var-list' import useConfigVision from '../../hooks/use-config-vision' -import type { QuestionClassifierNodeType, Topic } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' -import { useUpdateNodeInternals } from 'reactflow' -import { AppModeEnum } from '@/types/app' +import { useStore } from '../../store' +import { BlockEnum, VarType } from '../../types' +import useAvailableVarList from '../_base/hooks/use-available-var-list' const useConfig = (id: string, payload: QuestionClassifierNodeType) => { const updateNodeInternals = useUpdateNodeInternals() @@ -56,7 +57,7 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => { }, }) - const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => { + const handleModelChanged = useCallback((model: { provider: string, modelId: string, mode?: string }) => { const newInputs = produce(inputRef.current, (draft) => { draft.model.provider = model.provider draft.model.name = model.modelId diff --git a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts index 79c63cf1da..095809eba2 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts @@ -1,21 +1,21 @@ import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' +import type { QuestionClassifierNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, Var, Variable } from '@/app/components/workflow/types' -import { InputVarType, VarType } from '@/app/components/workflow/types' -import type { QuestionClassifierNodeType } from './types' -import useNodeCrud from '../_base/hooks/use-node-crud' -import { useCallback } from 'react' -import useConfigVision from '../../hooks/use-config-vision' import { noop } from 'lodash-es' -import { findVariableWhenOnLLMVision } from '../utils' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { InputVarType, VarType } from '@/app/components/workflow/types' +import useConfigVision from '../../hooks/use-config-vision' import useAvailableVarList from '../_base/hooks/use-available-var-list' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { findVariableWhenOnLLMVision } from '../utils' const i18nPrefix = 'workflow.nodes.questionClassifiers' type Params = { - id: string, - payload: QuestionClassifierNodeType, + id: string + payload: QuestionClassifierNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -134,9 +134,9 @@ const useSingleRunFormParams = ({ } const getDependentVar = (variable: string) => { - if(variable === 'query') + if (variable === 'query') return payload.query_variable_selector - if(variable === '#files#') + if (variable === '#files#') return payload.vision.configs?.variable_selector return false diff --git a/web/app/components/workflow/nodes/start/components/var-item.tsx b/web/app/components/workflow/nodes/start/components/var-item.tsx index 6bce5d0f0f..317a733d9b 100644 --- a/web/app/components/workflow/nodes/start/components/var-item.tsx +++ b/web/app/components/workflow/nodes/start/components/var-item.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useRef } from 'react' -import { useBoolean, useHover } from 'ahooks' -import { useTranslation } from 'react-i18next' +import type { InputVar, MoreInfo } from '@/app/components/workflow/types' import { RiDeleteBinLine, } from '@remixicon/react' -import InputVarTypeIcon from '../../_base/components/input-var-type-icon' -import type { InputVar, MoreInfo } from '@/app/components/workflow/types' +import { useBoolean, useHover } from 'ahooks' +import { noop } from 'lodash-es' +import React, { useCallback, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' +import Badge from '@/app/components/base/badge' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general' -import Badge from '@/app/components/base/badge' -import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' -import { noop } from 'lodash-es' import { cn } from '@/utils/classnames' +import InputVarTypeIcon from '../../_base/components/input-var-type-icon' type Props = { className?: string @@ -23,8 +23,8 @@ type Props = { onRemove?: () => void rightContent?: React.JSX.Element varKeys?: string[] - showLegacyBadge?: boolean, - canDrag?: boolean, + showLegacyBadge?: boolean + canDrag?: boolean } const VarItem: FC<Props> = ({ @@ -49,47 +49,52 @@ const VarItem: FC<Props> = ({ const handlePayloadChange = useCallback((payload: InputVar, moreInfo?: MoreInfo) => { const isValid = onChange(payload, moreInfo) - if(!isValid) + if (!isValid) return hideEditVarModal() }, [onChange, hideEditVarModal]) return ( <div ref={ref} className={cn('flex h-8 cursor-pointer items-center justify-between rounded-lg border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 shadow-xs hover:shadow-md', className)}> - <div className='flex w-0 grow items-center space-x-1'> + <div className="flex w-0 grow items-center space-x-1"> <Variable02 className={cn('h-3.5 w-3.5 text-text-accent', canDrag && 'group-hover:opacity-0')} /> - <div title={payload.variable} className='max-w-[130px] shrink-0 truncate text-[13px] font-medium text-text-secondary'>{payload.variable}</div> - {payload.label && (<><div className='shrink-0 text-xs font-medium text-text-quaternary'>·</div> - <div title={payload.label as string} className='max-w-[130px] truncate text-[13px] font-medium text-text-tertiary'>{payload.label as string}</div> - </>)} + <div title={payload.variable} className="max-w-[130px] shrink-0 truncate text-[13px] font-medium text-text-secondary">{payload.variable}</div> + {payload.label && ( + <> + <div className="shrink-0 text-xs font-medium text-text-quaternary">·</div> + <div title={payload.label as string} className="max-w-[130px] truncate text-[13px] font-medium text-text-tertiary">{payload.label as string}</div> + </> + )} {showLegacyBadge && ( <Badge - text='LEGACY' - className='shrink-0 border-text-accent-secondary text-text-accent-secondary' + text="LEGACY" + className="shrink-0 border-text-accent-secondary text-text-accent-secondary" /> )} </div> - <div className='ml-2 flex shrink-0 items-center'> - {rightContent || (<> - {(!isHovering || readonly) - ? ( - <> - {payload.required && ( - <div className='mr-2 text-xs font-normal text-text-tertiary'>{t('workflow.nodes.start.required')}</div> - )} - <InputVarTypeIcon type={payload.type} className='h-3.5 w-3.5 text-text-tertiary' /> - </> - ) - : (!readonly && ( - <> - <div onClick={showEditVarModal} className='mr-1 cursor-pointer rounded-md p-1 hover:bg-state-base-hover'> - <Edit03 className='h-4 w-4 text-text-tertiary' /> - </div> - <div onClick={onRemove} className='group cursor-pointer rounded-md p-1 hover:bg-state-destructive-hover'> - <RiDeleteBinLine className='h-4 w-4 text-text-tertiary group-hover:text-text-destructive' /> - </div> - </> - ))} - </>)} + <div className="ml-2 flex shrink-0 items-center"> + {rightContent || ( + <> + {(!isHovering || readonly) + ? ( + <> + {payload.required && ( + <div className="mr-2 text-xs font-normal text-text-tertiary">{t('workflow.nodes.start.required')}</div> + )} + <InputVarTypeIcon type={payload.type} className="h-3.5 w-3.5 text-text-tertiary" /> + </> + ) + : (!readonly && ( + <> + <div onClick={showEditVarModal} className="mr-1 cursor-pointer rounded-md p-1 hover:bg-state-base-hover"> + <Edit03 className="h-4 w-4 text-text-tertiary" /> + </div> + <div onClick={onRemove} className="group cursor-pointer rounded-md p-1 hover:bg-state-destructive-hover"> + <RiDeleteBinLine className="h-4 w-4 text-text-tertiary group-hover:text-text-destructive" /> + </div> + </> + ))} + </> + )} </div> { diff --git a/web/app/components/workflow/nodes/start/components/var-list.tsx b/web/app/components/workflow/nodes/start/components/var-list.tsx index 4b5177bb3e..5ae45c7192 100644 --- a/web/app/components/workflow/nodes/start/components/var-list.tsx +++ b/web/app/components/workflow/nodes/start/components/var-list.tsx @@ -1,20 +1,21 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' -import VarItem from './var-item' -import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types' -import { ReactSortable } from 'react-sortablejs' +import type { InputVar, MoreInfo } from '@/app/components/workflow/types' import { RiDraggable } from '@remixicon/react' +import { produce } from 'immer' +import React, { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { ReactSortable } from 'react-sortablejs' +import Toast from '@/app/components/base/toast' +import { ChangeType } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' import { hasDuplicateStr } from '@/utils/var' -import Toast from '@/app/components/base/toast' +import VarItem from './var-item' type Props = { readonly: boolean list: InputVar[] - onChange: (list: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => void + onChange: (list: InputVar[], moreInfo?: { index: number, payload: MoreInfo }) => void } const VarList: FC<Props> = ({ @@ -80,7 +81,7 @@ const VarList: FC<Props> = ({ if (list.length === 0) { return ( - <div className='flex h-[42px] items-center justify-center rounded-md bg-components-panel-bg text-xs font-normal leading-[18px] text-text-tertiary'> + <div className="flex h-[42px] items-center justify-center rounded-md bg-components-panel-bg text-xs font-normal leading-[18px] text-text-tertiary"> {t('workflow.nodes.start.noVarTip')} </div> ) @@ -90,15 +91,15 @@ const VarList: FC<Props> = ({ return ( <ReactSortable - className='space-y-1' + className="space-y-1" list={listWithIds} setList={(list) => { onChange(list.map(item => item.variable)) }} - handle='.handle' - ghostClass='opacity-50' + handle=".handle" + ghostClass="opacity-50" animation={150} > {listWithIds.map((itemWithId, index) => ( - <div key={itemWithId.id} className='group relative'> + <div key={itemWithId.id} className="group relative"> <VarItem className={cn(canDrag && 'handle')} readonly={readonly} @@ -108,10 +109,13 @@ const VarList: FC<Props> = ({ varKeys={list.map(item => item.variable)} canDrag={canDrag} /> - {canDrag && <RiDraggable className={cn( - 'handle absolute left-3 top-2.5 hidden h-3 w-3 cursor-pointer text-text-tertiary', - 'group-hover:block', - )} />} + {canDrag && ( + <RiDraggable className={cn( + 'handle absolute left-3 top-2.5 hidden h-3 w-3 cursor-pointer text-text-tertiary', + 'group-hover:block', + )} + /> + )} </div> ))} </ReactSortable> diff --git a/web/app/components/workflow/nodes/start/default.ts b/web/app/components/workflow/nodes/start/default.ts index 60584b5144..aead27c139 100644 --- a/web/app/components/workflow/nodes/start/default.ts +++ b/web/app/components/workflow/nodes/start/default.ts @@ -1,7 +1,7 @@ import type { NodeDefault } from '../../types' import type { StartNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: 0.1, diff --git a/web/app/components/workflow/nodes/start/node.tsx b/web/app/components/workflow/nodes/start/node.tsx index 7c02858d1b..e8642ff616 100644 --- a/web/app/components/workflow/nodes/start/node.tsx +++ b/web/app/components/workflow/nodes/start/node.tsx @@ -1,10 +1,11 @@ import type { FC } from 'react' +import type { StartNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import InputVarTypeIcon from '../_base/components/input-var-type-icon' -import type { StartNodeType } from './types' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import type { NodeProps } from '@/app/components/workflow/types' +import InputVarTypeIcon from '../_base/components/input-var-type-icon' + const i18nPrefix = 'workflow.nodes.start' const Node: FC<NodeProps<StartNodeType>> = ({ @@ -17,18 +18,18 @@ const Node: FC<NodeProps<StartNodeType>> = ({ return null return ( - <div className='mb-1 px-3 py-1'> - <div className='space-y-0.5'> + <div className="mb-1 px-3 py-1"> + <div className="space-y-0.5"> {variables.map(variable => ( - <div key={variable.variable} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1'> - <div className='flex w-0 grow items-center space-x-1'> - <Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' /> - <span className='system-xs-regular w-0 grow truncate text-text-secondary'>{variable.variable}</span> + <div key={variable.variable} className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1"> + <div className="flex w-0 grow items-center space-x-1"> + <Variable02 className="h-3.5 w-3.5 shrink-0 text-text-accent" /> + <span className="system-xs-regular w-0 grow truncate text-text-secondary">{variable.variable}</span> </div> - <div className='ml-1 flex items-center space-x-1'> - {variable.required && <span className='system-2xs-regular-uppercase text-text-tertiary'>{t(`${i18nPrefix}.required`)}</span>} - <InputVarTypeIcon type={variable.type} className='h-3 w-3 text-text-tertiary' /> + <div className="ml-1 flex items-center space-x-1"> + {variable.required && <span className="system-2xs-regular-uppercase text-text-tertiary">{t(`${i18nPrefix}.required`)}</span>} + <InputVarTypeIcon type={variable.type} className="h-3 w-3 text-text-tertiary" /> </div> </div> ))} diff --git a/web/app/components/workflow/nodes/start/panel.tsx b/web/app/components/workflow/nodes/start/panel.tsx index a560bd2e63..5871ab1852 100644 --- a/web/app/components/workflow/nodes/start/panel.tsx +++ b/web/app/components/workflow/nodes/start/panel.tsx @@ -1,16 +1,16 @@ import type { FC } from 'react' +import type { StartNodeType } from './types' +import type { InputVar, NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' -import VarList from './components/var-list' -import VarItem from './components/var-item' -import useConfig from './use-config' -import type { StartNodeType } from './types' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import AddButton from '@/app/components/base/button/add-button' import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' -import type { InputVar, NodePanelProps } from '@/app/components/workflow/types' +import AddButton from '@/app/components/base/button/add-button' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' +import VarItem from './components/var-item' +import VarList from './components/var-list' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.start' @@ -35,13 +35,14 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({ const handleAddVarConfirm = (payload: InputVar) => { const isValid = handleAddVariable(payload) - if (!isValid) return + if (!isValid) + return hideAddVarModal() } return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-2'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-2"> <Field title={t(`${i18nPrefix}.inputField`)} operations={ @@ -55,8 +56,8 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({ onChange={handleVarListChange} /> - <div className='mt-1 space-y-1'> - <Split className='my-2' /> + <div className="mt-1 space-y-1"> + <Split className="my-2" /> { isChatMode && ( <VarItem @@ -64,12 +65,13 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({ payload={{ variable: 'userinput.query', } as any} - rightContent={ - <div className='text-xs font-normal text-text-tertiary'> + rightContent={( + <div className="text-xs font-normal text-text-tertiary"> String </div> - } - />) + )} + /> + ) } <VarItem @@ -78,11 +80,11 @@ const Panel: FC<NodePanelProps<StartNodeType>> = ({ payload={{ variable: 'userinput.files', } as any} - rightContent={ - <div className='text-xs font-normal text-text-tertiary'> + rightContent={( + <div className="text-xs font-normal text-text-tertiary"> Array[File] </div> - } + )} /> </div> </> diff --git a/web/app/components/workflow/nodes/start/use-config.ts b/web/app/components/workflow/nodes/start/use-config.ts index 5e4d1b01c6..8eed650f98 100644 --- a/web/app/components/workflow/nodes/start/use-config.ts +++ b/web/app/components/workflow/nodes/start/use-config.ts @@ -1,19 +1,19 @@ -import { useCallback, useState } from 'react' -import { produce } from 'immer' -import { useBoolean } from 'ahooks' import type { StartNodeType } from './types' -import { ChangeType } from '@/app/components/workflow/types' import type { InputVar, MoreInfo, ValueSelector } from '@/app/components/workflow/types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { useBoolean } from 'ahooks' +import { produce } from 'immer' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Toast from '@/app/components/base/toast' import { useIsChatMode, useNodesReadOnly, useWorkflow, } from '@/app/components/workflow/hooks' -import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { ChangeType } from '@/app/components/workflow/types' import { hasDuplicateStr } from '@/utils/var' -import Toast from '@/app/components/base/toast' -import { useTranslation } from 'react-i18next' +import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' const useConfig = (id: string, payload: StartNodeType) => { const { t } = useTranslation() @@ -41,12 +41,12 @@ const useConfig = (id: string, payload: StartNodeType) => { }] = useBoolean(false) const [removedVar, setRemovedVar] = useState<ValueSelector>([]) const [removedIndex, setRemoveIndex] = useState(0) - const handleVarListChange = useCallback((newList: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => { + const handleVarListChange = useCallback((newList: InputVar[], moreInfo?: { index: number, payload: MoreInfo }) => { if (moreInfo?.payload?.type === ChangeType.remove) { const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { return varItem.name === moreInfo?.payload?.payload?.beforeKey })?.id - if(varId) + if (varId) deleteInspectVar(id, varId) if (isVarUsedInNodes([id, moreInfo?.payload?.payload?.beforeKey || ''])) { @@ -66,7 +66,7 @@ const useConfig = (id: string, payload: StartNodeType) => { handleOutVarRenameChange(id, [id, inputs.variables[moreInfo.index].variable], [id, changedVar.variable]) renameInspectVarName(id, inputs.variables[moreInfo.index].variable, changedVar.variable) } - else if(moreInfo?.payload?.type !== ChangeType.remove) { // edit var type + else if (moreInfo?.payload?.type !== ChangeType.remove) { // edit var type deleteNodeInspectorVars(id) } }, [deleteInspectVar, deleteNodeInspectorVars, handleOutVarRenameChange, id, inputs, isVarUsedInNodes, nodesWithInspectVars, renameInspectVarName, setInputs, showRemoveVarConfirm]) @@ -87,11 +87,11 @@ const useConfig = (id: string, payload: StartNodeType) => { const newList = newInputs.variables let errorMsgKey = '' let typeName = '' - if(hasDuplicateStr(newList.map(item => item.variable))) { + if (hasDuplicateStr(newList.map(item => item.variable))) { errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists' typeName = 'appDebug.variableConfig.varName' } - else if(hasDuplicateStr(newList.map(item => item.label as string))) { + else if (hasDuplicateStr(newList.map(item => item.label as string))) { errorMsgKey = 'appDebug.varKeyError.keyAlreadyExists' typeName = 'appDebug.variableConfig.labelName' } diff --git a/web/app/components/workflow/nodes/start/use-single-run-form-params.ts b/web/app/components/workflow/nodes/start/use-single-run-form-params.ts index ed2b3900d2..a2d381ffbc 100644 --- a/web/app/components/workflow/nodes/start/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/start/use-single-run-form-params.ts @@ -1,14 +1,14 @@ import type { RefObject } from 'react' -import { useTranslation } from 'react-i18next' -import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import type { ValueSelector } from '@/app/components/workflow/types' -import { type InputVar, InputVarType, type Variable } from '@/app/components/workflow/types' import type { StartNodeType } from './types' +import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' +import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' +import { useTranslation } from 'react-i18next' +import { InputVarType } from '@/app/components/workflow/types' import { useIsChatMode } from '../../hooks' type Params = { - id: string, - payload: StartNodeType, + id: string + payload: StartNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] diff --git a/web/app/components/workflow/nodes/template-transform/default.ts b/web/app/components/workflow/nodes/template-transform/default.ts index 1a3c092c0b..90e0f60184 100644 --- a/web/app/components/workflow/nodes/template-transform/default.ts +++ b/web/app/components/workflow/nodes/template-transform/default.ts @@ -1,8 +1,9 @@ import type { NodeDefault } from '../../types' import type { TemplateTransformNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' + const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ diff --git a/web/app/components/workflow/nodes/template-transform/node.tsx b/web/app/components/workflow/nodes/template-transform/node.tsx index e6925c488f..3a4c5c3319 100644 --- a/web/app/components/workflow/nodes/template-transform/node.tsx +++ b/web/app/components/workflow/nodes/template-transform/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' -import React from 'react' import type { TemplateTransformNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' const Node: FC<NodeProps<TemplateTransformNodeType>> = () => { return ( diff --git a/web/app/components/workflow/nodes/template-transform/panel.tsx b/web/app/components/workflow/nodes/template-transform/panel.tsx index 29c34ee663..c8fc293329 100644 --- a/web/app/components/workflow/nodes/template-transform/panel.tsx +++ b/web/app/components/workflow/nodes/template-transform/panel.tsx @@ -1,19 +1,19 @@ import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' +import type { TemplateTransformNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import { RiQuestionLine, } from '@remixicon/react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import AddButton from '@/app/components/base/button/add-button' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' import { CodeLanguage } from '../code/types' import useConfig from './use-config' -import type { TemplateTransformNodeType } from './types' -import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list' -import AddButton from '@/app/components/base/button/add-button' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' -import type { NodePanelProps } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.nodes.templateTransform' @@ -36,8 +36,8 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({ } = useConfig(id, data) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> <Field title={t(`${i18nPrefix}.inputVars`)} @@ -64,20 +64,21 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({ readOnly={readOnly} language={CodeLanguage.python3} title={ - <div className='uppercase'>{t(`${i18nPrefix}.code`)}</div> + <div className="uppercase">{t(`${i18nPrefix}.code`)}</div> } - headerRight={ - <div className='flex items-center'> + headerRight={( + <div className="flex items-center"> <a - className='flex h-[18px] items-center space-x-0.5 text-xs font-normal text-text-tertiary' + className="flex h-[18px] items-center space-x-0.5 text-xs font-normal text-text-tertiary" href="https://jinja.palletsprojects.com/en/3.1.x/templates/" - target='_blank'> + target="_blank" + > <span>{t(`${i18nPrefix}.codeSupportTip`)}</span> - <RiQuestionLine className='h-3 w-3' /> + <RiQuestionLine className="h-3 w-3" /> </a> - <div className='mx-1.5 h-3 w-px bg-divider-regular'></div> + <div className="mx-1.5 h-3 w-px bg-divider-regular"></div> </div> - } + )} value={inputs.template} onChange={handleCodeChange} /> @@ -87,8 +88,8 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({ <OutputVars> <> <VarItem - name='output' - type='string' + name="output" + type="string" description={t(`${i18nPrefix}.outputVars.output`)} /> </> diff --git a/web/app/components/workflow/nodes/template-transform/use-config.ts b/web/app/components/workflow/nodes/template-transform/use-config.ts index 3e5b0f3d31..ed62ea2fa0 100644 --- a/web/app/components/workflow/nodes/template-transform/use-config.ts +++ b/web/app/components/workflow/nodes/template-transform/use-config.ts @@ -1,15 +1,15 @@ -import { useCallback, useEffect, useRef } from 'react' -import { produce } from 'immer' -import useVarList from '../_base/hooks/use-var-list' import type { Var, Variable } from '../../types' -import { VarType } from '../../types' -import { useStore } from '../../store' import type { TemplateTransformNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { produce } from 'immer' +import { useCallback, useEffect, useRef } from 'react' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { useStore } from '../../store' +import { VarType } from '../../types' +import useVarList from '../_base/hooks/use-var-list' const useConfig = (id: string, payload: TemplateTransformNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() diff --git a/web/app/components/workflow/nodes/template-transform/use-single-run-form-params.ts b/web/app/components/workflow/nodes/template-transform/use-single-run-form-params.ts index 172ece6ce6..d8dcfaea92 100644 --- a/web/app/components/workflow/nodes/template-transform/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/template-transform/use-single-run-form-params.ts @@ -1,12 +1,12 @@ import type { RefObject } from 'react' +import type { TemplateTransformNodeType } from './types' import type { InputVar, Variable } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' import useNodeCrud from '../_base/hooks/use-node-crud' -import type { TemplateTransformNodeType } from './types' type Params = { - id: string, - payload: TemplateTransformNodeType, + id: string + payload: TemplateTransformNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] diff --git a/web/app/components/workflow/nodes/tool/components/copy-id.tsx b/web/app/components/workflow/nodes/tool/components/copy-id.tsx index 977db5f234..39ffd7edd1 100644 --- a/web/app/components/workflow/nodes/tool/components/copy-id.tsx +++ b/web/app/components/workflow/nodes/tool/components/copy-id.tsx @@ -1,9 +1,9 @@ 'use client' -import React, { useState } from 'react' -import { useTranslation } from 'react-i18next' import { RiFileCopyLine } from '@remixicon/react' import copy from 'copy-to-clipboard' import { debounce } from 'lodash-es' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' type Props = { @@ -26,7 +26,7 @@ const CopyFeedbackNew = ({ content }: Props) => { }, 100) return ( - <div className='inline-flex w-full pb-0.5' onClick={e => e.stopPropagation()} onMouseLeave={onMouseLeave}> + <div className="inline-flex w-full pb-0.5" onClick={e => e.stopPropagation()} onMouseLeave={onMouseLeave}> <Tooltip popupContent={ (isCopied @@ -35,13 +35,15 @@ const CopyFeedbackNew = ({ content }: Props) => { } > <div - className='group/copy flex w-full items-center gap-0.5 ' + className="group/copy flex w-full items-center gap-0.5 " onClick={onClickCopy} > <div - className='system-2xs-regular w-0 grow cursor-pointer truncate text-text-quaternary group-hover:text-text-tertiary' - >{content}</div> - <RiFileCopyLine className='h-3 w-3 shrink-0 text-text-tertiary opacity-0 group-hover/copy:opacity-100' /> + className="system-2xs-regular w-0 grow cursor-pointer truncate text-text-quaternary group-hover:text-text-tertiary" + > + {content} + </div> + <RiFileCopyLine className="h-3 w-3 shrink-0 text-text-tertiary opacity-0 group-hover/copy:opacity-100" /> </div> </Tooltip> </div> diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 605cbab75e..4f5d5f24bc 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -1,23 +1,23 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' import type { ToolVarInputs } from '../types' -import { VarType as VarKindType } from '../types' -import { cn } from '@/utils/classnames' -import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Tool } from '@/app/components/tools/types' +import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' +import { produce } from 'immer' +import { noop } from 'lodash-es' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import { VarType } from '@/app/components/workflow/types' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' -import { noop } from 'lodash-es' -import type { Tool } from '@/app/components/tools/types' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import { VarType } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' +import { VarType as VarKindType } from '../types' type Props = { readOnly: boolean @@ -158,7 +158,7 @@ const InputVarList: FC<Props> = ({ }, [onOpen]) return ( - <div className='space-y-3'> + <div className="space-y-3"> { schema.map((schema, index) => { const { @@ -180,11 +180,11 @@ const InputVarList: FC<Props> = ({ const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector return ( - <div key={variable} className='space-y-1'> - <div className='flex items-center space-x-2 leading-[18px]'> - <span className='code-sm-semibold text-text-secondary'>{label[language] || label.en_US}</span> - <span className='system-xs-regular text-text-tertiary'>{paramType(type)}</span> - {required && <span className='system-xs-regular text-util-colors-orange-dark-orange-dark-600'>Required</span>} + <div key={variable} className="space-y-1"> + <div className="flex items-center space-x-2 leading-[18px]"> + <span className="code-sm-semibold text-text-secondary">{label[language] || label.en_US}</span> + <span className="system-xs-regular text-text-tertiary">{paramType(type)}</span> + {required && <span className="system-xs-regular text-util-colors-orange-dark-orange-dark-600">Required</span>} </div> {isString && ( <Input @@ -196,7 +196,7 @@ const InputVarList: FC<Props> = ({ availableNodes={availableNodesWithParent} onFocusChange={handleInputFocus(variable)} placeholder={t('workflow.nodes.http.insertVarPlaceholder')!} - placeholderClassName='!leading-[21px]' + placeholderClassName="!leading-[21px]" /> )} {(isNumber || isSelect) && ( @@ -238,7 +238,7 @@ const InputVarList: FC<Props> = ({ )} {isModelSelector && ( <ModelParameterModal - popupClassName='!w-[387px]' + popupClassName="!w-[387px]" isAdvancedMode isInWorkflow value={varInput as any} @@ -247,7 +247,7 @@ const InputVarList: FC<Props> = ({ scope={scope} /> )} - {tooltip && <div className='body-xs-regular text-text-tertiary'>{tooltip[language] || tooltip.en_US}</div>} + {tooltip && <div className="body-xs-regular text-text-tertiary">{tooltip[language] || tooltip.en_US}</div>} </div> ) }) diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx index 98a88e5e0f..e0191ce2d8 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -1,16 +1,16 @@ +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' import { memo, } from 'react' import { useTranslation } from 'react-i18next' import PromptEditor from '@/app/components/base/prompt-editor' -import Placeholder from './placeholder' -import type { - Node, - NodeOutPutVar, -} from '@/app/components/workflow/types' +import { useStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' -import { useStore } from '@/app/components/workflow/store' +import Placeholder from './placeholder' type MixedVariableTextInputProps = { readOnly?: boolean @@ -43,7 +43,7 @@ const MixedVariableTextInput = ({ 'hover:border-components-input-border-hover hover:bg-components-input-bg-hover', 'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs', )} - className='caret:text-text-accent' + className="caret:text-text-accent" editable={!readOnly} value={value} workflowVariableBlock={{ diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx index d6e0bbc059..5c599dd059 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx @@ -1,10 +1,9 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { $insertNodes, FOCUS_COMMAND } from 'lexical' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import { FOCUS_COMMAND } from 'lexical' -import { $insertNodes } from 'lexical' -import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node' import Badge from '@/app/components/base/badge' +import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node' type PlaceholderProps = { disableVariableInsertion?: boolean @@ -24,19 +23,19 @@ const Placeholder = ({ disableVariableInsertion = false }: PlaceholderProps) => return ( <div - className='pointer-events-auto flex h-full w-full cursor-text items-center px-2' + className="pointer-events-auto flex h-full w-full cursor-text items-center px-2" onClick={(e) => { e.stopPropagation() handleInsert('') }} > - <div className='flex grow items-center'> + <div className="flex grow items-center"> {t('workflow.nodes.tool.insertPlaceholder1')} {(!disableVariableInsertion) && ( <> - <div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div> + <div className="system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder">/</div> <div - className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary' + className="system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary" onMouseDown={((e) => { e.preventDefault() e.stopPropagation() @@ -49,8 +48,8 @@ const Placeholder = ({ disableVariableInsertion = false }: PlaceholderProps) => )} </div> <Badge - className='shrink-0' - text='String' + className="shrink-0" + text="String" uppercase={false} /> </div> diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx index ade29beddb..c8d60be789 100644 --- a/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import type { ToolVarInputs } from '../../types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' -import ToolFormItem from './item' -import type { ToolWithProvider } from '@/app/components/workflow/types' import type { Tool } from '@/app/components/tools/types' +import type { ToolWithProvider } from '@/app/components/workflow/types' +import ToolFormItem from './item' type Props = { readOnly: boolean @@ -35,7 +35,7 @@ const ToolForm: FC<Props> = ({ extraParams, }) => { return ( - <div className='space-y-1'> + <div className="space-y-1"> { schema.map((schema, index) => ( <ToolFormItem @@ -51,7 +51,7 @@ const ToolForm: FC<Props> = ({ showManageInputField={showManageInputField} onManageInputField={onManageInputField} extraParams={extraParams} - providerType='tool' + providerType="tool" /> )) } diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx index 567266abde..83d4ee9eef 100644 --- a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx +++ b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' +import type { ToolVarInputs } from '../../types' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Tool } from '@/app/components/tools/types' +import type { ToolWithProvider } from '@/app/components/workflow/types' import { RiBracesLine, } from '@remixicon/react' -import type { ToolVarInputs } from '../../types' -import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useBoolean } from 'ahooks' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' -import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item' -import { useBoolean } from 'ahooks' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import SchemaModal from '@/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal' -import type { ToolWithProvider } from '@/app/components/workflow/types' -import type { Tool } from '@/app/components/tools/types' +import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item' type Props = { readOnly: boolean @@ -53,39 +53,41 @@ const ToolFormItem: FC<Props> = ({ setFalse: hideSchema, }] = useBoolean(false) return ( - <div className='space-y-0.5 py-1'> + <div className="space-y-0.5 py-1"> <div> - <div className='flex h-6 items-center'> - <div className='system-sm-medium text-text-secondary'>{label[language] || label.en_US}</div> + <div className="flex h-6 items-center"> + <div className="system-sm-medium text-text-secondary">{label[language] || label.en_US}</div> {required && ( - <div className='system-xs-regular ml-1 text-text-destructive-secondary'>*</div> + <div className="system-xs-regular ml-1 text-text-destructive-secondary">*</div> )} {!showDescription && tooltip && ( <Tooltip - popupContent={<div className='w-[200px]'> - {tooltip[language] || tooltip.en_US} - </div>} - triggerClassName='ml-1 w-4 h-4' + popupContent={( + <div className="w-[200px]"> + {tooltip[language] || tooltip.en_US} + </div> + )} + triggerClassName="ml-1 w-4 h-4" asChild={false} /> )} {showSchemaButton && ( <> - <div className='system-xs-regular ml-1 mr-0.5 text-text-quaternary'>·</div> + <div className="system-xs-regular ml-1 mr-0.5 text-text-quaternary">·</div> <Button - variant='ghost' - size='small' + variant="ghost" + size="small" onClick={showSchema} - className='system-xs-regular px-1 text-text-tertiary' + className="system-xs-regular px-1 text-text-tertiary" > - <RiBracesLine className='mr-1 size-3.5' /> + <RiBracesLine className="mr-1 size-3.5" /> <span>JSON Schema</span> </Button> </> )} </div> {showDescription && tooltip && ( - <div className='body-xs-regular pb-0.5 text-text-tertiary'>{tooltip[language] || tooltip.en_US}</div> + <div className="body-xs-regular pb-0.5 text-text-tertiary">{tooltip[language] || tooltip.en_US}</div> )} </div> <FormInputItem diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index 0fcd321a29..4d8046ef1c 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -1,11 +1,11 @@ -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import type { NodeDefault, ToolWithProvider, Var } from '../../types' import type { ToolNodeType } from './types' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { TOOL_OUTPUT_STRUCT } from '../../constants' import { CollectionType } from '@/app/components/tools/types' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' import { canFindTool } from '@/utils' +import { TOOL_OUTPUT_STRUCT } from '../../constants' import { Type } from '../llm/types' import { resolveVarType } from './output-schema-utils' @@ -104,13 +104,15 @@ const nodeDefault: NodeDefault<ToolNodeType> = { type, des: output.description, schemaType, - children: output.type === 'object' ? { - schema: { - type: Type.object, - properties: output.properties, - additionalProperties: false, - }, - } : undefined, + children: output.type === 'object' + ? { + schema: { + type: Type.object, + properties: output.properties, + additionalProperties: false, + }, + } + : undefined, }) }) res = [ diff --git a/web/app/components/workflow/nodes/tool/node.tsx b/web/app/components/workflow/nodes/tool/node.tsx index 6aa483e8b0..e2bcd26bd2 100644 --- a/web/app/components/workflow/nodes/tool/node.tsx +++ b/web/app/components/workflow/nodes/tool/node.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react' -import React, { useEffect } from 'react' -import type { NodeProps } from '@/app/components/workflow/types' -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' -import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' -import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' import type { ToolNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' +import React, { useEffect } from 'react' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' +import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' +import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' const Node: FC<NodeProps<ToolNodeType>> = ({ id, @@ -43,12 +43,12 @@ const Node: FC<NodeProps<ToolNodeType>> = ({ return null return ( - <div className='relative mb-1 px-3 py-1'> + <div className="relative mb-1 px-3 py-1"> {showInstallButton && ( - <div className='pointer-events-auto absolute right-3 top-[-32px] z-40'> + <div className="pointer-events-auto absolute right-3 top-[-32px] z-40"> <InstallPluginButton - size='small' - className='!font-medium !text-text-accent' + size="small" + className="!font-medium !text-text-accent" extraIdentifiers={[ data.plugin_id, data.provider_id, @@ -60,24 +60,24 @@ const Node: FC<NodeProps<ToolNodeType>> = ({ </div> )} {hasConfigs && ( - <div className='space-y-0.5' aria-disabled={shouldDim}> + <div className="space-y-0.5" aria-disabled={shouldDim}> {toolConfigs.map((key, index) => ( - <div key={index} className='flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'> - <div title={key} className='max-w-[100px] shrink-0 truncate text-xs font-medium uppercase text-text-tertiary'> + <div key={index} className="flex h-6 items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"> + <div title={key} className="max-w-[100px] shrink-0 truncate text-xs font-medium uppercase text-text-tertiary"> {key} </div> {typeof tool_configurations[key].value === 'string' && ( - <div title={tool_configurations[key].value} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'> + <div title={tool_configurations[key].value} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary"> {paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value} </div> )} {typeof tool_configurations[key].value === 'number' && ( - <div title={Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'> + <div title={Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary"> {Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value} </div> )} {typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && ( - <div title={tool_configurations[key].model} className='w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary'> + <div title={tool_configurations[key].model} className="w-0 shrink-0 grow truncate text-right text-xs font-normal text-text-secondary"> {tool_configurations[key].model} </div> )} diff --git a/web/app/components/workflow/nodes/tool/output-schema-utils.ts b/web/app/components/workflow/nodes/tool/output-schema-utils.ts index 684ff0b29f..141c679da0 100644 --- a/web/app/components/workflow/nodes/tool/output-schema-utils.ts +++ b/web/app/components/workflow/nodes/tool/output-schema-utils.ts @@ -1,13 +1,14 @@ +import type { SchemaTypeDefinition } from '@/service/use-common' import { VarType } from '@/app/components/workflow/types' import { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' -import type { SchemaTypeDefinition } from '@/service/use-common' /** * Normalizes a JSON Schema type to a simple string type. * Handles complex schemas with oneOf, anyOf, allOf. */ export const normalizeJsonSchemaType = (schema: any): string | undefined => { - if (!schema) return undefined + if (!schema) + return undefined const { type, properties, items, oneOf, anyOf, allOf } = schema if (Array.isArray(type)) @@ -51,7 +52,7 @@ export const pickItemSchema = (schema: any) => { export const resolveVarType = ( schema: any, schemaTypeDefinitions?: SchemaTypeDefinition[], -): { type: VarType; schemaType?: string } => { +): { type: VarType, schemaType?: string } => { const schemaType = getMatchedSchemaType(schema, schemaTypeDefinitions) const normalizedType = normalizeJsonSchemaType(schema) diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index 3a623208e5..3e1b778a7a 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -1,18 +1,18 @@ import type { FC } from 'react' +import type { ToolNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import Split from '../_base/components/split' -import type { ToolNodeType } from './types' -import useConfig from './use-config' -import ToolForm from './components/tool-form' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import type { NodePanelProps } from '@/app/components/workflow/types' import Loading from '@/app/components/base/loading' +import Field from '@/app/components/workflow/nodes/_base/components/field' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' import { useStore } from '@/app/components/workflow/store' import { wrapStructuredVarItem } from '@/app/components/workflow/utils/tool' +import Split from '../_base/components/split' import useMatchSchemaType, { getMatchedSchemaType } from '../_base/components/variable/use-match-schema-type' +import ToolForm from './components/tool-form' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.tool' @@ -44,19 +44,19 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ if (isLoading) { return ( - <div className='flex h-[200px] items-center justify-center'> + <div className="flex h-[200px] items-center justify-center"> <Loading /> </div> ) } return ( - <div className='pt-2'> + <div className="pt-2"> {!isShowAuthBtn && ( - <div className='relative'> + <div className="relative"> {toolInputVarSchema.length > 0 && ( <Field - className='px-4' + className="px-4" title={t(`${i18nPrefix}.inputVars`)} > <ToolForm @@ -74,7 +74,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ )} {toolInputVarSchema.length > 0 && toolSettingSchema.length > 0 && ( - <Split className='mt-1' /> + <Split className="mt-1" /> )} {toolSettingSchema.length > 0 && ( @@ -102,20 +102,20 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ <OutputVars> <> <VarItem - name='text' - type='string' + name="text" + type="string" description={t(`${i18nPrefix}.outputVars.text`)} isIndent={hasObjectOutput} /> <VarItem - name='files' - type='array[file]' + name="files" + type="array[file]" description={t(`${i18nPrefix}.outputVars.files.title`)} isIndent={hasObjectOutput} /> <VarItem - name='json' - type='array[object]' + name="json" + type="array[object]" description={t(`${i18nPrefix}.outputVars.json`)} isIndent={hasObjectOutput} /> @@ -126,7 +126,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({ <div key={outputItem.name}> {outputItem.value?.type === 'object' ? ( <StructureOutputItem - rootClassName='code-sm-semibold text-text-secondary' + rootClassName="code-sm-semibold text-text-secondary" payload={wrapStructuredVarItem(outputItem, schemaType)} /> ) : ( diff --git a/web/app/components/workflow/nodes/tool/types.ts b/web/app/components/workflow/nodes/tool/types.ts index da3b7f7b31..9a58e8a299 100644 --- a/web/app/components/workflow/nodes/tool/types.ts +++ b/web/app/components/workflow/nodes/tool/types.ts @@ -1,6 +1,6 @@ +import type { ResourceVarInputs } from '../_base/types' import type { Collection, CollectionType } from '@/app/components/tools/types' import type { CommonNodeType } from '@/app/components/workflow/types' -import type { ResourceVarInputs } from '../_base/types' // Use base types directly export { VarKindType as VarType } from '../_base/types' diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index fe3fe543e9..6e1924dd43 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -1,23 +1,21 @@ +import type { ToolNodeType, ToolVarInputs } from './types' +import type { InputVar } from '@/app/components/workflow/types' +import { useBoolean } from 'ahooks' +import { produce } from 'immer' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import { useBoolean } from 'ahooks' -import { useWorkflowStore } from '../../store' -import type { ToolNodeType, ToolVarInputs } from './types' +import Toast from '@/app/components/base/toast' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { CollectionType } from '@/app/components/tools/types' -import { updateBuiltInToolCredential } from '@/service/tools' import { getConfiguredValue, toolParametersToFormSchemas, } from '@/app/components/tools/utils/to-form-schema' -import Toast from '@/app/components/base/toast' -import type { InputVar } from '@/app/components/workflow/types' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' -import { canFindTool } from '@/utils' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { updateBuiltInToolCredential } from '@/service/tools' import { useAllBuiltInTools, useAllCustomTools, @@ -25,6 +23,8 @@ import { useAllWorkflowTools, useInvalidToolsByType, } from '@/service/use-tools' +import { canFindTool } from '@/utils' +import { useWorkflowStore } from '../../store' const useConfig = (id: string, payload: ToolNodeType) => { const workflowStore = useWorkflowStore() @@ -133,7 +133,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { if (typeof value === 'string') newConfig[key] = value === 'true' || value === '1' - if (typeof value === 'number') newConfig[key] = value === 1 + if (typeof value === 'number') + newConfig[key] = value === 1 } if (schema?.type === 'number-input') { @@ -149,7 +150,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { ) const [notSetDefaultValue, setNotSetDefaultValue] = useState(false) const toolSettingValue = useMemo(() => { - if (notSetDefaultValue) return tool_configurations + if (notSetDefaultValue) + return tool_configurations return getConfiguredValue(tool_configurations, toolSettingSchema) }, [notSetDefaultValue, toolSettingSchema, tool_configurations]) const setToolSettingValue = useCallback( @@ -188,7 +190,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { } useEffect(() => { - if (!currTool) return + if (!currTool) + return const inputsWithDefaultValue = formattingParameters() const { setControlPromptEditorRerenderKey } = workflowStore.getState() setInputs(inputsWithDefaultValue) @@ -231,7 +234,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { const outputSchema = useMemo(() => { const res: any[] = [] const output_schema = currTool?.output_schema - if (!output_schema || !output_schema.properties) return res + if (!output_schema || !output_schema.properties) + return res Object.keys(output_schema.properties).forEach((outputKey) => { const output = output_schema.properties[outputKey] @@ -266,7 +270,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { const hasObjectOutput = useMemo(() => { const output_schema = currTool?.output_schema - if (!output_schema || !output_schema.properties) return false + if (!output_schema || !output_schema.properties) + return false const properties = output_schema.properties return Object.keys(properties).some( key => properties[key].type === 'object', diff --git a/web/app/components/workflow/nodes/tool/use-get-data-for-check-more.ts b/web/app/components/workflow/nodes/tool/use-get-data-for-check-more.ts index a68f12fc37..78ebab7bff 100644 --- a/web/app/components/workflow/nodes/tool/use-get-data-for-check-more.ts +++ b/web/app/components/workflow/nodes/tool/use-get-data-for-check-more.ts @@ -3,7 +3,7 @@ import useConfig from './use-config' type Params = { id: string - payload: ToolNodeType, + payload: ToolNodeType } const useGetDataForCheckMore = ({ diff --git a/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts b/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts index b393711e98..ca1e61be37 100644 --- a/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/tool/use-single-run-form-params.ts @@ -1,19 +1,19 @@ import type { RefObject } from 'react' -import type { InputVar, Variable } from '@/app/components/workflow/types' -import { useCallback, useMemo, useState } from 'react' -import useNodeCrud from '../_base/hooks/use-node-crud' -import { type ToolNodeType, VarType } from './types' -import type { ValueSelector } from '@/app/components/workflow/types' +import type { ToolNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import { produce } from 'immer' +import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' +import { produce } from 'immer' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import formatToTracingNodeList from '@/app/components/workflow/run/utils/format-log' import { useToolIcon } from '../../hooks' +import useNodeCrud from '../_base/hooks/use-node-crud' +import { VarType } from './types' type Params = { - id: string, - payload: ToolNodeType, + id: string + payload: ToolNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -61,7 +61,7 @@ const useSingleRunFormParams = ({ Object.keys(inputs.tool_parameters).forEach((key: string) => { const { type, value } = inputs.tool_parameters[key] if (type === VarType.constant && (value === undefined || value === null)) { - if(!draft.tool_parameters || !draft.tool_parameters[key]) + if (!draft.tool_parameters || !draft.tool_parameters[key]) return draft[key] = value } diff --git a/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/index.tsx b/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/index.tsx index 93bf788c34..7ed42ea019 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/index.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/index.tsx @@ -1,10 +1,10 @@ 'use client' +import type { FC } from 'react' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Event } from '@/app/components/tools/types' -import type { FC } from 'react' +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import type { PluginTriggerVarInputs } from '@/app/components/workflow/nodes/trigger-plugin/types' import TriggerFormItem from './item' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' type Props = { readOnly: boolean @@ -33,7 +33,7 @@ const TriggerForm: FC<Props> = ({ disableVariableInsertion = false, }) => { return ( - <div className='space-y-1'> + <div className="space-y-1"> { schema.map((schema, index) => ( <TriggerFormItem diff --git a/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/item.tsx b/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/item.tsx index 678c12f02a..ad1e7747d4 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/item.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/components/trigger-form/item.tsx @@ -1,19 +1,19 @@ 'use client' import type { FC } from 'react' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Event } from '@/app/components/tools/types' +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import type { PluginTriggerVarInputs } from '@/app/components/workflow/nodes/trigger-plugin/types' import { RiBracesLine, } from '@remixicon/react' -import type { PluginTriggerVarInputs } from '@/app/components/workflow/nodes/trigger-plugin/types' -import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { useBoolean } from 'ahooks' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' -import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item' -import { useBoolean } from 'ahooks' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import SchemaModal from '@/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal' -import type { Event } from '@/app/components/tools/types' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item' type Props = { readOnly: boolean @@ -49,39 +49,41 @@ const TriggerFormItem: FC<Props> = ({ setFalse: hideSchema, }] = useBoolean(false) return ( - <div className='space-y-0.5 py-1'> + <div className="space-y-0.5 py-1"> <div> - <div className='flex h-6 items-center'> - <div className='system-sm-medium text-text-secondary'>{label[language] || label.en_US}</div> + <div className="flex h-6 items-center"> + <div className="system-sm-medium text-text-secondary">{label[language] || label.en_US}</div> {required && ( - <div className='system-xs-regular ml-1 text-text-destructive-secondary'>*</div> + <div className="system-xs-regular ml-1 text-text-destructive-secondary">*</div> )} {!showDescription && tooltip && ( <Tooltip - popupContent={<div className='w-[200px]'> - {tooltip[language] || tooltip.en_US} - </div>} - triggerClassName='ml-1 w-4 h-4' + popupContent={( + <div className="w-[200px]"> + {tooltip[language] || tooltip.en_US} + </div> + )} + triggerClassName="ml-1 w-4 h-4" asChild={false} /> )} {showSchemaButton && ( <> - <div className='system-xs-regular ml-1 mr-0.5 text-text-quaternary'>·</div> + <div className="system-xs-regular ml-1 mr-0.5 text-text-quaternary">·</div> <Button - variant='ghost' - size='small' + variant="ghost" + size="small" onClick={showSchema} - className='system-xs-regular px-1 text-text-tertiary' + className="system-xs-regular px-1 text-text-tertiary" > - <RiBracesLine className='mr-1 size-3.5' /> + <RiBracesLine className="mr-1 size-3.5" /> <span>JSON Schema</span> </Button> </> )} </div> {showDescription && tooltip && ( - <div className='body-xs-regular pb-0.5 text-text-tertiary'>{tooltip[language] || tooltip.en_US}</div> + <div className="body-xs-regular pb-0.5 text-text-tertiary">{tooltip[language] || tooltip.en_US}</div> )} </div> <FormInputItem @@ -93,7 +95,7 @@ const TriggerFormItem: FC<Props> = ({ inPanel={inPanel} currentTool={currentEvent} currentProvider={currentProvider} - providerType='trigger' + providerType="trigger" extraParams={extraParams} disableVariableInsertion={disableVariableInsertion} /> diff --git a/web/app/components/workflow/nodes/trigger-plugin/default.ts b/web/app/components/workflow/nodes/trigger-plugin/default.ts index 928534e07c..6e3aac1339 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/default.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/default.ts @@ -1,13 +1,15 @@ -import type { SchemaTypeDefinition } from '@/service/use-common' import type { NodeDefault, Var } from '../../types' +import type { Field, StructuredOutput } from '../llm/types' +import type { PluginTriggerNodeType } from './types' +import type { SchemaTypeDefinition } from '@/service/use-common' import { BlockEnum, VarType } from '../../types' import { genNodeMetaData } from '../../utils' import { VarKindType } from '../_base/types' -import { type Field, type StructuredOutput, Type } from '../llm/types' -import type { PluginTriggerNodeType } from './types' +import { Type } from '../llm/types' const normalizeJsonSchemaType = (schema: any): string | undefined => { - if (!schema) return undefined + if (!schema) + return undefined const { type, properties, items, oneOf, anyOf, allOf } = schema if (Array.isArray(type)) @@ -55,7 +57,7 @@ const extractSchemaType = (schema: any, _schemaTypeDefinitions?: SchemaTypeDefin const resolveVarType = ( schema: any, schemaTypeDefinitions?: SchemaTypeDefinition[], -): { type: VarType; schemaType?: string } => { +): { type: VarType, schemaType?: string } => { const schemaType = extractSchemaType(schema, schemaTypeDefinitions) const normalizedType = normalizeJsonSchemaType(schema) @@ -195,9 +197,9 @@ const buildOutputVars = (schema: Record<string, any>, schemaTypeDefinitions?: Sc if (normalizedType === 'object') { const childProperties = propertySchema?.properties ? Object.entries(propertySchema.properties).reduce((acc, [key, value]) => { - acc[key] = convertJsonSchemaToField(value, schemaTypeDefinitions) - return acc - }, {} as Record<string, Field>) + acc[key] = convertJsonSchemaToField(value, schemaTypeDefinitions) + return acc + }, {} as Record<string, Field>) : {} const required = Array.isArray(propertySchema?.required) ? propertySchema.required.filter(Boolean) : undefined @@ -263,7 +265,7 @@ const nodeDefault: NodeDefault<PluginTriggerNodeType> = { } const targetParam = typeof rawParam === 'object' && rawParam !== null && 'type' in rawParam - ? rawParam as { type: VarKindType; value: any } + ? rawParam as { type: VarKindType, value: any } : { type: VarKindType.constant, value: rawParam } const { type, value } = targetParam @@ -277,8 +279,9 @@ const nodeDefault: NodeDefault<PluginTriggerNodeType> = { || value === null || value === '' || (Array.isArray(value) && value.length === 0) - ) + ) { errorMessage = t('workflow.errorMsg.fieldRequired', { field: field.label }) + } } }) } diff --git a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts index 983b8512de..36bcbf1cc7 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts @@ -1,3 +1,4 @@ +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import { useCallback, useState } from 'react' import { useBuildTriggerSubscription, @@ -5,7 +6,6 @@ import { useUpdateTriggerSubscriptionBuilder, useVerifyTriggerSubscriptionBuilder, } from '@/service/use-triggers' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' // Helper function to serialize complex values to strings for backend encryption const serializeFormValues = (values: Record<string, any>): Record<string, string> => { @@ -51,7 +51,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState const buildSubscription = useBuildTriggerSubscription() const startAuth = useCallback(async () => { - if (builderId) return // Prevent multiple calls if already started + if (builderId) + return // Prevent multiple calls if already started setIsLoading(true) setError(null) diff --git a/web/app/components/workflow/nodes/trigger-plugin/node.tsx b/web/app/components/workflow/nodes/trigger-plugin/node.tsx index 0eee4cb8b4..da4dc83d34 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/node.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/node.tsx @@ -1,12 +1,12 @@ -import NodeStatus, { NodeStatusEnum } from '@/app/components/base/node-status' -import type { NodeProps } from '@/app/components/workflow/types' import type { FC } from 'react' +import type { PluginTriggerNodeType } from './types' +import type { NodeProps } from '@/app/components/workflow/types' import React, { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' -import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' +import NodeStatus, { NodeStatusEnum } from '@/app/components/base/node-status' import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' -import type { PluginTriggerNodeType } from './types' +import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' +import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' import useConfig from './use-config' const formatConfigValue = (rawValue: any): string => { diff --git a/web/app/components/workflow/nodes/trigger-plugin/panel.tsx b/web/app/components/workflow/nodes/trigger-plugin/panel.tsx index 9b4d8058b1..ffa3a5503c 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/panel.tsx @@ -1,14 +1,14 @@ import type { FC } from 'react' -import React from 'react' import type { PluginTriggerNodeType } from './types' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import type { NodePanelProps } from '@/app/components/workflow/types' -import useConfig from './use-config' -import TriggerForm from './components/trigger-form' +import React from 'react' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' -import { Type } from '../llm/types' import { BlockEnum } from '@/app/components/workflow/types' +import { Type } from '../llm/types' +import TriggerForm from './components/trigger-form' +import useConfig from './use-config' const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({ id, @@ -35,11 +35,11 @@ const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({ })) return ( - <div className='mt-2'> + <div className="mt-2"> {/* Dynamic Parameters Form - Only show when authenticated */} {triggerParameterSchema.length > 0 && subscriptionSelected && ( <> - <div className='px-4 pb-4'> + <div className="px-4 pb-4"> <TriggerForm readOnly={readOnly} nodeId={id} @@ -69,20 +69,22 @@ const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({ ))} {Object.entries(outputSchema.properties || {}).map(([name, schema]: [string, any]) => ( <div key={name}> - {schema.type === 'object' ? ( - <StructureOutputItem - rootClassName='code-sm-semibold text-text-secondary' - payload={{ - schema: { - type: Type.object, - properties: { - [name]: schema, - }, - additionalProperties: false, - }, - }} - /> - ) : null} + {schema.type === 'object' + ? ( + <StructureOutputItem + rootClassName="code-sm-semibold text-text-secondary" + payload={{ + schema: { + type: Type.object, + properties: { + [name]: schema, + }, + additionalProperties: false, + }, + }} + /> + ) + : null} </div> ))} </> diff --git a/web/app/components/workflow/nodes/trigger-plugin/types.ts b/web/app/components/workflow/nodes/trigger-plugin/types.ts index 6dba97d795..8dd6aa9657 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/types.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/types.ts @@ -1,6 +1,6 @@ -import type { CommonNodeType } from '@/app/components/workflow/types' -import type { CollectionType } from '@/app/components/tools/types' import type { ResourceVarInputs } from '../_base/types' +import type { CollectionType } from '@/app/components/tools/types' +import type { CommonNodeType } from '@/app/components/workflow/types' export type PluginTriggerNodeType = CommonNodeType & { provider_id: string diff --git a/web/app/components/workflow/nodes/trigger-plugin/use-check-params.ts b/web/app/components/workflow/nodes/trigger-plugin/use-check-params.ts index 16b763f11a..7c23378498 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/use-check-params.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/use-check-params.ts @@ -1,8 +1,8 @@ -import { useCallback } from 'react' import type { PluginTriggerNodeType } from './types' -import { useAllTriggerPlugins } from '@/service/use-triggers' -import { useGetLanguage } from '@/context/i18n' +import { useCallback } from 'react' import { getTriggerCheckParams } from '@/app/components/workflow/utils/trigger' +import { useGetLanguage } from '@/context/i18n' +import { useAllTriggerPlugins } from '@/service/use-triggers' type Params = { id: string diff --git a/web/app/components/workflow/nodes/trigger-plugin/use-config.ts b/web/app/components/workflow/nodes/trigger-plugin/use-config.ts index cf66913e58..a746f1a410 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/use-config.ts @@ -1,20 +1,19 @@ -import { useCallback, useEffect, useMemo } from 'react' +import type { PluginTriggerNodeType, PluginTriggerVarInputs } from './types' +import type { Event } from '@/app/components/tools/types' +import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' +import type { InputVar } from '@/app/components/workflow/types' import { produce } from 'immer' -import type { PluginTriggerNodeType } from './types' -import type { PluginTriggerVarInputs } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { useNodesReadOnly } from '@/app/components/workflow/hooks' -import { - useAllTriggerPlugins, - useTriggerSubscriptions, -} from '@/service/use-triggers' +import { useCallback, useEffect, useMemo } from 'react' import { getConfiguredValue, toolParametersToFormSchemas, } from '@/app/components/tools/utils/to-form-schema' -import type { InputVar } from '@/app/components/workflow/types' -import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types' -import type { Event } from '@/app/components/tools/types' +import { useNodesReadOnly } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { + useAllTriggerPlugins, + useTriggerSubscriptions, +} from '@/service/use-triggers' import { VarKindType } from '../_base/types' const normalizeEventParameters = ( @@ -121,7 +120,8 @@ const useConfig = (id: string, payload: PluginTriggerNodeType) => { // Dynamic trigger parameters (from specific trigger.parameters) const triggerSpecificParameterSchema = useMemo(() => { - if (!currentEvent) return [] + if (!currentEvent) + return [] return toolParametersToFormSchemas(currentEvent.parameters) }, [currentEvent]) diff --git a/web/app/components/workflow/nodes/trigger-plugin/utils/form-helpers.ts b/web/app/components/workflow/nodes/trigger-plugin/utils/form-helpers.ts index 36090d9771..cd49bd8ffe 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/utils/form-helpers.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/utils/form-helpers.ts @@ -45,8 +45,8 @@ export const deepSanitizeFormValues = (values: Record<string, any>, visited = ne */ export const findMissingRequiredField = ( formData: Record<string, any>, - requiredFields: Array<{ name: string; label: any }>, -): { name: string; label: any } | null => { + requiredFields: Array<{ name: string, label: any }>, +): { name: string, label: any } | null => { for (const field of requiredFields) { if (!formData[field.name] || formData[field.name] === '') return field diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx index d0de74a6ef..b4f62de436 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx @@ -1,7 +1,7 @@ +import type { ScheduleFrequency } from '../types' import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { SimpleSelect } from '@/app/components/base/select' -import type { ScheduleFrequency } from '../types' type FrequencySelectorProps = { frequency: ScheduleFrequency diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx index 6dc88c85bf..724fedc7b2 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx @@ -1,8 +1,8 @@ +import type { ScheduleMode } from '../types' +import { RiCalendarLine, RiCodeLine } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiCalendarLine, RiCodeLine } from '@remixicon/react' import { SegmentedControl } from '@/app/components/base/segmented-control' -import type { ScheduleMode } from '../types' type ModeSwitcherProps = { mode: ScheduleMode diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx index 6ae5d2cf67..35ffaff939 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx @@ -1,7 +1,7 @@ +import type { ScheduleMode } from '../types' import React from 'react' import { useTranslation } from 'react-i18next' import { Asterisk, CalendarCheckLine } from '@/app/components/base/icons/src/vender/workflow' -import type { ScheduleMode } from '../types' type ModeToggleProps = { mode: ScheduleMode diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx index d7cce79328..e5a50522e1 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx @@ -1,6 +1,6 @@ +import { RiQuestionLine } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiQuestionLine } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' type MonthlyDaysSelectorProps = { @@ -53,18 +53,20 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp : 'border-divider-subtle text-text-tertiary hover:border-divider-regular hover:text-text-secondary' }`} > - {day === 'last' ? ( - <div className="flex items-center justify-center gap-1"> - <span>{t('workflow.nodes.triggerSchedule.lastDay')}</span> - <Tooltip - popupContent={t('workflow.nodes.triggerSchedule.lastDayTooltip')} - > - <RiQuestionLine className="h-3 w-3 text-text-quaternary" /> - </Tooltip> - </div> - ) : ( - day - )} + {day === 'last' + ? ( + <div className="flex items-center justify-center gap-1"> + <span>{t('workflow.nodes.triggerSchedule.lastDay')}</span> + <Tooltip + popupContent={t('workflow.nodes.triggerSchedule.lastDayTooltip')} + > + <RiQuestionLine className="h-3 w-3 text-text-quaternary" /> + </Tooltip> + </div> + ) + : ( + day + )} </button> ))} {/* Fill empty cells in the last row (Last day takes 2 cols, so need 1 less) */} diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx index 02e85e2724..c84bca483c 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx @@ -1,6 +1,6 @@ +import type { ScheduleTriggerNodeType } from '../types' import React from 'react' import { useTranslation } from 'react-i18next' -import type { ScheduleTriggerNodeType } from '../types' import { getFormattedExecutionTimes } from '../utils/execution-time-calculator' type NextExecutionTimesProps = { diff --git a/web/app/components/workflow/nodes/trigger-schedule/default.ts b/web/app/components/workflow/nodes/trigger-schedule/default.ts index 69f93c33f4..bd7f1976ba 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/default.ts +++ b/web/app/components/workflow/nodes/trigger-schedule/default.ts @@ -1,14 +1,15 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { ScheduleTriggerNodeType } from './types' +import { BlockEnum } from '../../types' +import { genNodeMetaData } from '../../utils' +import { getDefaultScheduleConfig } from './constants' import { isValidCronExpression } from './utils/cron-parser' import { getNextExecutionTimes } from './utils/execution-time-calculator' -import { getDefaultScheduleConfig } from './constants' -import { genNodeMetaData } from '../../utils' const isValidTimeFormat = (time: string): boolean => { const timeRegex = /^(0?\d|1[0-2]):[0-5]\d (AM|PM)$/ - if (!timeRegex.test(time)) return false + if (!timeRegex.test(time)) + return false const [timePart, period] = time.split(' ') const [hour, minute] = timePart.split(':') @@ -16,8 +17,8 @@ const isValidTimeFormat = (time: string): boolean => { const minuteNum = Number.parseInt(minute, 10) return hourNum >= 1 && hourNum <= 12 - && minuteNum >= 0 && minuteNum <= 59 - && ['AM', 'PM'].includes(period) + && minuteNum >= 0 && minuteNum <= 59 + && ['AM', 'PM'].includes(period) } const validateHourlyConfig = (config: any, t: any): string => { @@ -41,7 +42,8 @@ const validateDailyConfig = (config: any, t: any): string => { const validateWeeklyConfig = (config: any, t: any): string => { const dailyError = validateDailyConfig(config, t) - if (dailyError) return dailyError + if (dailyError) + return dailyError const i18nPrefix = 'workflow.errorMsg' @@ -59,7 +61,8 @@ const validateWeeklyConfig = (config: any, t: any): string => { const validateMonthlyConfig = (config: any, t: any): string => { const dailyError = validateDailyConfig(config, t) - if (dailyError) return dailyError + if (dailyError) + return dailyError const i18nPrefix = 'workflow.errorMsg' diff --git a/web/app/components/workflow/nodes/trigger-schedule/node.tsx b/web/app/components/workflow/nodes/trigger-schedule/node.tsx index 9870ef211a..45e9b2afdb 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/node.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/node.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' import type { ScheduleTriggerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' +import { useTranslation } from 'react-i18next' import { getNextExecutionTime } from './utils/execution-time-calculator' const i18nPrefix = 'workflow.nodes.triggerSchedule' diff --git a/web/app/components/workflow/nodes/trigger-schedule/panel.tsx b/web/app/components/workflow/nodes/trigger-schedule/panel.tsx index 2a7c661339..8daedc50a9 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/panel.tsx @@ -1,17 +1,17 @@ import type { FC } from 'react' +import type { ScheduleTriggerNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import type { ScheduleTriggerNodeType } from './types' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import type { NodePanelProps } from '@/app/components/workflow/types' -import ModeToggle from './components/mode-toggle' -import FrequencySelector from './components/frequency-selector' -import WeekdaySelector from './components/weekday-selector' import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' -import NextExecutionTimes from './components/next-execution-times' -import MonthlyDaysSelector from './components/monthly-days-selector' -import OnMinuteSelector from './components/on-minute-selector' import Input from '@/app/components/base/input' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import FrequencySelector from './components/frequency-selector' +import ModeToggle from './components/mode-toggle' +import MonthlyDaysSelector from './components/monthly-days-selector' +import NextExecutionTimes from './components/next-execution-times' +import OnMinuteSelector from './components/on-minute-selector' +import WeekdaySelector from './components/weekday-selector' import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.triggerSchedule' @@ -33,16 +33,16 @@ const Panel: FC<NodePanelProps<ScheduleTriggerNodeType>> = ({ } = useConfig(id, data) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-3 pt-2'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-3 pt-2"> <Field title={t(`${i18nPrefix}.title`)} - operations={ + operations={( <ModeToggle mode={inputs.mode} onChange={handleModeChange} /> - } + )} > <div className="space-y-3"> @@ -59,35 +59,37 @@ const Panel: FC<NodePanelProps<ScheduleTriggerNodeType>> = ({ /> </div> <div className="col-span-2"> - {inputs.frequency === 'hourly' ? ( - <OnMinuteSelector - value={inputs.visual_config?.on_minute} - onChange={handleOnMinuteChange} - /> - ) : ( - <> - <label className="mb-2 block text-xs font-medium text-gray-500"> - {t('workflow.nodes.triggerSchedule.time')} - </label> - <TimePicker - notClearable={true} - timezone={inputs.timezone} - value={inputs.visual_config?.time || '12:00 AM'} - triggerFullWidth={true} - onChange={(time) => { - if (time) { - const timeString = time.format('h:mm A') - handleTimeChange(timeString) - } - }} - onClear={() => { - handleTimeChange('12:00 AM') - }} - placeholder={t('workflow.nodes.triggerSchedule.selectTime')} - showTimezone={true} - /> - </> - )} + {inputs.frequency === 'hourly' + ? ( + <OnMinuteSelector + value={inputs.visual_config?.on_minute} + onChange={handleOnMinuteChange} + /> + ) + : ( + <> + <label className="mb-2 block text-xs font-medium text-gray-500"> + {t('workflow.nodes.triggerSchedule.time')} + </label> + <TimePicker + notClearable={true} + timezone={inputs.timezone} + value={inputs.visual_config?.time || '12:00 AM'} + triggerFullWidth={true} + onChange={(time) => { + if (time) { + const timeString = time.format('h:mm A') + handleTimeChange(timeString) + } + }} + onClear={() => { + handleTimeChange('12:00 AM') + }} + placeholder={t('workflow.nodes.triggerSchedule.selectTime')} + showTimezone={true} + /> + </> + )} </div> </div> diff --git a/web/app/components/workflow/nodes/trigger-schedule/use-config.ts b/web/app/components/workflow/nodes/trigger-schedule/use-config.ts index 06e29ccd84..a3e5959f2e 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-schedule/use-config.ts @@ -1,7 +1,7 @@ -import { useCallback, useMemo } from 'react' import type { ScheduleFrequency, ScheduleMode, ScheduleTriggerNodeType } from './types' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' +import { useCallback, useMemo } from 'react' import { useNodesReadOnly } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { useAppContext } from '@/context/app-context' import { getDefaultVisualConfig } from './constants' diff --git a/web/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator.ts b/web/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator.ts index aef122ba25..6bd6bc41dc 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator.ts +++ b/web/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator.ts @@ -1,6 +1,6 @@ import type { ScheduleTriggerNodeType } from '../types' -import { isValidCronExpression, parseCronExpression } from './cron-parser' import { convertTimezoneToOffsetStr } from '@/app/components/base/date-and-time-picker/utils/dayjs' +import { isValidCronExpression, parseCronExpression } from './cron-parser' const DEFAULT_TIMEZONE = 'UTC' @@ -107,8 +107,10 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb const [time, period] = defaultTime.split(' ') const [hour, minute] = time.split(':') let displayHour = Number.parseInt(hour) - if (period === 'PM' && displayHour !== 12) displayHour += 12 - if (period === 'AM' && displayHour === 12) displayHour = 0 + if (period === 'PM' && displayHour !== 12) + displayHour += 12 + if (period === 'AM' && displayHour === 12) + displayHour = 0 // Check if today's configured time has already passed const todayExecution = new Date(userToday) @@ -132,8 +134,10 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb const [time, period] = defaultTime.split(' ') const [hour, minute] = time.split(':') let displayHour = Number.parseInt(hour) - if (period === 'PM' && displayHour !== 12) displayHour += 12 - if (period === 'AM' && displayHour === 12) displayHour = 0 + if (period === 'PM' && displayHour !== 12) + displayHour += 12 + if (period === 'AM' && displayHour === 12) + displayHour = 0 // Get current time completely in user timezone const userCurrentTime = getUserTimezoneCurrentTime(timezone) @@ -145,10 +149,12 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb let hasValidDays = false for (const selectedDay of selectedDays) { - if (executionCount >= count) break + if (executionCount >= count) + break const targetDay = dayMap[selectedDay as keyof typeof dayMap] - if (targetDay === undefined) continue + if (targetDay === undefined) + continue hasValidDays = true @@ -174,7 +180,8 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb } } - if (!hasValidDays) break + if (!hasValidDays) + break weekOffset++ } @@ -192,8 +199,10 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb const [time, period] = defaultTime.split(' ') const [hour, minute] = time.split(':') let displayHour = Number.parseInt(hour) - if (period === 'PM' && displayHour !== 12) displayHour += 12 - if (period === 'AM' && displayHour === 12) displayHour = 0 + if (period === 'PM' && displayHour !== 12) + displayHour += 12 + if (period === 'AM' && displayHour === 12) + displayHour = 0 // Get current time completely in user timezone const userCurrentTime = getUserTimezoneCurrentTime(timezone) @@ -237,7 +246,8 @@ export const getNextExecutionTimes = (data: ScheduleTriggerNodeType, count: numb monthlyExecutions.sort((a, b) => a.getTime() - b.getTime()) for (const execution of monthlyExecutions) { - if (executionCount >= count) break + if (executionCount >= count) + break times.push(execution) executionCount++ } diff --git a/web/app/components/workflow/nodes/trigger-schedule/utils/integration.spec.ts b/web/app/components/workflow/nodes/trigger-schedule/utils/integration.spec.ts index a3d28de112..cfc502d141 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/utils/integration.spec.ts +++ b/web/app/components/workflow/nodes/trigger-schedule/utils/integration.spec.ts @@ -1,7 +1,7 @@ -import { isValidCronExpression, parseCronExpression } from './cron-parser' -import { getNextExecutionTime, getNextExecutionTimes } from './execution-time-calculator' import type { ScheduleTriggerNodeType } from '../types' import { BlockEnum } from '../../../types' +import { isValidCronExpression, parseCronExpression } from './cron-parser' +import { getNextExecutionTime, getNextExecutionTimes } from './execution-time-calculator' // Comprehensive integration tests for cron-parser and execution-time-calculator compatibility describe('cron-parser + execution-time-calculator integration', () => { @@ -176,9 +176,12 @@ describe('cron-parser + execution-time-calculator integration', () => { expect(date.getHours()).toBe(hour) expect(date.getMinutes()).toBe(minute) - if (weekday !== undefined) expect(date.getDay()).toBe(weekday) - if (day !== undefined) expect(date.getDate()).toBe(day) - if (month !== undefined) expect(date.getMonth()).toBe(month) + if (weekday !== undefined) + expect(date.getDay()).toBe(weekday) + if (day !== undefined) + expect(date.getDate()).toBe(day) + if (month !== undefined) + expect(date.getMonth()).toBe(month) }) }) }) diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx index eaf3f399d7..a6644d7312 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx @@ -1,16 +1,17 @@ 'use client' import type { FC, ReactNode } from 'react' -import React, { useCallback, useMemo } from 'react' import { RiDeleteBinLine } from '@remixicon/react' -import Input from '@/app/components/base/input' +import React, { useCallback, useMemo } from 'react' import Checkbox from '@/app/components/base/checkbox' +import Input from '@/app/components/base/input' import { SimpleSelect } from '@/app/components/base/select' -import { replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' import { cn } from '@/utils/classnames' +import { replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' // Tiny utility to judge whether a cell value is effectively present const isPresent = (v: unknown): boolean => { - if (typeof v === 'string') return v.trim() !== '' + if (typeof v === 'string') + return v.trim() !== '' return !(v === '' || v === null || v === undefined || v === false) } // Column configuration types for table components @@ -102,14 +103,17 @@ const GenericTable: FC<GenericTableProps> = ({ }, [data, emptyRowData, readonly]) const removeRow = useCallback((dataIndex: number) => { - if (readonly) return - if (dataIndex < 0 || dataIndex >= data.length) return // ignore virtual rows + if (readonly) + return + if (dataIndex < 0 || dataIndex >= data.length) + return // ignore virtual rows const newData = data.filter((_, i) => i !== dataIndex) onChange(newData) }, [data, readonly, onChange]) const updateRow = useCallback((dataIndex: number | null, key: string, value: unknown) => { - if (readonly) return + if (readonly) + return if (dataIndex !== null && dataIndex < data.length) { // Editing existing configured row @@ -283,13 +287,15 @@ const GenericTable: FC<GenericTableProps> = ({ <h4 className="system-sm-semibold-uppercase text-text-secondary">{title}</h4> </div> - {showPlaceholder ? ( - <div className="flex h-7 items-center justify-center rounded-lg border border-divider-regular bg-components-panel-bg text-xs font-normal leading-[18px] text-text-quaternary"> - {placeholder} - </div> - ) : ( - renderTable() - )} + {showPlaceholder + ? ( + <div className="flex h-7 items-center justify-center rounded-lg border border-divider-regular bg-components-panel-bg text-xs font-normal leading-[18px] text-text-quaternary"> + {placeholder} + </div> + ) + : ( + renderTable() + )} </div> ) } diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx index 25e3cd4137..da54cac16a 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' +import type { WebhookHeader } from '../types' +import type { ColumnConfig, GenericTableRow } from './generic-table' import React from 'react' import { useTranslation } from 'react-i18next' import GenericTable from './generic-table' -import type { ColumnConfig, GenericTableRow } from './generic-table' -import type { WebhookHeader } from '../types' type HeaderTableProps = { readonly?: boolean diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx index bf030c4340..1fa038ff73 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import type { WebhookParameter } from '../types' +import type { ColumnConfig, GenericTableRow } from './generic-table' import React, { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import GenericTable from './generic-table' -import type { ColumnConfig, GenericTableRow } from './generic-table' -import type { WebhookParameter } from '../types' -import { createParameterTypeOptions, normalizeParameterType } from '../utils/parameter-type-utils' import { VarType } from '@/app/components/workflow/types' +import { createParameterTypeOptions, normalizeParameterType } from '../utils/parameter-type-utils' +import GenericTable from './generic-table' type ParameterTableProps = { title: string @@ -29,9 +29,7 @@ const ParameterTable: FC<ParameterTableProps> = ({ // Memoize typeOptions to prevent unnecessary re-renders that cause SimpleSelect state resets const typeOptions = useMemo(() => - createParameterTypeOptions(contentType), - [contentType], - ) + createParameterTypeOptions(contentType), [contentType]) // Define columns based on component type - matching prototype design const columns: ColumnConfig[] = [ diff --git a/web/app/components/workflow/nodes/trigger-webhook/default.ts b/web/app/components/workflow/nodes/trigger-webhook/default.ts index 5071a79913..d6c39ca2d8 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/default.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/default.ts @@ -1,7 +1,7 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' -import { genNodeMetaData } from '../../utils' import type { WebhookTriggerNodeType } from './types' +import { BlockEnum } from '../../types' +import { genNodeMetaData } from '../../utils' import { isValidParameterType } from './utils/parameter-type-utils' import { createWebhookRawVariable } from './utils/raw-variable' diff --git a/web/app/components/workflow/nodes/trigger-webhook/node.tsx b/web/app/components/workflow/nodes/trigger-webhook/node.tsx index 40c3b441da..77f42b6db2 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/node.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' -import React from 'react' import type { WebhookTriggerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' +import React from 'react' const Node: FC<NodeProps<WebhookTriggerNodeType>> = ({ data, diff --git a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx index 1de18bd806..efc541bbb3 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx @@ -1,24 +1,24 @@ import type { FC } from 'react' +import type { HttpMethod, WebhookTriggerNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' + +import copy from 'copy-to-clipboard' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' - -import type { HttpMethod, WebhookTriggerNodeType } from './types' -import useConfig from './use-config' -import ParameterTable from './components/parameter-table' -import HeaderTable from './components/header-table' -import ParagraphInput from './components/paragraph-input' -import { OutputVariablesContent } from './utils/render-output-vars' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import OutputVars from '@/app/components/workflow/nodes/_base/components/output-vars' -import type { NodePanelProps } from '@/app/components/workflow/types' -import InputWithCopy from '@/app/components/base/input-with-copy' import { InputNumber } from '@/app/components/base/input-number' +import InputWithCopy from '@/app/components/base/input-with-copy' import { SimpleSelect } from '@/app/components/base/select' import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' -import copy from 'copy-to-clipboard' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import OutputVars from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' import { isPrivateOrLocalAddress } from '@/utils/urlValidation' +import HeaderTable from './components/header-table' +import ParagraphInput from './components/paragraph-input' +import ParameterTable from './components/parameter-table' +import useConfig from './use-config' +import { OutputVariablesContent } from './utils/render-output-vars' const i18nPrefix = 'workflow.nodes.triggerWebhook' @@ -70,8 +70,8 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({ }, [readOnly, inputs.webhook_url, generateWebhookUrl]) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-3 pt-2'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-3 pt-2"> {/* Webhook URL Section */} <Field title={t(`${i18nPrefix}.webhookUrl`)}> <div className="space-y-1"> @@ -225,7 +225,7 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({ <Split /> - <div className=''> + <div className=""> <OutputVars collapsed={outputVarsCollapsed} onCollapse={setOutputVarsCollapsed} diff --git a/web/app/components/workflow/nodes/trigger-webhook/types.ts b/web/app/components/workflow/nodes/trigger-webhook/types.ts index 90cfd40434..f5938ed00b 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/types.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/types.ts @@ -1,4 +1,4 @@ -import type { CommonNodeType, VarType, Variable } from '@/app/components/workflow/types' +import type { CommonNodeType, Variable, VarType } from '@/app/components/workflow/types' export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' diff --git a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts index 9b525ec758..dec79b8eaf 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts @@ -1,15 +1,15 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { useTranslation } from 'react-i18next' import type { HttpMethod, WebhookHeader, WebhookParameter, WebhookTriggerNodeType } from './types' +import type { Variable } from '@/app/components/workflow/types' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { useStore as useAppStore } from '@/app/components/app/store' +import Toast from '@/app/components/base/toast' import { useNodesReadOnly, useWorkflow } from '@/app/components/workflow/hooks' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' -import { useStore as useAppStore } from '@/app/components/app/store' -import { fetchWebhookUrl } from '@/service/apps' -import type { Variable } from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' -import Toast from '@/app/components/base/toast' +import { fetchWebhookUrl } from '@/service/apps' import { checkKeys, hasDuplicateStr } from '@/utils/var' import { WEBHOOK_RAW_VARIABLE_NAME } from './utils/raw-variable' @@ -88,7 +88,7 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { return false } - if(hasDuplicateStr(sanitizedEntries.map(entry => entry.sanitizedName))) { + if (hasDuplicateStr(sanitizedEntries.map(entry => entry.sanitizedName))) { Toast.notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { @@ -126,7 +126,8 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { // Remove variables that no longer exist in newData for this specific source type draft.variables = draft.variables.filter((v) => { // Keep variables from other sources - if (v.label !== sourceType) return true + if (v.label !== sourceType) + return true return newVarNames.has(v.variable) }) diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts b/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts index 2be7d4c65f..d4975cc597 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/raw-variable.ts @@ -1,4 +1,5 @@ -import { VarType, type Variable } from '@/app/components/workflow/types' +import type { Variable } from '@/app/components/workflow/types' +import { VarType } from '@/app/components/workflow/types' export const WEBHOOK_RAW_VARIABLE_NAME = '_webhook_raw' export const WEBHOOK_RAW_VARIABLE_LABEL = 'raw' diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx index 0e9cb8a309..984ffc03dd 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' -import React from 'react' import type { Variable } from '@/app/components/workflow/types' +import React from 'react' type OutputVariablesContentProps = { variables?: Variable[] @@ -27,11 +27,14 @@ type VarItemProps = { const VarItem: FC<VarItemProps> = ({ prefix, name, type }) => { return ( - <div className='py-1'> - <div className='flex items-center leading-[18px]'> - <span className='code-sm-regular text-text-tertiary'>{prefix}.</span> - <span className='code-sm-semibold text-text-secondary'>{name}</span> - <span className='system-xs-regular ml-2 text-text-tertiary'>{type}</span> + <div className="py-1"> + <div className="flex items-center leading-[18px]"> + <span className="code-sm-regular text-text-tertiary"> + {prefix} + . + </span> + <span className="code-sm-semibold text-text-secondary">{name}</span> + <span className="system-xs-regular ml-2 text-text-tertiary">{type}</span> </div> </div> ) @@ -52,7 +55,7 @@ export const OutputVariablesContent: FC<OutputVariablesContentProps> = ({ variab const labelA = typeof a.label === 'string' ? a.label : '' const labelB = typeof b.label === 'string' ? b.label : '' return (LABEL_ORDER[labelA as keyof typeof LABEL_ORDER] || 999) - - (LABEL_ORDER[labelB as keyof typeof LABEL_ORDER] || 999) + - (LABEL_ORDER[labelB as keyof typeof LABEL_ORDER] || 999) }) return ( diff --git a/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx index 2f3ca14b5d..c7c2efa6ba 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx @@ -1,23 +1,23 @@ -import { - memo, - useCallback, - useState, -} from 'react' -import { useVariableAssigner } from '../../hooks' import type { VariableAssignerNodeType } from '../../types' -import { cn } from '@/utils/classnames' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import { Plus02 } from '@/app/components/base/icons/src/vender/line/general' -import AddVariablePopup from '@/app/components/workflow/nodes/_base/components/add-variable-popup' import type { NodeOutPutVar, ValueSelector, Var, } from '@/app/components/workflow/types' +import { + memo, + useCallback, + useState, +} from 'react' +import { Plus02 } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import AddVariablePopup from '@/app/components/workflow/nodes/_base/components/add-variable-popup' +import { cn } from '@/utils/classnames' +import { useVariableAssigner } from '../../hooks' export type AddVariableProps = { variableAssignerNodeId: string @@ -48,9 +48,10 @@ const AddVariable = ({ <div className={cn( open && '!flex', variableAssignerNodeData.selected && '!flex', - )}> + )} + > <PortalToFollowElem - placement={'right'} + placement="right" offset={4} open={open} onOpenChange={setOpen} @@ -75,7 +76,7 @@ const AddVariable = ({ /> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[1000]'> + <PortalToFollowElemContent className="z-[1000]"> <AddVariablePopup onSelect={handleSelectVariable} availableVars={availableVars} diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx index 540dfecfd9..fdffecb8f6 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx @@ -1,29 +1,29 @@ -import { - memo, - useMemo, -} from 'react' -import { useTranslation } from 'react-i18next' -import { useNodes } from 'reactflow' -import { useStore } from '../../../store' -import { BlockEnum } from '../../../types' import type { Node, ValueSelector, VarType, } from '../../../types' import type { VariableAssignerNodeType } from '../types' +import { + memo, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { + VariableLabelInNode, +} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { isExceptionVariable } from '@/app/components/workflow/utils' +import { cn } from '@/utils/classnames' +import { useStore } from '../../../store' +import { BlockEnum } from '../../../types' import { useGetAvailableVars, useVariableAssigner, } from '../hooks' import { filterVar } from '../utils' import AddVariable from './add-variable' -import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import { cn } from '@/utils/classnames' -import { isExceptionVariable } from '@/app/components/workflow/utils' -import { - VariableLabelInNode, -} from '@/app/components/workflow/nodes/_base/components/variable/variable-label' const i18nPrefix = 'workflow.nodes.variableAssigner' type GroupItem = { @@ -90,7 +90,7 @@ const NodeGroupItem = ({ onMouseEnter={() => groupEnabled && handleGroupItemMouseEnter(item.targetHandleId)} onMouseLeave={handleGroupItemMouseLeave} > - <div className='flex h-4 items-center justify-between text-[10px] font-medium text-text-tertiary'> + <div className="flex h-4 items-center justify-between text-[10px] font-medium text-text-tertiary"> <span className={cn( 'grow truncate uppercase', @@ -100,9 +100,9 @@ const NodeGroupItem = ({ > {item.title} </span> - <div className='flex items-center'> - <span className='ml-2 shrink-0'>{item.type}</span> - <div className='ml-2 mr-1 h-2.5 w-[1px] bg-divider-regular'></div> + <div className="flex items-center"> + <span className="ml-2 shrink-0">{item.type}</span> + <div className="ml-2 mr-1 h-2.5 w-[1px] bg-divider-regular"></div> <AddVariable availableVars={availableVars} variableAssignerNodeId={item.variableAssignerNodeId} @@ -125,7 +125,7 @@ const NodeGroupItem = ({ } { !!item.variables.length && ( - <div className='space-y-0.5'> + <div className="space-y-0.5"> { item.variables.map((variable = [], index) => { const isSystem = isSystemVar(variable) diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx index f891f44e7a..d722c1d231 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx @@ -1,17 +1,17 @@ +import type { Node, ValueSelector } from '@/app/components/workflow/types' import { memo, useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' -import { VarBlockIcon } from '@/app/components/workflow/block-icon' -import { Line3 } from '@/app/components/base/icons/src/public/common' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' -import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import Badge from '@/app/components/base/badge' -import type { Node, ValueSelector } from '@/app/components/workflow/types' -import { isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { Line3 } from '@/app/components/base/icons/src/public/common' +import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' import { InputField } from '@/app/components/base/icons/src/vender/pipeline' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { VarBlockIcon } from '@/app/components/workflow/block-icon' +import { isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { cn } from '@/utils/classnames' type NodeVariableItemProps = { node: Node @@ -39,9 +39,9 @@ const NodeVariableItem = ({ const isChatVar = isConversationVar(variable) const isRagVar = isRagVariableVar(variable) const varName = useMemo(() => { - if(isSystem) + if (isSystem) return `sys.${variable[variable.length - 1]}` - if(isRagVar) + if (isRagVar) return variable[variable.length - 1] return variable.slice(1).join('.') }, [isRagVar, isSystem, variable]) @@ -49,19 +49,19 @@ const NodeVariableItem = ({ const VariableIcon = useMemo(() => { if (isEnv) { return ( - <Env className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' /> + <Env className="h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600" /> ) } if (isChatVar) { return ( - <BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' /> + <BubbleX className="h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700" /> ) } - if(isRagVar) { + if (isRagVar) { return ( - <InputField className='h-3.5 w-3.5 shrink-0 text-text-accent' /> + <InputField className="h-3.5 w-3.5 shrink-0 text-text-accent" /> ) } @@ -95,31 +95,32 @@ const NodeVariableItem = ({ 'relative flex items-center gap-1 self-stretch rounded-md bg-workflow-block-parma-bg p-[3px] pl-[5px]', showBorder && '!bg-state-base-hover', className, - )}> - <div className='flex w-0 grow items-center'> + )} + > + <div className="flex w-0 grow items-center"> { node && ( <> - <div className='shrink-0 p-[1px]'> + <div className="shrink-0 p-[1px]"> <VarBlockIcon - className='!text-text-primary' + className="!text-text-primary" type={node.data.type} /> </div> <div - className='mx-0.5 shrink-[1000] truncate text-xs font-medium text-text-secondary' + className="mx-0.5 shrink-[1000] truncate text-xs font-medium text-text-secondary" title={node?.data.title} > {node?.data.title} </div> - <Line3 className='mr-0.5 shrink-0'></Line3> + <Line3 className="mr-0.5 shrink-0"></Line3> </> ) } {VariableIcon} {VariableName} </div> - {writeMode && <Badge className='shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} />} + {writeMode && <Badge className="shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}`)} />} </div> ) } diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx index 90a30f4845..277c44744b 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx @@ -1,22 +1,22 @@ 'use client' -import React, { useCallback } from 'react' import type { ChangeEvent, FC } from 'react' -import { useTranslation } from 'react-i18next' -import { produce } from 'immer' -import { useBoolean } from 'ahooks' +import type { VarGroupItem as VarGroupItemType } from '../types' +import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { RiDeleteBinLine, } from '@remixicon/react' -import type { VarGroupItem as VarGroupItemType } from '../types' +import { useBoolean } from 'ahooks' +import { produce } from 'immer' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { Folder } from '@/app/components/base/icons/src/vender/line/files' +import Toast from '@/app/components/base/toast' +import Field from '@/app/components/workflow/nodes/_base/components/field' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { VarType } from '@/app/components/workflow/types' +import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' import VarReferencePicker from '../../_base/components/variable/var-reference-picker' import VarList from '../components/var-list' -import Field from '@/app/components/workflow/nodes/_base/components/field' -import { VarType } from '@/app/components/workflow/types' -import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { Folder } from '@/app/components/base/icons/src/vender/line/files' -import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' -import Toast from '@/app/components/base/toast' const i18nPrefix = 'workflow.nodes.variableAssigner' @@ -104,67 +104,72 @@ const VarGroupItem: FC<Props> = ({ return ( <Field - className='group' + className="group" title={groupEnabled - ? <div className='flex items-center'> - <div className='flex items-center !normal-case'> - <Folder className='mr-0.5 h-3.5 w-3.5' /> - {(!isEditGroupName) - ? ( - <div className='system-sm-semibold flex h-6 cursor-text items-center rounded-lg px-1 text-text-secondary hover:bg-gray-100' onClick={setEditGroupName}> - {payload.group_name} - </div> - ) - : ( - <input - type='text' - className='h-6 rounded-lg border border-gray-300 bg-white px-1 focus:outline-none' - // style={{ - // width: `${((payload.group_name?.length || 0) + 1) / 2}em`, - // }} - size={payload.group_name?.length} // to fit the input width - autoFocus - value={payload.group_name} - onChange={handleGroupNameChange} - onBlur={setNotEditGroupName} - maxLength={30} - />)} + ? ( + <div className="flex items-center"> + <div className="flex items-center !normal-case"> + <Folder className="mr-0.5 h-3.5 w-3.5" /> + {(!isEditGroupName) + ? ( + <div className="system-sm-semibold flex h-6 cursor-text items-center rounded-lg px-1 text-text-secondary hover:bg-gray-100" onClick={setEditGroupName}> + {payload.group_name} + </div> + ) + : ( + <input + type="text" + className="h-6 rounded-lg border border-gray-300 bg-white px-1 focus:outline-none" + // style={{ + // width: `${((payload.group_name?.length || 0) + 1) / 2}em`, + // }} + size={payload.group_name?.length} // to fit the input width + autoFocus + value={payload.group_name} + onChange={handleGroupNameChange} + onBlur={setNotEditGroupName} + maxLength={30} + /> + )} - </div> - {canRemove && ( - <div - className='ml-0.5 hidden cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block' - onClick={onRemove} - > - <RiDeleteBinLine - className='h-4 w-4' - /> + </div> + {canRemove && ( + <div + className="ml-0.5 hidden cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block" + onClick={onRemove} + > + <RiDeleteBinLine + className="h-4 w-4" + /> + </div> + )} </div> - )} - </div> + ) : t(`${i18nPrefix}.title`)!} - operations={ - <div className='flex h-6 items-center space-x-2'> + operations={( + <div className="flex h-6 items-center space-x-2"> {payload.variables.length > 0 && ( - <div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 text-text-tertiary'>{payload.output_type}</div> + <div className="system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 text-text-tertiary">{payload.output_type}</div> )} { !readOnly - ? <VarReferencePicker - isAddBtnTrigger - readonly={false} - nodeId={nodeId} - isShowNodeName - value={[]} - onChange={handleAddVariable} - defaultVarKindType={VarKindType.variable} - filterVar={filterVar} - availableVars={availableVars} - /> + ? ( + <VarReferencePicker + isAddBtnTrigger + readonly={false} + nodeId={nodeId} + isShowNodeName + value={[]} + onChange={handleAddVariable} + defaultVarKindType={VarKindType.variable} + filterVar={filterVar} + availableVars={availableVars} + /> + ) : undefined } </div> - } + )} > <VarList readonly={readOnly} diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx index d38ee51465..a767e704fe 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx @@ -1,14 +1,14 @@ 'use client' import type { FC } from 'react' -import { useTranslation } from 'react-i18next' -import React, { useCallback } from 'react' -import { produce } from 'immer' -import RemoveButton from '../../../_base/components/remove-button' -import ListNoDataPlaceholder from '../../../_base/components/list-no-data-placeholder' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { produce } from 'immer' import { noop } from 'lodash-es' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import ListNoDataPlaceholder from '../../../_base/components/list-no-data-placeholder' +import RemoveButton from '../../../_base/components/remove-button' type Props = { readonly: boolean @@ -59,14 +59,14 @@ const VarList: FC<Props> = ({ } return ( - <div className='space-y-2'> + <div className="space-y-2"> {list.map((item, index) => ( - <div className='flex items-center space-x-1' key={index}> + <div className="flex items-center space-x-1" key={index}> <VarReferencePicker readonly={readonly} nodeId={nodeId} isShowNodeName - className='grow' + className="grow" value={item} onChange={handleVarReferenceChange(index)} onOpen={handleOpen(index)} diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts b/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts index 7e781a4068..71d0fb72ea 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/use-var-list.ts @@ -1,7 +1,7 @@ -import { useCallback } from 'react' -import { produce } from 'immer' import type { VariableAssignerNodeType } from '../../types' import type { ValueSelector } from '@/app/components/workflow/types' +import { produce } from 'immer' +import { useCallback } from 'react' type Params = { id: string diff --git a/web/app/components/workflow/nodes/variable-assigner/default.ts b/web/app/components/workflow/nodes/variable-assigner/default.ts index 825bad5b9c..7cc723be99 100644 --- a/web/app/components/workflow/nodes/variable-assigner/default.ts +++ b/web/app/components/workflow/nodes/variable-assigner/default.ts @@ -1,8 +1,9 @@ -import { type NodeDefault, VarType } from '../../types' +import type { NodeDefault } from '../../types' import type { VariableAssignerNodeType } from './types' -import { genNodeMetaData } from '@/app/components/workflow/utils' -import { BlockEnum } from '@/app/components/workflow/types' import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { VarType } from '../../types' const i18nPrefix = 'workflow' diff --git a/web/app/components/workflow/nodes/variable-assigner/hooks.ts b/web/app/components/workflow/nodes/variable-assigner/hooks.ts index 29e0ee16d1..b20cee79c7 100644 --- a/web/app/components/workflow/nodes/variable-assigner/hooks.ts +++ b/web/app/components/workflow/nodes/variable-assigner/hooks.ts @@ -1,27 +1,27 @@ -import { useCallback } from 'react' -import { - useStoreApi, -} from 'reactflow' -import { useNodes } from 'reactflow' +import type { + Node, + ValueSelector, + Var, +} from '../../types' +import type { + VarGroupItem, + VariableAssignerNodeType, +} from './types' +import { produce } from 'immer' import { uniqBy } from 'lodash-es' -import { produce } from 'immer' +import { useCallback } from 'react' +import { + useNodes, + useStoreApi, +} from 'reactflow' import { useIsChatMode, useNodeDataUpdate, useWorkflow, useWorkflowVariables, } from '../../hooks' -import type { - Node, - ValueSelector, - Var, -} from '../../types' import { useWorkflowStore } from '../../store' -import type { - VarGroupItem, - VariableAssignerNodeType, -} from './types' export const useVariableAssigner = () => { const store = useStoreApi() diff --git a/web/app/components/workflow/nodes/variable-assigner/node.tsx b/web/app/components/workflow/nodes/variable-assigner/node.tsx index 5246ba2a8a..70223c7c7e 100644 --- a/web/app/components/workflow/nodes/variable-assigner/node.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/node.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react' +import type { NodeProps } from 'reactflow' +import type { VariableAssignerNodeType } from './types' import { memo, useMemo, useRef, } from 'react' -import type { NodeProps } from 'reactflow' import { useTranslation } from 'react-i18next' import NodeGroupItem from './components/node-group-item' -import type { VariableAssignerNodeType } from './types' const i18nPrefix = 'workflow.nodes.variableAssigner' @@ -43,7 +43,7 @@ const Node: FC<NodeProps<VariableAssignerNodeType>> = (props) => { }, [t, advanced_settings, data, id]) return ( - <div className='relative mb-1 space-y-0.5 px-1' ref={ref}> + <div className="relative mb-1 space-y-0.5 px-1" ref={ref}> { groups.map((item) => { return ( @@ -54,7 +54,7 @@ const Node: FC<NodeProps<VariableAssignerNodeType>> = (props) => { ) }) } - </div > + </div> ) } diff --git a/web/app/components/workflow/nodes/variable-assigner/panel.tsx b/web/app/components/workflow/nodes/variable-assigner/panel.tsx index a605808f95..0a6c1c3c84 100644 --- a/web/app/components/workflow/nodes/variable-assigner/panel.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/panel.tsx @@ -1,17 +1,17 @@ import type { FC } from 'react' +import type { VariableAssignerNodeType } from './types' +import type { NodePanelProps } from '@/app/components/workflow/types' import React from 'react' import { useTranslation } from 'react-i18next' -import Field from '../_base/components/field' -import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' -import useConfig from './use-config' -import type { VariableAssignerNodeType } from './types' -import VarGroupItem from './components/var-group-item' -import { cn } from '@/utils/classnames' -import type { NodePanelProps } from '@/app/components/workflow/types' -import Split from '@/app/components/workflow/nodes/_base/components/split' -import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import Switch from '@/app/components/base/switch' import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' +import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import { cn } from '@/utils/classnames' +import Field from '../_base/components/field' +import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' +import VarGroupItem from './components/var-group-item' +import useConfig from './use-config' const i18nPrefix = 'workflow.nodes.variableAssigner' const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({ @@ -38,62 +38,64 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({ } = useConfig(id, data) return ( - <div className='mt-2'> - <div className='space-y-4 px-4 pb-4'> + <div className="mt-2"> + <div className="space-y-4 px-4 pb-4"> {!isEnableGroup ? ( - <VarGroupItem - readOnly={readOnly} - nodeId={id} - payload={{ - output_type: inputs.output_type, - variables: inputs.variables, - }} - onChange={handleListOrTypeChange} - groupEnabled={false} - availableVars={getAvailableVars(id, 'target', filterVar(inputs.output_type), true)} - /> - ) - : (<div> - <div className='space-y-2'> - {inputs.advanced_settings?.groups.map((item, index) => ( - <div key={item.groupId}> - <VarGroupItem - readOnly={readOnly} - nodeId={id} - payload={item} - onChange={handleListOrTypeChangeInGroup(item.groupId)} - groupEnabled - canRemove={!readOnly && inputs.advanced_settings?.groups.length > 1} - onRemove={handleGroupRemoved(item.groupId)} - onGroupNameChange={handleVarGroupNameChange(item.groupId)} - availableVars={getAvailableVars(id, item.groupId, filterVar(item.output_type), true)} - /> - {index !== inputs.advanced_settings?.groups.length - 1 && <Split className='my-4' />} - </div> + <VarGroupItem + readOnly={readOnly} + nodeId={id} + payload={{ + output_type: inputs.output_type, + variables: inputs.variables, + }} + onChange={handleListOrTypeChange} + groupEnabled={false} + availableVars={getAvailableVars(id, 'target', filterVar(inputs.output_type), true)} + /> + ) + : ( + <div> + <div className="space-y-2"> + {inputs.advanced_settings?.groups.map((item, index) => ( + <div key={item.groupId}> + <VarGroupItem + readOnly={readOnly} + nodeId={id} + payload={item} + onChange={handleListOrTypeChangeInGroup(item.groupId)} + groupEnabled + canRemove={!readOnly && inputs.advanced_settings?.groups.length > 1} + onRemove={handleGroupRemoved(item.groupId)} + onGroupNameChange={handleVarGroupNameChange(item.groupId)} + availableVars={getAvailableVars(id, item.groupId, filterVar(item.output_type), true)} + /> + {index !== inputs.advanced_settings?.groups.length - 1 && <Split className="my-4" />} + </div> - ))} - </div> - <AddButton - className='mt-2' - text={t(`${i18nPrefix}.addGroup`)} - onClick={handleAddGroup} - /> - </div>)} + ))} + </div> + <AddButton + className="mt-2" + text={t(`${i18nPrefix}.addGroup`)} + onClick={handleAddGroup} + /> + </div> + )} </div> <Split /> <div className={cn('px-4 pt-4', isEnableGroup ? 'pb-4' : 'pb-2')}> <Field title={t(`${i18nPrefix}.aggregationGroup`)} tooltip={t(`${i18nPrefix}.aggregationGroupTip`)!} - operations={ + operations={( <Switch defaultValue={isEnableGroup} onChange={handleGroupEnabledChange} - size='md' + size="md" disabled={readOnly} /> - } + )} /> </div> {isEnableGroup && ( diff --git a/web/app/components/workflow/nodes/variable-assigner/use-config.ts b/web/app/components/workflow/nodes/variable-assigner/use-config.ts index 583f779575..286eed523d 100644 --- a/web/app/components/workflow/nodes/variable-assigner/use-config.ts +++ b/web/app/components/workflow/nodes/variable-assigner/use-config.ts @@ -1,19 +1,19 @@ -import { useCallback, useRef, useState } from 'react' -import { produce } from 'immer' -import { useBoolean, useDebounceFn } from 'ahooks' -import { v4 as uuid4 } from 'uuid' import type { ValueSelector, Var } from '../../types' -import { VarType } from '../../types' import type { VarGroupItem, VariableAssignerNodeType } from './types' -import { useGetAvailableVars } from './hooks' -import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' - +import { useBoolean, useDebounceFn } from 'ahooks' +import { produce } from 'immer' +import { useCallback, useRef, useState } from 'react' +import { v4 as uuid4 } from 'uuid' import { useNodesReadOnly, useWorkflow, } from '@/app/components/workflow/hooks' +import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud' +import { VarType } from '../../types' +import { useGetAvailableVars } from './hooks' + const useConfig = (id: string, payload: VariableAssignerNodeType) => { const { deleteNodeInspectorVars, @@ -165,7 +165,7 @@ const useConfig = (id: string, payload: VariableAssignerNodeType) => { }) handleOutVarRenameChange(id, [id, inputs.advanced_settings.groups[index].group_name, 'output'], [id, name, 'output']) setInputs(newInputs) - if(!(id in oldNameRecord.current)) + if (!(id in oldNameRecord.current)) oldNameRecord.current[id] = inputs.advanced_settings.groups[index].group_name renameInspectNameWithDebounce(id, name) } diff --git a/web/app/components/workflow/nodes/variable-assigner/use-single-run-form-params.ts b/web/app/components/workflow/nodes/variable-assigner/use-single-run-form-params.ts index 8e67675d3e..874c546fd8 100644 --- a/web/app/components/workflow/nodes/variable-assigner/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/variable-assigner/use-single-run-form-params.ts @@ -1,11 +1,11 @@ import type { RefObject } from 'react' +import type { VariableAssignerNodeType } from './types' import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types' import { useCallback } from 'react' -import type { VariableAssignerNodeType } from './types' type Params = { - id: string, - payload: VariableAssignerNodeType, + id: string + payload: VariableAssignerNodeType runInputData: Record<string, any> runInputDataRef: RefObject<Record<string, any>> getInputVars: (textList: string[]) => InputVar[] @@ -52,7 +52,7 @@ const useSingleRunFormParams = ({ const existVarsKey: Record<string, boolean> = {} const uniqueVarInputs: InputVar[] = [] varInputs.forEach((input) => { - if(!input) + if (!input) return if (!existVarsKey[input.variable]) { existVarsKey[input.variable] = true @@ -72,10 +72,10 @@ const useSingleRunFormParams = ({ })() const getDependentVars = () => { - if(payload.advanced_settings?.group_enabled) { + if (payload.advanced_settings?.group_enabled) { const vars: ValueSelector[][] = [] payload.advanced_settings.groups.forEach((group) => { - if(group.variables) + if (group.variables) vars.push([...group.variables]) }) return vars diff --git a/web/app/components/workflow/note-node/constants.ts b/web/app/components/workflow/note-node/constants.ts index 223ce72e77..b2fa223690 100644 --- a/web/app/components/workflow/note-node/constants.ts +++ b/web/app/components/workflow/note-node/constants.ts @@ -2,7 +2,7 @@ import { NoteTheme } from './types' export const CUSTOM_NOTE_NODE = 'custom-note' -export const THEME_MAP: Record<string, { outer: string; title: string; bg: string; border: string }> = { +export const THEME_MAP: Record<string, { outer: string, title: string, bg: string, border: string }> = { [NoteTheme.blue]: { outer: 'border-util-colors-blue-blue-500', title: 'bg-util-colors-blue-blue-100', diff --git a/web/app/components/workflow/note-node/hooks.ts b/web/app/components/workflow/note-node/hooks.ts index 29642f90df..6924f31af5 100644 --- a/web/app/components/workflow/note-node/hooks.ts +++ b/web/app/components/workflow/note-node/hooks.ts @@ -1,7 +1,7 @@ -import { useCallback } from 'react' import type { EditorState } from 'lexical' -import { WorkflowHistoryEvent, useNodeDataUpdate, useWorkflowHistory } from '../hooks' import type { NoteTheme } from './types' +import { useCallback } from 'react' +import { useNodeDataUpdate, useWorkflowHistory, WorkflowHistoryEvent } from '../hooks' export const useNote = (id: string) => { const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() diff --git a/web/app/components/workflow/note-node/index.tsx b/web/app/components/workflow/note-node/index.tsx index 6b81a0fe1f..e482d240a2 100644 --- a/web/app/components/workflow/note-node/index.tsx +++ b/web/app/components/workflow/note-node/index.tsx @@ -1,26 +1,26 @@ +import type { NodeProps } from 'reactflow' +import type { NoteNodeType } from './types' +import { useClickAway } from 'ahooks' import { memo, useRef, } from 'react' import { useTranslation } from 'react-i18next' -import { useClickAway } from 'ahooks' -import type { NodeProps } from 'reactflow' -import NodeResizer from '../nodes/_base/components/node-resizer' -import { useWorkflowHistoryStore } from '../workflow-history-store' +import { cn } from '@/utils/classnames' import { useNodeDataUpdate, useNodesInteractions, } from '../hooks' +import NodeResizer from '../nodes/_base/components/node-resizer' import { useStore } from '../store' +import { useWorkflowHistoryStore } from '../workflow-history-store' +import { THEME_MAP } from './constants' +import { useNote } from './hooks' import { NoteEditor, NoteEditorContextProvider, NoteEditorToolbar, } from './note-editor' -import { THEME_MAP } from './constants' -import { useNote } from './hooks' -import type { NoteNodeType } from './types' -import { cn } from '@/utils/classnames' const Icon = () => { return ( @@ -90,10 +90,12 @@ const NoteNode = ({ className={cn( 'h-2 shrink-0 rounded-t-md opacity-50', THEME_MAP[theme].title, - )}></div> + )} + > + </div> { data.selected && !data._isTempNode && ( - <div className='absolute left-1/2 top-[-41px] -translate-x-1/2'> + <div className="absolute left-1/2 top-[-41px] -translate-x-1/2"> <NoteEditorToolbar theme={theme} onThemeChange={handleThemeChange} @@ -106,10 +108,11 @@ const NoteNode = ({ </div> ) } - <div className='grow overflow-y-auto px-3 py-2.5'> + <div className="grow overflow-y-auto px-3 py-2.5"> <div className={cn( data.selected && 'nodrag nopan nowheel cursor-text', - )}> + )} + > <NoteEditor containerElement={ref.current} placeholder={t('workflow.nodes.note.editor.placeholder') || ''} @@ -120,7 +123,7 @@ const NoteNode = ({ </div> { data.showAuthor && ( - <div className='p-3 pt-0 text-xs text-text-tertiary'> + <div className="p-3 pt-0 text-xs text-text-tertiary"> {data.author} </div> ) diff --git a/web/app/components/workflow/note-node/note-editor/context.tsx b/web/app/components/workflow/note-node/note-editor/context.tsx index c9e7eb56c8..362693f4a4 100644 --- a/web/app/components/workflow/note-node/note-editor/context.tsx +++ b/web/app/components/workflow/note-node/note-editor/context.tsx @@ -1,16 +1,16 @@ 'use client' -import { - createContext, - memo, - useRef, -} from 'react' -import { LexicalComposer } from '@lexical/react/LexicalComposer' import { LinkNode } from '@lexical/link' import { ListItemNode, ListNode, } from '@lexical/list' +import { LexicalComposer } from '@lexical/react/LexicalComposer' +import { + createContext, + memo, + useRef, +} from 'react' import { createNoteEditorStore } from './store' import theme from './theme' diff --git a/web/app/components/workflow/note-node/note-editor/editor.tsx b/web/app/components/workflow/note-node/note-editor/editor.tsx index c91988c532..bdabd8d2e7 100644 --- a/web/app/components/workflow/note-node/note-editor/editor.tsx +++ b/web/app/components/workflow/note-node/note-editor/editor.tsx @@ -1,22 +1,22 @@ 'use client' +import type { EditorState } from 'lexical' +import { ClickableLinkPlugin } from '@lexical/react/LexicalClickableLinkPlugin' +import { ContentEditable } from '@lexical/react/LexicalContentEditable' +import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary' +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' +import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin' +import { ListPlugin } from '@lexical/react/LexicalListPlugin' +import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin' +import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' import { memo, useCallback, } from 'react' -import type { EditorState } from 'lexical' -import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' -import { ContentEditable } from '@lexical/react/LexicalContentEditable' -import { ClickableLinkPlugin } from '@lexical/react/LexicalClickableLinkPlugin' -import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin' -import { ListPlugin } from '@lexical/react/LexicalListPlugin' -import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary' -import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' -import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin' -import LinkEditorPlugin from './plugins/link-editor-plugin' -import FormatDetectorPlugin from './plugins/format-detector-plugin' // import TreeView from '@/app/components/base/prompt-editor/plugins/tree-view' import Placeholder from '@/app/components/base/prompt-editor/plugins/placeholder' +import FormatDetectorPlugin from './plugins/format-detector-plugin' +import LinkEditorPlugin from './plugins/link-editor-plugin' type EditorProps = { placeholder?: string @@ -35,18 +35,18 @@ const Editor = ({ }, [onChange]) return ( - <div className='relative'> + <div className="relative"> <RichTextPlugin - contentEditable={ + contentEditable={( <div> <ContentEditable onFocus={() => setShortcutsEnabled?.(false)} onBlur={() => setShortcutsEnabled?.(true)} spellCheck={false} - className='h-full w-full text-text-secondary caret-primary-600 outline-none' + className="h-full w-full text-text-secondary caret-primary-600 outline-none" /> </div> - } + )} placeholder={<Placeholder value={placeholder} compact />} ErrorBoundary={LexicalErrorBoundary} /> diff --git a/web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts b/web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts index bc7e855c3b..0ad7e78283 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts +++ b/web/app/components/workflow/note-node/note-editor/plugins/format-detector-plugin/hooks.ts @@ -1,18 +1,18 @@ -import { - useCallback, - useEffect, -} from 'react' +import type { LinkNode } from '@lexical/link' +import { $isLinkNode } from '@lexical/link' +import { $isListItemNode } from '@lexical/list' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { mergeRegister } from '@lexical/utils' import { $getSelection, $isRangeSelection, } from 'lexical' -import { mergeRegister } from '@lexical/utils' -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import type { LinkNode } from '@lexical/link' -import { $isLinkNode } from '@lexical/link' -import { $isListItemNode } from '@lexical/list' -import { getSelectedNode } from '../../utils' +import { + useCallback, + useEffect, +} from 'react' import { useNoteEditorStore } from '../../store' +import { getSelectedNode } from '../../utils' export const useFormatDetector = () => { const [editor] = useLexicalComposerContext() diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx index 556b7409c7..3f5472ad56 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx @@ -1,27 +1,27 @@ import { - memo, - useEffect, - useState, -} from 'react' -import { escape } from 'lodash-es' -import { - FloatingPortal, flip, + FloatingPortal, offset, shift, useFloating, } from '@floating-ui/react' -import { useTranslation } from 'react-i18next' -import { useClickAway } from 'ahooks' import { RiEditLine, RiExternalLinkLine, RiLinkUnlinkM, } from '@remixicon/react' +import { useClickAway } from 'ahooks' +import { escape } from 'lodash-es' +import { + memo, + useEffect, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' +import { cn } from '@/utils/classnames' import { useStore } from '../../store' import { useLink } from './hooks' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' type LinkEditorComponentProps = { containerElement: HTMLDivElement | null @@ -80,15 +80,15 @@ const LinkEditorComponent = ({ !linkOperatorShow && ( <> <input - className='mr-0.5 h-6 w-[196px] appearance-none rounded-sm bg-transparent p-1 text-[13px] text-components-input-text-filled outline-none' + className="mr-0.5 h-6 w-[196px] appearance-none rounded-sm bg-transparent p-1 text-[13px] text-components-input-text-filled outline-none" value={url} onChange={e => setUrl(e.target.value)} placeholder={t('workflow.nodes.note.editor.enterUrl') || ''} autoFocus /> <Button - variant='primary' - size='small' + variant="primary" + size="small" disabled={!url} onClick={() => handleSaveLink(url)} > @@ -101,38 +101,38 @@ const LinkEditorComponent = ({ linkOperatorShow && ( <> <a - className='flex h-6 items-center rounded-md px-2 hover:bg-state-base-hover' + className="flex h-6 items-center rounded-md px-2 hover:bg-state-base-hover" href={escape(url)} - target='_blank' - rel='noreferrer' + target="_blank" + rel="noreferrer" > - <RiExternalLinkLine className='mr-1 h-3 w-3' /> - <div className='mr-1'> + <RiExternalLinkLine className="mr-1 h-3 w-3" /> + <div className="mr-1"> {t('workflow.nodes.note.editor.openLink')} </div> <div title={escape(url)} - className='max-w-[140px] truncate text-text-accent' + className="max-w-[140px] truncate text-text-accent" > {escape(url)} </div> </a> - <div className='mx-1 h-3.5 w-[1px] bg-divider-regular'></div> + <div className="mx-1 h-3.5 w-[1px] bg-divider-regular"></div> <div - className='mr-0.5 flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover' + className="mr-0.5 flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover" onClick={(e) => { e.stopPropagation() setLinkOperatorShow(false) }} > - <RiEditLine className='mr-1 h-3 w-3' /> + <RiEditLine className="mr-1 h-3 w-3" /> {t('common.operation.edit')} </div> <div - className='flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover' + className="flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover" onClick={handleUnlink} > - <RiLinkUnlinkM className='mr-1 h-3 w-3' /> + <RiLinkUnlinkM className="mr-1 h-3 w-3" /> {t('workflow.nodes.note.editor.unlink')} </div> </> diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts index 8be8b55196..2c6e014b15 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts @@ -1,23 +1,23 @@ +import { + TOGGLE_LINK_COMMAND, +} from '@lexical/link' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { + mergeRegister, +} from '@lexical/utils' +import { + CLICK_COMMAND, + COMMAND_PRIORITY_LOW, +} from 'lexical' +import { escape } from 'lodash-es' import { useCallback, useEffect, } from 'react' import { useTranslation } from 'react-i18next' -import { - CLICK_COMMAND, - COMMAND_PRIORITY_LOW, -} from 'lexical' -import { - mergeRegister, -} from '@lexical/utils' -import { - TOGGLE_LINK_COMMAND, -} from '@lexical/link' -import { escape } from 'lodash-es' -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { useToastContext } from '@/app/components/base/toast' import { useNoteEditorStore } from '../../store' import { urlRegExp } from '../../utils' -import { useToastContext } from '@/app/components/base/toast' export const useOpenLink = () => { const [editor] = useLexicalComposerContext() diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx index a5b3df6504..e7d4a99cfb 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/index.tsx @@ -2,8 +2,8 @@ import { memo, } from 'react' import { useStore } from '../../store' -import { useOpenLink } from './hooks' import LinkEditorComponent from './component' +import { useOpenLink } from './hooks' type LinkEditorPluginProps = { containerElement: HTMLDivElement | null diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx index ccf70fdc90..f88848ad9d 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx @@ -2,14 +2,14 @@ import { memo, useState, } from 'react' -import { NoteTheme } from '../../types' -import { THEME_MAP } from '../../constants' -import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import { cn } from '@/utils/classnames' +import { THEME_MAP } from '../../constants' +import { NoteTheme } from '../../types' export const COLOR_LIST = [ { @@ -58,29 +58,31 @@ const ColorPicker = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='top' + placement="top" offset={4} > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> <div className={cn( 'flex h-8 w-8 cursor-pointer items-center justify-center rounded-md hover:bg-black/5', open && 'bg-black/5', - )}> + )} + > <div className={cn( 'h-4 w-4 rounded-full border border-black/5', THEME_MAP[theme].title, )} - ></div> + > + </div> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent> - <div className='grid grid-cols-3 grid-rows-2 gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg'> + <div className="grid grid-cols-3 grid-rows-2 gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg"> { COLOR_LIST.map(color => ( <div key={color.key} - className='group relative flex h-8 w-8 cursor-pointer items-center justify-center rounded-md' + className="group relative flex h-8 w-8 cursor-pointer items-center justify-center rounded-md" onClick={(e) => { e.stopPropagation() onThemeChange(color.key) @@ -92,13 +94,15 @@ const ColorPicker = ({ 'absolute left-1/2 top-1/2 hidden h-5 w-5 -translate-x-1/2 -translate-y-1/2 rounded-full border-[1.5px] group-hover:block', color.outer, )} - ></div> + > + </div> <div className={cn( 'absolute left-1/2 top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-black/5', color.inner, )} - ></div> + > + </div> </div> )) } diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx index 1a1d8f20ec..c0d7eb9d95 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx @@ -1,8 +1,3 @@ -import { - memo, - useMemo, -} from 'react' -import { useTranslation } from 'react-i18next' import { RiBold, RiItalic, @@ -10,10 +5,15 @@ import { RiListUnordered, RiStrikethrough, } from '@remixicon/react' +import { + memo, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' import { useStore } from '../store' import { useCommand } from './hooks' -import { cn } from '@/utils/classnames' -import Tooltip from '@/app/components/base/tooltip' type CommandProps = { type: 'bold' | 'italic' | 'strikethrough' | 'link' | 'bullet' diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx index a6554b3e11..35bbbd6893 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/divider.tsx @@ -1,6 +1,6 @@ const Divider = () => { return ( - <div className='mx-1 h-3.5 w-[1px] bg-divider-regular'></div> + <div className="mx-1 h-3.5 w-[1px] bg-divider-regular"></div> ) } diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx index b03e176482..7072c6e64a 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx @@ -1,14 +1,14 @@ -import { memo } from 'react' import { RiFontSize } from '@remixicon/react' +import { memo } from 'react' import { useTranslation } from 'react-i18next' -import { useFontSize } from './hooks' -import { cn } from '@/utils/classnames' +import { Check } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { Check } from '@/app/components/base/icons/src/vender/line/general' +import { cn } from '@/utils/classnames' +import { useFontSize } from './hooks' const FontSizeSelector = () => { const { t } = useTranslation() @@ -37,25 +37,26 @@ const FontSizeSelector = () => { <PortalToFollowElem open={fontSizeSelectorShow} onOpenChange={handleOpenFontSizeSelector} - placement='bottom-start' + placement="bottom-start" offset={2} > <PortalToFollowElemTrigger onClick={() => handleOpenFontSizeSelector(!fontSizeSelectorShow)}> <div className={cn( 'flex h-8 cursor-pointer items-center rounded-md pl-2 pr-1.5 text-[13px] font-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', fontSizeSelectorShow && 'bg-state-base-hover text-text-secondary', - )}> - <RiFontSize className='mr-1 h-4 w-4' /> + )} + > + <RiFontSize className="mr-1 h-4 w-4" /> {FONT_SIZE_LIST.find(font => font.key === fontSize)?.value || t('workflow.nodes.note.editor.small')} </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent> - <div className='w-[120px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 text-text-secondary shadow-xl'> + <div className="w-[120px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 text-text-secondary shadow-xl"> { FONT_SIZE_LIST.map(font => ( <div key={font.key} - className='flex h-8 cursor-pointer items-center justify-between rounded-md pl-3 pr-2 hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-md pl-3 pr-2 hover:bg-state-base-hover" onClick={(e) => { e.stopPropagation() handleFontSize(font.key) @@ -69,7 +70,7 @@ const FontSizeSelector = () => { </div> { fontSize === font.key && ( - <Check className='h-4 w-4 text-text-accent' /> + <Check className="h-4 w-4 text-text-accent" /> ) } </div> diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts b/web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts index 8ed942d8d6..39f6a9dc5c 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts +++ b/web/app/components/workflow/note-node/note-editor/toolbar/hooks.ts @@ -1,8 +1,15 @@ import { - useCallback, - useEffect, - useState, -} from 'react' + $isLinkNode, + TOGGLE_LINK_COMMAND, +} from '@lexical/link' +import { INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { + $getSelectionStyleValueForProperty, + $patchStyleText, + $setBlocksType, +} from '@lexical/selection' +import { mergeRegister } from '@lexical/utils' import { $createParagraphNode, $getSelection, @@ -13,17 +20,10 @@ import { SELECTION_CHANGE_COMMAND, } from 'lexical' import { - $getSelectionStyleValueForProperty, - $patchStyleText, - $setBlocksType, -} from '@lexical/selection' -import { INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list' -import { mergeRegister } from '@lexical/utils' -import { - $isLinkNode, - TOGGLE_LINK_COMMAND, -} from '@lexical/link' -import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' + useCallback, + useEffect, + useState, +} from 'react' import { useNoteEditorStore } from '../store' import { getSelectedNode } from '../utils' diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/index.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/index.tsx index fd2613d79d..c7b8fa9787 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/index.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/index.tsx @@ -1,10 +1,10 @@ -import { memo } from 'react' -import Divider from './divider' import type { ColorPickerProps } from './color-picker' -import ColorPicker from './color-picker' -import FontSizeSelector from './font-size-selector' -import Command from './command' import type { OperatorProps } from './operator' +import { memo } from 'react' +import ColorPicker from './color-picker' +import Command from './command' +import Divider from './divider' +import FontSizeSelector from './font-size-selector' import Operator from './operator' type ToolbarProps = ColorPickerProps & OperatorProps @@ -18,7 +18,7 @@ const Toolbar = ({ onShowAuthorChange, }: ToolbarProps) => { return ( - <div className='inline-flex items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-sm'> + <div className="inline-flex items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-sm"> <ColorPicker theme={theme} onThemeChange={onThemeChange} @@ -26,12 +26,12 @@ const Toolbar = ({ <Divider /> <FontSizeSelector /> <Divider /> - <div className='flex items-center space-x-0.5'> - <Command type='bold' /> - <Command type='italic' /> - <Command type='strikethrough' /> - <Command type='link' /> - <Command type='bullet' /> + <div className="flex items-center space-x-0.5"> + <Command type="bold" /> + <Command type="italic" /> + <Command type="strikethrough" /> + <Command type="link" /> + <Command type="bullet" /> </div> <Divider /> <Operator diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx index 8ba0ad4caf..643a7240f8 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx @@ -1,17 +1,17 @@ +import { RiMoreFill } from '@remixicon/react' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiMoreFill } from '@remixicon/react' -import { cn } from '@/utils/classnames' -import ShortcutsName from '@/app/components/workflow/shortcuts-name' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import Switch from '@/app/components/base/switch' +import ShortcutsName from '@/app/components/workflow/shortcuts-name' +import { cn } from '@/utils/classnames' export type OperatorProps = { onCopy: () => void @@ -34,7 +34,7 @@ const Operator = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom-end' + placement="bottom-end" offset={4} > <PortalToFollowElemTrigger onClick={() => setOpen(!open)}> @@ -44,14 +44,14 @@ const Operator = ({ open && 'bg-state-base-hover text-text-secondary', )} > - <RiMoreFill className='h-4 w-4' /> + <RiMoreFill className="h-4 w-4" /> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent> - <div className='min-w-[192px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl'> - <div className='p-1'> + <div className="min-w-[192px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl"> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => { onCopy() setOpen(false) @@ -61,7 +61,7 @@ const Operator = ({ <ShortcutsName keys={['ctrl', 'c']} /> </div> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => { onDuplicate() setOpen(false) @@ -71,24 +71,24 @@ const Operator = ({ <ShortcutsName keys={['ctrl', 'd']} /> </div> </div> - <div className='h-px bg-divider-subtle'></div> - <div className='p-1'> + <div className="h-px bg-divider-subtle"></div> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={e => e.stopPropagation()} > <div>{t('workflow.nodes.note.editor.showAuthor')}</div> <Switch - size='l' + size="l" defaultValue={showAuthor} onChange={onShowAuthorChange} /> </div> </div> - <div className='h-px bg-divider-subtle'></div> - <div className='p-1'> + <div className="h-px bg-divider-subtle"></div> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive' + className="flex h-8 cursor-pointer items-center justify-between rounded-md px-3 text-sm text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive" onClick={() => { onDelete() setOpen(false) diff --git a/web/app/components/workflow/note-node/note-editor/utils.ts b/web/app/components/workflow/note-node/note-editor/utils.ts index c241e93bf1..dff98a8301 100644 --- a/web/app/components/workflow/note-node/note-editor/utils.ts +++ b/web/app/components/workflow/note-node/note-editor/utils.ts @@ -1,5 +1,5 @@ -import { $isAtNodeEnd } from '@lexical/selection' import type { ElementNode, RangeSelection, TextNode } from 'lexical' +import { $isAtNodeEnd } from '@lexical/selection' export function getSelectedNode( selection: RangeSelection, @@ -19,4 +19,4 @@ export function getSelectedNode( } // eslint-disable-next-line sonarjs/empty-string-repetition -export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/ +export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-]*)?\??[-+=&;%@.\w]*#?\w*)?)/ diff --git a/web/app/components/workflow/operator/add-block.tsx b/web/app/components/workflow/operator/add-block.tsx index d1c69ba63d..f52e440c98 100644 --- a/web/app/components/workflow/operator/add-block.tsx +++ b/web/app/components/workflow/operator/add-block.tsx @@ -1,16 +1,21 @@ +import type { OffsetOptions } from '@floating-ui/react' +import type { + OnSelectBlock, +} from '@/app/components/workflow/types' +import { RiAddCircleFill } from '@remixicon/react' import { memo, useCallback, useState, } from 'react' -import { RiAddCircleFill } from '@remixicon/react' -import { useStoreApi } from 'reactflow' import { useTranslation } from 'react-i18next' -import type { OffsetOptions } from '@floating-ui/react' +import { useStoreApi } from 'reactflow' +import BlockSelector from '@/app/components/workflow/block-selector' import { - generateNewNode, - getNodeCustomTypeByNodeDataType, -} from '../utils' + BlockEnum, +} from '@/app/components/workflow/types' +import { FlowType } from '@/types/common' +import { cn } from '@/utils/classnames' import { useAvailableBlocks, useIsChatMode, @@ -20,16 +25,11 @@ import { } from '../hooks' import { useHooksStore } from '../hooks-store' import { useWorkflowStore } from '../store' -import TipPopup from './tip-popup' -import { cn } from '@/utils/classnames' -import BlockSelector from '@/app/components/workflow/block-selector' -import type { - OnSelectBlock, -} from '@/app/components/workflow/types' import { - BlockEnum, -} from '@/app/components/workflow/types' -import { FlowType } from '@/types/common' + generateNewNode, + getNodeCustomTypeByNodeDataType, +} from '../utils' +import TipPopup from './tip-popup' type AddBlockProps = { renderTrigger?: (open: boolean) => React.ReactNode @@ -93,8 +93,9 @@ const AddBlock = ({ 'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, open && 'bg-state-accent-active text-text-accent', - )}> - <RiAddCircleFill className='h-4 w-4' /> + )} + > + <RiAddCircleFill className="h-4 w-4" /> </div> </TipPopup> ) @@ -106,13 +107,13 @@ const AddBlock = ({ onOpenChange={handleOpenChange} disabled={nodesReadOnly} onSelect={handleSelect} - placement='right-start' + placement="right-start" offset={offset ?? { mainAxis: 4, crossAxis: -8, }} trigger={renderTrigger || renderTriggerElement} - popupClassName='!min-w-[256px]' + popupClassName="!min-w-[256px]" availableBlocksTypes={availableNextBlocks} showStartTab={showStartTab} /> diff --git a/web/app/components/workflow/operator/control.tsx b/web/app/components/workflow/operator/control.tsx index 636f83bb2d..394ccbe3dd 100644 --- a/web/app/components/workflow/operator/control.tsx +++ b/web/app/components/workflow/operator/control.tsx @@ -1,8 +1,4 @@ import type { MouseEvent } from 'react' -import { - memo, -} from 'react' -import { useTranslation } from 'react-i18next' import { RiAspectRatioFill, RiAspectRatioLine, @@ -11,22 +7,26 @@ import { RiHand, RiStickyNoteAddLine, } from '@remixicon/react' +import { + memo, +} from 'react' +import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' +import Divider from '../../base/divider' import { useNodesReadOnly, useWorkflowCanvasMaximize, useWorkflowMoveMode, useWorkflowOrganize, } from '../hooks' +import { useStore } from '../store' import { ControlMode, } from '../types' -import { useStore } from '../store' -import Divider from '../../base/divider' import AddBlock from './add-block' -import TipPopup from './tip-popup' -import MoreActions from './more-actions' import { useOperator } from './hooks' -import { cn } from '@/utils/classnames' +import MoreActions from './more-actions' +import TipPopup from './tip-popup' const Control = () => { const { t } = useTranslation() @@ -50,7 +50,7 @@ const Control = () => { } return ( - <div className='pointer-events-auto flex flex-col items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 text-text-tertiary shadow-lg'> + <div className="pointer-events-auto flex flex-col items-center rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 text-text-tertiary shadow-lg"> <AddBlock /> <TipPopup title={t('workflow.nodes.note.addNote')}> <div @@ -60,10 +60,10 @@ const Control = () => { )} onClick={addNote} > - <RiStickyNoteAddLine className='h-4 w-4' /> + <RiStickyNoteAddLine className="h-4 w-4" /> </div> </TipPopup> - <Divider className='my-1 w-3.5' /> + <Divider className="my-1 w-3.5" /> <TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}> <div className={cn( @@ -73,7 +73,7 @@ const Control = () => { )} onClick={handleModePointer} > - <RiCursorLine className='h-4 w-4' /> + <RiCursorLine className="h-4 w-4" /> </div> </TipPopup> <TipPopup title={t('workflow.common.handMode')} shortcuts={['h']}> @@ -85,10 +85,10 @@ const Control = () => { )} onClick={handleModeHand} > - <RiHand className='h-4 w-4' /> + <RiHand className="h-4 w-4" /> </div> </TipPopup> - <Divider className='my-1 w-3.5' /> + <Divider className="my-1 w-3.5" /> <TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}> <div className={cn( @@ -97,7 +97,7 @@ const Control = () => { )} onClick={handleLayout} > - <RiFunctionAddLine className='h-4 w-4' /> + <RiFunctionAddLine className="h-4 w-4" /> </div> </TipPopup> <TipPopup title={maximizeCanvas ? t('workflow.panel.minimize') : t('workflow.panel.maximize')} shortcuts={['f']}> @@ -109,8 +109,8 @@ const Control = () => { )} onClick={handleToggleMaximizeCanvas} > - {maximizeCanvas && <RiAspectRatioFill className='h-4 w-4' />} - {!maximizeCanvas && <RiAspectRatioLine className='h-4 w-4' />} + {maximizeCanvas && <RiAspectRatioFill className="h-4 w-4" />} + {!maximizeCanvas && <RiAspectRatioLine className="h-4 w-4" />} </div> </TipPopup> <MoreActions /> diff --git a/web/app/components/workflow/operator/hooks.ts b/web/app/components/workflow/operator/hooks.ts index edec10bda7..23248a89a3 100644 --- a/web/app/components/workflow/operator/hooks.ts +++ b/web/app/components/workflow/operator/hooks.ts @@ -1,10 +1,10 @@ -import { useCallback } from 'react' -import { generateNewNode } from '../utils' -import { useWorkflowStore } from '../store' import type { NoteNodeType } from '../note-node/types' +import { useCallback } from 'react' +import { useAppContext } from '@/context/app-context' import { CUSTOM_NOTE_NODE } from '../note-node/constants' import { NoteTheme } from '../note-node/types' -import { useAppContext } from '@/context/app-context' +import { useWorkflowStore } from '../store' +import { generateNewNode } from '../utils' export const useOperator = () => { const workflowStore = useWorkflowStore() diff --git a/web/app/components/workflow/operator/index.tsx b/web/app/components/workflow/operator/index.tsx index b4fcf184a7..519eb1fdb7 100644 --- a/web/app/components/workflow/operator/index.tsx +++ b/web/app/components/workflow/operator/index.tsx @@ -1,11 +1,11 @@ -import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import type { Node } from 'reactflow' +import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import { MiniMap } from 'reactflow' import UndoRedo from '../header/undo-redo' -import ZoomInOut from './zoom-in-out' -import VariableTrigger from '../variable-inspect/trigger' -import VariableInspectPanel from '../variable-inspect' import { useStore } from '../store' +import VariableInspectPanel from '../variable-inspect' +import VariableTrigger from '../variable-inspect/trigger' +import ZoomInOut from './zoom-in-out' export type OperatorProps = { handleUndo: () => void @@ -51,19 +51,19 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => { return ( <div ref={bottomPanelRef} - className='absolute bottom-0 left-0 right-0 z-10 px-1' + className="absolute bottom-0 left-0 right-0 z-10 px-1" style={ { width: bottomPanelWidth, } } > - <div className='flex justify-between px-1 pb-2'> - <div className='flex items-center gap-2'> + <div className="flex justify-between px-1 pb-2"> + <div className="flex items-center gap-2"> <UndoRedo handleUndo={handleUndo} handleRedo={handleRedo} /> </div> <VariableTrigger /> - <div className='relative'> + <div className="relative"> <MiniMap pannable zoomable @@ -71,11 +71,11 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => { width: 102, height: 72, }} - maskColor='var(--color-workflow-minimap-bg)' + maskColor="var(--color-workflow-minimap-bg)" nodeClassName={getMiniMapNodeClassName} nodeStrokeWidth={3} - className='!absolute !bottom-10 z-[9] !m-0 !h-[73px] !w-[103px] !rounded-lg !border-[0.5px] - !border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5' + className="!absolute !bottom-10 z-[9] !m-0 !h-[73px] !w-[103px] !rounded-lg !border-[0.5px] + !border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5" /> <ZoomInOut /> </div> diff --git a/web/app/components/workflow/operator/more-actions.tsx b/web/app/components/workflow/operator/more-actions.tsx index 52c81612da..558af4e152 100644 --- a/web/app/components/workflow/operator/more-actions.tsx +++ b/web/app/components/workflow/operator/more-actions.tsx @@ -1,26 +1,26 @@ import type { FC } from 'react' +import { RiExportLine, RiMoreFill } from '@remixicon/react' +import { toJpeg, toPng, toSvg } from 'html-to-image' import { memo, useCallback, useMemo, useState, } from 'react' -import { useShallow } from 'zustand/react/shallow' import { useTranslation } from 'react-i18next' -import { RiExportLine, RiMoreFill } from '@remixicon/react' -import { toJpeg, toPng, toSvg } from 'html-to-image' -import { useNodesReadOnly } from '../hooks' -import TipPopup from './tip-popup' -import { cn } from '@/utils/classnames' +import { getNodesBounds, useReactFlow } from 'reactflow' +import { useShallow } from 'zustand/react/shallow' +import { useStore as useAppStore } from '@/app/components/app/store' +import ImagePreview from '@/app/components/base/image-uploader/image-preview' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { getNodesBounds, useReactFlow } from 'reactflow' -import ImagePreview from '@/app/components/base/image-uploader/image-preview' import { useStore } from '@/app/components/workflow/store' -import { useStore as useAppStore } from '@/app/components/app/store' +import { cn } from '@/utils/classnames' +import { useNodesReadOnly } from '../hooks' +import TipPopup from './tip-popup' const MoreActions: FC = () => { const { t } = useTranslation() @@ -38,7 +38,8 @@ const MoreActions: FC = () => { }))) const crossAxisOffset = useMemo(() => { - if (maximizeCanvas) return 40 + if (maximizeCanvas) + return 40 return appSidebarExpand === 'expand' ? 188 : 40 }, [appSidebarExpand, maximizeCanvas]) @@ -51,7 +52,8 @@ const MoreActions: FC = () => { setOpen(false) const flowElement = document.querySelector('.react-flow__viewport') as HTMLElement - if (!flowElement) return + if (!flowElement) + return try { let filename = appName || knowledgeName @@ -197,58 +199,58 @@ const MoreActions: FC = () => { )} onClick={handleTrigger} > - <RiMoreFill className='h-4 w-4' /> + <RiMoreFill className="h-4 w-4" /> </div> </TipPopup> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='min-w-[180px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'> - <div className='p-1'> - <div className='flex items-center gap-2 px-2 py-1 text-xs font-medium text-text-tertiary'> - <RiExportLine className='h-3 w-3' /> + <PortalToFollowElemContent className="z-10"> + <div className="min-w-[180px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg"> + <div className="p-1"> + <div className="flex items-center gap-2 px-2 py-1 text-xs font-medium text-text-tertiary"> + <RiExportLine className="h-3 w-3" /> {t('workflow.common.exportImage')} </div> - <div className='px-2 py-1 text-xs font-medium text-text-tertiary'> + <div className="px-2 py-1 text-xs font-medium text-text-tertiary"> {t('workflow.common.currentView')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('png')} > {t('workflow.common.exportPNG')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('jpeg')} > {t('workflow.common.exportJPEG')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('svg')} > {t('workflow.common.exportSVG')} </div> - <div className='border-border-divider mx-2 my-1 border-t' /> + <div className="border-border-divider mx-2 my-1 border-t" /> - <div className='px-2 py-1 text-xs font-medium text-text-tertiary'> + <div className="px-2 py-1 text-xs font-medium text-text-tertiary"> {t('workflow.common.currentWorkflow')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('png', true)} > {t('workflow.common.exportPNG')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('jpeg', true)} > {t('workflow.common.exportJPEG')} </div> <div - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover" onClick={() => handleExportImage('svg', true)} > {t('workflow.common.exportSVG')} diff --git a/web/app/components/workflow/operator/tip-popup.tsx b/web/app/components/workflow/operator/tip-popup.tsx index 3721ed8118..226a889359 100644 --- a/web/app/components/workflow/operator/tip-popup.tsx +++ b/web/app/components/workflow/operator/tip-popup.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' -import ShortcutsName from '../shortcuts-name' import Tooltip from '@/app/components/base/tooltip' +import ShortcutsName from '../shortcuts-name' type TipPopupProps = { title: string @@ -16,15 +16,15 @@ const TipPopup = ({ <Tooltip needsDelay={false} offset={4} - popupClassName='p-0 bg-transparent' - popupContent={ - <div className='flex items-center gap-1 rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg backdrop-blur-[5px]'> - <span className='system-xs-medium text-text-secondary'>{title}</span> + popupClassName="p-0 bg-transparent" + popupContent={( + <div className="flex items-center gap-1 rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg p-1.5 shadow-lg backdrop-blur-[5px]"> + <span className="system-xs-medium text-text-secondary">{title}</span> { shortcuts && <ShortcutsName keys={shortcuts} /> } </div> - } + )} > {children} </Tooltip> diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 703304c27c..12d603e9b8 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -1,34 +1,34 @@ import type { FC } from 'react' +import { + RiZoomInLine, + RiZoomOutLine, +} from '@remixicon/react' import { Fragment, memo, useCallback, useState, } from 'react' -import { - RiZoomInLine, - RiZoomOutLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useReactFlow, useViewport, } from 'reactflow' -import { - useNodesSyncDraft, - useWorkflowReadOnly, -} from '../hooks' - -import ShortcutsName from '../shortcuts-name' -import Divider from '../../base/divider' -import TipPopup from './tip-popup' -import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import { cn } from '@/utils/classnames' +import Divider from '../../base/divider' +import { + useNodesSyncDraft, + useWorkflowReadOnly, +} from '../hooks' +import ShortcutsName from '../shortcuts-name' +import TipPopup from './tip-popup' + enum ZoomType { zoomIn = 'zoomIn', zoomOut = 'zoomOut', @@ -121,7 +121,7 @@ const ZoomInOut: FC = () => { return ( <PortalToFollowElem - placement='top-start' + placement="top-start" open={open} onOpenChange={setOpen} offset={{ @@ -135,10 +135,12 @@ const ZoomInOut: FC = () => { p-0.5 text-[13px] shadow-lg backdrop-blur-[5px] hover:bg-state-base-hover ${workflowReadOnly && '!cursor-not-allowed opacity-50'} - `}> + `} + > <div className={cn( 'flex h-8 w-[98px] items-center justify-between rounded-lg', - )}> + )} + > <TipPopup title={t('workflow.operator.zoomOut')} shortcuts={['ctrl', '-']} @@ -153,10 +155,13 @@ const ZoomInOut: FC = () => { zoomOut() }} > - <RiZoomOutLine className='h-4 w-4 text-text-tertiary hover:text-text-secondary' /> + <RiZoomOutLine className="h-4 w-4 text-text-tertiary hover:text-text-secondary" /> </div> </TipPopup> - <div onClick={handleTrigger} className={cn('system-sm-medium w-[34px] text-text-tertiary hover:text-text-secondary')}>{Number.parseFloat(`${zoom * 100}`).toFixed(0)}%</div> + <div onClick={handleTrigger} className={cn('system-sm-medium w-[34px] text-text-tertiary hover:text-text-secondary')}> + {Number.parseFloat(`${zoom * 100}`).toFixed(0)} + % + </div> <TipPopup title={t('workflow.operator.zoomIn')} shortcuts={['ctrl', '+']} @@ -171,32 +176,32 @@ const ZoomInOut: FC = () => { zoomIn() }} > - <RiZoomInLine className='h-4 w-4 text-text-tertiary hover:text-text-secondary' /> + <RiZoomInLine className="h-4 w-4 text-text-tertiary hover:text-text-secondary" /> </div> </TipPopup> </div> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[145px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]'> + <PortalToFollowElemContent className="z-10"> + <div className="w-[145px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]"> { ZOOM_IN_OUT_OPTIONS.map((options, i) => ( <Fragment key={i}> { i !== 0 && ( - <Divider className='m-0' /> + <Divider className="m-0" /> ) } - <div className='p-1'> + <div className="p-1"> { options.map(option => ( <div key={option.key} - className='system-md-regular flex h-8 cursor-pointer items-center justify-between space-x-1 rounded-lg py-1.5 pl-3 pr-2 text-text-secondary hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center justify-between space-x-1 rounded-lg py-1.5 pl-3 pr-2 text-text-secondary hover:bg-state-base-hover" onClick={() => handleZoom(option.key)} > <span>{option.text}</span> - <div className='flex items-center space-x-0.5'> + <div className="flex items-center space-x-0.5"> { option.key === ZoomType.zoomToFit && ( <ShortcutsName keys={['ctrl', '1']} /> diff --git a/web/app/components/workflow/panel-contextmenu.tsx b/web/app/components/workflow/panel-contextmenu.tsx index 8d0811f853..1ae2653d83 100644 --- a/web/app/components/workflow/panel-contextmenu.tsx +++ b/web/app/components/workflow/panel-contextmenu.tsx @@ -1,13 +1,12 @@ +import { useClickAway } from 'ahooks' import { memo, useEffect, useRef, } from 'react' import { useTranslation } from 'react-i18next' -import { useClickAway } from 'ahooks' +import { cn } from '@/utils/classnames' import Divider from '../base/divider' -import ShortcutsName from './shortcuts-name' -import { useStore } from './store' import { useDSL, useNodesInteractions, @@ -16,7 +15,8 @@ import { } from './hooks' import AddBlock from './operator/add-block' import { useOperator } from './operator/hooks' -import { cn } from '@/utils/classnames' +import ShortcutsName from './shortcuts-name' +import { useStore } from './store' const PanelContextmenu = () => { const { t } = useTranslation() @@ -42,7 +42,7 @@ const PanelContextmenu = () => { const renderTrigger = () => { return ( <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" > {t('workflow.common.addBlock')} </div> @@ -54,14 +54,14 @@ const PanelContextmenu = () => { return ( <div - className='absolute z-[9] w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg' + className="absolute z-[9] w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg" style={{ left: panelMenu.left, top: panelMenu.top, }} ref={ref} > - <div className='p-1'> + <div className="p-1"> <AddBlock renderTrigger={renderTrigger} offset={{ @@ -70,7 +70,7 @@ const PanelContextmenu = () => { }} /> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={(e) => { e.stopPropagation() handleAddNote() @@ -80,7 +80,7 @@ const PanelContextmenu = () => { {t('workflow.nodes.note.addNote')} </div> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => { handleStartWorkflowRun() handlePaneContextmenuCancel() @@ -90,8 +90,8 @@ const PanelContextmenu = () => { <ShortcutsName keys={['alt', 'r']} /> </div> </div> - <Divider className='m-0' /> - <div className='p-1'> + <Divider className="m-0" /> + <div className="p-1"> <div className={cn( 'flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary', @@ -108,16 +108,16 @@ const PanelContextmenu = () => { <ShortcutsName keys={['ctrl', 'v']} /> </div> </div> - <Divider className='m-0' /> - <div className='p-1'> + <Divider className="m-0" /> + <div className="p-1"> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => exportCheck?.()} > {t('app.export')} </div> <div - className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => setShowImportDSLModal(true)} > {t('workflow.common.importDSL')} diff --git a/web/app/components/workflow/panel/chat-record/index.tsx b/web/app/components/workflow/panel/chat-record/index.tsx index 5ab3b45340..6afe463c30 100644 --- a/web/app/components/workflow/panel/chat-record/index.tsx +++ b/web/app/components/workflow/panel/chat-record/index.tsx @@ -1,25 +1,25 @@ +import type { IChatItem } from '@/app/components/base/chat/chat/type' +import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' +import { RiCloseLine } from '@remixicon/react' import { memo, useCallback, useEffect, useState, } from 'react' -import { RiCloseLine } from '@remixicon/react' +import { useStore as useAppStore } from '@/app/components/app/store' +import Chat from '@/app/components/base/chat/chat' +import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' +import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' +import Loading from '@/app/components/base/loading' +import { fetchConversationMessages } from '@/service/debug' +import { useWorkflowRun } from '../../hooks' import { useStore, useWorkflowStore, } from '../../store' -import { useWorkflowRun } from '../../hooks' import { formatWorkflowRunIdentifier } from '../../utils' import UserInput from './user-input' -import Chat from '@/app/components/base/chat/chat' -import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' -import { fetchConversationMessages } from '@/service/debug' -import { useStore as useAppStore } from '@/app/components/app/store' -import Loading from '@/app/components/base/loading' -import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' -import type { IChatItem } from '@/app/components/base/chat/chat/type' -import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' function getFormattedChatList(messages: any[]) { const res: ChatItem[] = [] @@ -87,48 +87,48 @@ const ChatRecord = () => { return ( <div - className='flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-chatbot-bg shadow-xl' + className="flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-chatbot-bg shadow-xl" // style={{ // background: 'linear-gradient(156deg, rgba(242, 244, 247, 0.80) 0%, rgba(242, 244, 247, 0.00) 99.43%), var(--white, #FFF)', // }} > {!fetched && ( - <div className='flex h-full items-center justify-center'> + <div className="flex h-full items-center justify-center"> <Loading /> </div> )} {fetched && ( <> - <div className='flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> + <div className="flex shrink-0 items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary"> {`TEST CHAT${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`} <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={() => { handleLoadBackupDraft() workflowStore.setState({ historyWorkflowData: undefined }) }} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> - <div className='h-0 grow'> + <div className="h-0 grow"> <Chat config={{ supportCitationHitInfo: true, questionEditEnable: false, } as any} chatList={threadChatItems} - chatContainerClassName='px-3' - chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto' - chatFooterClassName='px-4 rounded-b-2xl' - chatFooterInnerClassName='pb-4 w-full max-w-full mx-auto' + chatContainerClassName="px-3" + chatContainerInnerClassName="pt-6 w-full max-w-full mx-auto" + chatFooterClassName="px-4 rounded-b-2xl" + chatFooterInnerClassName="pb-4 w-full max-w-full mx-auto" chatNode={<UserInput />} noChatInput allToolIcons={{}} showPromptLog switchSibling={switchSibling} noSpacing - chatAnswerContainerInner='!pr-2' + chatAnswerContainerInner="!pr-2" /> </div> </> diff --git a/web/app/components/workflow/panel/chat-record/user-input.tsx b/web/app/components/workflow/panel/chat-record/user-input.tsx index 7b90435928..ecc54a5e54 100644 --- a/web/app/components/workflow/panel/chat-record/user-input.tsx +++ b/web/app/components/workflow/panel/chat-record/user-input.tsx @@ -1,9 +1,9 @@ +import { RiArrowDownSLine } from '@remixicon/react' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { RiArrowDownSLine } from '@remixicon/react' const UserInput = () => { const { t } = useTranslation() @@ -32,15 +32,15 @@ const UserInput = () => { /> {t('workflow.panel.userInputField').toLocaleUpperCase()} </div> - <div className='px-2 pb-3 pt-1'> + <div className="px-2 pb-3 pt-1"> { expanded && ( - <div className='py-2 text-[13px] text-text-primary'> + <div className="py-2 text-[13px] text-text-primary"> { variables.map((variable: any) => ( <div key={variable.variable} - className='mb-2 last-of-type:mb-0' + className="mb-2 last-of-type:mb-0" > </div> )) diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx index 810d5ba5d5..3d22437830 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { useTranslation } from 'react-i18next' import { RiAddLine } from '@remixicon/react' import { produce } from 'immer' -import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import BoolValue from './bool-value' +import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' import { cn } from '@/utils/classnames' +import BoolValue from './bool-value' type Props = { className?: string @@ -50,20 +50,20 @@ const ArrayValueList: FC<Props> = ({ return ( <div className={cn('w-full space-y-2', className)}> {list.map((item, index) => ( - <div className='flex items-center space-x-1' key={index}> + <div className="flex items-center space-x-1" key={index}> <BoolValue value={item} onChange={handleChange(index)} /> <RemoveButton - className='!bg-gray-100 !p-2 hover:!bg-gray-200' + className="!bg-gray-100 !p-2 hover:!bg-gray-200" onClick={handleItemRemove(index)} /> </div> ))} - <Button variant='tertiary' className='w-full' onClick={handleItemAdd}> - <RiAddLine className='mr-1 h-4 w-4' /> + <Button variant="tertiary" className="w-full" onClick={handleItemAdd}> + <RiAddLine className="mr-1 h-4 w-4" /> <span>{t('workflow.chatVariable.modal.addArrayValue')}</span> </Button> </div> diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx index f7d0c69d89..e1025eda6a 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' -import { useTranslation } from 'react-i18next' import { RiAddLine } from '@remixicon/react' import { produce } from 'immer' -import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' +import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' type Props = { isString: boolean @@ -47,9 +47,9 @@ const ArrayValueList: FC<Props> = ({ }, [list, onChange]) return ( - <div className='w-full space-y-2'> + <div className="w-full space-y-2"> {list.map((item, index) => ( - <div className='flex items-center space-x-1' key={index}> + <div className="flex items-center space-x-1" key={index}> <Input placeholder={t('workflow.chatVariable.modal.arrayValue') || ''} value={list[index]} @@ -57,13 +57,13 @@ const ArrayValueList: FC<Props> = ({ type={isString ? 'text' : 'number'} /> <RemoveButton - className='!bg-gray-100 !p-2 hover:!bg-gray-200' + className="!bg-gray-100 !p-2 hover:!bg-gray-200" onClick={handleItemRemove(index)} /> </div> ))} - <Button variant='tertiary' className='w-full' onClick={handleItemAdd}> - <RiAddLine className='mr-1 h-4 w-4' /> + <Button variant="tertiary" className="w-full" onClick={handleItemAdd}> + <RiAddLine className="mr-1 h-4 w-4" /> <span>{t('workflow.chatVariable.modal.addArrayValue')}</span> </Button> </div> diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx index 864fefd9a2..89b309db58 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx @@ -20,15 +20,17 @@ const BoolValue: FC<Props> = ({ }, [onChange]) return ( - <div className='flex w-full space-x-1'> - <OptionCard className='grow' + <div className="flex w-full space-x-1"> + <OptionCard + className="grow" selected={booleanValue} - title='True' + title="True" onSelect={handleChange(true)} /> - <OptionCard className='grow' + <OptionCard + className="grow" selected={!booleanValue} - title='False' + title="False" onSelect={handleChange(false)} /> </div> diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx index f223aca5eb..1132434122 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' +import { produce } from 'immer' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { produce } from 'immer' import { useContext } from 'use-context-selector' import { ToastContext } from '@/app/components/base/toast' -import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select' import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' +import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' type Props = { @@ -91,30 +91,30 @@ const ObjectValueItem: FC<Props> = ({ }, [handleItemAdd, index, list.length]) return ( - <div className='group flex border-t border-gray-200'> + <div className="group flex border-t border-gray-200"> {/* Key */} - <div className='w-[120px] border-r border-gray-200'> + <div className="w-[120px] border-r border-gray-200"> <input - className='system-xs-regular placeholder:system-xs-regular block h-7 w-full appearance-none px-2 text-text-secondary caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:bg-state-base-hover focus:bg-components-input-bg-active' + className="system-xs-regular placeholder:system-xs-regular block h-7 w-full appearance-none px-2 text-text-secondary caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:bg-state-base-hover focus:bg-components-input-bg-active" placeholder={t('workflow.chatVariable.modal.objectKey') || ''} value={list[index].key} onChange={handleKeyChange(index)} /> </div> {/* Type */} - <div className='w-[96px] border-r border-gray-200'> + <div className="w-[96px] border-r border-gray-200"> <VariableTypeSelector inCell value={list[index].type} list={typeList} onSelect={handleTypeChange(index)} - popupClassName='w-[120px]' + popupClassName="w-[120px]" /> </div> {/* Value */} - <div className='relative w-[230px]'> + <div className="relative w-[230px]"> <input - className='system-xs-regular placeholder:system-xs-regular block h-7 w-full appearance-none px-2 text-text-secondary caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:bg-state-base-hover focus:bg-components-input-bg-active' + className="system-xs-regular placeholder:system-xs-regular block h-7 w-full appearance-none px-2 text-text-secondary caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:bg-state-base-hover focus:bg-components-input-bg-active" placeholder={t('workflow.chatVariable.modal.objectValue') || ''} value={list[index].value} onChange={handleValueChange(index)} @@ -124,7 +124,7 @@ const ObjectValueItem: FC<Props> = ({ /> {list.length > 1 && !isFocus && ( <RemoveButton - className='absolute right-1 top-0.5 z-10 hidden group-hover:block' + className="absolute right-1 top-0.5 z-10 hidden group-hover:block" onClick={handleItemRemove(index)} /> )} diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx index 830cf94f69..0e39bfcfcc 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx @@ -16,11 +16,11 @@ const ObjectValueList: FC<Props> = ({ const { t } = useTranslation() return ( - <div className='w-full overflow-hidden rounded-lg border border-gray-200'> - <div className='system-xs-medium flex h-7 items-center uppercase text-text-tertiary'> - <div className='flex h-full w-[120px] items-center border-r border-gray-200 pl-2'>{t('workflow.chatVariable.modal.objectKey')}</div> - <div className='flex h-full w-[96px] items-center border-r border-gray-200 pl-2'>{t('workflow.chatVariable.modal.objectType')}</div> - <div className='flex h-full w-[230px] items-center pl-2 pr-1'>{t('workflow.chatVariable.modal.objectValue')}</div> + <div className="w-full overflow-hidden rounded-lg border border-gray-200"> + <div className="system-xs-medium flex h-7 items-center uppercase text-text-tertiary"> + <div className="flex h-full w-[120px] items-center border-r border-gray-200 pl-2">{t('workflow.chatVariable.modal.objectKey')}</div> + <div className="flex h-full w-[96px] items-center border-r border-gray-200 pl-2">{t('workflow.chatVariable.modal.objectType')}</div> + <div className="flex h-full w-[230px] items-center pl-2 pr-1">{t('workflow.chatVariable.modal.objectValue')}</div> </div> {list.map((item, index) => ( <ObjectValueItem diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx index 54e8fc4f6c..a43179026e 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx @@ -1,8 +1,8 @@ -import { memo, useState } from 'react' -import { capitalize } from 'lodash-es' -import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' import type { ConversationVariable } from '@/app/components/workflow/types' +import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' +import { capitalize } from 'lodash-es' +import { memo, useState } from 'react' +import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' import { cn } from '@/utils/classnames' type VariableItemProps = { @@ -21,27 +21,28 @@ const VariableItem = ({ <div className={cn( 'radius-md mb-1 border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 py-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover', destructive && 'border-state-destructive-border hover:bg-state-destructive-hover', - )}> - <div className='flex items-center justify-between'> - <div className='flex grow items-center gap-1'> - <BubbleX className='h-4 w-4 text-util-colors-teal-teal-700' /> - <div className='system-sm-medium text-text-primary'>{item.name}</div> - <div className='system-xs-medium text-text-tertiary'>{capitalize(item.value_type)}</div> + )} + > + <div className="flex items-center justify-between"> + <div className="flex grow items-center gap-1"> + <BubbleX className="h-4 w-4 text-util-colors-teal-teal-700" /> + <div className="system-sm-medium text-text-primary">{item.name}</div> + <div className="system-xs-medium text-text-tertiary">{capitalize(item.value_type)}</div> </div> - <div className='flex shrink-0 items-center gap-1 text-text-tertiary'> - <div className='radius-md cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary'> - <RiEditLine className='h-4 w-4' onClick={() => onEdit(item)}/> + <div className="flex shrink-0 items-center gap-1 text-text-tertiary"> + <div className="radius-md cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary"> + <RiEditLine className="h-4 w-4" onClick={() => onEdit(item)} /> </div> <div - className='radius-md cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive' + className="radius-md cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive" onMouseOver={() => setDestructive(true)} onMouseOut={() => setDestructive(false)} > - <RiDeleteBinLine className='h-4 w-4' onClick={() => onDelete(item)}/> + <RiDeleteBinLine className="h-4 w-4" onClick={() => onDelete(item)} /> </div> </div> </div> - <div className='system-xs-regular truncate text-text-tertiary'>{item.description}</div> + <div className="system-xs-regular truncate text-text-tertiary">{item.description}</div> </div> ) } diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx index 9d19b61093..1fe4e5fe5a 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx @@ -1,15 +1,15 @@ 'use client' +import type { ConversationVariable } from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' import Button from '@/app/components/base/button' -import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { ConversationVariable } from '@/app/components/workflow/types' +import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal' type Props = { open: boolean @@ -38,7 +38,7 @@ const VariableModalTrigger = ({ if (open) onClose() }} - placement='left-start' + placement="left-start" offset={{ mainAxis: 8, alignmentAxis: showTip ? -278 : -48, @@ -48,13 +48,14 @@ const VariableModalTrigger = ({ setOpen(v => !v) if (open) onClose() - }}> - <Button variant='primary'> - <RiAddLine className='mr-1 h-4 w-4' /> - <span className='system-sm-medium'>{t('workflow.chatVariable.button')}</span> + }} + > + <Button variant="primary"> + <RiAddLine className="mr-1 h-4 w-4" /> + <span className="system-sm-medium">{t('workflow.chatVariable.button')}</span> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[11]'> + <PortalToFollowElemContent className="z-[11]"> <VariableModal chatVar={chatVar} onSave={onSave} diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx index 15f8a081fb..e30da0fff3 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx @@ -1,23 +1,19 @@ +import type { ConversationVariable } from '@/app/components/workflow/types' +import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react' import React, { useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { v4 as uuid4 } from 'uuid' -import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react' -import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select' -import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list' -import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item' -import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { ToastContext } from '@/app/components/base/toast' -import { useStore } from '@/app/components/workflow/store' -import type { ConversationVariable } from '@/app/components/workflow/types' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list' +import { DEFAULT_OBJECT_VALUE } from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item' +import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list' +import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' -import { cn } from '@/utils/classnames' -import BoolValue from './bool-value' -import ArrayBoolList from './array-bool-list' import { arrayBoolPlaceholder, arrayNumberPlaceholder, @@ -25,7 +21,11 @@ import { arrayStringPlaceholder, objectPlaceholder, } from '@/app/components/workflow/panel/chat-variable-panel/utils' +import { useStore } from '@/app/components/workflow/store' +import { cn } from '@/utils/classnames' import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' +import ArrayBoolList from './array-bool-list' +import BoolValue from './bool-value' export type ModalPropsType = { chatVar?: ConversationVariable @@ -147,7 +147,7 @@ const ChatVariableModal = ({ setEditInJSON(true) if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object) setEditInJSON(false) - if(v === ChatVarType.Boolean) + if (v === ChatVarType.Boolean) setValue(false) if (v === ChatVarType.ArrayBoolean) setValue([false]) @@ -197,8 +197,8 @@ const ChatVariableModal = ({ } } - if(type === ChatVarType.ArrayBoolean) { - if(editInJSON) + if (type === ChatVarType.ArrayBoolean) { + if (editInJSON) setEditorContent(JSON.stringify(value.map((item: boolean) => item ? 'True' : 'False'))) } setEditInJSON(editInJSON) @@ -213,7 +213,7 @@ const ChatVariableModal = ({ setEditorContent(content) try { let newValue = JSON.parse(content) - if(type === ChatVarType.ArrayBoolean) { + if (type === ChatVarType.ArrayBoolean) { newValue = newValue.map((item: string | boolean) => { if (item === 'True' || item === 'true' || item === true) return true @@ -271,75 +271,75 @@ const ChatVariableModal = ({ <div className={cn('flex h-full w-[360px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl', type === ChatVarType.Object && 'w-[480px]')} > - <div className='system-xl-semibold mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary'> + <div className="system-xl-semibold mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary"> {!chatVar ? t('workflow.chatVariable.modal.title') : t('workflow.chatVariable.modal.editTitle')} - <div className='flex items-center'> + <div className="flex items-center"> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={onClose} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='max-h-[480px] overflow-y-auto px-4 py-2'> + <div className="max-h-[480px] overflow-y-auto px-4 py-2"> {/* name */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.name')}</div> - <div className='flex'> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.chatVariable.modal.name')}</div> + <div className="flex"> <Input placeholder={t('workflow.chatVariable.modal.namePlaceholder') || ''} value={name} onChange={handleVarNameChange} onBlur={e => checkVariableName(e.target.value)} - type='text' + type="text" /> </div> </div> {/* type */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.type')}</div> - <div className='flex'> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.chatVariable.modal.type')}</div> + <div className="flex"> <VariableTypeSelector value={type} list={typeList} onSelect={handleTypeChange} - popupClassName='w-[327px]' + popupClassName="w-[327px]" /> </div> </div> {/* default value */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary"> <div>{t('workflow.chatVariable.modal.value')}</div> {(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber || type === ChatVarType.ArrayBoolean) && ( <Button - variant='ghost' - size='small' - className='text-text-tertiary' + variant="ghost" + size="small" + className="text-text-tertiary" onClick={() => handleEditorChange(!editInJSON)} > - {editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />} + {editInJSON ? <RiInputField className="mr-1 h-3.5 w-3.5" /> : <RiDraftLine className="mr-1 h-3.5 w-3.5" />} {editInJSON ? t('workflow.chatVariable.modal.oneByOne') : t('workflow.chatVariable.modal.editInJSON')} </Button> )} {type === ChatVarType.Object && ( <Button - variant='ghost' - size='small' - className='text-text-tertiary' + variant="ghost" + size="small" + className="text-text-tertiary" onClick={() => handleEditorChange(!editInJSON)} > - {editInJSON ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />} + {editInJSON ? <RiInputField className="mr-1 h-3.5 w-3.5" /> : <RiDraftLine className="mr-1 h-3.5 w-3.5" />} {editInJSON ? t('workflow.chatVariable.modal.editInForm') : t('workflow.chatVariable.modal.editInJSON')} </Button> )} </div> - <div className='flex'> + <div className="flex"> {type === ChatVarType.String && ( // Input will remove \n\r, so use Textarea just like description area <textarea - className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' + className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" value={value} placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''} onChange={e => setValue(e.target.value)} @@ -350,7 +350,7 @@ const ChatVariableModal = ({ placeholder={t('workflow.chatVariable.modal.valuePlaceholder') || ''} value={value} onChange={e => setValue(Number(e.target.value))} - type='number' + type="number" /> )} {type === ChatVarType.Boolean && ( @@ -387,13 +387,13 @@ const ChatVariableModal = ({ )} {editInJSON && ( - <div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}> + <div className="w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1" style={{ height: editorMinHeight }}> <CodeEditor isExpand noWrapper language={CodeLanguage.json} value={editorContent} - placeholder={<div className='whitespace-pre'>{placeholder}</div>} + placeholder={<div className="whitespace-pre">{placeholder}</div>} onChange={handleEditorValueChange} /> </div> @@ -401,11 +401,11 @@ const ChatVariableModal = ({ </div> </div> {/* description */} - <div className=''> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.chatVariable.modal.description')}</div> - <div className='flex'> + <div className=""> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.chatVariable.modal.description')}</div> + <div className="flex"> <textarea - className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' + className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" value={description} placeholder={t('workflow.chatVariable.modal.descriptionPlaceholder') || ''} onChange={e => setDescription(e.target.value)} @@ -413,10 +413,10 @@ const ChatVariableModal = ({ </div> </div> </div> - <div className='flex flex-row-reverse rounded-b-2xl p-4 pt-2'> - <div className='flex gap-2'> + <div className="flex flex-row-reverse rounded-b-2xl p-4 pt-2"> + <div className="flex gap-2"> <Button onClick={onClose}>{t('common.operation.cancel')}</Button> - <Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button> + <Button variant="primary" onClick={handleSave}>{t('common.operation.save')}</Button> </div> </div> </div> diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx index 360775a06c..1922374941 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState } from 'react' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' +import React, { useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, @@ -29,32 +29,40 @@ const VariableTypeSelector = ({ <PortalToFollowElem open={open} onOpenChange={() => setOpen(v => !v)} - placement='bottom' + placement="bottom" > - <PortalToFollowElemTrigger className='w-full' onClick={() => setOpen(v => !v)}> + <PortalToFollowElemTrigger className="w-full" onClick={() => setOpen(v => !v)}> <div className={cn( 'flex w-full cursor-pointer items-center px-2', !inCell && 'radius-md bg-components-input-bg-normal py-1 hover:bg-state-base-hover-alt', inCell && 'py-0.5 hover:bg-state-base-hover', open && !inCell && 'bg-state-base-hover-alt hover:bg-state-base-hover-alt', open && inCell && 'bg-state-base-hover hover:bg-state-base-hover', - )}> + )} + > <div className={cn( 'system-sm-regular grow truncate p-1 text-components-input-text-filled', inCell && 'system-xs-regular text-text-secondary', - )}>{value}</div> - <RiArrowDownSLine className='ml-0.5 h-4 w-4 text-text-quaternary' /> + )} + > + {value} + </div> + <RiArrowDownSLine className="ml-0.5 h-4 w-4 text-text-quaternary" /> </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent className={cn('z-[11] w-full', popupClassName)}> - <div className='radius-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <div className="radius-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> {list.map((item: any) => ( - <div key={item} className='radius-md flex cursor-pointer items-center gap-2 py-[6px] pl-3 pr-2 hover:bg-state-base-hover' onClick={() => { - onSelect(item) - setOpen(false) - }}> - <div className='system-md-regular grow truncate text-text-secondary'>{item}</div> - {value === item && <RiCheckLine className='h-4 w-4 text-text-accent' />} + <div + key={item} + className="radius-md flex cursor-pointer items-center gap-2 py-[6px] pl-3 pr-2 hover:bg-state-base-hover" + onClick={() => { + onSelect(item) + setOpen(false) + }} + > + <div className="system-md-regular grow truncate text-text-secondary">{item}</div> + {value === item && <RiCheckLine className="h-4 w-4 text-text-accent" />} </div> ))} </div> diff --git a/web/app/components/workflow/panel/chat-variable-panel/index.tsx b/web/app/components/workflow/panel/chat-variable-panel/index.tsx index b075cabf5d..e0396d4a68 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/index.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/index.tsx @@ -1,25 +1,25 @@ +import type { + ConversationVariable, +} from '@/app/components/workflow/types' +import { RiBookOpenLine, RiCloseLine } from '@remixicon/react' import { memo, useCallback, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { useStoreApi, } from 'reactflow' -import { RiBookOpenLine, RiCloseLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useStore } from '@/app/components/workflow/store' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import { BubbleX, LongArrowLeft, LongArrowRight } from '@/app/components/base/icons/src/vender/line/others' import BlockIcon from '@/app/components/workflow/block-icon' -import VariableModalTrigger from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger' -import VariableItem from '@/app/components/workflow/panel/chat-variable-panel/components/variable-item' -import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm' -import type { - ConversationVariable, -} from '@/app/components/workflow/types' -import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft' +import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm' +import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import VariableItem from '@/app/components/workflow/panel/chat-variable-panel/components/variable-item' +import VariableModalTrigger from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger' +import { useStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { useDocLink } from '@/context/i18n' import { cn } from '@/utils/classnames' @@ -128,64 +128,68 @@ const ChatVariablePanel = () => { 'relative flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-components-panel-bg-alt', )} > - <div className='system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary'> + <div className="system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary"> {t('workflow.chatVariable.panelTitle')} - <div className='flex items-center gap-1'> + <div className="flex items-center gap-1"> <ActionButton state={showTip ? ActionButtonState.Active : undefined} onClick={() => setShowTip(!showTip)}> - <RiBookOpenLine className='h-4 w-4' /> + <RiBookOpenLine className="h-4 w-4" /> </ActionButton> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={() => setShowChatVariablePanel(false)} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> {showTip && ( - <div className='shrink-0 px-3 pb-2 pt-2.5'> - <div className='radius-2xl relative bg-background-section-burn p-3'> - <div className='system-2xs-medium-uppercase inline-block rounded-[5px] border border-divider-deep px-[5px] py-[3px] text-text-tertiary'>TIPS</div> - <div className='system-sm-regular mb-4 mt-1 text-text-secondary'> + <div className="shrink-0 px-3 pb-2 pt-2.5"> + <div className="radius-2xl relative bg-background-section-burn p-3"> + <div className="system-2xs-medium-uppercase inline-block rounded-[5px] border border-divider-deep px-[5px] py-[3px] text-text-tertiary">TIPS</div> + <div className="system-sm-regular mb-4 mt-1 text-text-secondary"> {t('workflow.chatVariable.panelDescription')} - <a target='_blank' rel='noopener noreferrer' className='text-text-accent' + <a + target="_blank" + rel="noopener noreferrer" + className="text-text-accent" href={docLink('/guides/workflow/variables#conversation-variables', { 'zh-Hans': '/guides/workflow/variables#会话变量', 'ja-JP': '/guides/workflow/variables#会話変数', - })}> + })} + > {t('workflow.chatVariable.docLink')} </a> </div> - <div className='flex items-center gap-2'> - <div className='radius-lg flex flex-col border border-workflow-block-border bg-workflow-block-bg p-3 pb-4 shadow-md'> - <BubbleX className='mb-1 h-4 w-4 shrink-0 text-util-colors-teal-teal-700' /> - <div className='system-xs-semibold text-text-secondary'>conversation_var</div> - <div className='system-2xs-regular text-text-tertiary'>String</div> + <div className="flex items-center gap-2"> + <div className="radius-lg flex flex-col border border-workflow-block-border bg-workflow-block-bg p-3 pb-4 shadow-md"> + <BubbleX className="mb-1 h-4 w-4 shrink-0 text-util-colors-teal-teal-700" /> + <div className="system-xs-semibold text-text-secondary">conversation_var</div> + <div className="system-2xs-regular text-text-tertiary">String</div> </div> - <div className='grow'> - <div className='mb-2 flex items-center gap-2 py-1'> - <div className='flex h-3 w-16 shrink-0 items-center gap-1 px-1'> - <LongArrowLeft className='h-2 grow text-text-quaternary' /> - <div className='system-2xs-medium shrink-0 text-text-tertiary'>WRITE</div> + <div className="grow"> + <div className="mb-2 flex items-center gap-2 py-1"> + <div className="flex h-3 w-16 shrink-0 items-center gap-1 px-1"> + <LongArrowLeft className="h-2 grow text-text-quaternary" /> + <div className="system-2xs-medium shrink-0 text-text-tertiary">WRITE</div> </div> - <BlockIcon className='shrink-0' type={BlockEnum.Assigner} /> - <div className='system-xs-semibold grow truncate text-text-secondary'>{t('workflow.blocks.assigner')}</div> + <BlockIcon className="shrink-0" type={BlockEnum.Assigner} /> + <div className="system-xs-semibold grow truncate text-text-secondary">{t('workflow.blocks.assigner')}</div> </div> - <div className='flex items-center gap-2 py-1'> - <div className='flex h-3 w-16 shrink-0 items-center gap-1 px-1'> - <div className='system-2xs-medium shrink-0 text-text-tertiary'>READ</div> - <LongArrowRight className='h-2 grow text-text-quaternary' /> + <div className="flex items-center gap-2 py-1"> + <div className="flex h-3 w-16 shrink-0 items-center gap-1 px-1"> + <div className="system-2xs-medium shrink-0 text-text-tertiary">READ</div> + <LongArrowRight className="h-2 grow text-text-quaternary" /> </div> - <BlockIcon className='shrink-0' type={BlockEnum.LLM} /> - <div className='system-xs-semibold grow truncate text-text-secondary'>{t('workflow.blocks.llm')}</div> + <BlockIcon className="shrink-0" type={BlockEnum.LLM} /> + <div className="system-xs-semibold grow truncate text-text-secondary">{t('workflow.blocks.llm')}</div> </div> </div> </div> - <div className='absolute right-[38px] top-[-4px] z-10 h-3 w-3 rotate-45 bg-background-section-burn' /> + <div className="absolute right-[38px] top-[-4px] z-10 h-3 w-3 rotate-45 bg-background-section-burn" /> </div> </div> )} - <div className='shrink-0 px-4 pb-3 pt-2'> + <div className="shrink-0 px-4 pb-3 pt-2"> <VariableModalTrigger open={showVariableModal} setOpen={setShowVariableModal} @@ -195,7 +199,7 @@ const ChatVariablePanel = () => { onClose={() => setCurrentVar(undefined)} /> </div> - <div className='grow overflow-y-auto rounded-b-2xl px-4'> + <div className="grow overflow-y-auto rounded-b-2xl px-4"> {varList.map(chatVar => ( <VariableItem key={chatVar.id} diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index 682e91ea81..8ff356cad7 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -1,28 +1,28 @@ +import type { StartNodeType } from '../../nodes/start/types' +import type { ChatWrapperRefType } from './index' +import type { ChatItem, OnSend } from '@/app/components/base/chat/types' +import type { FileEntity } from '@/app/components/base/file-uploader/types' import { memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react' import { useNodes } from 'reactflow' -import { BlockEnum } from '../../types' -import { - useStore, - useWorkflowStore, -} from '../../store' -import type { StartNodeType } from '../../nodes/start/types' -import Empty from './empty' -import UserInput from './user-input' -import ConversationVariableModal from './conversation-variable-modal' -import { useChat } from './hooks' -import type { ChatWrapperRefType } from './index' +import { useStore as useAppStore } from '@/app/components/app/store' import Chat from '@/app/components/base/chat/chat' -import type { ChatItem, OnSend } from '@/app/components/base/chat/types' +import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils' import { useFeatures } from '@/app/components/base/features/hooks' +import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { fetchSuggestedQuestions, stopChatMessageResponding, } from '@/service/debug' -import { useStore as useAppStore } from '@/app/components/app/store' -import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/chat/utils' -import type { FileEntity } from '@/app/components/base/file-uploader/types' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' +import { + useStore, + useWorkflowStore, +} from '../../store' +import { BlockEnum } from '../../types' +import ConversationVariableModal from './conversation-variable-modal' +import Empty from './empty' +import { useChat } from './hooks' +import UserInput from './user-input' type ChatWrapperProps = { showConversationVariableModal: boolean @@ -39,7 +39,7 @@ const ChatWrapper = ( showInputsFieldsPanel, onHide, }: ChatWrapperProps & { - ref: React.RefObject<ChatWrapperRefType>; + ref: React.RefObject<ChatWrapperRefType> }, ) => { const nodes = useNodes<StartNodeType>() @@ -118,11 +118,7 @@ const ChatWrapper = ( const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => { const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId)! const parentAnswer = chatList.find(item => item.id === question.parentMessageId) - doSend(editedQuestion ? editedQuestion.message : question.content, - editedQuestion ? editedQuestion.files : question.message_files, - true, - isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null, - ) + doSend(editedQuestion ? editedQuestion.message : question.content, editedQuestion ? editedQuestion.files : question.message_files, true, isValidGeneratedAnswer(parentAnswer) ? parentAnswer : null) }, [chatList, doSend]) const { eventEmitter } = useEventEmitterContextContext() @@ -160,10 +156,10 @@ const ChatWrapper = ( } as any} chatList={chatList} isResponding={isResponding} - chatContainerClassName='px-3' - chatContainerInnerClassName='pt-6 w-full max-w-full mx-auto' - chatFooterClassName='px-4 rounded-bl-2xl' - chatFooterInnerClassName='pb-0' + chatContainerClassName="px-3" + chatContainerInnerClassName="pt-6 w-full max-w-full mx-auto" + chatFooterClassName="px-4 rounded-bl-2xl" + chatFooterInnerClassName="pb-0" showFileUpload showFeatureBar onFeatureBarClick={setShowFeaturesPanel} @@ -185,7 +181,7 @@ const ChatWrapper = ( noSpacing suggestedQuestions={suggestedQuestions} showPromptLog - chatAnswerContainerInner='!pr-2' + chatAnswerContainerInner="!pr-2" switchSibling={setTargetMessageId} /> {showConversationVariableModal && ( diff --git a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx index 043a842f23..117247901e 100644 --- a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx @@ -1,27 +1,26 @@ 'use client' -import React, { useCallback } from 'react' -import { useMount } from 'ahooks' -import { useTranslation } from 'react-i18next' -import { capitalize } from 'lodash-es' -import copy from 'copy-to-clipboard' +import type { + ConversationVariable, +} from '@/app/components/workflow/types' import { RiCloseLine } from '@remixicon/react' -import Modal from '@/app/components/base/modal' -import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { useMount } from 'ahooks' +import copy from 'copy-to-clipboard' +import { capitalize, noop } from 'lodash-es' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { Copy, CopyCheck, } from '@/app/components/base/icons/src/vender/line/files' -import { useStore } from '@/app/components/workflow/store' -import type { - ConversationVariable, -} from '@/app/components/workflow/types' -import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' +import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' +import Modal from '@/app/components/base/modal' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' +import { useStore } from '@/app/components/workflow/store' import useTimestamp from '@/hooks/use-timestamp' import { fetchCurrentValueOfConversationVariable } from '@/service/workflow' import { cn } from '@/utils/classnames' -import { noop } from 'lodash-es' export type Props = { conversationID: string @@ -80,14 +79,14 @@ const ConversationVariableModal = ({ onClose={noop} className={cn('h-[640px] w-[920px] max-w-[920px] p-0')} > - <div className='absolute right-4 top-4 cursor-pointer p-2' onClick={onHide}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="absolute right-4 top-4 cursor-pointer p-2" onClick={onHide}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> - <div className='flex h-full w-full'> + <div className="flex h-full w-full"> {/* LEFT */} - <div className='flex h-full w-[224px] shrink-0 flex-col border-r border-divider-burn bg-background-sidenav-bg'> - <div className='system-xl-semibold shrink-0 pb-3 pl-5 pr-4 pt-5 text-text-primary'>{t('workflow.chatVariable.panelTitle')}</div> - <div className='grow overflow-y-auto px-3 py-2'> + <div className="flex h-full w-[224px] shrink-0 flex-col border-r border-divider-burn bg-background-sidenav-bg"> + <div className="system-xl-semibold shrink-0 pb-3 pl-5 pr-4 pt-5 text-text-primary">{t('workflow.chatVariable.panelTitle')}</div> + <div className="grow overflow-y-auto px-3 py-2"> {varList.map(chatVar => ( <div key={chatVar.id} className={cn('radius-md group mb-0.5 flex cursor-pointer items-center p-2 hover:bg-state-base-hover', currentVar.id === chatVar.id && 'bg-state-base-hover')} onClick={() => setCurrentVar(chatVar)}> <BubbleX className={cn('mr-1 h-4 w-4 shrink-0 text-text-tertiary group-hover:text-util-colors-teal-teal-700', currentVar.id === chatVar.id && 'text-util-colors-teal-teal-700')} /> @@ -97,40 +96,46 @@ const ConversationVariableModal = ({ </div> </div> {/* RIGHT */} - <div className='flex h-full w-0 grow flex-col bg-components-panel-bg'> - <div className='shrink-0 p-4 pb-2'> - <div className='flex items-center gap-1 py-1'> - <div className='system-xl-semibold text-text-primary'>{currentVar.name}</div> - <div className='system-xs-medium text-text-tertiary'>{capitalize(currentVar.value_type)}</div> + <div className="flex h-full w-0 grow flex-col bg-components-panel-bg"> + <div className="shrink-0 p-4 pb-2"> + <div className="flex items-center gap-1 py-1"> + <div className="system-xl-semibold text-text-primary">{currentVar.name}</div> + <div className="system-xs-medium text-text-tertiary">{capitalize(currentVar.value_type)}</div> </div> </div> - <div className='flex h-0 grow flex-col p-4 pt-2'> - <div className='mb-2 flex shrink-0 items-center gap-2'> - <div className='system-xs-medium-uppercase shrink-0 text-text-tertiary'>{t('workflow.chatVariable.storedContent').toLocaleUpperCase()}</div> - <div className='h-px grow' style={{ - background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)', - }}></div> + <div className="flex h-0 grow flex-col p-4 pt-2"> + <div className="mb-2 flex shrink-0 items-center gap-2"> + <div className="system-xs-medium-uppercase shrink-0 text-text-tertiary">{t('workflow.chatVariable.storedContent').toLocaleUpperCase()}</div> + <div + className="h-px grow" + style={{ + background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255) 100%)', + }} + > + </div> {latestValueTimestampMap[currentVar.id] && ( - <div className='system-xs-regular shrink-0 text-text-tertiary'>{t('workflow.chatVariable.updatedAt')}{formatTime(latestValueTimestampMap[currentVar.id], t('appLog.dateTimeFormat') as string)}</div> + <div className="system-xs-regular shrink-0 text-text-tertiary"> + {t('workflow.chatVariable.updatedAt')} + {formatTime(latestValueTimestampMap[currentVar.id], t('appLog.dateTimeFormat') as string)} + </div> )} </div> - <div className='grow overflow-y-auto'> + <div className="grow overflow-y-auto"> {currentVar.value_type !== ChatVarType.Number && currentVar.value_type !== ChatVarType.String && ( - <div className='flex h-full flex-col rounded-lg bg-components-input-bg-normal px-2 pb-2'> - <div className='flex h-7 shrink-0 items-center justify-between pl-3 pr-2 pt-1'> - <div className='system-xs-semibold text-text-secondary'>JSON</div> - <div className='flex items-center p-1'> + <div className="flex h-full flex-col rounded-lg bg-components-input-bg-normal px-2 pb-2"> + <div className="flex h-7 shrink-0 items-center justify-between pl-3 pr-2 pt-1"> + <div className="system-xs-semibold text-text-secondary">JSON</div> + <div className="flex items-center p-1"> {!isCopied ? ( - <Copy className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={handleCopy} /> - ) + <Copy className="h-4 w-4 cursor-pointer text-text-tertiary" onClick={handleCopy} /> + ) : ( - <CopyCheck className='h-4 w-4 text-text-tertiary' /> - ) - } + <CopyCheck className="h-4 w-4 text-text-tertiary" /> + )} </div> </div> - <div className='grow pl-4'> + <div className="grow pl-4"> <CodeEditor readOnly noWrapper @@ -143,7 +148,7 @@ const ConversationVariableModal = ({ </div> )} {(currentVar.value_type === ChatVarType.Number || currentVar.value_type === ChatVarType.String) && ( - <div className='system-md-regular h-full overflow-y-auto overflow-x-hidden rounded-lg bg-components-input-bg-normal px-4 py-3 text-components-input-text-filled'>{latestValueMap[currentVar.id] || ''}</div> + <div className="system-md-regular h-full overflow-y-auto overflow-x-hidden rounded-lg bg-components-input-bg-normal px-4 py-3 text-components-input-text-filled">{latestValueMap[currentVar.id] || ''}</div> )} </div> </div> diff --git a/web/app/components/workflow/panel/debug-and-preview/empty.tsx b/web/app/components/workflow/panel/debug-and-preview/empty.tsx index 7f120c3536..6c5e9ca5c2 100644 --- a/web/app/components/workflow/panel/debug-and-preview/empty.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/empty.tsx @@ -5,11 +5,11 @@ const Empty = () => { const { t } = useTranslation() return ( - <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'> - <div className='mb-2 flex justify-center'> - <ChatBotSlim className='h-12 w-12 text-gray-300' /> + <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"> + <div className="mb-2 flex justify-center"> + <ChatBotSlim className="h-12 w-12 text-gray-300" /> </div> - <div className='w-[256px] text-center text-[13px] text-gray-400'> + <div className="w-[256px] text-center text-[13px] text-gray-400"> {t('workflow.common.previewPlaceholder')} </div> </div> diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 4cc6e92ace..6eb1ea0b76 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -1,3 +1,12 @@ +import type { InputForm } from '@/app/components/base/chat/chat/type' +import type { + ChatItem, + ChatItemInTree, + Inputs, +} from '@/app/components/base/chat/types' +import type { FileEntity } from '@/app/components/base/file-uploader/types' +import { produce, setAutoFreeze } from 'immer' +import { uniqBy } from 'lodash-es' import { useCallback, useEffect, @@ -6,35 +15,26 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { produce, setAutoFreeze } from 'immer' -import { uniqBy } from 'lodash-es' -import { - useSetWorkflowVarsWithValue, - useWorkflowRun, -} from '../../hooks' -import { NodeRunningStatus, WorkflowRunningStatus } from '../../types' -import { useWorkflowStore } from '../../store' -import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../constants' -import type { - ChatItem, - ChatItemInTree, - Inputs, -} from '@/app/components/base/chat/types' -import type { InputForm } from '@/app/components/base/chat/chat/type' import { getProcessedInputs, processOpeningStatement, } from '@/app/components/base/chat/chat/utils' -import { useToastContext } from '@/app/components/base/toast' -import { TransferMethod } from '@/types/app' +import { getThreadMessages } from '@/app/components/base/chat/utils' import { getProcessedFiles, getProcessedFilesFromResponse, } from '@/app/components/base/file-uploader/utils' -import type { FileEntity } from '@/app/components/base/file-uploader/types' -import { getThreadMessages } from '@/app/components/base/chat/utils' +import { useToastContext } from '@/app/components/base/toast' import { useInvalidAllLastRun } from '@/service/use-workflow' +import { TransferMethod } from '@/types/app' +import { DEFAULT_ITER_TIMES, DEFAULT_LOOP_TIMES } from '../../constants' +import { + useSetWorkflowVarsWithValue, + useWorkflowRun, +} from '../../hooks' import { useHooksStore } from '../../hooks-store' +import { useWorkflowStore } from '../../store' +import { NodeRunningStatus, WorkflowRunningStatus } from '../../types' type GetAbortController = (abortController: AbortController) => void type SendCallback = { diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index 7f8deb3a74..3005b68a9c 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -1,3 +1,7 @@ +import type { StartNodeType } from '../../nodes/start/types' + +import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' +import { debounce, noop } from 'lodash-es' import { memo, useCallback, @@ -5,25 +9,21 @@ import { useRef, useState, } from 'react' - -import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' +import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' +import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' +import Tooltip from '@/app/components/base/tooltip' +import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { useStore } from '@/app/components/workflow/store' +import { cn } from '@/utils/classnames' import { useWorkflowInteractions, } from '../../hooks' -import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' -import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' -import { BlockEnum } from '../../types' -import type { StartNodeType } from '../../nodes/start/types' import { useResizePanel } from '../../nodes/_base/hooks/use-resize-panel' +import { BlockEnum } from '../../types' import ChatWrapper from './chat-wrapper' -import { cn } from '@/utils/classnames' -import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' -import Tooltip from '@/app/components/base/tooltip' -import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' -import { useStore } from '@/app/components/workflow/store' -import { debounce, noop } from 'lodash-es' export type ChatWrapperRefType = { handleRestart: () => void @@ -81,11 +81,12 @@ const DebugAndPreview = () => { }) return ( - <div className='relative h-full'> + <div className="relative h-full"> <div ref={triggerRef} - className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'> - <div className='h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid'></div> + className="absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center" + > + <div className="h-10 w-0.5 rounded-sm bg-state-base-handle hover:h-full hover:bg-state-accent-solid active:h-full active:bg-state-accent-solid"></div> </div> <div ref={containerRef} @@ -94,38 +95,38 @@ const DebugAndPreview = () => { )} style={{ width: `${panelWidth}px` }} > - <div className='system-xl-semibold flex shrink-0 items-center justify-between px-4 pb-2 pt-3 text-text-primary'> - <div className='h-8'>{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div> - <div className='flex items-center gap-1'> + <div className="system-xl-semibold flex shrink-0 items-center justify-between px-4 pb-2 pt-3 text-text-primary"> + <div className="h-8">{t('workflow.common.debugAndPreview').toLocaleUpperCase()}</div> + <div className="flex items-center gap-1"> <Tooltip popupContent={t('common.operation.refresh')} > <ActionButton onClick={() => handleRestartChat()}> - <RefreshCcw01 className='h-4 w-4' /> + <RefreshCcw01 className="h-4 w-4" /> </ActionButton> </Tooltip> {visibleVariables.length > 0 && ( - <div className='relative'> + <div className="relative"> <Tooltip popupContent={t('workflow.panel.userInputField')} > <ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}> - <RiEqualizer2Line className='h-4 w-4' /> + <RiEqualizer2Line className="h-4 w-4" /> </ActionButton> </Tooltip> - {expanded && <div className='absolute bottom-[-17px] right-[5px] z-10 h-3 w-3 rotate-45 border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg' />} + {expanded && <div className="absolute bottom-[-17px] right-[5px] z-10 h-3 w-3 rotate-45 border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg" />} </div> )} - <div className='mx-3 h-3.5 w-[1px] bg-divider-regular'></div> + <div className="mx-3 h-3.5 w-[1px] bg-divider-regular"></div> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={handleCancelDebugAndPreviewPanel} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='grow overflow-y-auto rounded-b-2xl'> + <div className="grow overflow-y-auto rounded-b-2xl"> <ChatWrapper ref={chatRef} showConversationVariableModal={showConversationVariableModal} diff --git a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx index 75acfac8b2..8b438d995a 100644 --- a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx @@ -1,15 +1,15 @@ +import type { StartNodeType } from '../../nodes/start/types' import { memo, } from 'react' import { useNodes } from 'reactflow' +import { cn } from '@/utils/classnames' import FormItem from '../../nodes/_base/components/before-run-form/form-item' -import { BlockEnum } from '../../types' import { useStore, useWorkflowStore, } from '../../store' -import type { StartNodeType } from '../../nodes/start/types' -import { cn } from '@/utils/classnames' +import { BlockEnum } from '../../types' const UserInput = () => { const workflowStore = useWorkflowStore() @@ -36,11 +36,11 @@ const UserInput = () => { return ( <div className={cn('relative z-[1] rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs')}> - <div className='px-4 pb-4 pt-3'> + <div className="px-4 pb-4 pt-3"> {visibleVariables.map((variable, index) => ( <div key={variable.variable} - className='mb-4 last-of-type:mb-0' + className="mb-4 last-of-type:mb-0" > <FormItem autoFocus={index === 0} diff --git a/web/app/components/workflow/panel/env-panel/env-item.tsx b/web/app/components/workflow/panel/env-panel/env-item.tsx index f0afe4d6d0..64d6610643 100644 --- a/web/app/components/workflow/panel/env-panel/env-item.tsx +++ b/web/app/components/workflow/panel/env-panel/env-item.tsx @@ -1,9 +1,9 @@ -import { memo, useState } from 'react' -import { capitalize } from 'lodash-es' +import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react' +import { capitalize } from 'lodash-es' +import { memo, useState } from 'react' import { Env } from '@/app/components/base/icons/src/vender/line/others' import { useStore } from '@/app/components/workflow/store' -import type { EnvironmentVariable } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' type EnvItemProps = { @@ -24,37 +24,36 @@ const EnvItem = ({ <div className={cn( 'radius-md group mb-1 border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-hover', destructive && 'border-state-destructive-border hover:bg-state-destructive-hover', - )}> - <div className='px-2.5 py-2'> - <div className='flex items-center justify-between'> - <div className='flex grow items-center gap-1'> - <Env className='h-4 w-4 text-util-colors-violet-violet-600' /> - <div className='system-sm-medium text-text-primary'>{env.name}</div> - <div className='system-xs-medium text-text-tertiary'>{capitalize(env.value_type)}</div> - {env.value_type === 'secret' && <RiLock2Line className='h-3 w-3 text-text-tertiary' />} + )} + > + <div className="px-2.5 py-2"> + <div className="flex items-center justify-between"> + <div className="flex grow items-center gap-1"> + <Env className="h-4 w-4 text-util-colors-violet-violet-600" /> + <div className="system-sm-medium text-text-primary">{env.name}</div> + <div className="system-xs-medium text-text-tertiary">{capitalize(env.value_type)}</div> + {env.value_type === 'secret' && <RiLock2Line className="h-3 w-3 text-text-tertiary" />} </div> - <div className='flex shrink-0 items-center gap-1 text-text-tertiary'> - <div className='radius-md cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary'> - <RiEditLine className='h-4 w-4' onClick={() => onEdit(env)}/> + <div className="flex shrink-0 items-center gap-1 text-text-tertiary"> + <div className="radius-md cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary"> + <RiEditLine className="h-4 w-4" onClick={() => onEdit(env)} /> </div> <div - className='radius-md cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive' + className="radius-md cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive" onMouseOver={() => setDestructive(true)} onMouseOut={() => setDestructive(false)} > - <RiDeleteBinLine className='h-4 w-4' onClick={() => onDelete(env)} /> + <RiDeleteBinLine className="h-4 w-4" onClick={() => onDelete(env)} /> </div> </div> </div> - <div className='system-xs-regular truncate text-text-tertiary'>{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div> + <div className="system-xs-regular truncate text-text-tertiary">{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div> </div> {env.description && ( <> - <div className={'h-[0.5px] bg-divider-subtle'} /> - <div className={cn('rounded-bl-[8px] rounded-br-[8px] bg-background-default-subtle px-2.5 py-2 group-hover:bg-transparent', - destructive && 'bg-state-destructive-hover hover:bg-state-destructive-hover', - )}> - <div className='system-xs-regular truncate text-text-tertiary'>{env.description}</div> + <div className="h-[0.5px] bg-divider-subtle" /> + <div className={cn('rounded-bl-[8px] rounded-br-[8px] bg-background-default-subtle px-2.5 py-2 group-hover:bg-transparent', destructive && 'bg-state-destructive-hover hover:bg-state-destructive-hover')}> + <div className="system-xs-regular truncate text-text-tertiary">{env.description}</div> </div> </> )} diff --git a/web/app/components/workflow/panel/env-panel/index.tsx b/web/app/components/workflow/panel/env-panel/index.tsx index 47aeb94b47..b11159cfbb 100644 --- a/web/app/components/workflow/panel/env-panel/index.tsx +++ b/web/app/components/workflow/panel/env-panel/index.tsx @@ -1,23 +1,23 @@ +import type { + EnvironmentVariable, +} from '@/app/components/workflow/types' +import { RiCloseLine } from '@remixicon/react' import { memo, useCallback, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { useStoreApi, } from 'reactflow' -import { RiCloseLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useStore } from '@/app/components/workflow/store' -import VariableTrigger from '@/app/components/workflow/panel/env-panel/variable-trigger' -import EnvItem from '@/app/components/workflow/panel/env-panel/env-item' -import type { - EnvironmentVariable, -} from '@/app/components/workflow/types' -import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm' -import { cn } from '@/utils/classnames' import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft' +import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm' +import { findUsedVarNodes, updateNodeVars } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import EnvItem from '@/app/components/workflow/panel/env-panel/env-item' +import VariableTrigger from '@/app/components/workflow/panel/env-panel/variable-trigger' +import { useStore } from '@/app/components/workflow/store' +import { cn } from '@/utils/classnames' const EnvPanel = () => { const { t } = useTranslation() @@ -152,19 +152,19 @@ const EnvPanel = () => { 'relative flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-components-panel-bg-alt', )} > - <div className='system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary'> + <div className="system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary"> {t('workflow.env.envPanelTitle')} - <div className='flex items-center'> + <div className="flex items-center"> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={() => setShowEnvPanel(false)} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='system-sm-regular shrink-0 px-4 py-1 text-text-tertiary'>{t('workflow.env.envDescription')}</div> - <div className='shrink-0 px-4 pb-3 pt-2'> + <div className="system-sm-regular shrink-0 px-4 py-1 text-text-tertiary">{t('workflow.env.envDescription')}</div> + <div className="shrink-0 px-4 pb-3 pt-2"> <VariableTrigger open={showVariableModal} setOpen={setShowVariableModal} @@ -173,7 +173,7 @@ const EnvPanel = () => { onClose={() => setCurrentVar(undefined)} /> </div> - <div className='grow overflow-y-auto rounded-b-2xl px-4'> + <div className="grow overflow-y-auto rounded-b-2xl px-4"> {envList.map(env => ( <EnvItem key={env.id} diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx index acf3ca0a0b..6cf193fe96 100644 --- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx @@ -1,14 +1,14 @@ +import type { EnvironmentVariable } from '@/app/components/workflow/types' +import { RiCloseLine } from '@remixicon/react' import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { v4 as uuid4 } from 'uuid' -import { RiCloseLine } from '@remixicon/react' import { useContext } from 'use-context-selector' +import { v4 as uuid4 } from 'uuid' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' -import Tooltip from '@/app/components/base/tooltip' import { ToastContext } from '@/app/components/base/toast' +import Tooltip from '@/app/components/base/tooltip' import { useStore } from '@/app/components/workflow/store' -import type { EnvironmentVariable } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var' @@ -86,89 +86,107 @@ const VariableModal = ({ <div className={cn('flex h-full w-[360px] flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl')} > - <div className='system-xl-semibold mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary'> + <div className="system-xl-semibold mb-3 flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary"> {!env ? t('workflow.env.modal.title') : t('workflow.env.modal.editTitle')} - <div className='flex items-center'> + <div className="flex items-center"> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={onClose} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='px-4 py-2'> + <div className="px-4 py-2"> {/* type */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.env.modal.type')}</div> - <div className='flex gap-2'> - <div className={cn( - 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', - type === 'string' && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', - )} onClick={() => setType('string')}>String</div> - <div className={cn( - 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', - type === 'number' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg font-medium text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', - )} onClick={() => { - setType('number') - if (!(/^\d$/).test(value)) - setValue('') - }}>Number</div> - <div className={cn( - 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', - type === 'secret' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg font-medium text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', - )} onClick={() => setType('secret')}> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.env.modal.type')}</div> + <div className="flex gap-2"> + <div + className={cn( + 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', + type === 'string' && 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', + )} + onClick={() => setType('string')} + > + String + </div> + <div + className={cn( + 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', + type === 'number' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg font-medium text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', + )} + onClick={() => { + setType('number') + if (!(/^\d$/).test(value)) + setValue('') + }} + > + Number + </div> + <div + className={cn( + 'radius-md system-sm-regular flex w-[106px] cursor-pointer items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs', + type === 'secret' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg font-medium text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', + )} + onClick={() => setType('secret')} + > <span>Secret</span> <Tooltip - popupContent={ - <div className='w-[240px]'> + popupContent={( + <div className="w-[240px]"> {t('workflow.env.modal.secretTip')} </div> - } - triggerClassName='ml-0.5 w-3.5 h-3.5' + )} + triggerClassName="ml-0.5 w-3.5 h-3.5" /> </div> </div> </div> {/* name */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.env.modal.name')}</div> - <div className='flex'> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.env.modal.name')}</div> + <div className="flex"> <Input placeholder={t('workflow.env.modal.namePlaceholder') || ''} value={name} onChange={handleVarNameChange} onBlur={e => checkVariableName(e.target.value)} - type='text' + type="text" /> </div> </div> {/* value */} - <div className='mb-4'> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.env.modal.value')}</div> - <div className='flex'> + <div className="mb-4"> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.env.modal.value')}</div> + <div className="flex"> { - type !== 'number' ? <textarea - className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' - value={value} - placeholder={t('workflow.env.modal.valuePlaceholder') || ''} - onChange={e => setValue(e.target.value)} - /> - : <Input - placeholder={t('workflow.env.modal.valuePlaceholder') || ''} - value={value} - onChange={e => setValue(e.target.value)} - type="number" - /> + type !== 'number' + ? ( + <textarea + className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" + value={value} + placeholder={t('workflow.env.modal.valuePlaceholder') || ''} + onChange={e => setValue(e.target.value)} + /> + ) + : ( + <Input + placeholder={t('workflow.env.modal.valuePlaceholder') || ''} + value={value} + onChange={e => setValue(e.target.value)} + type="number" + /> + ) } </div> </div> {/* description */} - <div className=''> - <div className='system-sm-semibold mb-1 flex h-6 items-center text-text-secondary'>{t('workflow.env.modal.description')}</div> - <div className='flex'> + <div className=""> + <div className="system-sm-semibold mb-1 flex h-6 items-center text-text-secondary">{t('workflow.env.modal.description')}</div> + <div className="flex"> <textarea - className='system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' + className="system-sm-regular placeholder:system-sm-regular block h-20 w-full resize-none appearance-none rounded-lg border border-transparent bg-components-input-bg-normal p-2 text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" value={description} placeholder={t('workflow.env.modal.descriptionPlaceholder') || ''} onChange={e => setDescription(e.target.value)} @@ -176,10 +194,10 @@ const VariableModal = ({ </div> </div> </div> - <div className='flex flex-row-reverse rounded-b-2xl p-4 pt-2'> - <div className='flex gap-2'> + <div className="flex flex-row-reverse rounded-b-2xl p-4 pt-2"> + <div className="flex gap-2"> <Button onClick={onClose}>{t('common.operation.cancel')}</Button> - <Button variant='primary' onClick={handleSave}>{t('common.operation.save')}</Button> + <Button variant="primary" onClick={handleSave}>{t('common.operation.save')}</Button> </div> </div> </div> diff --git a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx index 604fceef81..30551562a7 100644 --- a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx @@ -1,15 +1,15 @@ 'use client' +import type { EnvironmentVariable } from '@/app/components/workflow/types' +import { RiAddLine } from '@remixicon/react' import React from 'react' import { useTranslation } from 'react-i18next' -import { RiAddLine } from '@remixicon/react' import Button from '@/app/components/base/button' -import VariableModal from '@/app/components/workflow/panel/env-panel/variable-modal' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import type { EnvironmentVariable } from '@/app/components/workflow/types' +import VariableModal from '@/app/components/workflow/panel/env-panel/variable-modal' type Props = { open: boolean @@ -36,7 +36,7 @@ const VariableTrigger = ({ if (open) onClose() }} - placement='left-start' + placement="left-start" offset={{ mainAxis: 8, alignmentAxis: -104, @@ -46,13 +46,14 @@ const VariableTrigger = ({ setOpen(v => !v) if (open) onClose() - }}> - <Button variant='primary'> - <RiAddLine className='mr-1 h-4 w-4' /> - <span className='system-sm-medium'>{t('workflow.env.envPanelButton')}</span> + }} + > + <Button variant="primary"> + <RiAddLine className="mr-1 h-4 w-4" /> + <span className="system-sm-medium">{t('workflow.env.envPanelButton')}</span> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[11]'> + <PortalToFollowElemContent className="z-[11]"> <VariableModal env={env} onSave={onSave} diff --git a/web/app/components/workflow/panel/global-variable-panel/index.tsx b/web/app/components/workflow/panel/global-variable-panel/index.tsx index ab7cf4708b..947393d95a 100644 --- a/web/app/components/workflow/panel/global-variable-panel/index.tsx +++ b/web/app/components/workflow/panel/global-variable-panel/index.tsx @@ -1,16 +1,16 @@ +import type { GlobalVariable } from '../../types' + +import { RiCloseLine } from '@remixicon/react' import { memo, } from 'react' - -import { RiCloseLine } from '@remixicon/react' -import type { GlobalVariable } from '../../types' -import Item from './item' +import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/workflow/store' import { cn } from '@/utils/classnames' -import { useTranslation } from 'react-i18next' -import { useIsChatMode } from '../../hooks' import { isInWorkflowPage } from '../../constants' +import { useIsChatMode } from '../../hooks' +import Item from './item' const Panel = () => { const { t } = useTranslation() @@ -19,16 +19,17 @@ const Panel = () => { const isWorkflowPage = isInWorkflowPage() const globalVariableList: GlobalVariable[] = [ - ...(isChatMode ? [{ - name: 'conversation_id', - value_type: 'string' as const, - description: t('workflow.globalVar.fieldsDescription.conversationId'), - }, - { - name: 'dialog_count', - value_type: 'number' as const, - description: t('workflow.globalVar.fieldsDescription.dialogCount'), - }] : []), + ...(isChatMode + ? [{ + name: 'conversation_id', + value_type: 'string' as const, + description: t('workflow.globalVar.fieldsDescription.conversationId'), + }, { + name: 'dialog_count', + value_type: 'number' as const, + description: t('workflow.globalVar.fieldsDescription.dialogCount'), + }] + : []), { name: 'user_id', value_type: 'string', @@ -50,11 +51,13 @@ const Panel = () => { description: t('workflow.globalVar.fieldsDescription.workflowRunId'), }, // is workflow - ...((isWorkflowPage && !isChatMode) ? [{ - name: 'timestamp', - value_type: 'number' as const, - description: t('workflow.globalVar.fieldsDescription.triggerTimestamp'), - }] : []), + ...((isWorkflowPage && !isChatMode) + ? [{ + name: 'timestamp', + value_type: 'number' as const, + description: t('workflow.globalVar.fieldsDescription.triggerTimestamp'), + }] + : []), ] return ( @@ -63,20 +66,20 @@ const Panel = () => { 'relative flex h-full w-[420px] flex-col rounded-l-2xl border border-components-panel-border bg-components-panel-bg-alt', )} > - <div className='system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary'> + <div className="system-xl-semibold flex shrink-0 items-center justify-between p-4 pb-0 text-text-primary"> {t('workflow.globalVar.title')} - <div className='flex items-center'> + <div className="flex items-center"> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center' + className="flex h-6 w-6 cursor-pointer items-center justify-center" onClick={() => setShowPanel(false)} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> </div> - <div className='system-sm-regular shrink-0 px-4 py-1 text-text-tertiary'>{t('workflow.globalVar.description')}</div> + <div className="system-sm-regular shrink-0 px-4 py-1 text-text-tertiary">{t('workflow.globalVar.description')}</div> - <div className='mt-4 grow overflow-y-auto rounded-b-2xl px-4'> + <div className="mt-4 grow overflow-y-auto rounded-b-2xl px-4"> {globalVariableList.map(item => ( <Item key={item.name} diff --git a/web/app/components/workflow/panel/global-variable-panel/item.tsx b/web/app/components/workflow/panel/global-variable-panel/item.tsx index c88222efb4..f82579dedb 100644 --- a/web/app/components/workflow/panel/global-variable-panel/item.tsx +++ b/web/app/components/workflow/panel/global-variable-panel/item.tsx @@ -1,8 +1,8 @@ -import { memo } from 'react' -import { capitalize } from 'lodash-es' -import { GlobalVariable as GlobalVariableIcon } from '@/app/components/base/icons/src/vender/line/others' - import type { GlobalVariable } from '@/app/components/workflow/types' +import { capitalize } from 'lodash-es' +import { memo } from 'react' + +import { GlobalVariable as GlobalVariableIcon } from '@/app/components/base/icons/src/vender/line/others' import { cn } from '@/utils/classnames' type Props = { @@ -15,18 +15,19 @@ const Item = ({ return ( <div className={cn( 'radius-md mb-1 border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 py-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover', - )}> - <div className='flex items-center justify-between'> - <div className='flex grow items-center gap-1'> - <GlobalVariableIcon className='h-4 w-4 text-util-colors-orange-orange-600' /> - <div className='system-sm-medium text-text-primary'> - <span className='text-text-tertiary'>sys.</span> + )} + > + <div className="flex items-center justify-between"> + <div className="flex grow items-center gap-1"> + <GlobalVariableIcon className="h-4 w-4 text-util-colors-orange-orange-600" /> + <div className="system-sm-medium text-text-primary"> + <span className="text-text-tertiary">sys.</span> {payload.name} </div> - <div className='system-xs-medium text-text-tertiary'>{capitalize(payload.value_type)}</div> + <div className="system-xs-medium text-text-tertiary">{capitalize(payload.value_type)}</div> </div> </div> - <div className='system-xs-regular mt-1.5 truncate text-text-tertiary'>{payload.description}</div> + <div className="system-xs-regular mt-1.5 truncate text-text-tertiary">{payload.description}</div> </div> ) } diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index 8b4ba27ec3..88ada8b11e 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react' -import { memo, useCallback, useEffect, useRef } from 'react' import type { VersionHistoryPanelProps } from '@/app/components/workflow/panel/version-history-panel' -import { useShallow } from 'zustand/react/shallow' +import dynamic from 'next/dynamic' +import { memo, useCallback, useEffect, useRef } from 'react' import { useStore as useReactflow } from 'reactflow' +import { useShallow } from 'zustand/react/shallow' +import { cn } from '@/utils/classnames' import { Panel as NodePanel } from '../nodes' import { useStore } from '../store' import EnvPanel from './env-panel' -import { cn } from '@/utils/classnames' -import dynamic from 'next/dynamic' const VersionHistoryPanel = dynamic(() => import('@/app/components/workflow/panel/version-history-panel'), { ssr: false, @@ -44,7 +44,8 @@ const useResizeObserver = ( useEffect(() => { const element = elementRef.current - if (!element) return + if (!element) + return const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { diff --git a/web/app/components/workflow/panel/inputs-panel.tsx b/web/app/components/workflow/panel/inputs-panel.tsx index 4c9de03b8a..d1791ebe7e 100644 --- a/web/app/components/workflow/panel/inputs-panel.tsx +++ b/web/app/components/workflow/panel/inputs-panel.tsx @@ -1,3 +1,4 @@ +import type { StartNodeType } from '../nodes/start/types' import { memo, useCallback, @@ -5,25 +6,24 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' +import Button from '@/app/components/base/button' +import { useCheckInputsForms } from '@/app/components/base/chat/chat/check-input-forms-hooks' +import { + getProcessedInputs, +} from '@/app/components/base/chat/chat/utils' +import { TransferMethod } from '../../base/text-generation/types' +import { useWorkflowRun } from '../hooks' +import { useHooksStore } from '../hooks-store' import FormItem from '../nodes/_base/components/before-run-form/form-item' +import { + useStore, + useWorkflowStore, +} from '../store' import { BlockEnum, InputVarType, WorkflowRunningStatus, } from '../types' -import { - useStore, - useWorkflowStore, -} from '../store' -import { useWorkflowRun } from '../hooks' -import type { StartNodeType } from '../nodes/start/types' -import { TransferMethod } from '../../base/text-generation/types' -import Button from '@/app/components/base/button' -import { - getProcessedInputs, -} from '@/app/components/base/chat/chat/utils' -import { useCheckInputsForms } from '@/app/components/base/chat/chat/check-input-forms-hooks' -import { useHooksStore } from '../hooks-store' type Props = { onRun: () => void @@ -105,16 +105,16 @@ const InputsPanel = ({ onRun }: Props) => { return ( <> - <div className='px-4 pb-2 pt-3'> + <div className="px-4 pb-2 pt-3"> { variables.map((variable, index) => ( <div key={variable.variable} - className='mb-2 last-of-type:mb-0' + className="mb-2 last-of-type:mb-0" > <FormItem autoFocus={index === 0} - className='!block' + className="!block" payload={variable} value={initialInputs[variable.variable]} onChange={v => handleValueChange(variable.variable, v)} @@ -123,11 +123,11 @@ const InputsPanel = ({ onRun }: Props) => { )) } </div> - <div className='flex items-center justify-between px-4 py-2'> + <div className="flex items-center justify-between px-4 py-2"> <Button - variant='primary' + variant="primary" disabled={!canRun || workflowRunningData?.result?.status === WorkflowRunningStatus.Running} - className='w-full' + className="w-full" onClick={doRun} > {t('workflow.singleRun.startRun')} diff --git a/web/app/components/workflow/panel/record.tsx b/web/app/components/workflow/panel/record.tsx index e9c677d235..ff84c73db9 100644 --- a/web/app/components/workflow/panel/record.tsx +++ b/web/app/components/workflow/panel/record.tsx @@ -1,9 +1,9 @@ -import { memo, useCallback } from 'react' import type { WorkflowRunDetailResponse } from '@/models/log' -import Run from '../run' -import { useStore } from '../store' +import { memo, useCallback } from 'react' import { useWorkflowUpdate } from '../hooks' import { useHooksStore } from '../hooks-store' +import Run from '../run' +import { useStore } from '../store' import { formatWorkflowRunIdentifier } from '../utils' const Record = () => { @@ -21,8 +21,8 @@ const Record = () => { }, [handleUpdateWorkflowCanvas]) return ( - <div className='flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> - <div className='system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary'> + <div className="flex h-full w-[400px] flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"> + <div className="system-xl-semibold flex items-center justify-between p-4 pb-0 text-text-primary"> {`Test Run${formatWorkflowRunIdentifier(historyWorkflowData?.finished_at)}`} </div> <Run diff --git a/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx b/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx index a2b7e1afdd..47dc68687d 100644 --- a/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx @@ -1,15 +1,16 @@ -import React, { type FC, useCallback } from 'react' +import type { FC } from 'react' import { RiMoreFill } from '@remixicon/react' -import { VersionHistoryContextMenuOptions } from '../../../types' -import MenuItem from './menu-item' -import useContextMenu from './use-context-menu' +import React, { useCallback } from 'react' +import Button from '@/app/components/base/button' +import Divider from '@/app/components/base/divider' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import Divider from '@/app/components/base/divider' +import { VersionHistoryContextMenuOptions } from '../../../types' +import MenuItem from './menu-item' +import useContextMenu from './use-context-menu' export type ContextMenuProps = { isShowDelete: boolean @@ -33,7 +34,7 @@ const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => { return ( <PortalToFollowElem - placement={'bottom-end'} + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 0, @@ -42,13 +43,13 @@ const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => { onOpenChange={setOpen} > <PortalToFollowElemTrigger> - <Button size='small' className='px-1' onClick={handleClickTrigger}> - <RiMoreFill className='h-4 w-4' /> + <Button size="small" className="px-1" onClick={handleClickTrigger}> + <RiMoreFill className="h-4 w-4" /> </Button> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='flex w-[184px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'> - <div className='flex flex-col p-1'> + <PortalToFollowElemContent className="z-10"> + <div className="flex w-[184px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]"> + <div className="flex flex-col p-1"> { options.map((option) => { return ( @@ -64,8 +65,8 @@ const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => { { isShowDelete && ( <> - <Divider type='horizontal' className='my-0 h-px bg-divider-subtle' /> - <div className='p-1'> + <Divider type="horizontal" className="my-0 h-px bg-divider-subtle" /> + <div className="p-1"> <MenuItem item={deleteOperation} isDestructive diff --git a/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx b/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx index 61916b0200..a307056148 100644 --- a/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx @@ -1,5 +1,6 @@ -import React, { type FC } from 'react' +import type { FC } from 'react' import type { VersionHistoryContextMenuOptions } from '../../../types' +import React from 'react' import { cn } from '@/utils/classnames' type MenuItemProps = { @@ -29,7 +30,8 @@ const MenuItem: FC<MenuItemProps> = ({ <div className={cn( 'system-md-regular flex-1 text-text-primary', isDestructive && 'hover:text-text-destructive', - )}> + )} + > {item.name} </div> </div> diff --git a/web/app/components/workflow/panel/version-history-panel/context-menu/use-context-menu.ts b/web/app/components/workflow/panel/version-history-panel/context-menu/use-context-menu.ts index 242b77a5fa..caf7b9a14d 100644 --- a/web/app/components/workflow/panel/version-history-panel/context-menu/use-context-menu.ts +++ b/web/app/components/workflow/panel/version-history-panel/context-menu/use-context-menu.ts @@ -1,8 +1,8 @@ +import type { ContextMenuProps } from './index' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { VersionHistoryContextMenuOptions } from '../../../types' -import type { ContextMenuProps } from './index' import { useStore } from '@/app/components/workflow/store' +import { VersionHistoryContextMenuOptions } from '../../../types' const useContextMenu = (props: ContextMenuProps) => { const { @@ -24,18 +24,20 @@ const useContextMenu = (props: ContextMenuProps) => { }, isNamedVersion ? { - key: VersionHistoryContextMenuOptions.edit, - name: t('workflow.versionHistory.editVersionInfo'), - } + key: VersionHistoryContextMenuOptions.edit, + name: t('workflow.versionHistory.editVersionInfo'), + } : { - key: VersionHistoryContextMenuOptions.edit, - name: t('workflow.versionHistory.nameThisVersion'), - }, + key: VersionHistoryContextMenuOptions.edit, + name: t('workflow.versionHistory.nameThisVersion'), + }, // todo: pipeline support export specific version DSL - ...(!pipelineId ? [{ - key: VersionHistoryContextMenuOptions.exportDSL, - name: t('app.export'), - }] : []), + ...(!pipelineId + ? [{ + key: VersionHistoryContextMenuOptions.exportDSL, + name: t('app.export'), + }] + : []), { key: VersionHistoryContextMenuOptions.copyId, name: t('workflow.versionHistory.copyId'), diff --git a/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx b/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx index 8ba1494000..3ad7d0dc8a 100644 --- a/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx +++ b/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx @@ -1,8 +1,9 @@ -import React, { type FC } from 'react' -import Modal from '@/app/components/base/modal' +import type { FC } from 'react' import type { VersionHistory } from '@/types/workflow' +import React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' +import Modal from '@/app/components/base/modal' type DeleteConfirmModalProps = { isOpen: boolean @@ -19,24 +20,26 @@ const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({ }) => { const { t } = useTranslation() - return <Modal className='p-0' isShow={isOpen} onClose={onClose}> - <div className='flex flex-col gap-y-2 p-6 pb-4 '> - <div className='title-2xl-semi-bold text-text-primary'> - {`${t('common.operation.delete')} ${versionInfo.marked_name || t('workflow.versionHistory.defaultName')}`} + return ( + <Modal className="p-0" isShow={isOpen} onClose={onClose}> + <div className="flex flex-col gap-y-2 p-6 pb-4 "> + <div className="title-2xl-semi-bold text-text-primary"> + {`${t('common.operation.delete')} ${versionInfo.marked_name || t('workflow.versionHistory.defaultName')}`} + </div> + <p className="system-md-regular text-text-secondary"> + {t('workflow.versionHistory.deletionTip')} + </p> </div> - <p className='system-md-regular text-text-secondary'> - {t('workflow.versionHistory.deletionTip')} - </p> - </div> - <div className='flex items-center justify-end gap-x-2 p-6'> - <Button onClick={onClose}> - {t('common.operation.cancel')} - </Button> - <Button variant='warning' onClick={onDelete.bind(null, versionInfo.id)}> - {t('common.operation.delete')} - </Button> - </div> - </Modal> + <div className="flex items-center justify-end gap-x-2 p-6"> + <Button onClick={onClose}> + {t('common.operation.cancel')} + </Button> + <Button variant="warning" onClick={onDelete.bind(null, versionInfo.id)}> + {t('common.operation.delete')} + </Button> + </div> + </Modal> + ) } export default DeleteConfirmModal diff --git a/web/app/components/workflow/panel/version-history-panel/empty.tsx b/web/app/components/workflow/panel/version-history-panel/empty.tsx index e3f3a6e910..c020c076ad 100644 --- a/web/app/components/workflow/panel/version-history-panel/empty.tsx +++ b/web/app/components/workflow/panel/version-history-panel/empty.tsx @@ -1,7 +1,8 @@ -import Button from '@/app/components/base/button' +import type { FC } from 'react' import { RiHistoryLine } from '@remixicon/react' -import React, { type FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' type EmptyProps = { onResetFilter: () => void @@ -12,19 +13,21 @@ const Empty: FC<EmptyProps> = ({ }) => { const { t } = useTranslation() - return <div className='flex h-5/6 w-full flex-col justify-center gap-y-2'> - <div className='flex justify-center'> - <RiHistoryLine className='h-10 w-10 text-text-empty-state-icon' /> + return ( + <div className="flex h-5/6 w-full flex-col justify-center gap-y-2"> + <div className="flex justify-center"> + <RiHistoryLine className="h-10 w-10 text-text-empty-state-icon" /> + </div> + <div className="system-xs-regular flex justify-center text-text-tertiary"> + {t('workflow.versionHistory.filter.empty')} + </div> + <div className="flex justify-center"> + <Button size="small" onClick={onResetFilter}> + {t('workflow.versionHistory.filter.reset')} + </Button> + </div> </div> - <div className='system-xs-regular flex justify-center text-text-tertiary'> - {t('workflow.versionHistory.filter.empty')} - </div> - <div className='flex justify-center'> - <Button size='small' onClick={onResetFilter}> - {t('workflow.versionHistory.filter.reset')} - </Button> - </div> - </div> + ) } export default React.memo(Empty) diff --git a/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx b/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx index 4301a8e582..c9a7c28112 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx @@ -1,6 +1,7 @@ -import { RiCheckLine } from '@remixicon/react' -import React, { type FC } from 'react' +import type { FC } from 'react' import type { WorkflowVersionFilterOptions } from '../../../types' +import { RiCheckLine } from '@remixicon/react' +import React from 'react' type FilterItemProps = { item: { @@ -18,13 +19,13 @@ const FilterItem: FC<FilterItemProps> = ({ }) => { return ( <div - className='flex cursor-pointer items-center justify-between gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover' + className="flex cursor-pointer items-center justify-between gap-x-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover" onClick={() => { onClick(item.key) }} > - <div className='system-md-regular flex-1 text-text-primary'>{item.name}</div> - {isSelected && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />} + <div className="system-md-regular flex-1 text-text-primary">{item.name}</div> + {isSelected && <RiCheckLine className="h-4 w-4 shrink-0 text-text-accent" />} </div> ) } diff --git a/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx b/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx index 0eabd50702..6db331338b 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx @@ -1,4 +1,5 @@ -import React, { type FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' @@ -14,16 +15,16 @@ const FilterSwitch: FC<FilterSwitchProps> = ({ const { t } = useTranslation() return ( - <div className='flex items-center p-1'> - <div className='flex w-full items-center gap-x-1 px-2 py-1.5'> - <div className='system-md-regular flex-1 px-1 text-text-secondary'> + <div className="flex items-center p-1"> + <div className="flex w-full items-center gap-x-1 px-2 py-1.5"> + <div className="system-md-regular flex-1 px-1 text-text-secondary"> {t('workflow.versionHistory.filter.onlyShowNamedVersions')} </div> <Switch defaultValue={enabled} onChange={v => handleSwitch(v)} - size='md' - className='shrink-0' + size="md" + className="shrink-0" /> </div> </div> diff --git a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx index e37bbe2269..8def221926 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx @@ -1,16 +1,17 @@ -import React, { type FC, useCallback, useState } from 'react' +import type { FC } from 'react' import { RiFilter3Line } from '@remixicon/react' -import { WorkflowVersionFilterOptions } from '../../../types' -import { useFilterOptions } from './use-filter' -import FilterItem from './filter-item' -import FilterSwitch from './filter-switch' +import React, { useCallback, useState } from 'react' +import Divider from '@/app/components/base/divider' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Divider from '@/app/components/base/divider' import { cn } from '@/utils/classnames' +import { WorkflowVersionFilterOptions } from '../../../types' +import FilterItem from './filter-item' +import FilterSwitch from './filter-switch' +import { useFilterOptions } from './use-filter' type FilterProps = { filterValue: WorkflowVersionFilterOptions @@ -36,7 +37,7 @@ const Filter: FC<FilterProps> = ({ return ( <PortalToFollowElem - placement={'bottom-end'} + placement="bottom-end" offset={{ mainAxis: 4, crossAxis: 55, @@ -54,9 +55,9 @@ const Filter: FC<FilterProps> = ({ <RiFilter3Line className={cn('h-4 w-4', isFiltering ? 'text-text-accent' : ' text-text-tertiary')} /> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[12]'> - <div className='flex w-[248px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'> - <div className='flex flex-col p-1'> + <PortalToFollowElemContent className="z-[12]"> + <div className="flex w-[248px] flex-col rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]"> + <div className="flex flex-col p-1"> { options.map((option) => { return ( @@ -70,7 +71,7 @@ const Filter: FC<FilterProps> = ({ }) } </div> - <Divider type='horizontal' className='my-0 h-px bg-divider-subtle' /> + <Divider type="horizontal" className="my-0 h-px bg-divider-subtle" /> <FilterSwitch enabled={isOnlyShowNamedVersions} handleSwitch={handleSwitch} /> </div> </PortalToFollowElemContent> diff --git a/web/app/components/workflow/panel/version-history-panel/index.tsx b/web/app/components/workflow/panel/version-history-panel/index.tsx index eeb4f50720..0bdb608d94 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.tsx @@ -1,24 +1,24 @@ 'use client' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { VersionHistory } from '@/types/workflow' import { RiArrowDownDoubleLine, RiCloseLine, RiLoader2Line } from '@remixicon/react' import copy from 'copy-to-clipboard' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import VersionInfoModal from '@/app/components/app/app-publisher/version-info-modal' +import Divider from '@/app/components/base/divider' +import Toast from '@/app/components/base/toast' +import { useSelector as useAppContextSelector } from '@/context/app-context' +import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow' import { useDSL, useNodesSyncDraft, useWorkflowRun } from '../../hooks' +import { useHooksStore } from '../../hooks-store' import { useStore, useWorkflowStore } from '../../store' import { VersionHistoryContextMenuOptions, WorkflowVersionFilterOptions } from '../../types' -import VersionHistoryItem from './version-history-item' -import Filter from './filter' -import type { VersionHistory } from '@/types/workflow' -import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow' -import Divider from '@/app/components/base/divider' -import Loading from './loading' -import Empty from './empty' -import { useSelector as useAppContextSelector } from '@/context/app-context' -import RestoreConfirmModal from './restore-confirm-modal' import DeleteConfirmModal from './delete-confirm-modal' -import VersionInfoModal from '@/app/components/app/app-publisher/version-info-modal' -import Toast from '@/app/components/base/toast' -import { useHooksStore } from '../../hooks-store' +import Empty from './empty' +import Filter from './filter' +import Loading from './loading' +import RestoreConfirmModal from './restore-confirm-modal' +import VersionHistoryItem from './version-history-item' const HISTORY_PER_PAGE = 10 const INITIAL_PAGE = 1 @@ -222,87 +222,95 @@ export const VersionHistoryPanel = ({ }, [t, updateWorkflow, resetWorkflowVersionHistory, updateVersionUrl]) return ( - <div className='flex h-full w-[268px] flex-col rounded-l-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'> - <div className='flex items-center gap-x-2 px-4 pt-3'> - <div className='system-xl-semibold flex-1 py-1 text-text-primary'>{t('workflow.versionHistory.title')}</div> + <div className="flex h-full w-[268px] flex-col rounded-l-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5"> + <div className="flex items-center gap-x-2 px-4 pt-3"> + <div className="system-xl-semibold flex-1 py-1 text-text-primary">{t('workflow.versionHistory.title')}</div> <Filter filterValue={filterValue} isOnlyShowNamedVersions={isOnlyShowNamedVersions} onClickFilterItem={handleClickFilterItem} handleSwitch={handleSwitch} /> - <Divider type='vertical' className='mx-1 h-3.5' /> + <Divider type="vertical" className="mx-1 h-3.5" /> <div - className='flex h-6 w-6 cursor-pointer items-center justify-center p-0.5' + className="flex h-6 w-6 cursor-pointer items-center justify-center p-0.5" onClick={handleClose} > - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> <div className="flex h-0 flex-1 flex-col"> <div className="flex-1 overflow-y-auto px-3 py-2"> {(isFetching && !versionHistory?.pages?.length) ? ( - <Loading /> - ) + <Loading /> + ) : ( - <> - {versionHistory?.pages?.map((page, pageNumber) => ( - page.items?.map((item, idx) => { - const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 - return <VersionHistoryItem - key={item.id} - item={item} - currentVersion={currentVersion} - latestVersionId={latestVersionId || ''} - onClick={handleVersionClick} - handleClickMenuItem={handleClickMenuItem.bind(null, item)} - isLast={isLast} - /> - }) - ))} - {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( - <Empty onResetFilter={handleResetFilter} /> - )} - </> - )} + <> + {versionHistory?.pages?.map((page, pageNumber) => ( + page.items?.map((item, idx) => { + const isLast = pageNumber === versionHistory.pages.length - 1 && idx === page.items.length - 1 + return ( + <VersionHistoryItem + key={item.id} + item={item} + currentVersion={currentVersion} + latestVersionId={latestVersionId || ''} + onClick={handleVersionClick} + handleClickMenuItem={handleClickMenuItem.bind(null, item)} + isLast={isLast} + /> + ) + }) + ))} + {!isFetching && (!versionHistory?.pages?.length || !versionHistory.pages[0].items.length) && ( + <Empty onResetFilter={handleResetFilter} /> + )} + </> + )} </div> {hasNextPage && ( - <div className='p-2'> + <div className="p-2"> <div - className='flex cursor-pointer items-center gap-x-1' + className="flex cursor-pointer items-center gap-x-1" onClick={handleNextPage} > - <div className='item-center flex justify-center p-0.5'> + <div className="item-center flex justify-center p-0.5"> {isFetching - ? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' /> - : <RiArrowDownDoubleLine className='h-3.5 w-3.5 text-text-accent' />} + ? <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-text-accent" /> + : <RiArrowDownDoubleLine className="h-3.5 w-3.5 text-text-accent" />} </div> - <div className='system-xs-medium-uppercase py-[1px] text-text-accent'> + <div className="system-xs-medium-uppercase py-[1px] text-text-accent"> {t('workflow.common.loadMore')} </div> </div> </div> )} </div> - {restoreConfirmOpen && (<RestoreConfirmModal - isOpen={restoreConfirmOpen} - versionInfo={operatedItem!} - onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.restore)} - onRestore={handleRestore} - />)} - {deleteConfirmOpen && (<DeleteConfirmModal - isOpen={deleteConfirmOpen} - versionInfo={operatedItem!} - onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.delete)} - onDelete={handleDelete} - />)} - {editModalOpen && (<VersionInfoModal - isOpen={editModalOpen} - versionInfo={operatedItem} - onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.edit)} - onPublish={handleUpdateWorkflow} - />)} + {restoreConfirmOpen && ( + <RestoreConfirmModal + isOpen={restoreConfirmOpen} + versionInfo={operatedItem!} + onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.restore)} + onRestore={handleRestore} + /> + )} + {deleteConfirmOpen && ( + <DeleteConfirmModal + isOpen={deleteConfirmOpen} + versionInfo={operatedItem!} + onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.delete)} + onDelete={handleDelete} + /> + )} + {editModalOpen && ( + <VersionInfoModal + isOpen={editModalOpen} + versionInfo={operatedItem} + onClose={handleCancel.bind(null, VersionHistoryContextMenuOptions.edit)} + onPublish={handleUpdateWorkflow} + /> + )} </div> ) } diff --git a/web/app/components/workflow/panel/version-history-panel/loading/index.tsx b/web/app/components/workflow/panel/version-history-panel/loading/index.tsx index 2c4db667ea..a6dadcd652 100644 --- a/web/app/components/workflow/panel/version-history-panel/loading/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/loading/index.tsx @@ -10,10 +10,12 @@ const itemConfig = Array.from({ length: 8 }).map((_, index) => { }) const Loading = () => { - return <div className='relative w-full overflow-y-hidden'> - <div className='absolute left-0 top-0 z-10 h-full w-full bg-dataset-chunk-list-mask-bg' /> - {itemConfig.map((config, index) => <Item key={index} {...config} />)} - </div> + return ( + <div className="relative w-full overflow-y-hidden"> + <div className="absolute left-0 top-0 z-10 h-full w-full bg-dataset-chunk-list-mask-bg" /> + {itemConfig.map((config, index) => <Item key={index} {...config} />)} + </div> + ) } export default Loading diff --git a/web/app/components/workflow/panel/version-history-panel/loading/item.tsx b/web/app/components/workflow/panel/version-history-panel/loading/item.tsx index ff2d746801..c17d725fb3 100644 --- a/web/app/components/workflow/panel/version-history-panel/loading/item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/loading/item.tsx @@ -1,4 +1,5 @@ -import React, { type FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { cn } from '@/utils/classnames' type ItemProps = { @@ -15,18 +16,18 @@ const Item: FC<ItemProps> = ({ isLast, }) => { return ( - <div className='relative flex gap-x-1 p-2' > - {!isLast && <div className='absolute left-4 top-6 h-[calc(100%-0.75rem)] w-0.5 bg-divider-subtle' />} - <div className=' flex h-5 w-[18px] shrink-0 items-center justify-center'> - <div className='h-2 w-2 rounded-lg border-[2px] border-text-quaternary' /> + <div className="relative flex gap-x-1 p-2"> + {!isLast && <div className="absolute left-4 top-6 h-[calc(100%-0.75rem)] w-0.5 bg-divider-subtle" />} + <div className=" flex h-5 w-[18px] shrink-0 items-center justify-center"> + <div className="h-2 w-2 rounded-lg border-[2px] border-text-quaternary" /> </div> - <div className='flex grow flex-col gap-y-0.5'> - <div className='flex h-3.5 items-center'> + <div className="flex grow flex-col gap-y-0.5"> + <div className="flex h-3.5 items-center"> <div className={cn('h-2 w-full rounded-sm bg-text-quaternary opacity-20', titleWidth)} /> </div> { !isFirst && ( - <div className='flex h-3 items-center'> + <div className="flex h-3 items-center"> <div className={cn('h-1.5 w-full rounded-sm bg-text-quaternary opacity-20', releaseNotesWidth)} /> </div> ) diff --git a/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx b/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx index d8394d20c0..09bc5d79b4 100644 --- a/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx +++ b/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx @@ -1,8 +1,9 @@ -import React, { type FC } from 'react' -import Modal from '@/app/components/base/modal' +import type { FC } from 'react' import type { VersionHistory } from '@/types/workflow' +import React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' +import Modal from '@/app/components/base/modal' type RestoreConfirmModalProps = { isOpen: boolean @@ -19,24 +20,26 @@ const RestoreConfirmModal: FC<RestoreConfirmModalProps> = ({ }) => { const { t } = useTranslation() - return <Modal className='p-0' isShow={isOpen} onClose={onClose}> - <div className='flex flex-col gap-y-2 p-6 pb-4 '> - <div className='title-2xl-semi-bold text-text-primary'> - {`${t('workflow.common.restore')} ${versionInfo.marked_name || t('workflow.versionHistory.defaultName')}`} + return ( + <Modal className="p-0" isShow={isOpen} onClose={onClose}> + <div className="flex flex-col gap-y-2 p-6 pb-4 "> + <div className="title-2xl-semi-bold text-text-primary"> + {`${t('workflow.common.restore')} ${versionInfo.marked_name || t('workflow.versionHistory.defaultName')}`} + </div> + <p className="system-md-regular text-text-secondary"> + {t('workflow.versionHistory.restorationTip')} + </p> </div> - <p className='system-md-regular text-text-secondary'> - {t('workflow.versionHistory.restorationTip')} - </p> - </div> - <div className='flex items-center justify-end gap-x-2 p-6'> - <Button onClick={onClose}> - {t('common.operation.cancel')} - </Button> - <Button variant='primary' onClick={onRestore.bind(null, versionInfo)}> - {t('workflow.common.restore')} - </Button> - </div> - </Modal> + <div className="flex items-center justify-end gap-x-2 p-6"> + <Button onClick={onClose}> + {t('common.operation.cancel')} + </Button> + <Button variant="primary" onClick={onRestore.bind(null, versionInfo)}> + {t('workflow.common.restore')} + </Button> + </div> + </Modal> + ) } export default RestoreConfirmModal diff --git a/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx b/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx index 558e8ab720..7739d10af2 100644 --- a/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx @@ -1,10 +1,11 @@ -import React, { useEffect, useState } from 'react' -import dayjs from 'dayjs' -import { useTranslation } from 'react-i18next' -import ContextMenu from './context-menu' -import { cn } from '@/utils/classnames' +import type { VersionHistoryContextMenuOptions } from '../../types' import type { VersionHistory } from '@/types/workflow' -import { type VersionHistoryContextMenuOptions, WorkflowVersion } from '../../types' +import dayjs from 'dayjs' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' +import { WorkflowVersion } from '../../types' +import ContextMenu from './context-menu' type VersionHistoryItemProps = { item: VersionHistory @@ -80,38 +81,41 @@ const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ setOpen(true) }} > - {!isLast && <div className='absolute left-4 top-6 h-[calc(100%-0.75rem)] w-0.5 bg-divider-subtle' />} - <div className=' flex h-5 w-[18px] shrink-0 items-center justify-center'> + {!isLast && <div className="absolute left-4 top-6 h-[calc(100%-0.75rem)] w-0.5 bg-divider-subtle" />} + <div className=" flex h-5 w-[18px] shrink-0 items-center justify-center"> <div className={cn( 'h-2 w-2 rounded-lg border-[2px]', isSelected ? 'border-text-accent' : 'border-text-quaternary', - )}/> + )} + /> </div> - <div className='flex grow flex-col gap-y-0.5 overflow-hidden'> - <div className='mr-6 flex h-5 items-center gap-x-1'> + <div className="flex grow flex-col gap-y-0.5 overflow-hidden"> + <div className="mr-6 flex h-5 items-center gap-x-1"> <div className={cn( 'system-sm-semibold truncate py-[1px]', isSelected ? 'text-text-accent' : 'text-text-secondary', - )}> + )} + > {isDraft ? t('workflow.versionHistory.currentDraft') : item.marked_name || t('workflow.versionHistory.defaultName')} </div> {isLatest && ( - <div className='system-2xs-medium-uppercase flex h-5 shrink-0 items-center rounded-md border border-text-accent-secondary - bg-components-badge-bg-dimm px-[5px] text-text-accent-secondary'> + <div className="system-2xs-medium-uppercase flex h-5 shrink-0 items-center rounded-md border border-text-accent-secondary + bg-components-badge-bg-dimm px-[5px] text-text-accent-secondary" + > {t('workflow.versionHistory.latest')} </div> )} </div> { !isDraft && ( - <div className='system-xs-regular break-words text-text-secondary'> + <div className="system-xs-regular break-words text-text-secondary"> {item.marked_comment || ''} </div> ) } { !isDraft && ( - <div className='system-xs-regular truncate text-text-tertiary'> + <div className="system-xs-regular truncate text-text-tertiary"> {`${formatTime(item.created_at)} · ${item.created_by.name}`} </div> ) @@ -119,7 +123,7 @@ const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ </div> {/* Context Menu */} {!isDraft && isHovering && ( - <div className='absolute right-1 top-1'> + <div className="absolute right-1 top-1"> <ContextMenu isShowDelete={!isLatest} isNamedVersion={!!item.marked_name} diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index 0a702d5ce3..9daf42186f 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -1,31 +1,31 @@ +import { + RiClipboardLine, + RiCloseLine, +} from '@remixicon/react' +import copy from 'copy-to-clipboard' import { memo, useCallback, useEffect, useState, } from 'react' -import { - RiClipboardLine, - RiCloseLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' -import copy from 'copy-to-clipboard' -import ResultText from '../run/result-text' -import ResultPanel from '../run/result-panel' -import TracingPanel from '../run/tracing-panel' +import Button from '@/app/components/base/button' +import Loading from '@/app/components/base/loading' +import { cn } from '@/utils/classnames' +import Toast from '../../base/toast' import { useWorkflowInteractions, } from '../hooks' +import ResultPanel from '../run/result-panel' +import ResultText from '../run/result-text' +import TracingPanel from '../run/tracing-panel' import { useStore } from '../store' import { WorkflowRunningStatus, } from '../types' import { formatWorkflowRunIdentifier } from '../utils' -import Toast from '../../base/toast' import InputsPanel from './inputs-panel' -import { cn } from '@/utils/classnames' -import Loading from '@/app/components/base/loading' -import Button from '@/app/components/base/button' const WorkflowPreview = () => { const { t } = useTranslation() @@ -95,23 +95,22 @@ const WorkflowPreview = () => { }, [resize, stopResizing]) return ( - <div className={ - 'relative flex h-full flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl' - } - style={{ width: `${panelWidth}px` }} + <div + className="relative flex h-full flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl" + style={{ width: `${panelWidth}px` }} > <div className="absolute bottom-0 left-[3px] top-1/2 z-50 h-6 w-[3px] cursor-col-resize rounded bg-gray-300" onMouseDown={startResizing} /> - <div className='flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary'> + <div className="flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary"> {`Test Run${formatWorkflowRunIdentifier(workflowRunningData?.result.finished_at)}`} - <div className='cursor-pointer p-1' onClick={() => handleCancelDebugAndPreviewPanel()}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="cursor-pointer p-1" onClick={() => handleCancelDebugAndPreviewPanel()}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> - <div className='relative flex grow flex-col'> - <div className='flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4'> + <div className="relative flex grow flex-col"> + <div className="flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4"> {showInputsPanel && ( <div className={cn( @@ -119,7 +118,9 @@ const WorkflowPreview = () => { currentTab === 'INPUT' && '!border-[rgb(21,94,239)] text-text-secondary', )} onClick={() => switchTab('INPUT')} - >{t('runLog.input')}</div> + > + {t('runLog.input')} + </div> )} <div className={cn( @@ -132,7 +133,9 @@ const WorkflowPreview = () => { return switchTab('RESULT') }} - >{t('runLog.result')}</div> + > + {t('runLog.result')} + </div> <div className={cn( 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', @@ -144,7 +147,9 @@ const WorkflowPreview = () => { return switchTab('DETAIL') }} - >{t('runLog.detail')}</div> + > + {t('runLog.detail')} + </div> <div className={cn( 'mr-6 cursor-pointer border-b-2 border-transparent py-3 text-[13px] font-semibold leading-[18px] text-text-tertiary', @@ -156,12 +161,15 @@ const WorkflowPreview = () => { return switchTab('TRACING') }} - >{t('runLog.tracing')}</div> + > + {t('runLog.tracing')} + </div> </div> <div className={cn( 'h-0 grow overflow-y-auto rounded-b-2xl bg-components-panel-bg', (currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-background-section-burn', - )}> + )} + > {currentTab === 'INPUT' && showInputsPanel && ( <InputsPanel onRun={() => switchTab('RESULT')} /> )} @@ -184,8 +192,9 @@ const WorkflowPreview = () => { else copy(JSON.stringify(content)) Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) - }}> - <RiClipboardLine className='h-3.5 w-3.5' /> + }} + > + <RiClipboardLine className="h-3.5 w-3.5" /> <div>{t('common.operation.copy')}</div> </Button> )} @@ -211,18 +220,18 @@ const WorkflowPreview = () => { /> )} {currentTab === 'DETAIL' && !workflowRunningData?.result && ( - <div className='flex h-full items-center justify-center bg-components-panel-bg'> + <div className="flex h-full items-center justify-center bg-components-panel-bg"> <Loading /> </div> )} {currentTab === 'TRACING' && ( <TracingPanel - className='bg-background-section-burn' + className="bg-background-section-burn" list={workflowRunningData?.tracing || []} /> )} {currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && ( - <div className='flex h-full items-center justify-center !bg-background-section-burn'> + <div className="flex h-full items-center justify-center !bg-background-section-burn"> <Loading /> </div> )} diff --git a/web/app/components/workflow/plugin-dependency/hooks.ts b/web/app/components/workflow/plugin-dependency/hooks.ts index 1fa7af3dab..ff7b85fa09 100644 --- a/web/app/components/workflow/plugin-dependency/hooks.ts +++ b/web/app/components/workflow/plugin-dependency/hooks.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react' -import { useStore as usePluginDependenciesStore } from './store' -import { useMutationCheckDependencies } from '@/service/use-plugins' import { useCheckPipelineDependencies } from '@/service/use-pipeline' +import { useMutationCheckDependencies } from '@/service/use-plugins' +import { useStore as usePluginDependenciesStore } from './store' export const usePluginDependencies = () => { const { mutateAsync: checkWorkflowDependencies } = useMutationCheckDependencies() diff --git a/web/app/components/workflow/plugin-dependency/index.tsx b/web/app/components/workflow/plugin-dependency/index.tsx index 185722e1b7..69ee457527 100644 --- a/web/app/components/workflow/plugin-dependency/index.tsx +++ b/web/app/components/workflow/plugin-dependency/index.tsx @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import { useStore } from './store' import InstallBundle from '@/app/components/plugins/install-plugin/install-bundle' +import { useStore } from './store' const PluginDependency = () => { const dependencies = useStore(s => s.dependencies) diff --git a/web/app/components/workflow/plugin-dependency/store.ts b/web/app/components/workflow/plugin-dependency/store.ts index a8e1d8171a..71b8420697 100644 --- a/web/app/components/workflow/plugin-dependency/store.ts +++ b/web/app/components/workflow/plugin-dependency/store.ts @@ -1,5 +1,5 @@ -import { create } from 'zustand' import type { Dependency } from '@/app/components/plugins/types' +import { create } from 'zustand' type Shape = { dependencies: Dependency[] diff --git a/web/app/components/workflow/run/agent-log/agent-log-item.tsx b/web/app/components/workflow/run/agent-log/agent-log-item.tsx index fd375fde45..3e67f2a1b1 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-item.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-item.tsx @@ -1,20 +1,20 @@ -import { - useMemo, - useState, -} from 'react' +import type { AgentLogItemWithChildren } from '@/types/workflow' import { RiArrowRightSLine, RiListView, } from '@remixicon/react' -import { cn } from '@/utils/classnames' +import { + useMemo, + useState, +} from 'react' import Button from '@/app/components/base/button' -import type { AgentLogItemWithChildren } from '@/types/workflow' -import NodeStatusIcon from '@/app/components/workflow/nodes/_base/components/node-status-icon' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' -import BlockIcon from '@/app/components/workflow/block-icon' -import { BlockEnum } from '@/app/components/workflow/types' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' +import BlockIcon from '@/app/components/workflow/block-icon' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import NodeStatusIcon from '@/app/components/workflow/nodes/_base/components/node-status-icon' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' type AgentLogItemProps = { item: AgentLogItemWithChildren @@ -54,7 +54,7 @@ const AgentLogItem = ({ }, [status]) return ( - <div className='rounded-[10px] border-[0.5px] border-components-panel-border bg-background-default'> + <div className="rounded-[10px] border-[0.5px] border-components-panel-border bg-background-default"> <div className={cn( 'flex cursor-pointer items-center pb-2 pl-1.5 pr-3 pt-2', @@ -64,43 +64,46 @@ const AgentLogItem = ({ > { expanded - ? <RiArrowRightSLine className='h-4 w-4 shrink-0 rotate-90 text-text-quaternary' /> - : <RiArrowRightSLine className='h-4 w-4 shrink-0 text-text-quaternary' /> + ? <RiArrowRightSLine className="h-4 w-4 shrink-0 rotate-90 text-text-quaternary" /> + : <RiArrowRightSLine className="h-4 w-4 shrink-0 text-text-quaternary" /> } <BlockIcon - className='mr-1.5 shrink-0' + className="mr-1.5 shrink-0" type={toolIcon ? BlockEnum.Tool : BlockEnum.Agent} toolIcon={toolIcon} /> <div - className='system-sm-semibold-uppercase grow truncate text-text-secondary' + className="system-sm-semibold-uppercase grow truncate text-text-secondary" title={label} > {label} </div> { metadata?.elapsed_time && ( - <div className='system-xs-regular mr-2 shrink-0 text-text-tertiary'>{metadata?.elapsed_time?.toFixed(3)}s</div> + <div className="system-xs-regular mr-2 shrink-0 text-text-tertiary"> + {metadata?.elapsed_time?.toFixed(3)} + s + </div> ) } <NodeStatusIcon status={mergeStatus} /> </div> { expanded && ( - <div className='p-1 pt-0'> + <div className="p-1 pt-0"> { !!children?.length && ( <Button - className='mb-1 flex w-full items-center justify-between' - variant='tertiary' + className="mb-1 flex w-full items-center justify-between" + variant="tertiary" onClick={() => onShowAgentOrToolLog(item)} > - <div className='flex items-center'> - <RiListView className='mr-1 h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <div className="flex items-center"> + <RiListView className="mr-1 h-4 w-4 shrink-0 text-components-button-tertiary-text" /> {`${children.length} Action Logs`} </div> - <div className='flex'> - <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <div className="flex"> + <RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> </div> </Button> ) diff --git a/web/app/components/workflow/run/agent-log/agent-log-nav-more.tsx b/web/app/components/workflow/run/agent-log/agent-log-nav-more.tsx index 6062946ede..fda802150f 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-nav-more.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-nav-more.tsx @@ -1,12 +1,12 @@ -import { useState } from 'react' +import type { AgentLogItemWithChildren } from '@/types/workflow' import { RiMoreLine } from '@remixicon/react' +import { useState } from 'react' +import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import Button from '@/app/components/base/button' -import type { AgentLogItemWithChildren } from '@/types/workflow' type AgentLogNavMoreProps = { options: AgentLogItemWithChildren[] @@ -20,7 +20,7 @@ const AgentLogNavMore = ({ return ( <PortalToFollowElem - placement='bottom-start' + placement="bottom-start" offset={{ mainAxis: 2, crossAxis: -54, @@ -30,19 +30,19 @@ const AgentLogNavMore = ({ > <PortalToFollowElemTrigger> <Button - className='h-6 w-6' - variant='ghost-accent' + className="h-6 w-6" + variant="ghost-accent" > - <RiMoreLine className='h-4 w-4' /> + <RiMoreLine className="h-4 w-4" /> </Button> </PortalToFollowElemTrigger> <PortalToFollowElemContent> - <div className='w-[136px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'> + <div className="w-[136px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"> { options.map(option => ( <div key={option.message_id} - className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover" onClick={() => { onShowAgentOrToolLog(option) setOpen(false) diff --git a/web/app/components/workflow/run/agent-log/agent-log-nav.tsx b/web/app/components/workflow/run/agent-log/agent-log-nav.tsx index 9307f317e7..91abcdb99a 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-nav.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-nav.tsx @@ -1,8 +1,8 @@ +import type { AgentLogItemWithChildren } from '@/types/workflow' import { RiArrowLeftLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import AgentLogNavMore from './agent-log-nav-more' import Button from '@/app/components/base/button' -import type { AgentLogItemWithChildren } from '@/types/workflow' +import AgentLogNavMore from './agent-log-nav-more' type AgentLogNavProps = { agentOrToolLogItemStack: AgentLogItemWithChildren[] @@ -19,41 +19,41 @@ const AgentLogNav = ({ const end = agentOrToolLogItemStack.at(-1) return ( - <div className='flex h-8 items-center bg-components-panel-bg p-1 pr-3'> + <div className="flex h-8 items-center bg-components-panel-bg p-1 pr-3"> <Button - className='shrink-0 px-[5px]' - size='small' - variant='ghost-accent' + className="shrink-0 px-[5px]" + size="small" + variant="ghost-accent" onClick={() => { onShowAgentOrToolLog() }} > - <RiArrowLeftLine className='mr-1 h-3.5 w-3.5' /> + <RiArrowLeftLine className="mr-1 h-3.5 w-3.5" /> AGENT </Button> - <div className='system-xs-regular mx-0.5 shrink-0 text-divider-deep'>/</div> + <div className="system-xs-regular mx-0.5 shrink-0 text-divider-deep">/</div> { agentOrToolLogItemStackLength > 1 ? ( - <Button - className='shrink-0 px-[5px]' - size='small' - variant='ghost-accent' - onClick={() => onShowAgentOrToolLog(first)} - > - {t('workflow.nodes.agent.strategy.label')} - </Button> - ) + <Button + className="shrink-0 px-[5px]" + size="small" + variant="ghost-accent" + onClick={() => onShowAgentOrToolLog(first)} + > + {t('workflow.nodes.agent.strategy.label')} + </Button> + ) : ( - <div className='system-xs-medium-uppercase flex items-center px-[5px] text-text-tertiary'> - {t('workflow.nodes.agent.strategy.label')} - </div> - ) + <div className="system-xs-medium-uppercase flex items-center px-[5px] text-text-tertiary"> + {t('workflow.nodes.agent.strategy.label')} + </div> + ) } { !!mid.length && ( <> - <div className='system-xs-regular mx-0.5 shrink-0 text-divider-deep'>/</div> + <div className="system-xs-regular mx-0.5 shrink-0 text-divider-deep">/</div> <AgentLogNavMore options={mid} onShowAgentOrToolLog={onShowAgentOrToolLog} @@ -64,8 +64,8 @@ const AgentLogNav = ({ { !!end && agentOrToolLogItemStackLength > 1 && ( <> - <div className='system-xs-regular mx-0.5 shrink-0 text-divider-deep'>/</div> - <div className='system-xs-medium-uppercase flex items-center px-[5px] text-text-tertiary'> + <div className="system-xs-regular mx-0.5 shrink-0 text-divider-deep">/</div> + <div className="system-xs-medium-uppercase flex items-center px-[5px] text-text-tertiary"> {end.label} </div> </> diff --git a/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx index 85b37d72d6..e5381079dc 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx @@ -1,9 +1,9 @@ -import { RiArrowRightLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' import type { AgentLogItemWithChildren, NodeTracing, } from '@/types/workflow' +import { RiArrowRightLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' type AgentLogTriggerProps = { nodeInfo: NodeTracing @@ -19,27 +19,27 @@ const AgentLogTrigger = ({ return ( <div - className='cursor-pointer rounded-[10px] bg-components-button-tertiary-bg' + className="cursor-pointer rounded-[10px] bg-components-button-tertiary-bg" onClick={() => { onShowAgentOrToolLog({ message_id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren) }} > - <div className='system-2xs-medium-uppercase flex items-center px-3 pt-2 text-text-tertiary'> + <div className="system-2xs-medium-uppercase flex items-center px-3 pt-2 text-text-tertiary"> {t('workflow.nodes.agent.strategy.label')} </div> - <div className='flex items-center pb-1.5 pl-3 pr-2 pt-1'> + <div className="flex items-center pb-1.5 pl-3 pr-2 pt-1"> { agentStrategy && ( - <div className='system-xs-medium grow text-text-secondary'> + <div className="system-xs-medium grow text-text-secondary"> {agentStrategy} </div> ) } <div - className='system-xs-regular-uppercase flex shrink-0 cursor-pointer items-center px-[1px] text-text-tertiary' + className="system-xs-regular-uppercase flex shrink-0 cursor-pointer items-center px-[1px] text-text-tertiary" > {t('runLog.detail')} - <RiArrowRightLine className='ml-0.5 h-3.5 w-3.5' /> + <RiArrowRightLine className="ml-0.5 h-3.5 w-3.5" /> </div> </div> </div> diff --git a/web/app/components/workflow/run/agent-log/agent-result-panel.tsx b/web/app/components/workflow/run/agent-log/agent-result-panel.tsx index 933f08b9da..6c9a3c0975 100644 --- a/web/app/components/workflow/run/agent-log/agent-result-panel.tsx +++ b/web/app/components/workflow/run/agent-log/agent-result-panel.tsx @@ -1,8 +1,8 @@ +import type { AgentLogItemWithChildren } from '@/types/workflow' import { RiAlertFill } from '@remixicon/react' import { useTranslation } from 'react-i18next' import AgentLogItem from './agent-log-item' import AgentLogNav from './agent-log-nav' -import type { AgentLogItemWithChildren } from '@/types/workflow' type AgentResultPanelProps = { agentOrToolLogItemStack: AgentLogItemWithChildren[] @@ -19,35 +19,34 @@ const AgentResultPanel = ({ const list = agentOrToolLogListMap[top.message_id] return ( - <div className='overflow-y-auto bg-background-section'> + <div className="overflow-y-auto bg-background-section"> <AgentLogNav agentOrToolLogItemStack={agentOrToolLogItemStack} onShowAgentOrToolLog={onShowAgentOrToolLog} /> - { - <div className='space-y-1 p-2'> - { - list.map(item => ( - <AgentLogItem - key={item.message_id} - item={item} - onShowAgentOrToolLog={onShowAgentOrToolLog} - /> - )) - } - </div> - } + <div className="space-y-1 p-2"> + { + list.map(item => ( + <AgentLogItem + key={item.message_id} + item={item} + onShowAgentOrToolLog={onShowAgentOrToolLog} + /> + )) + } + </div> { top.hasCircle && ( - <div className='mt-1 flex items-center rounded-xl border border-components-panel-border bg-components-panel-bg-blur px-3 pr-2 shadow-md'> + <div className="mt-1 flex items-center rounded-xl border border-components-panel-border bg-components-panel-bg-blur px-3 pr-2 shadow-md"> <div - className='absolute inset-0 rounded-xl opacity-[0.4]' + className="absolute inset-0 rounded-xl opacity-[0.4]" style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)', }} - ></div> - <RiAlertFill className='mr-1.5 h-4 w-4 text-text-warning-secondary' /> - <div className='system-xs-medium text-text-primary'> + > + </div> + <RiAlertFill className="mr-1.5 h-4 w-4 text-text-warning-secondary" /> + <div className="system-xs-medium text-text-primary"> {t('runLog.circularInvocationTip')} </div> </div> diff --git a/web/app/components/workflow/run/hooks.ts b/web/app/components/workflow/run/hooks.ts index df54aa0240..593836f5b3 100644 --- a/web/app/components/workflow/run/hooks.ts +++ b/web/app/components/workflow/run/hooks.ts @@ -1,9 +1,3 @@ -import { - useCallback, - useRef, - useState, -} from 'react' -import { useBoolean } from 'ahooks' import type { AgentLogItemWithChildren, IterationDurationMap, @@ -11,6 +5,12 @@ import type { LoopVariableMap, NodeTracing, } from '@/types/workflow' +import { useBoolean } from 'ahooks' +import { + useCallback, + useRef, + useState, +} from 'react' export const useLogs = () => { const [showRetryDetail, { diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 9162429c4d..1bff24e2cc 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -1,20 +1,20 @@ 'use client' import type { FC } from 'react' +import type { WorkflowRunDetailResponse } from '@/models/log' +import type { NodeTracing } from '@/types/workflow' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import Loading from '@/app/components/base/loading' +import { ToastContext } from '@/app/components/base/toast' +import { WorkflowRunningStatus } from '@/app/components/workflow/types' +import { fetchRunDetail, fetchTracingList } from '@/service/log' +import { cn } from '@/utils/classnames' +import { useStore } from '../store' import OutputPanel from './output-panel' import ResultPanel from './result-panel' import StatusPanel from './status' -import { WorkflowRunningStatus } from '@/app/components/workflow/types' import TracingPanel from './tracing-panel' -import { cn } from '@/utils/classnames' -import { ToastContext } from '@/app/components/base/toast' -import Loading from '@/app/components/base/loading' -import { fetchRunDetail, fetchTracingList } from '@/service/log' -import type { NodeTracing } from '@/types/workflow' -import type { WorkflowRunDetailResponse } from '@/models/log' -import { useStore } from '../store' export type RunProps = { hideResult?: boolean @@ -118,9 +118,9 @@ const RunPanel: FC<RunProps> = ({ }, [loading]) return ( - <div className='relative flex grow flex-col'> + <div className="relative flex grow flex-col"> {/* tab */} - <div className='flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4'> + <div className="flex shrink-0 items-center border-b-[0.5px] border-divider-subtle px-4"> {!hideResult && ( <div className={cn( @@ -128,7 +128,9 @@ const RunPanel: FC<RunProps> = ({ currentTab === 'RESULT' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('RESULT')} - >{t('runLog.result')}</div> + > + {t('runLog.result')} + </div> )} <div className={cn( @@ -136,19 +138,23 @@ const RunPanel: FC<RunProps> = ({ currentTab === 'DETAIL' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('DETAIL')} - >{t('runLog.detail')}</div> + > + {t('runLog.detail')} + </div> <div className={cn( 'system-sm-semibold-uppercase mr-6 cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary', currentTab === 'TRACING' && '!border-util-colors-blue-brand-blue-brand-600 text-text-primary', )} onClick={() => switchTab('TRACING')} - >{t('runLog.tracing')}</div> + > + {t('runLog.tracing')} + </div> </div> {/* panel detail */} <div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-xl bg-components-panel-bg')}> {loading && ( - <div className='flex h-full items-center justify-center bg-components-panel-bg'> + <div className="flex h-full items-center justify-center bg-components-panel-bg"> <Loading /> </div> )} @@ -185,7 +191,7 @@ const RunPanel: FC<RunProps> = ({ )} {!loading && currentTab === 'TRACING' && ( <TracingPanel - className='bg-background-section-burn' + className="bg-background-section-burn" list={list} /> )} diff --git a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx index d1926da15e..a4286d94b0 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-log-trigger.tsx @@ -1,12 +1,12 @@ -import { useTranslation } from 'react-i18next' -import { RiArrowRightSLine } from '@remixicon/react' -import Button from '@/app/components/base/button' import type { IterationDurationMap, NodeTracing, } from '@/types/workflow' -import { NodeRunningStatus } from '@/app/components/workflow/types' +import { RiArrowRightSLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { Iteration } from '@/app/components/base/icons/src/vender/workflow' +import { NodeRunningStatus } from '@/app/components/workflow/types' type IterationLogTriggerProps = { nodeInfo: NodeTracing @@ -21,7 +21,8 @@ const IterationLogTrigger = ({ const { t } = useTranslation() const filterNodesForInstance = (key: string): NodeTracing[] => { - if (!allExecutions) return [] + if (!allExecutions) + return [] const parallelNodes = allExecutions.filter(exec => exec.execution_metadata?.parallel_mode_run_id === key, @@ -117,9 +118,10 @@ const IterationLogTrigger = ({ // Find all failed iteration nodes allExecutions.forEach((exec) => { if (exec.execution_metadata?.iteration_id === nodeInfo.node_id - && exec.status === NodeRunningStatus.Failed - && exec.execution_metadata?.iteration_index !== undefined) + && exec.status === NodeRunningStatus.Failed + && exec.execution_metadata?.iteration_index !== undefined) { failedIterationIndices.add(exec.execution_metadata.iteration_index) + } }) } @@ -129,17 +131,20 @@ const IterationLogTrigger = ({ return ( <Button - className='flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover' + className="flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover" onClick={handleOnShowIterationDetail} > - <Iteration className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> - <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.iteration.iteration', { count: displayIterationCount })}{errorCount > 0 && ( - <> - {t('workflow.nodes.iteration.comma')} - {t('workflow.nodes.iteration.error', { count: errorCount })} - </> - )}</div> - <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <Iteration className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> + <div className="system-sm-medium flex-1 text-left text-components-button-tertiary-text"> + {t('workflow.nodes.iteration.iteration', { count: displayIterationCount })} + {errorCount > 0 && ( + <> + {t('workflow.nodes.iteration.comma')} + {t('workflow.nodes.iteration.error', { count: errorCount })} + </> + )} + </div> + <RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> </Button> ) } diff --git a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx index 60293dbd2b..5933e897bd 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx @@ -1,18 +1,19 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { IterationDurationMap, NodeTracing } from '@/types/workflow' import { RiArrowLeftLine, RiArrowRightSLine, RiErrorWarningLine, RiLoader2Line, } from '@remixicon/react' -import { NodeRunningStatus } from '@/app/components/workflow/types' -import TracingPanel from '@/app/components/workflow/run/tracing-panel' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import { Iteration } from '@/app/components/base/icons/src/vender/workflow' +import TracingPanel from '@/app/components/workflow/run/tracing-panel' +import { NodeRunningStatus } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' -import type { IterationDurationMap, NodeTracing } from '@/types/workflow' + const i18nPrefix = 'workflow.singleRun' type Props = { @@ -48,15 +49,15 @@ const IterationResultPanel: FC<Props> = ({ const hasDurationMap = iterDurationMap && Object.keys(iterDurationMap).length !== 0 if (hasFailed) - return <RiErrorWarningLine className='h-4 w-4 text-text-destructive' /> + return <RiErrorWarningLine className="h-4 w-4 text-text-destructive" /> if (isRunning) - return <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-primary-600' /> + return <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-primary-600" /> return ( <> {hasDurationMap && ( - <div className='system-xs-regular text-text-tertiary'> + <div className="system-xs-regular text-text-tertiary"> {countIterDuration(iteration, iterDurationMap)} </div> )} @@ -71,20 +72,20 @@ const IterationResultPanel: FC<Props> = ({ } return ( - <div className='bg-components-panel-bg'> + <div className="bg-components-panel-bg"> <div - className='flex h-8 cursor-pointer items-center border-b-[0.5px] border-b-divider-regular px-4 text-text-accent-secondary' + className="flex h-8 cursor-pointer items-center border-b-[0.5px] border-b-divider-regular px-4 text-text-accent-secondary" onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() onBack() }} > - <RiArrowLeftLine className='mr-1 h-4 w-4' /> - <div className='system-sm-medium'>{t(`${i18nPrefix}.back`)}</div> + <RiArrowLeftLine className="mr-1 h-4 w-4" /> + <div className="system-sm-medium">{t(`${i18nPrefix}.back`)}</div> </div> {/* List */} - <div className='bg-components-panel-bg p-2'> + <div className="bg-components-panel-bg p-2"> {list.map((iteration, index) => ( <div key={index} className={cn('mb-1 overflow-hidden rounded-xl border-none bg-background-section-burn')}> <div @@ -96,27 +97,33 @@ const IterationResultPanel: FC<Props> = ({ onClick={() => toggleIteration(index)} > <div className={cn('flex grow items-center gap-2')}> - <div className='flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500'> - <Iteration className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500"> + <Iteration className="h-3 w-3 text-text-primary-on-surface" /> </div> - <span className='system-sm-semibold-uppercase grow text-text-primary'> - {t(`${i18nPrefix}.iteration`)} {index + 1} + <span className="system-sm-semibold-uppercase grow text-text-primary"> + {t(`${i18nPrefix}.iteration`)} + {' '} + {index + 1} </span> {iterationStatusShow(index, iteration, iterDurationMap)} </div> </div> - {expandedIterations[index] && <div - className="h-px grow bg-divider-subtle" - ></div>} + {expandedIterations[index] && ( + <div + className="h-px grow bg-divider-subtle" + > + </div> + )} <div className={cn( 'transition-all duration-200', expandedIterations[index] ? 'opacity-100' : 'max-h-0 overflow-hidden opacity-0', - )}> + )} + > <TracingPanel list={iteration} - className='bg-background-section-burn' + className="bg-background-section-burn" /> </div> </div> diff --git a/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx b/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx index b086312baf..7280c29d05 100644 --- a/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx +++ b/web/app/components/workflow/run/loop-log/loop-log-trigger.tsx @@ -1,11 +1,11 @@ -import { useTranslation } from 'react-i18next' -import { RiArrowRightSLine } from '@remixicon/react' -import Button from '@/app/components/base/button' import type { LoopDurationMap, LoopVariableMap, NodeTracing, } from '@/types/workflow' +import { RiArrowRightSLine } from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { Loop } from '@/app/components/base/icons/src/vender/workflow' type LoopLogTriggerProps = { @@ -21,7 +21,8 @@ const LoopLogTrigger = ({ const { t } = useTranslation() const filterNodesForInstance = (key: string): NodeTracing[] => { - if (!allExecutions) return [] + if (!allExecutions) + return [] const parallelNodes = allExecutions.filter(exec => exec.execution_metadata?.parallel_mode_run_id === key, @@ -90,17 +91,20 @@ const LoopLogTrigger = ({ return ( <Button - className='flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover' + className="flex w-full cursor-pointer items-center gap-2 self-stretch rounded-lg border-none bg-components-button-tertiary-bg-hover px-3 py-2 hover:bg-components-button-tertiary-bg-hover" onClick={handleOnShowLoopDetail} > - <Loop className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> - <div className='system-sm-medium flex-1 text-left text-components-button-tertiary-text'>{t('workflow.nodes.loop.loop', { count: displayLoopCount })}{errorCount > 0 && ( - <> - {t('workflow.nodes.loop.comma')} - {t('workflow.nodes.loop.error', { count: errorCount })} - </> - )}</div> - <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <Loop className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> + <div className="system-sm-medium flex-1 text-left text-components-button-tertiary-text"> + {t('workflow.nodes.loop.loop', { count: displayLoopCount })} + {errorCount > 0 && ( + <> + {t('workflow.nodes.loop.comma')} + {t('workflow.nodes.loop.error', { count: errorCount })} + </> + )} + </div> + <RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> </Button> ) } diff --git a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx index 5ce2101d63..758148d8c1 100644 --- a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx @@ -1,20 +1,21 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { LoopDurationMap, LoopVariableMap, NodeTracing } from '@/types/workflow' import { RiArrowLeftLine, RiArrowRightSLine, RiErrorWarningLine, RiLoader2Line, } from '@remixicon/react' -import { NodeRunningStatus } from '@/app/components/workflow/types' -import TracingPanel from '@/app/components/workflow/run/tracing-panel' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import { Loop } from '@/app/components/base/icons/src/vender/workflow' -import { cn } from '@/utils/classnames' -import type { LoopDurationMap, LoopVariableMap, NodeTracing } from '@/types/workflow' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import TracingPanel from '@/app/components/workflow/run/tracing-panel' +import { NodeRunningStatus } from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' + const i18nPrefix = 'workflow.singleRun' type Props = { @@ -54,15 +55,15 @@ const LoopResultPanel: FC<Props> = ({ const hasDurationMap = loopDurationMap && Object.keys(loopDurationMap).length !== 0 if (hasFailed) - return <RiErrorWarningLine className='h-4 w-4 text-text-destructive' /> + return <RiErrorWarningLine className="h-4 w-4 text-text-destructive" /> if (isRunning) - return <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-primary-600' /> + return <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-primary-600" /> return ( <> {hasDurationMap && ( - <div className='system-xs-regular text-text-tertiary'> + <div className="system-xs-regular text-text-tertiary"> {countLoopDuration(loop, loopDurationMap)} </div> )} @@ -77,20 +78,20 @@ const LoopResultPanel: FC<Props> = ({ } return ( - <div className='bg-components-panel-bg'> + <div className="bg-components-panel-bg"> <div - className='flex h-8 cursor-pointer items-center border-b-[0.5px] border-b-divider-regular px-4 text-text-accent-secondary' + className="flex h-8 cursor-pointer items-center border-b-[0.5px] border-b-divider-regular px-4 text-text-accent-secondary" onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() onBack() }} > - <RiArrowLeftLine className='mr-1 h-4 w-4' /> - <div className='system-sm-medium'>{t(`${i18nPrefix}.back`)}</div> + <RiArrowLeftLine className="mr-1 h-4 w-4" /> + <div className="system-sm-medium">{t(`${i18nPrefix}.back`)}</div> </div> {/* List */} - <div className='bg-components-panel-bg p-2'> + <div className="bg-components-panel-bg p-2"> {list.map((loop, index) => ( <div key={index} className={cn('mb-1 overflow-hidden rounded-xl border-none bg-background-section-burn')}> <div @@ -102,27 +103,33 @@ const LoopResultPanel: FC<Props> = ({ onClick={() => toggleLoop(index)} > <div className={cn('flex grow items-center gap-2')}> - <div className='flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500'> - <Loop className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500"> + <Loop className="h-3 w-3 text-text-primary-on-surface" /> </div> - <span className='system-sm-semibold-uppercase grow text-text-primary'> - {t(`${i18nPrefix}.loop`)} {index + 1} + <span className="system-sm-semibold-uppercase grow text-text-primary"> + {t(`${i18nPrefix}.loop`)} + {' '} + {index + 1} </span> {loopStatusShow(index, loop, loopDurationMap)} </div> </div> - {expandedLoops[index] && <div - className="h-px grow bg-divider-subtle" - ></div>} + {expandedLoops[index] && ( + <div + className="h-px grow bg-divider-subtle" + > + </div> + )} <div className={cn( 'transition-all duration-200', expandedLoops[index] ? 'opacity-100' : 'max-h-0 overflow-hidden opacity-0', - )}> + )} + > { loopVariableMap?.[index] && ( - <div className='p-2 pb-0'> + <div className="p-2 pb-0"> <CodeEditor readOnly title={<div>{t('workflow.nodes.loop.loopVariables').toLocaleUpperCase()}</div>} @@ -136,7 +143,7 @@ const LoopResultPanel: FC<Props> = ({ } <TracingPanel list={loop} - className='bg-background-section-burn' + className="bg-background-section-burn" /> </div> </div> diff --git a/web/app/components/workflow/run/loop-result-panel.tsx b/web/app/components/workflow/run/loop-result-panel.tsx index e87e8e54f7..61ba6db115 100644 --- a/web/app/components/workflow/run/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-result-panel.tsx @@ -1,16 +1,16 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { NodeTracing } from '@/types/workflow' import { RiArrowRightSLine, RiCloseLine, } from '@remixicon/react' -import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows' -import TracingPanel from './tracing-panel' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { cn } from '@/utils/classnames' -import type { NodeTracing } from '@/types/workflow' +import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows' +import TracingPanel from './tracing-panel' const i18nPrefix = 'workflow.singleRun' @@ -40,17 +40,17 @@ const LoopResultPanel: FC<Props> = ({ const main = ( <> <div className={cn(!noWrap && 'shrink-0 ', 'px-4 pt-3')}> - <div className='flex h-8 shrink-0 items-center justify-between'> - <div className='system-xl-semibold truncate text-text-primary'> + <div className="flex h-8 shrink-0 items-center justify-between"> + <div className="system-xl-semibold truncate text-text-primary"> {t(`${i18nPrefix}.testRunLoop`)} </div> - <div className='ml-2 shrink-0 cursor-pointer p-1' onClick={onHide}> - <RiCloseLine className='h-4 w-4 text-text-tertiary' /> + <div className="ml-2 shrink-0 cursor-pointer p-1" onClick={onHide}> + <RiCloseLine className="h-4 w-4 text-text-tertiary" /> </div> </div> - <div className='flex cursor-pointer items-center space-x-1 py-2 text-text-accent-secondary' onClick={onBack}> - <ArrowNarrowLeft className='h-4 w-4' /> - <div className='system-sm-medium'>{t(`${i18nPrefix}.back`)}</div> + <div className="flex cursor-pointer items-center space-x-1 py-2 text-text-accent-secondary" onClick={onBack}> + <ArrowNarrowLeft className="h-4 w-4" /> + <div className="system-sm-medium">{t(`${i18nPrefix}.back`)}</div> </div> </div> {/* List */} @@ -66,30 +66,37 @@ const LoopResultPanel: FC<Props> = ({ onClick={() => toggleLoop(index)} > <div className={cn('flex grow items-center gap-2')}> - <div className='flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500'> - <Loop className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-4 w-4 shrink-0 items-center justify-center rounded-[5px] border-divider-subtle bg-util-colors-cyan-cyan-500"> + <Loop className="h-3 w-3 text-text-primary-on-surface" /> </div> - <span className='system-sm-semibold-uppercase grow text-text-primary'> - {t(`${i18nPrefix}.loop`)} {index + 1} + <span className="system-sm-semibold-uppercase grow text-text-primary"> + {t(`${i18nPrefix}.loop`)} + {' '} + {index + 1} </span> <RiArrowRightSLine className={cn( 'h-4 w-4 shrink-0 text-text-tertiary transition-transform duration-200', expandedLoops[index] && 'rotate-90', - )} /> + )} + /> </div> </div> - {expandedLoops[index] && <div - className="h-px grow bg-divider-subtle" - ></div>} + {expandedLoops[index] && ( + <div + className="h-px grow bg-divider-subtle" + > + </div> + )} <div className={cn( 'transition-all duration-200', expandedLoops[index] ? 'opacity-100' : 'max-h-0 overflow-hidden opacity-0', - )}> + )} + > <TracingPanel list={loop} - className='bg-background-section-burn' + className="bg-background-section-burn" /> </div> @@ -109,16 +116,16 @@ const LoopResultPanel: FC<Props> = ({ return ( <div - className='absolute inset-0 z-10 rounded-2xl pt-10' + className="absolute inset-0 z-10 rounded-2xl pt-10" style={{ backgroundColor: 'rgba(16, 24, 40, 0.20)', }} onClick={handleNotBubble} > - <div className='flex h-full flex-col rounded-2xl bg-components-panel-bg'> + <div className="flex h-full flex-col rounded-2xl bg-components-panel-bg"> {main} </div> - </div > + </div> ) } export default React.memo(LoopResultPanel) diff --git a/web/app/components/workflow/run/meta.tsx b/web/app/components/workflow/run/meta.tsx index 13c35a42b8..20a03a2c5a 100644 --- a/web/app/components/workflow/run/meta.tsx +++ b/web/app/components/workflow/run/meta.tsx @@ -26,14 +26,14 @@ const MetaData: FC<Props> = ({ const { formatTime } = useTimestamp() return ( - <div className='relative'> - <div className='system-xs-medium-uppercase h-6 py-1 text-text-tertiary'>{t('runLog.meta.title')}</div> - <div className='py-1'> - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.status')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="relative"> + <div className="system-xs-medium-uppercase h-6 py-1 text-text-tertiary">{t('runLog.meta.title')}</div> + <div className="py-1"> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.status')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-16 rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-16 rounded-sm bg-text-quaternary" /> )} {status === 'succeeded' && ( <span>SUCCESS</span> @@ -52,44 +52,44 @@ const MetaData: FC<Props> = ({ )} </div> </div> - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.executor')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.executor')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-[88px] rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-[88px] rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{executor || 'N/A'}</span> )} </div> </div> - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.startTime')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.startTime')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-[72px] rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-[72px] rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{startTime ? formatTime(startTime, t('appLog.dateTimeFormat') as string) : '-'}</span> )} </div> </div> - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.time')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.time')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-[72px] rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-[72px] rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{time ? `${time.toFixed(3)}s` : '-'}</span> )} </div> </div> - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.tokens')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.tokens')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-[48px] rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-[48px] rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{`${tokens || 0} Tokens`}</span> @@ -97,11 +97,11 @@ const MetaData: FC<Props> = ({ </div> </div> {showSteps && ( - <div className='flex'> - <div className='system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary'>{t('runLog.meta.steps')}</div> - <div className='system-xs-regular grow px-2 py-1.5 text-text-secondary'> + <div className="flex"> + <div className="system-xs-regular w-[104px] shrink-0 truncate px-2 py-1.5 text-text-tertiary">{t('runLog.meta.steps')}</div> + <div className="system-xs-regular grow px-2 py-1.5 text-text-secondary"> {status === 'running' && ( - <div className='my-1 h-2 w-[24px] rounded-sm bg-text-quaternary'/> + <div className="my-1 h-2 w-[24px] rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{steps}</span> diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 6482433cde..d4afb59a91 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -1,24 +1,5 @@ 'use client' -import { useTranslation } from 'react-i18next' import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { - RiAlertFill, - RiArrowRightSLine, - RiCheckboxCircleFill, - RiErrorWarningLine, - RiLoader2Line, -} from '@remixicon/react' -import BlockIcon from '../block-icon' -import { BlockEnum } from '../types' -import { RetryLogTrigger } from './retry-log' -import { IterationLogTrigger } from './iteration-log' -import { LoopLogTrigger } from './loop-log' -import { AgentLogTrigger } from './agent-log' -import { cn } from '@/utils/classnames' -import StatusContainer from '@/app/components/workflow/run/status-container' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import type { AgentLogItemWithChildren, IterationDurationMap, @@ -26,11 +7,30 @@ import type { LoopVariableMap, NodeTracing, } from '@/types/workflow' +import { + RiAlertFill, + RiArrowRightSLine, + RiCheckboxCircleFill, + RiErrorWarningLine, + RiLoader2Line, +} from '@remixicon/react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Tooltip from '@/app/components/base/tooltip' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import StatusContainer from '@/app/components/workflow/run/status-container' import { hasRetryNode } from '@/app/components/workflow/utils' import { useDocLink } from '@/context/i18n' -import Tooltip from '@/app/components/base/tooltip' +import { cn } from '@/utils/classnames' +import BlockIcon from '../block-icon' +import { BlockEnum } from '../types' import LargeDataAlert from '../variable-inspect/large-data-alert' +import { AgentLogTrigger } from './agent-log' +import { IterationLogTrigger } from './iteration-log' +import { LoopLogTrigger } from './loop-log' +import { RetryLogTrigger } from './retry-log' type Props = { className?: string @@ -113,7 +113,7 @@ const NodePanel: FC<Props> = ({ return ( <div className={cn('px-2 py-1', className)}> - <div className='group rounded-[10px] border border-components-panel-border bg-background-default shadow-xs transition-all hover:shadow-md'> + <div className="group rounded-[10px] border border-components-panel-border bg-background-default shadow-xs transition-all hover:shadow-md"> <div className={cn( 'flex cursor-pointer items-center pl-1 pr-3', @@ -133,22 +133,28 @@ const NodePanel: FC<Props> = ({ <BlockIcon size={inMessage ? 'xs' : 'sm'} className={cn('mr-2 shrink-0', inMessage && '!mr-1')} type={nodeInfo.node_type} toolIcon={nodeInfo.extras?.icon || nodeInfo.extras} /> <Tooltip popupContent={ - <div className='max-w-xs'>{nodeInfo.title}</div> + <div className="max-w-xs">{nodeInfo.title}</div> } > <div className={cn( 'system-xs-semibold-uppercase grow truncate text-text-secondary', hideInfo && '!text-xs', - )}>{nodeInfo.title}</div> + )} + > + {nodeInfo.title} + </div> </Tooltip> {nodeInfo.status !== 'running' && !hideInfo && ( - <div className='system-xs-regular shrink-0 text-text-tertiary'>{nodeInfo.execution_metadata?.total_tokens ? `${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens · ` : ''}{`${getTime(nodeInfo.elapsed_time || 0)}`}</div> + <div className="system-xs-regular shrink-0 text-text-tertiary"> + {nodeInfo.execution_metadata?.total_tokens ? `${getTokenCount(nodeInfo.execution_metadata?.total_tokens || 0)} tokens · ` : ''} + {`${getTime(nodeInfo.elapsed_time || 0)}`} + </div> )} {nodeInfo.status === 'succeeded' && ( - <RiCheckboxCircleFill className='ml-2 h-3.5 w-3.5 shrink-0 text-text-success' /> + <RiCheckboxCircleFill className="ml-2 h-3.5 w-3.5 shrink-0 text-text-success" /> )} {nodeInfo.status === 'failed' && ( - <RiErrorWarningLine className='ml-2 h-3.5 w-3.5 shrink-0 text-text-warning' /> + <RiErrorWarningLine className="ml-2 h-3.5 w-3.5 shrink-0 text-text-warning" /> )} {nodeInfo.status === 'stopped' && ( <RiAlertFill className={cn('ml-2 h-4 w-4 shrink-0 text-text-warning-secondary', inMessage && 'h-3.5 w-3.5')} /> @@ -157,14 +163,14 @@ const NodePanel: FC<Props> = ({ <RiAlertFill className={cn('ml-2 h-4 w-4 shrink-0 text-text-warning-secondary', inMessage && 'h-3.5 w-3.5')} /> )} {nodeInfo.status === 'running' && ( - <div className='flex shrink-0 items-center text-[13px] font-medium leading-[16px] text-text-accent'> - <span className='mr-2 text-xs font-normal'>Running</span> - <RiLoader2Line className='h-3.5 w-3.5 animate-spin' /> + <div className="flex shrink-0 items-center text-[13px] font-medium leading-[16px] text-text-accent"> + <span className="mr-2 text-xs font-normal">Running</span> + <RiLoader2Line className="h-3.5 w-3.5 animate-spin" /> </div> )} </div> {!collapseState && !hideProcessDetail && ( - <div className='px-1 pb-1'> + <div className="px-1 pb-1"> {/* The nav to the iteration detail */} {isIterationNode && !notShowIterationNav && onShowIterationDetail && ( <IterationLogTrigger @@ -197,29 +203,29 @@ const NodePanel: FC<Props> = ({ } <div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}> {(nodeInfo.status === 'stopped') && ( - <StatusContainer status='stopped'> + <StatusContainer status="stopped"> {t('workflow.tracing.stopBy', { user: nodeInfo.created_by ? nodeInfo.created_by.name : 'N/A' })} </StatusContainer> )} {(nodeInfo.status === 'exception') && ( - <StatusContainer status='stopped'> + <StatusContainer status="stopped"> {nodeInfo.error} <a href={docLink('/guides/workflow/error-handling/error-type')} - target='_blank' - className='text-text-accent' + target="_blank" + className="text-text-accent" > {t('workflow.common.learnMore')} </a> </StatusContainer> )} {nodeInfo.status === 'failed' && ( - <StatusContainer status='failed'> + <StatusContainer status="failed"> {nodeInfo.error} </StatusContainer> )} {nodeInfo.status === 'retry' && ( - <StatusContainer status='failed'> + <StatusContainer status="failed"> {nodeInfo.error} </StatusContainer> )} @@ -232,7 +238,7 @@ const NodePanel: FC<Props> = ({ language={CodeLanguage.json} value={nodeInfo.inputs} isJSONStringifyBeauty - footer={nodeInfo.inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />} + footer={nodeInfo.inputs_truncated && <LargeDataAlert textHasNoExport className="mx-1 mb-1 mt-2 h-7" />} /> </div> )} @@ -256,7 +262,7 @@ const NodePanel: FC<Props> = ({ value={nodeInfo.outputs} isJSONStringifyBeauty tip={<ErrorHandleTip type={nodeInfo.execution_metadata?.error_strategy} />} - footer={nodeInfo.outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={nodeInfo.outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />} + footer={nodeInfo.outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={nodeInfo.outputs_full_content?.download_url} className="mx-1 mb-1 mt-2 h-7" />} /> </div> )} diff --git a/web/app/components/workflow/run/output-panel.tsx b/web/app/components/workflow/run/output-panel.tsx index ad776a1651..9f43299059 100644 --- a/web/app/components/workflow/run/output-panel.tsx +++ b/web/app/components/workflow/run/output-panel.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' import { useMemo } from 'react' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' -import { Markdown } from '@/app/components/base/markdown' import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' import { FileList } from '@/app/components/base/file-uploader' -import StatusContainer from '@/app/components/workflow/run/status-container' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' +import { Markdown } from '@/app/components/base/markdown' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import StatusContainer from '@/app/components/workflow/run/status-container' type OutputPanelProps = { isRunning?: boolean @@ -54,24 +54,24 @@ const OutputPanel: FC<OutputPanelProps> = ({ return getProcessedFilesFromResponse(fileList) }, [outputs]) return ( - <div className='p-2'> + <div className="p-2"> {isRunning && ( - <div className='pl-[26px] pt-4'> - <LoadingAnim type='text' /> + <div className="pl-[26px] pt-4"> + <LoadingAnim type="text" /> </div> )} {!isRunning && error && ( - <div className='px-4'> - <StatusContainer status='failed'>{error}</StatusContainer> + <div className="px-4"> + <StatusContainer status="failed">{error}</StatusContainer> </div> )} {!isRunning && !outputs && ( - <div className='px-4 py-2'> - <Markdown content='No Output' /> + <div className="px-4 py-2"> + <Markdown content="No Output" /> </div> )} {isTextOutput && ( - <div className='px-4 py-2'> + <div className="px-4 py-2"> <Markdown content={ Array.isArray(outputs[Object.keys(outputs)[0]]) @@ -82,7 +82,7 @@ const OutputPanel: FC<OutputPanelProps> = ({ </div> )} {fileList.length > 0 && ( - <div className='px-4 py-2'> + <div className="px-4 py-2"> <FileList files={fileList} showDeleteAction={false} @@ -92,7 +92,7 @@ const OutputPanel: FC<OutputPanelProps> = ({ </div> )} {!isTextOutput && outputs && Object.keys(outputs).length > 0 && height! > 0 && ( - <div className='flex flex-col gap-2'> + <div className="flex flex-col gap-2"> <CodeEditor showFileList readOnly diff --git a/web/app/components/workflow/run/result-panel.tsx b/web/app/components/workflow/run/result-panel.tsx index a444860231..d8ca879025 100644 --- a/web/app/components/workflow/run/result-panel.tsx +++ b/web/app/components/workflow/run/result-panel.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' -import { useTranslation } from 'react-i18next' -import StatusPanel from './status' -import MetaData from './meta' -import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' -import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' -import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import type { AgentLogItemWithChildren, NodeTracing, } from '@/types/workflow' -import { BlockEnum } from '@/app/components/workflow/types' -import { hasRetryNode } from '@/app/components/workflow/utils' +import { useTranslation } from 'react-i18next' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log' import { IterationLogTrigger } from '@/app/components/workflow/run/iteration-log' import { LoopLogTrigger } from '@/app/components/workflow/run/loop-log' import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log' -import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log' +import { BlockEnum } from '@/app/components/workflow/types' +import { hasRetryNode } from '@/app/components/workflow/utils' import LargeDataAlert from '../variable-inspect/large-data-alert' +import MetaData from './meta' +import StatusPanel from './status' export type ResultPanelProps = { nodeInfo?: NodeTracing @@ -80,8 +80,8 @@ const ResultPanel: FC<ResultPanelProps> = ({ const isToolNode = nodeInfo?.node_type === BlockEnum.Tool && !!nodeInfo?.agentLog?.length return ( - <div className='bg-components-panel-bg py-2'> - <div className='px-4 py-2'> + <div className="bg-components-panel-bg py-2"> + <div className="px-4 py-2"> <StatusPanel status={status} time={elapsed_time} @@ -91,7 +91,7 @@ const ResultPanel: FC<ResultPanelProps> = ({ isListening={isListening} /> </div> - <div className='px-4'> + <div className="px-4"> { isIterationNode && handleShowIterationResultList && ( <IterationLogTrigger @@ -125,14 +125,14 @@ const ResultPanel: FC<ResultPanelProps> = ({ ) } </div> - <div className='flex flex-col gap-2 px-4 py-2'> + <div className="flex flex-col gap-2 px-4 py-2"> <CodeEditor readOnly title={<div>{t('workflow.common.input').toLocaleUpperCase()}</div>} language={CodeLanguage.json} value={inputs} isJSONStringifyBeauty - footer={inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />} + footer={inputs_truncated && <LargeDataAlert textHasNoExport className="mx-1 mb-1 mt-2 h-7" />} /> {process_data && ( <CodeEditor @@ -141,7 +141,7 @@ const ResultPanel: FC<ResultPanelProps> = ({ language={CodeLanguage.json} value={process_data} isJSONStringifyBeauty - footer={process_data_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />} + footer={process_data_truncated && <LargeDataAlert textHasNoExport className="mx-1 mb-1 mt-2 h-7" />} /> )} {(outputs || status === 'running') && ( @@ -152,14 +152,14 @@ const ResultPanel: FC<ResultPanelProps> = ({ value={outputs} isJSONStringifyBeauty tip={<ErrorHandleTip type={execution_metadata?.error_strategy} />} - footer={outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />} + footer={outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={outputs_full_content?.download_url} className="mx-1 mb-1 mt-2 h-7" />} /> )} </div> - <div className='px-4 py-2'> - <div className='divider-subtle h-[0.5px]' /> + <div className="px-4 py-2"> + <div className="divider-subtle h-[0.5px]" /> </div> - <div className='px-4 py-2'> + <div className="px-4 py-2"> <MetaData status={status} executor={created_by} diff --git a/web/app/components/workflow/run/result-text.tsx b/web/app/components/workflow/run/result-text.tsx index cf99c604d3..2bb1d379b4 100644 --- a/web/app/components/workflow/run/result-text.tsx +++ b/web/app/components/workflow/run/result-text.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' import { useTranslation } from 'react-i18next' +import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' +import { FileList } from '@/app/components/base/file-uploader' import { ImageIndentLeft } from '@/app/components/base/icons/src/vender/line/editor' import { Markdown } from '@/app/components/base/markdown' -import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' import StatusContainer from '@/app/components/workflow/run/status-container' -import { FileList } from '@/app/components/base/file-uploader' type ResultTextProps = { isRunning?: boolean @@ -24,26 +24,26 @@ const ResultText: FC<ResultTextProps> = ({ }) => { const { t } = useTranslation() return ( - <div className='bg-background-section-burn'> + <div className="bg-background-section-burn"> {isRunning && !outputs && ( - <div className='pl-[26px] pt-4'> - <LoadingAnim type='text' /> + <div className="pl-[26px] pt-4"> + <LoadingAnim type="text" /> </div> )} {!isRunning && error && ( - <div className='px-4 py-2'> - <StatusContainer status='failed'> + <div className="px-4 py-2"> + <StatusContainer status="failed"> {error} </StatusContainer> </div> )} {!isRunning && !outputs && !error && !allFiles?.length && ( - <div className='mt-[120px] flex flex-col items-center px-4 py-2 text-[13px] leading-[18px] text-gray-500'> - <ImageIndentLeft className='h-6 w-6 text-gray-400' /> - <div className='mr-2'>{t('runLog.resultEmpty.title')}</div> + <div className="mt-[120px] flex flex-col items-center px-4 py-2 text-[13px] leading-[18px] text-gray-500"> + <ImageIndentLeft className="h-6 w-6 text-gray-400" /> + <div className="mr-2">{t('runLog.resultEmpty.title')}</div> <div> {t('runLog.resultEmpty.tipLeft')} - <span onClick={onClick} className='cursor-pointer text-primary-600'>{t('runLog.resultEmpty.link')}</span> + <span onClick={onClick} className="cursor-pointer text-primary-600">{t('runLog.resultEmpty.link')}</span> {t('runLog.resultEmpty.tipRight')} </div> </div> @@ -51,13 +51,13 @@ const ResultText: FC<ResultTextProps> = ({ {(outputs || !!allFiles?.length) && ( <> {outputs && ( - <div className='px-4 py-2'> + <div className="px-4 py-2"> <Markdown content={outputs} /> </div> )} {!!allFiles?.length && allFiles.map(item => ( - <div key={item.varName} className='system-xs-regular flex flex-col gap-1 px-4 py-2'> - <div className='py-1 text-text-tertiary '>{item.varName}</div> + <div key={item.varName} className="system-xs-regular flex flex-col gap-1 px-4 py-2"> + <div className="py-1 text-text-tertiary ">{item.varName}</div> <FileList files={item.list} showDeleteAction={false} diff --git a/web/app/components/workflow/run/retry-log/retry-log-trigger.tsx b/web/app/components/workflow/run/retry-log/retry-log-trigger.tsx index 0a39e4fec7..cc6d255310 100644 --- a/web/app/components/workflow/run/retry-log/retry-log-trigger.tsx +++ b/web/app/components/workflow/run/retry-log/retry-log-trigger.tsx @@ -1,10 +1,10 @@ -import { useTranslation } from 'react-i18next' +import type { NodeTracing } from '@/types/workflow' import { RiArrowRightSLine, RiRestartFill, } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import type { NodeTracing } from '@/types/workflow' type RetryLogTriggerProps = { nodeInfo: NodeTracing @@ -25,15 +25,15 @@ const RetryLogTrigger = ({ return ( <Button - className='mb-1 flex w-full items-center justify-between' - variant='tertiary' + className="mb-1 flex w-full items-center justify-between" + variant="tertiary" onClick={handleShowRetryResultList} > - <div className='flex items-center'> - <RiRestartFill className='mr-0.5 h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <div className="flex items-center"> + <RiRestartFill className="mr-0.5 h-4 w-4 shrink-0 text-components-button-tertiary-text" /> {t('workflow.nodes.common.retry.retries', { num: retryDetail?.length })} </div> - <RiArrowRightSLine className='h-4 w-4 shrink-0 text-components-button-tertiary-text' /> + <RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" /> </Button> ) } diff --git a/web/app/components/workflow/run/retry-log/retry-result-panel.tsx b/web/app/components/workflow/run/retry-log/retry-result-panel.tsx index 3d1eb77c93..6547bfd47f 100644 --- a/web/app/components/workflow/run/retry-log/retry-result-panel.tsx +++ b/web/app/components/workflow/run/retry-log/retry-result-panel.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' -import { memo } from 'react' -import { useTranslation } from 'react-i18next' +import type { NodeTracing } from '@/types/workflow' import { RiArrowLeftLine, } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import TracingPanel from '../tracing-panel' -import type { NodeTracing } from '@/types/workflow' type Props = { list: NodeTracing[] @@ -23,14 +23,14 @@ const RetryResultPanel: FC<Props> = ({ return ( <div> <div - className='system-sm-medium flex h-8 cursor-pointer items-center bg-components-panel-bg px-4 text-text-accent-secondary' + className="system-sm-medium flex h-8 cursor-pointer items-center bg-components-panel-bg px-4 text-text-accent-secondary" onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() onBack() }} > - <RiArrowLeftLine className='mr-1 h-4 w-4' /> + <RiArrowLeftLine className="mr-1 h-4 w-4" /> {t('workflow.singleRun.back')} </div> <TracingPanel @@ -38,9 +38,9 @@ const RetryResultPanel: FC<Props> = ({ ...item, title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`, }))} - className='bg-background-section-burn' + className="bg-background-section-burn" /> - </div > + </div> ) } export default memo(RetryResultPanel) diff --git a/web/app/components/workflow/run/special-result-panel.tsx b/web/app/components/workflow/run/special-result-panel.tsx index d985950b7e..34a9e2fb37 100644 --- a/web/app/components/workflow/run/special-result-panel.tsx +++ b/web/app/components/workflow/run/special-result-panel.tsx @@ -1,7 +1,3 @@ -import { RetryResultPanel } from './retry-log' -import { IterationResultPanel } from './iteration-log' -import { LoopResultPanel } from './loop-log' -import { AgentResultPanel } from './agent-log' import type { AgentLogItemWithChildren, IterationDurationMap, @@ -9,6 +5,10 @@ import type { LoopVariableMap, NodeTracing, } from '@/types/workflow' +import { AgentResultPanel } from './agent-log' +import { IterationResultPanel } from './iteration-log' +import { LoopResultPanel } from './loop-log' +import { RetryResultPanel } from './retry-log' export type SpecialResultPanelProps = { showRetryDetail?: boolean @@ -54,7 +54,8 @@ const SpecialResultPanel = ({ <div onClick={(e) => { e.stopPropagation() e.nativeEvent.stopImmediatePropagation() - }}> + }} + > { !!showRetryDetail && !!retryResultList?.length && setShowRetryDetailFalse && ( <RetryResultPanel diff --git a/web/app/components/workflow/run/status-container.tsx b/web/app/components/workflow/run/status-container.tsx index 618c25a3f4..d935135231 100644 --- a/web/app/components/workflow/run/status-container.tsx +++ b/web/app/components/workflow/run/status-container.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' +import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' import { cn } from '@/utils/classnames' -import useTheme from '@/hooks/use-theme' type Props = { status: string @@ -43,7 +43,9 @@ const StatusContainer: FC<Props> = ({ 'absolute left-0 top-0 h-[50px] w-[65%] bg-no-repeat', theme === Theme.light && 'bg-[url(~@/app/components/workflow/run/assets/highlight.svg)]', theme === Theme.dark && 'bg-[url(~@/app/components/workflow/run/assets/highlight-dark.svg)]', - )}></div> + )} + > + </div> {children} </div> ) diff --git a/web/app/components/workflow/run/status.tsx b/web/app/components/workflow/run/status.tsx index 7b272ee444..e8dc6136f3 100644 --- a/web/app/components/workflow/run/status.tsx +++ b/web/app/components/workflow/run/status.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' import Indicator from '@/app/components/header/indicator' import StatusContainer from '@/app/components/workflow/run/status-container' import { useDocLink } from '@/context/i18n' +import { cn } from '@/utils/classnames' type ResultProps = { status: string @@ -28,12 +28,13 @@ const StatusPanel: FC<ResultProps> = ({ return ( <StatusContainer status={status}> - <div className='flex'> + <div className="flex"> <div className={cn( 'max-w-[120px] flex-[33%]', status === 'partial-succeeded' && 'min-w-[140px]', - )}> - <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t('runLog.resultPanel.status')}</div> + )} + > + <div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t('runLog.resultPanel.status')}</div> <div className={cn( 'system-xs-semibold-uppercase flex items-center gap-1', @@ -46,58 +47,58 @@ const StatusPanel: FC<ResultProps> = ({ > {status === 'running' && ( <> - <Indicator color={'blue'} /> + <Indicator color="blue" /> <span>{isListening ? 'Listening' : 'Running'}</span> </> )} {status === 'succeeded' && ( <> - <Indicator color={'green'} /> + <Indicator color="green" /> <span>SUCCESS</span> </> )} {status === 'partial-succeeded' && ( <> - <Indicator color={'green'} /> + <Indicator color="green" /> <span>PARTIAL SUCCESS</span> </> )} {status === 'exception' && ( <> - <Indicator color={'yellow'} /> + <Indicator color="yellow" /> <span>EXCEPTION</span> </> )} {status === 'failed' && ( <> - <Indicator color={'red'} /> + <Indicator color="red" /> <span>FAIL</span> </> )} {status === 'stopped' && ( <> - <Indicator color={'yellow'} /> + <Indicator color="yellow" /> <span>STOP</span> </> )} </div> </div> - <div className='max-w-[152px] flex-[33%]'> - <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t('runLog.resultPanel.time')}</div> - <div className='system-sm-medium flex items-center gap-1 text-text-secondary'> + <div className="max-w-[152px] flex-[33%]"> + <div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t('runLog.resultPanel.time')}</div> + <div className="system-sm-medium flex items-center gap-1 text-text-secondary"> {status === 'running' && ( - <div className='h-2 w-16 rounded-sm bg-text-quaternary' /> + <div className="h-2 w-16 rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{time ? `${time?.toFixed(3)}s` : '-'}</span> )} </div> </div> - <div className='flex-[33%]'> - <div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t('runLog.resultPanel.tokens')}</div> - <div className='system-sm-medium flex items-center gap-1 text-text-secondary'> + <div className="flex-[33%]"> + <div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t('runLog.resultPanel.tokens')}</div> + <div className="system-sm-medium flex items-center gap-1 text-text-secondary"> {status === 'running' && ( - <div className='h-2 w-20 rounded-sm bg-text-quaternary' /> + <div className="h-2 w-20 rounded-sm bg-text-quaternary" /> )} {status !== 'running' && ( <span>{`${tokens || 0} Tokens`}</span> @@ -107,13 +108,13 @@ const StatusPanel: FC<ResultProps> = ({ </div> {status === 'failed' && error && ( <> - <div className='my-2 h-[0.5px] bg-divider-subtle'/> - <div className='system-xs-regular whitespace-pre-wrap text-text-destructive'>{error}</div> + <div className="my-2 h-[0.5px] bg-divider-subtle" /> + <div className="system-xs-regular whitespace-pre-wrap text-text-destructive">{error}</div> { !!exceptionCounts && ( <> - <div className='my-2 h-[0.5px] bg-divider-subtle'/> - <div className='system-xs-regular text-text-destructive'> + <div className="my-2 h-[0.5px] bg-divider-subtle" /> + <div className="system-xs-regular text-text-destructive"> {t('workflow.nodes.common.errorHandle.partialSucceeded.tip', { num: exceptionCounts })} </div> </> @@ -124,8 +125,8 @@ const StatusPanel: FC<ResultProps> = ({ { status === 'partial-succeeded' && !!exceptionCounts && ( <> - <div className='my-2 h-[0.5px] bg-divider-deep'/> - <div className='system-xs-medium text-text-warning'> + <div className="my-2 h-[0.5px] bg-divider-deep" /> + <div className="system-xs-medium text-text-warning"> {t('workflow.nodes.common.errorHandle.partialSucceeded.tip', { num: exceptionCounts })} </div> </> @@ -134,13 +135,13 @@ const StatusPanel: FC<ResultProps> = ({ { status === 'exception' && ( <> - <div className='my-2 h-[0.5px] bg-divider-deep'/> - <div className='system-xs-medium text-text-warning'> + <div className="my-2 h-[0.5px] bg-divider-deep" /> + <div className="system-xs-medium text-text-warning"> {error} <a href={docLink('/guides/workflow/error-handling/error-type')} - target='_blank' - className='text-text-accent' + target="_blank" + className="text-text-accent" > {t('workflow.common.learnMore')} </a> diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx index 77f64dfba8..0e1d6578ab 100644 --- a/web/app/components/workflow/run/tracing-panel.tsx +++ b/web/app/components/workflow/run/tracing-panel.tsx @@ -1,22 +1,22 @@ 'use client' import type { FC } from 'react' +import type { NodeTracing } from '@/types/workflow' +import { + RiArrowDownSLine, + RiMenu4Line, +} from '@remixicon/react' import React, { useCallback, useState, } from 'react' -import { cn } from '@/utils/classnames' -import { - RiArrowDownSLine, - RiMenu4Line, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' +import formatNodeList from '@/app/components/workflow/run/utils/format-log' +import { cn } from '@/utils/classnames' import { useLogs } from './hooks' import NodePanel from './node' import SpecialResultPanel from './special-result-panel' -import type { NodeTracing } from '@/types/workflow' -import formatNodeList from '@/app/components/workflow/run/utils/format-log' type TracingPanelProps = { list: NodeTracing[] @@ -109,7 +109,8 @@ const TracingPanel: FC<TracingPanelProps> = ({ onMouseLeave={handleParallelMouseLeave} > <div className="mb-1 flex items-center"> - <button type="button" + <button + type="button" onClick={() => toggleCollapse(node.id)} className={cn( 'mr-2 transition-colors', @@ -124,13 +125,16 @@ const TracingPanel: FC<TracingPanelProps> = ({ <div className="mx-2 h-px grow bg-divider-subtle" style={{ background: 'linear-gradient(to right, rgba(16, 24, 40, 0.08), rgba(255, 255, 255, 0)' }} - ></div> + > + </div> </div> <div className={`relative pl-2 ${isCollapsed ? 'hidden' : ''}`}> <div className={cn( 'absolute bottom-0 left-[5px] top-0 w-[2px]', isHovered ? 'bg-text-accent-secondary' : 'bg-divider-subtle', - )}></div> + )} + > + </div> {parallelDetail.children!.map(renderNode)} </div> </div> diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.spec.ts b/web/app/components/workflow/run/utils/format-log/agent/index.spec.ts index 59cf0ce308..9359e227be 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/index.spec.ts @@ -2,12 +2,12 @@ import format from '.' import { agentNodeData, multiStepsCircle, oneStepCircle } from './data' describe('agent', () => { - test('list should transform to tree', () => { + it('list should transform to tree', () => { // console.log(format(agentNodeData.in as any)) expect(format(agentNodeData.in as any)).toEqual(agentNodeData.expect) }) - test('list should remove circle log item', () => { + it('list should remove circle log item', () => { // format(oneStepCircle.in as any) expect(format(oneStepCircle.in as any)).toEqual(oneStepCircle.expect) expect(format(multiStepsCircle.in as any)).toEqual(multiStepsCircle.expect) diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.ts b/web/app/components/workflow/run/utils/format-log/agent/index.ts index 311e56a269..a4c1ea5167 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/index.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/index.ts @@ -1,6 +1,6 @@ -import { BlockEnum } from '@/app/components/workflow/types' import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow' import { cloneDeep } from 'lodash-es' +import { BlockEnum } from '@/app/components/workflow/types' const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool] diff --git a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts index 741fa08ebf..55e1c2b406 100644 --- a/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts +++ b/web/app/components/workflow/run/utils/format-log/graph-to-log-struct.ts @@ -1,7 +1,7 @@ -type IterationInfo = { iterationId: string; iterationIndex: number } -type LoopInfo = { loopId: string; loopIndex: number } -type NodePlain = { nodeType: 'plain'; nodeId: string; } & (Partial<IterationInfo> & Partial<LoopInfo>) -type NodeComplex = { nodeType: string; nodeId: string; params: (NodePlain | (NodeComplex & (Partial<IterationInfo> & Partial<LoopInfo>)) | Node[] | number)[] } & (Partial<IterationInfo> & Partial<LoopInfo>) +type IterationInfo = { iterationId: string, iterationIndex: number } +type LoopInfo = { loopId: string, loopIndex: number } +type NodePlain = { nodeType: 'plain', nodeId: string } & (Partial<IterationInfo> & Partial<LoopInfo>) +type NodeComplex = { nodeType: string, nodeId: string, params: (NodePlain | (NodeComplex & (Partial<IterationInfo> & Partial<LoopInfo>)) | Node[] | number)[] } & (Partial<IterationInfo> & Partial<LoopInfo>) type Node = NodePlain | NodeComplex /** @@ -25,8 +25,10 @@ function parseTopLevelFlow(dsl: string): string[] { for (let i = 0; i < dsl.length; i++) { const char = dsl[i] - if (char === '(') nested++ - if (char === ')') nested-- + if (char === '(') + nested++ + if (char === ')') + nested-- if (char === '-' && dsl[i + 1] === '>' && nested === 0) { segments.push(buffer.trim()) buffer = '' @@ -61,8 +63,10 @@ function parseNode(nodeStr: string, parentIterationId?: string, parentLoopId?: s // Split the inner content by commas, respecting nested parentheses for (let i = 0; i < innerContent.length; i++) { const char = innerContent[i] - if (char === '(') nested++ - if (char === ')') nested-- + if (char === '(') + nested++ + if (char === ')') + nested-- if (char === ',' && nested === 0) { parts.push(buffer.trim()) @@ -137,12 +141,12 @@ function parseParams(paramParts: string[], parentIteration?: string, parentLoopI } type NodeData = { - id: string; - node_id: string; - title: string; - node_type?: string; - execution_metadata: Record<string, any>; - status: string; + id: string + node_id: string + title: string + node_type?: string + execution_metadata: Record<string, any> + status: string } /** @@ -181,13 +185,17 @@ function convertRetryNode(node: Node): NodeData[] { id: nodeId, node_id: nodeId, title: nodeId, - execution_metadata: iterationId ? { - iteration_id: iterationId, - iteration_index: iterationIndex || 0, - } : loopId ? { - loop_id: loopId, - loop_index: loopIndex || 0, - } : {}, + execution_metadata: iterationId + ? { + iteration_id: iterationId, + iteration_index: iterationIndex || 0, + } + : loopId + ? { + loop_id: loopId, + loop_index: loopIndex || 0, + } + : {}, status: 'retry', }) } diff --git a/web/app/components/workflow/run/utils/format-log/index.ts b/web/app/components/workflow/run/utils/format-log/index.ts index 4f97814e46..2c89e91571 100644 --- a/web/app/components/workflow/run/utils/format-log/index.ts +++ b/web/app/components/workflow/run/utils/format-log/index.ts @@ -1,11 +1,11 @@ import type { NodeTracing } from '@/types/workflow' +import { cloneDeep } from 'lodash-es' +import { BlockEnum } from '../../../types' +import formatAgentNode from './agent' import { addChildrenToIterationNode } from './iteration' import { addChildrenToLoopNode } from './loop' import formatParallelNode from './parallel' import formatRetryNode from './retry' -import formatAgentNode from './agent' -import { cloneDeep } from 'lodash-es' -import { BlockEnum } from '../../../types' const formatIterationAndLoopNode = (list: NodeTracing[], t: any) => { const clonedList = cloneDeep(list) diff --git a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts index f5feb5c367..855ac4c69d 100644 --- a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts @@ -1,12 +1,12 @@ +import { noop } from 'lodash-es' import format from '.' import graphToLogStruct from '../graph-to-log-struct' -import { noop } from 'lodash-es' describe('iteration', () => { const list = graphToLogStruct('start -> (iteration, iterationNode, plainNode1 -> plainNode2)') // const [startNode, iterationNode, ...iterations] = list const result = format(list as any, noop) - test('result should have no nodes in iteration node', () => { + it('result should have no nodes in iteration node', () => { expect((result as any).find((item: any) => !!item.execution_metadata?.iteration_id)).toBeUndefined() }) // test('iteration should put nodes in details', () => { diff --git a/web/app/components/workflow/run/utils/format-log/iteration/index.ts b/web/app/components/workflow/run/utils/format-log/iteration/index.ts index d0224d0259..fbb81118a1 100644 --- a/web/app/components/workflow/run/utils/format-log/iteration/index.ts +++ b/web/app/components/workflow/run/utils/format-log/iteration/index.ts @@ -1,11 +1,12 @@ -import { BlockEnum } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' +import { BlockEnum } from '@/app/components/workflow/types' import formatParallelNode from '../parallel' export function addChildrenToIterationNode(iterationNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing { const details: NodeTracing[][] = [] childrenNodes.forEach((item, index) => { - if (!item.execution_metadata) return + if (!item.execution_metadata) + return const { iteration_index = 0 } = item.execution_metadata const runIndex: number = iteration_index !== undefined ? iteration_index : index if (!details[runIndex]) diff --git a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts index 1f70cef02c..aee2a432c3 100644 --- a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts @@ -1,15 +1,15 @@ +import { noop } from 'lodash-es' import format from '.' import graphToLogStruct from '../graph-to-log-struct' -import { noop } from 'lodash-es' describe('loop', () => { const list = graphToLogStruct('start -> (loop, loopNode, plainNode1 -> plainNode2)') const [startNode, loopNode, ...loops] = list const result = format(list as any, noop) - test('result should have no nodes in loop node', () => { + it('result should have no nodes in loop node', () => { expect(result.find(item => !!item.execution_metadata?.loop_id)).toBeUndefined() }) - test('loop should put nodes in details', () => { + it('loop should put nodes in details', () => { expect(result).toEqual([ startNode, { diff --git a/web/app/components/workflow/run/utils/format-log/loop/index.ts b/web/app/components/workflow/run/utils/format-log/loop/index.ts index b12e12e48f..fd26c3916e 100644 --- a/web/app/components/workflow/run/utils/format-log/loop/index.ts +++ b/web/app/components/workflow/run/utils/format-log/loop/index.ts @@ -1,11 +1,12 @@ -import { BlockEnum } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' +import { BlockEnum } from '@/app/components/workflow/types' import formatParallelNode from '../parallel' export function addChildrenToLoopNode(loopNode: NodeTracing, childrenNodes: NodeTracing[]): NodeTracing { const details: NodeTracing[][] = [] childrenNodes.forEach((item) => { - if (!item.execution_metadata) return + if (!item.execution_metadata) + return const { parallel_mode_run_id, loop_index = 0 } = item.execution_metadata const runIndex: number = (parallel_mode_run_id || loop_index) as number if (!details[runIndex]) diff --git a/web/app/components/workflow/run/utils/format-log/parallel/index.ts b/web/app/components/workflow/run/utils/format-log/parallel/index.ts index 22c96918e9..e2c7592b5d 100644 --- a/web/app/components/workflow/run/utils/format-log/parallel/index.ts +++ b/web/app/components/workflow/run/utils/format-log/parallel/index.ts @@ -1,5 +1,5 @@ -import { BlockEnum } from '@/app/components/workflow/types' import type { NodeTracing } from '@/types/workflow' +import { BlockEnum } from '@/app/components/workflow/types' function printNodeStructure(node: NodeTracing, depth: number) { const indent = ' '.repeat(depth) @@ -12,11 +12,13 @@ function printNodeStructure(node: NodeTracing, depth: number) { } function addTitle({ - list, depth, belongParallelIndexInfo, + list, + depth, + belongParallelIndexInfo, }: { - list: NodeTracing[], - depth: number, - belongParallelIndexInfo?: string, + list: NodeTracing[] + depth: number + belongParallelIndexInfo?: string }, t: any) { let branchIndex = 0 const hasMoreThanOneParallel = list.filter(node => node.parallelDetail?.isParallelStartNode).length > 1 diff --git a/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts b/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts index 2ce9554ba4..cb823a0e91 100644 --- a/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/retry/index.spec.ts @@ -6,10 +6,10 @@ describe('retry', () => { const steps = graphToLogStruct('start -> (retry, retryNode, 3)') const [startNode, retryNode, ...retryDetail] = steps const result = format(steps as any) - test('should have no retry status nodes', () => { + it('should have no retry status nodes', () => { expect(result.find(item => item.status === 'retry')).toBeUndefined() }) - test('should put retry nodes in retryDetail', () => { + it('should put retry nodes in retryDetail', () => { expect(result).toEqual([ startNode, { diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx index 53392f2cd3..7ea18c94f3 100644 --- a/web/app/components/workflow/selection-contextmenu.tsx +++ b/web/app/components/workflow/selection-contextmenu.tsx @@ -1,13 +1,3 @@ -import { - memo, - useCallback, - useEffect, - useMemo, - useRef, -} from 'react' -import { useTranslation } from 'react-i18next' -import { useClickAway } from 'ahooks' -import { useStore as useReactFlowStore, useStoreApi } from 'reactflow' import { RiAlignBottom, RiAlignCenter, @@ -16,12 +6,21 @@ import { RiAlignRight, RiAlignTop, } from '@remixicon/react' -import { useNodesReadOnly, useNodesSyncDraft } from './hooks' +import { useClickAway } from 'ahooks' import { produce } from 'immer' -import { WorkflowHistoryEvent, useWorkflowHistory } from './hooks/use-workflow-history' -import { useStore } from './store' +import { + memo, + useCallback, + useEffect, + useMemo, + useRef, +} from 'react' +import { useTranslation } from 'react-i18next' +import { useStore as useReactFlowStore, useStoreApi } from 'reactflow' +import { useNodesReadOnly, useNodesSyncDraft } from './hooks' import { useSelectionInteractions } from './hooks/use-selection-interactions' -import { useWorkflowStore } from './store' +import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history' +import { useStore, useWorkflowStore } from './store' enum AlignType { Left = 'left', @@ -56,7 +55,8 @@ const SelectionContextmenu = () => { const menuRef = useRef<HTMLDivElement>(null) const menuPosition = useMemo(() => { - if (!selectionMenu) return { left: 0, top: 0 } + if (!selectionMenu) + return { left: 0, top: 0 } let left = selectionMenu.left let top = selectionMenu.top @@ -220,7 +220,8 @@ const SelectionContextmenu = () => { for (let i = 1; i < sortedNodes.length - 1; i++) { const nodeToAlign = sortedNodes[i] const currentNode = draft.find(n => n.id === nodeToAlign.id) - if (!currentNode) continue + if (!currentNode) + continue if (alignType === AlignType.DistributeHorizontal) { // Position = previous right edge + spacing @@ -272,7 +273,7 @@ const SelectionContextmenu = () => { // If container node is selected, add its children to the exclusion set if (selectedNodeIds.includes(node.id)) { // Add all its children to the childNodeIds set - node.data._children.forEach((child: { nodeId: string; nodeType: string }) => { + node.data._children.forEach((child: { nodeId: string, nodeType: string }) => { childNodeIds.add(child.nodeId) }) } @@ -373,78 +374,78 @@ const SelectionContextmenu = () => { return ( <div - className='absolute z-[9]' + className="absolute z-[9]" style={{ left: menuPosition.left, top: menuPosition.top, }} ref={ref} > - <div ref={menuRef} className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'> - <div className='p-1'> - <div className='system-xs-medium px-2 py-2 text-text-tertiary'> + <div ref={menuRef} className="w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl"> + <div className="p-1"> + <div className="system-xs-medium px-2 py-2 text-text-tertiary"> {t('workflow.operator.vertical')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Top)} > - <RiAlignTop className='h-4 w-4' /> + <RiAlignTop className="h-4 w-4" /> {t('workflow.operator.alignTop')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Middle)} > - <RiAlignCenter className='h-4 w-4 rotate-90' /> + <RiAlignCenter className="h-4 w-4 rotate-90" /> {t('workflow.operator.alignMiddle')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Bottom)} > - <RiAlignBottom className='h-4 w-4' /> + <RiAlignBottom className="h-4 w-4" /> {t('workflow.operator.alignBottom')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.DistributeVertical)} > - <RiAlignJustify className='h-4 w-4 rotate-90' /> + <RiAlignJustify className="h-4 w-4 rotate-90" /> {t('workflow.operator.distributeVertical')} </div> </div> - <div className='h-px bg-divider-regular'></div> - <div className='p-1'> - <div className='system-xs-medium px-2 py-2 text-text-tertiary'> + <div className="h-px bg-divider-regular"></div> + <div className="p-1"> + <div className="system-xs-medium px-2 py-2 text-text-tertiary"> {t('workflow.operator.horizontal')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Left)} > - <RiAlignLeft className='h-4 w-4' /> + <RiAlignLeft className="h-4 w-4" /> {t('workflow.operator.alignLeft')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Center)} > - <RiAlignCenter className='h-4 w-4' /> + <RiAlignCenter className="h-4 w-4" /> {t('workflow.operator.alignCenter')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.Right)} > - <RiAlignRight className='h-4 w-4' /> + <RiAlignRight className="h-4 w-4" /> {t('workflow.operator.alignRight')} </div> <div - className='flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover' + className="flex h-8 cursor-pointer items-center gap-2 rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover" onClick={() => handleAlignNodes(AlignType.DistributeHorizontal)} > - <RiAlignJustify className='h-4 w-4' /> + <RiAlignJustify className="h-4 w-4" /> {t('workflow.operator.distributeHorizontal')} </div> </div> diff --git a/web/app/components/workflow/shortcuts-name.tsx b/web/app/components/workflow/shortcuts-name.tsx index c901d0c2d1..d0ce007f61 100644 --- a/web/app/components/workflow/shortcuts-name.tsx +++ b/web/app/components/workflow/shortcuts-name.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' -import { getKeyboardKeyNameBySystem } from './utils' import { cn } from '@/utils/classnames' +import { getKeyboardKeyNameBySystem } from './utils' type ShortcutsNameProps = { keys: string[] @@ -16,7 +16,8 @@ const ShortcutsName = ({ <div className={cn( 'flex items-center gap-0.5', className, - )}> + )} + > { keys.map(key => ( <div diff --git a/web/app/components/workflow/simple-node/index.tsx b/web/app/components/workflow/simple-node/index.tsx index d6f0804f34..3da2c71bc5 100644 --- a/web/app/components/workflow/simple-node/index.tsx +++ b/web/app/components/workflow/simple-node/index.tsx @@ -1,10 +1,9 @@ import type { FC, } from 'react' -import { - memo, - useMemo, -} from 'react' +import type { + NodeProps, +} from '@/app/components/workflow/types' import { RiAlertFill, RiCheckboxCircleFill, @@ -12,20 +11,21 @@ import { RiLoader2Line, } from '@remixicon/react' import { - NodeTargetHandle, -} from '@/app/components/workflow/nodes/_base/components/node-handle' -import NodeControl from '@/app/components/workflow/nodes/_base/components/node-control' -import { cn } from '@/utils/classnames' + memo, + useMemo, +} from 'react' import BlockIcon from '@/app/components/workflow/block-icon' -import type { - NodeProps, -} from '@/app/components/workflow/types' -import { - NodeRunningStatus, -} from '@/app/components/workflow/types' import { useNodesReadOnly, } from '@/app/components/workflow/hooks' +import NodeControl from '@/app/components/workflow/nodes/_base/components/node-control' +import { + NodeTargetHandle, +} from '@/app/components/workflow/nodes/_base/components/node-handle' +import { + NodeRunningStatus, +} from '@/app/components/workflow/types' +import { cn } from '@/utils/classnames' type SimpleNodeProps = NodeProps @@ -80,8 +80,8 @@ const SimpleNode: FC<SimpleNodeProps> = ({ <NodeTargetHandle id={id} data={data} - handleClassName='!top-4 !-left-[9px] !translate-y-0' - handleId='target' + handleClassName="!top-4 !-left-[9px] !translate-y-0" + handleId="target" /> ) } @@ -95,15 +95,16 @@ const SimpleNode: FC<SimpleNodeProps> = ({ } <div className={cn( 'flex items-center rounded-t-2xl px-3 pb-2 pt-3', - )}> + )} + > <BlockIcon - className='mr-2 shrink-0' + className="mr-2 shrink-0" type={data.type} - size='md' + size="md" /> <div title={data.title} - className='system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary' + className="system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary" > <div> {data.title} @@ -111,22 +112,22 @@ const SimpleNode: FC<SimpleNodeProps> = ({ </div> { (data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && ( - <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-accent' /> + <RiLoader2Line className="h-3.5 w-3.5 animate-spin text-text-accent" /> ) } { data._runningStatus === NodeRunningStatus.Succeeded && ( - <RiCheckboxCircleFill className='h-3.5 w-3.5 text-text-success' /> + <RiCheckboxCircleFill className="h-3.5 w-3.5 text-text-success" /> ) } { data._runningStatus === NodeRunningStatus.Failed && ( - <RiErrorWarningFill className='h-3.5 w-3.5 text-text-destructive' /> + <RiErrorWarningFill className="h-3.5 w-3.5 text-text-destructive" /> ) } { data._runningStatus === NodeRunningStatus.Exception && ( - <RiAlertFill className='h-3.5 w-3.5 text-text-warning-secondary' /> + <RiAlertFill className="h-3.5 w-3.5 text-text-warning-secondary" /> ) } </div> diff --git a/web/app/components/workflow/store/__tests__/trigger-status.test.ts b/web/app/components/workflow/store/__tests__/trigger-status.test.ts index d7e1284487..56ef634547 100644 --- a/web/app/components/workflow/store/__tests__/trigger-status.test.ts +++ b/web/app/components/workflow/store/__tests__/trigger-status.test.ts @@ -1,6 +1,6 @@ +import type { EntryNodeStatus } from '../trigger-status' import { act, renderHook } from '@testing-library/react' import { useTriggerStatusStore } from '../trigger-status' -import type { EntryNodeStatus } from '../trigger-status' describe('useTriggerStatusStore', () => { beforeEach(() => { diff --git a/web/app/components/workflow/store/index.ts b/web/app/components/workflow/store/index.ts index 5ca06d2ec3..1d22a5837f 100644 --- a/web/app/components/workflow/store/index.ts +++ b/web/app/components/workflow/store/index.ts @@ -1,2 +1,2 @@ -export * from './workflow' export * from './trigger-status' +export * from './workflow' diff --git a/web/app/components/workflow/store/workflow/debug/inspect-vars-slice.ts b/web/app/components/workflow/store/workflow/debug/inspect-vars-slice.ts index 7d9797630d..cb1998befd 100644 --- a/web/app/components/workflow/store/workflow/debug/inspect-vars-slice.ts +++ b/web/app/components/workflow/store/workflow/debug/inspect-vars-slice.ts @@ -1,7 +1,7 @@ import type { StateCreator } from 'zustand' -import { produce } from 'immer' -import type { NodeWithVar, VarInInspect } from '@/types/workflow' import type { ValueSelector } from '../../../types' +import type { NodeWithVar, VarInInspect } from '@/types/workflow' +import { produce } from 'immer' type InspectVarsState = { currentFocusNodeId: string | null @@ -79,8 +79,7 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set) return targetVar.value = value targetVar.edited = true - }, - ) + }) return { nodesWithInspectVars: nodes, } @@ -97,8 +96,7 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set) return targetVar.value = value targetVar.edited = false - }, - ) + }) return { nodesWithInspectVars: nodes, } @@ -115,8 +113,7 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set) return targetVar.name = selector[1] targetVar.selector = selector - }, - ) + }) return { nodesWithInspectVars: nodes, } @@ -131,8 +128,7 @@ export const createInspectVarsSlice: StateCreator<InspectVarsSliceShape> = (set) const needChangeVarIndex = targetNode.vars.findIndex(varItem => varItem.id === varId) if (needChangeVarIndex !== -1) targetNode.vars.splice(needChangeVarIndex, 1) - }, - ) + }) return { nodesWithInspectVars: nodes, } diff --git a/web/app/components/workflow/store/workflow/index.ts b/web/app/components/workflow/store/workflow/index.ts index da857fd0f2..c2c0c00201 100644 --- a/web/app/components/workflow/store/workflow/index.ts +++ b/web/app/components/workflow/store/workflow/index.ts @@ -1,61 +1,61 @@ -import { useContext } from 'react' import type { StateCreator, } from 'zustand' +import type { ChatVariableSliceShape } from './chat-variable-slice' +import type { InspectVarsSliceShape } from './debug/inspect-vars-slice' +import type { EnvVariableSliceShape } from './env-variable-slice' +import type { FormSliceShape } from './form-slice' +import type { HelpLineSliceShape } from './help-line-slice' +import type { HistorySliceShape } from './history-slice' +import type { LayoutSliceShape } from './layout-slice' +import type { NodeSliceShape } from './node-slice' +import type { PanelSliceShape } from './panel-slice' +import type { ToolSliceShape } from './tool-slice' +import type { VersionSliceShape } from './version-slice' +import type { WorkflowDraftSliceShape } from './workflow-draft-slice' +import type { WorkflowSliceShape } from './workflow-slice' +import type { RagPipelineSliceShape } from '@/app/components/rag-pipeline/store' +import type { WorkflowSliceShape as WorkflowAppSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice' +import { useContext } from 'react' import { useStore as useZustandStore, } from 'zustand' import { createStore } from 'zustand/vanilla' -import type { ChatVariableSliceShape } from './chat-variable-slice' -import { createChatVariableSlice } from './chat-variable-slice' -import type { EnvVariableSliceShape } from './env-variable-slice' -import { createEnvVariableSlice } from './env-variable-slice' -import type { FormSliceShape } from './form-slice' -import { createFormSlice } from './form-slice' -import type { HelpLineSliceShape } from './help-line-slice' -import { createHelpLineSlice } from './help-line-slice' -import type { HistorySliceShape } from './history-slice' -import { createHistorySlice } from './history-slice' -import type { NodeSliceShape } from './node-slice' -import { createNodeSlice } from './node-slice' -import type { PanelSliceShape } from './panel-slice' -import { createPanelSlice } from './panel-slice' -import type { ToolSliceShape } from './tool-slice' -import { createToolSlice } from './tool-slice' -import type { VersionSliceShape } from './version-slice' -import { createVersionSlice } from './version-slice' -import type { WorkflowDraftSliceShape } from './workflow-draft-slice' -import { createWorkflowDraftSlice } from './workflow-draft-slice' -import type { WorkflowSliceShape } from './workflow-slice' -import { createWorkflowSlice } from './workflow-slice' -import type { InspectVarsSliceShape } from './debug/inspect-vars-slice' -import { createInspectVarsSlice } from './debug/inspect-vars-slice' - import { WorkflowContext } from '@/app/components/workflow/context' -import type { LayoutSliceShape } from './layout-slice' +import { createChatVariableSlice } from './chat-variable-slice' +import { createInspectVarsSlice } from './debug/inspect-vars-slice' +import { createEnvVariableSlice } from './env-variable-slice' +import { createFormSlice } from './form-slice' +import { createHelpLineSlice } from './help-line-slice' +import { createHistorySlice } from './history-slice' import { createLayoutSlice } from './layout-slice' -import type { WorkflowSliceShape as WorkflowAppSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice' -import type { RagPipelineSliceShape } from '@/app/components/rag-pipeline/store' +import { createNodeSlice } from './node-slice' + +import { createPanelSlice } from './panel-slice' +import { createToolSlice } from './tool-slice' +import { createVersionSlice } from './version-slice' +import { createWorkflowDraftSlice } from './workflow-draft-slice' +import { createWorkflowSlice } from './workflow-slice' export type SliceFromInjection = Partial<WorkflowAppSliceShape> - & Partial<RagPipelineSliceShape> + & Partial<RagPipelineSliceShape> export type Shape = ChatVariableSliceShape - & EnvVariableSliceShape - & FormSliceShape - & HelpLineSliceShape - & HistorySliceShape - & NodeSliceShape - & PanelSliceShape - & ToolSliceShape - & VersionSliceShape - & WorkflowDraftSliceShape - & WorkflowSliceShape - & InspectVarsSliceShape - & LayoutSliceShape - & SliceFromInjection + & EnvVariableSliceShape + & FormSliceShape + & HelpLineSliceShape + & HistorySliceShape + & NodeSliceShape + & PanelSliceShape + & ToolSliceShape + & VersionSliceShape + & WorkflowDraftSliceShape + & WorkflowSliceShape + & InspectVarsSliceShape + & LayoutSliceShape + & SliceFromInjection export type InjectWorkflowStoreSliceFn = StateCreator<SliceFromInjection> diff --git a/web/app/components/workflow/store/workflow/node-slice.ts b/web/app/components/workflow/store/workflow/node-slice.ts index 3463fdee57..b90f23e925 100644 --- a/web/app/components/workflow/store/workflow/node-slice.ts +++ b/web/app/components/workflow/store/workflow/node-slice.ts @@ -1,10 +1,10 @@ import type { StateCreator } from 'zustand' -import type { - Node, -} from '@/app/components/workflow/types' import type { VariableAssignerNodeType, } from '@/app/components/workflow/nodes/variable-assigner/types' +import type { + Node, +} from '@/app/components/workflow/types' import type { NodeTracing, } from '@/types/workflow' @@ -35,7 +35,7 @@ export type NodeSliceShape = { setShowAssignVariablePopup: (showAssignVariablePopup: NodeSliceShape['showAssignVariablePopup']) => void hoveringAssignVariableGroupId?: string setHoveringAssignVariableGroupId: (hoveringAssignVariableGroupId?: string) => void - connectingNodePayload?: { nodeId: string; nodeType: string; handleType: string; handleId: string | null } + connectingNodePayload?: { nodeId: string, nodeType: string, handleType: string, handleId: string | null } setConnectingNodePayload: (startConnectingPayload?: NodeSliceShape['connectingNodePayload']) => void enteringNodePayload?: { nodeId: string diff --git a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts index f20a9453e9..6c08c50e4a 100644 --- a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts @@ -1,11 +1,11 @@ -import type { StateCreator } from 'zustand' -import { debounce } from 'lodash-es' import type { Viewport } from 'reactflow' +import type { StateCreator } from 'zustand' import type { Edge, EnvironmentVariable, Node, } from '@/app/components/workflow/types' +import { debounce } from 'lodash-es' export type WorkflowDraftSliceShape = { backupDraft?: { diff --git a/web/app/components/workflow/store/workflow/workflow-slice.ts b/web/app/components/workflow/store/workflow/workflow-slice.ts index 35eeff07a7..df24058975 100644 --- a/web/app/components/workflow/store/workflow/workflow-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-slice.ts @@ -26,15 +26,15 @@ export type WorkflowSliceShape = { setListeningTriggerIsAll: (isAll: boolean) => void clipboardElements: Node[] setClipboardElements: (clipboardElements: Node[]) => void - selection: null | { x1: number; y1: number; x2: number; y2: number } + selection: null | { x1: number, y1: number, x2: number, y2: number } setSelection: (selection: WorkflowSliceShape['selection']) => void - bundleNodeSize: { width: number; height: number } | null + bundleNodeSize: { width: number, height: number } | null setBundleNodeSize: (bundleNodeSize: WorkflowSliceShape['bundleNodeSize']) => void controlMode: 'pointer' | 'hand' setControlMode: (controlMode: WorkflowSliceShape['controlMode']) => void - mousePosition: { pageX: number; pageY: number; elementX: number; elementY: number } + mousePosition: { pageX: number, pageY: number, elementX: number, elementY: number } setMousePosition: (mousePosition: WorkflowSliceShape['mousePosition']) => void - showConfirm?: { title: string; desc?: string; onConfirm: () => void } + showConfirm?: { title: string, desc?: string, onConfirm: () => void } setShowConfirm: (showConfirm: WorkflowSliceShape['showConfirm']) => void controlPromptEditorRerenderKey: number setControlPromptEditorRerenderKey: (controlPromptEditorRerenderKey: number) => void diff --git a/web/app/components/workflow/syncing-data-modal.tsx b/web/app/components/workflow/syncing-data-modal.tsx index fe3843d2fc..30c303ffe6 100644 --- a/web/app/components/workflow/syncing-data-modal.tsx +++ b/web/app/components/workflow/syncing-data-modal.tsx @@ -7,7 +7,7 @@ const SyncingDataModal = () => { return null return ( - <div className='absolute inset-0 z-[9999]'> + <div className="absolute inset-0 z-[9999]"> </div> ) } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 5ae8d530a8..740f1c1113 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -4,21 +4,20 @@ import type { Viewport, XYPosition, } from 'reactflow' -import type { Resolution, TransferMethod } from '@/types/app' -import type { PluginDefaultValue } from '@/app/components/workflow/block-selector/types' -import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import type { FileResponse, NodeTracing, PanelProps } from '@/types/workflow' +import type { Plugin, PluginMeta } from '@/app/components/plugins/types' import type { Collection, Tool } from '@/app/components/tools/types' -import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' +import type { BlockClassificationEnum, PluginDefaultValue } from '@/app/components/workflow/block-selector/types' import type { DefaultValueForm, ErrorHandleTypeEnum, } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types' import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' -import type { Plugin, PluginMeta } from '@/app/components/plugins/types' -import type { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import type { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import type { SchemaTypeDefinition } from '@/service/use-common' +import type { Resolution, TransferMethod } from '@/types/app' +import type { FileResponse, NodeTracing, PanelProps } from '@/types/workflow' export enum BlockEnum { Start = 'start', @@ -76,7 +75,7 @@ export type CommonNodeType<T = {}> = { _singleRunningStatus?: NodeRunningStatus _isCandidate?: boolean _isBundled?: boolean - _children?: { nodeId: string; nodeType: BlockEnum }[] + _children?: { nodeId: string, nodeType: BlockEnum }[] _isEntering?: boolean _showAddVariablePopup?: boolean _holdAddVariablePopup?: boolean @@ -122,13 +121,13 @@ export type CommonEdgeType = { isInLoop?: boolean loop_id?: string sourceType: BlockEnum - targetType: BlockEnum, - _isTemp?: boolean, + targetType: BlockEnum + _isTemp?: boolean } export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>> export type SelectedNode = Pick<Node, 'id' | 'data'> -export type NodeProps<T = unknown> = { id: string; data: CommonNodeType<T> } +export type NodeProps<T = unknown> = { id: string, data: CommonNodeType<T> } export type NodePanelProps<T> = { id: string data: CommonNodeType<T> @@ -337,7 +336,7 @@ export type NodeDefault<T = {}> = { } defaultValue: Partial<T> defaultRunInputData?: Record<string, any> - checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string } + checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean, errorMessage?: string } getOutputVars?: (payload: T, allPluginInfoList: Record<string, ToolWithProvider[]>, ragVariables?: Var[], utils?: { schemaTypeDefinitions?: SchemaTypeDefinition[] }) => Var[] @@ -495,7 +494,7 @@ export enum VersionHistoryContextMenuOptions { } export type ChildNodeTypeCount = { - [key: string]: number; + [key: string]: number } export const TRIGGER_NODE_TYPES = [ diff --git a/web/app/components/workflow/update-dsl-modal.tsx b/web/app/components/workflow/update-dsl-modal.tsx index be2dab7a3d..f5dcce4c9e 100644 --- a/web/app/components/workflow/update-dsl-modal.tsx +++ b/web/app/components/workflow/update-dsl-modal.tsx @@ -1,51 +1,51 @@ 'use client' import type { MouseEventHandler } from 'react' +import type { + CommonNodeType, + Node, +} from './types' +import { + RiAlertFill, + RiCloseLine, + RiFileDownloadLine, +} from '@remixicon/react' +import { load as yamlLoad } from 'js-yaml' import { memo, useCallback, useRef, useState, } from 'react' -import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' -import { load as yamlLoad } from 'js-yaml' +import { useContext } from 'use-context-selector' +import Uploader from '@/app/components/app/create-from-dsl-modal/uploader' +import { useStore as useAppStore } from '@/app/components/app/store' +import Button from '@/app/components/base/button' +import Modal from '@/app/components/base/modal' +import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' +import { ToastContext } from '@/app/components/base/toast' +import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { - RiAlertFill, - RiCloseLine, - RiFileDownloadLine, -} from '@remixicon/react' -import { WORKFLOW_DATA_UPDATE } from './constants' -import { - BlockEnum, - SupportUploadFileTypes, -} from './types' -import type { - CommonNodeType, - Node, -} from './types' -import { AppModeEnum } from '@/types/app' -import { - initialEdges, - initialNodes, -} from './utils' + DSLImportMode, + DSLImportStatus, +} from '@/models/app' import { importDSL, importDSLConfirm, } from '@/service/apps' import { fetchWorkflowDraft } from '@/service/workflow' +import { AppModeEnum } from '@/types/app' +import { WORKFLOW_DATA_UPDATE } from './constants' import { - DSLImportMode, - DSLImportStatus, -} from '@/models/app' -import Uploader from '@/app/components/app/create-from-dsl-modal/uploader' -import Button from '@/app/components/base/button' -import Modal from '@/app/components/base/modal' -import { ToastContext } from '@/app/components/base/toast' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { useStore as useAppStore } from '@/app/components/app/store' -import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' + BlockEnum, + SupportUploadFileTypes, +} from './types' +import { + initialEdges, + initialNodes, +} from './utils' type UpdateDSLModalProps = { onCancel: () => void @@ -67,7 +67,7 @@ const UpdateDSLModal = ({ const { eventEmitter } = useEventEmitterContextContext() const [show, setShow] = useState(true) const [showErrorModal, setShowErrorModal] = useState(false) - const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() + const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>() const [importId, setImportId] = useState<string>() const { handleCheckPluginDependencies } = usePluginDependencies() @@ -143,11 +143,11 @@ const UpdateDSLModal = ({ const nodes = data?.workflow?.graph?.nodes ?? [] const invalidNodes = appDetail?.mode === AppModeEnum.ADVANCED_CHAT ? [ - BlockEnum.End, - BlockEnum.TriggerWebhook, - BlockEnum.TriggerSchedule, - BlockEnum.TriggerPlugin, - ] + BlockEnum.End, + BlockEnum.TriggerWebhook, + BlockEnum.TriggerSchedule, + BlockEnum.TriggerPlugin, + ] : [BlockEnum.Answer] const hasInvalidNode = nodes.some((node: Node<CommonNodeType>) => { return invalidNodes.includes(node?.data?.type) @@ -257,32 +257,32 @@ const UpdateDSLModal = ({ return ( <> <Modal - className='w-[520px] rounded-2xl p-6' + className="w-[520px] rounded-2xl p-6" isShow={show} onClose={onCancel} > - <div className='mb-3 flex items-center justify-between'> - <div className='title-2xl-semi-bold text-text-primary'>{t('workflow.common.importDSL')}</div> - <div className='flex h-[22px] w-[22px] cursor-pointer items-center justify-center' onClick={onCancel}> - <RiCloseLine className='h-[18px] w-[18px] text-text-tertiary' /> + <div className="mb-3 flex items-center justify-between"> + <div className="title-2xl-semi-bold text-text-primary">{t('workflow.common.importDSL')}</div> + <div className="flex h-[22px] w-[22px] cursor-pointer items-center justify-center" onClick={onCancel}> + <RiCloseLine className="h-[18px] w-[18px] text-text-tertiary" /> </div> </div> - <div className='relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs'> - <div className='absolute left-0 top-0 h-full w-full bg-toast-warning-bg opacity-40' /> - <div className='flex items-start justify-center p-1'> - <RiAlertFill className='h-4 w-4 shrink-0 text-text-warning-secondary' /> + <div className="relative mb-2 flex grow gap-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs"> + <div className="absolute left-0 top-0 h-full w-full bg-toast-warning-bg opacity-40" /> + <div className="flex items-start justify-center p-1"> + <RiAlertFill className="h-4 w-4 shrink-0 text-text-warning-secondary" /> </div> - <div className='flex grow flex-col items-start gap-0.5 py-1'> - <div className='system-xs-medium whitespace-pre-line text-text-primary'>{t('workflow.common.importDSLTip')}</div> - <div className='flex items-start gap-1 self-stretch pb-0.5 pt-1'> + <div className="flex grow flex-col items-start gap-0.5 py-1"> + <div className="system-xs-medium whitespace-pre-line text-text-primary">{t('workflow.common.importDSLTip')}</div> + <div className="flex items-start gap-1 self-stretch pb-0.5 pt-1"> <Button - size='small' - variant='secondary' - className='z-[1000]' + size="small" + variant="secondary" + className="z-[1000]" onClick={onBackup} > - <RiFileDownloadLine className='h-3.5 w-3.5 text-components-button-secondary-text' /> - <div className='flex items-center justify-center gap-1 px-[3px]'> + <RiFileDownloadLine className="h-3.5 w-3.5 text-components-button-secondary-text" /> + <div className="flex items-center justify-center gap-1 px-[3px]"> {t('workflow.common.backupCurrentDraft')} </div> </Button> @@ -290,22 +290,22 @@ const UpdateDSLModal = ({ </div> </div> <div> - <div className='system-md-semibold pt-2 text-text-primary'> + <div className="system-md-semibold pt-2 text-text-primary"> {t('workflow.common.chooseDSL')} </div> - <div className='flex w-full flex-col items-start justify-center gap-4 self-stretch py-4'> + <div className="flex w-full flex-col items-start justify-center gap-4 self-stretch py-4"> <Uploader file={currentFile} updateFile={handleFile} - className='!mt-0 w-full' + className="!mt-0 w-full" /> </div> </div> - <div className='flex items-center justify-end gap-2 self-stretch pt-5'> + <div className="flex items-center justify-end gap-2 self-stretch pt-5"> <Button onClick={onCancel}>{t('app.newApp.Cancel')}</Button> <Button disabled={!currentFile || loading} - variant='warning' + variant="warning" onClick={handleImport} loading={loading} > @@ -316,21 +316,27 @@ const UpdateDSLModal = ({ <Modal isShow={showErrorModal} onClose={() => setShowErrorModal(false)} - className='w-[480px]' + className="w-[480px]" > - <div className='flex flex-col items-start gap-2 self-stretch pb-4'> - <div className='title-2xl-semi-bold text-text-primary'>{t('app.newApp.appCreateDSLErrorTitle')}</div> - <div className='system-md-regular flex grow flex-col text-text-secondary'> + <div className="flex flex-col items-start gap-2 self-stretch pb-4"> + <div className="title-2xl-semi-bold text-text-primary">{t('app.newApp.appCreateDSLErrorTitle')}</div> + <div className="system-md-regular flex grow flex-col text-text-secondary"> <div>{t('app.newApp.appCreateDSLErrorPart1')}</div> <div>{t('app.newApp.appCreateDSLErrorPart2')}</div> <br /> - <div>{t('app.newApp.appCreateDSLErrorPart3')}<span className='system-md-medium'>{versions?.importedVersion}</span></div> - <div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div> + <div> + {t('app.newApp.appCreateDSLErrorPart3')} + <span className="system-md-medium">{versions?.importedVersion}</span> + </div> + <div> + {t('app.newApp.appCreateDSLErrorPart4')} + <span className="system-md-medium">{versions?.systemVersion}</span> + </div> </div> </div> - <div className='flex items-start justify-end gap-2 self-stretch pt-6'> - <Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button> - <Button variant='primary' destructive onClick={onUpdateDSLConfirm}>{t('app.newApp.Confirm')}</Button> + <div className="flex items-start justify-end gap-2 self-stretch pt-6"> + <Button variant="secondary" onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button> + <Button variant="primary" destructive onClick={onUpdateDSLConfirm}>{t('app.newApp.Confirm')}</Button> </div> </Modal> </> diff --git a/web/app/components/workflow/utils/data-source.ts b/web/app/components/workflow/utils/data-source.ts index 5b2db5437d..3b349e034e 100644 --- a/web/app/components/workflow/utils/data-source.ts +++ b/web/app/components/workflow/utils/data-source.ts @@ -1,8 +1,8 @@ +import type { DataSourceNodeType } from '../nodes/data-source/types' import type { InputVar, ToolWithProvider, } from '../types' -import type { DataSourceNodeType } from '../nodes/data-source/types' import { CollectionType } from '@/app/components/tools/types' import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' diff --git a/web/app/components/workflow/utils/elk-layout.ts b/web/app/components/workflow/utils/elk-layout.ts index 69acbf9aff..05f81872bb 100644 --- a/web/app/components/workflow/utils/elk-layout.ts +++ b/web/app/components/workflow/utils/elk-layout.ts @@ -1,13 +1,11 @@ -import ELK from 'elkjs/lib/elk.bundled.js' import type { ElkNode, LayoutOptions } from 'elkjs/lib/elk-api' -import { cloneDeep } from 'lodash-es' +import type { CaseItem, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types' import type { Edge, Node, } from '@/app/components/workflow/types' -import { - BlockEnum, -} from '@/app/components/workflow/types' +import ELK from 'elkjs/lib/elk.bundled.js' +import { cloneDeep } from 'lodash-es' import { CUSTOM_NODE, NODE_LAYOUT_HORIZONTAL_PADDING, @@ -15,7 +13,9 @@ import { } from '@/app/components/workflow/constants' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' -import type { CaseItem, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types' +import { + BlockEnum, +} from '@/app/components/workflow/types' // Although the file name refers to Dagre, the implementation now relies on ELK's layered algorithm. // Keep the export signatures unchanged to minimise the blast radius while we migrate the layout stack. @@ -284,7 +284,7 @@ const collectLayout = (graph: ElkNode, predicate: (id: string) => boolean): Layo const buildIfElseWithPorts = ( ifElseNode: Node, edges: Edge[], -): { node: ElkNodeShape; portMap: Map<string, string> } | null => { +): { node: ElkNodeShape, portMap: Map<string, string> } | null => { const childEdges = edges.filter(edge => edge.source === ifElseNode.id) if (childEdges.length <= 1) diff --git a/web/app/components/workflow/utils/gen-node-meta-data.ts b/web/app/components/workflow/utils/gen-node-meta-data.ts index 9c4e9cabca..f45bfcb018 100644 --- a/web/app/components/workflow/utils/gen-node-meta-data.ts +++ b/web/app/components/workflow/utils/gen-node-meta-data.ts @@ -1,5 +1,5 @@ -import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' import type { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' export type GenNodeMetaDataParams = { classification?: BlockClassificationEnum diff --git a/web/app/components/workflow/utils/index.ts b/web/app/components/workflow/utils/index.ts index 53a423de34..715ce081a3 100644 --- a/web/app/components/workflow/utils/index.ts +++ b/web/app/components/workflow/utils/index.ts @@ -1,10 +1,10 @@ -export * from './node' -export * from './edge' -export * from './workflow-init' -export * from './elk-layout' export * from './common' -export * from './tool' -export * from './workflow' -export * from './variable' -export * from './gen-node-meta-data' export * from './data-source' +export * from './edge' +export * from './elk-layout' +export * from './gen-node-meta-data' +export * from './node' +export * from './tool' +export * from './variable' +export * from './workflow' +export * from './workflow-init' diff --git a/web/app/components/workflow/utils/node-navigation.ts b/web/app/components/workflow/utils/node-navigation.ts index 57106ae6ee..4294a113ab 100644 --- a/web/app/components/workflow/utils/node-navigation.ts +++ b/web/app/components/workflow/utils/node-navigation.ts @@ -7,8 +7,8 @@ * Interface for node selection event detail */ export type NodeSelectionDetail = { - nodeId: string; - focus?: boolean; + nodeId: string + focus?: boolean } /** diff --git a/web/app/components/workflow/utils/node.ts b/web/app/components/workflow/utils/node.ts index 97ca7553e8..b26e350b2a 100644 --- a/web/app/components/workflow/utils/node.ts +++ b/web/app/components/workflow/utils/node.ts @@ -1,12 +1,12 @@ -import { - Position, -} from 'reactflow' +import type { IterationNodeType } from '../nodes/iteration/types' +import type { LoopNodeType } from '../nodes/loop/types' import type { Node, } from '../types' import { - BlockEnum, -} from '../types' + Position, +} from 'reactflow' +import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants' import { CUSTOM_NODE, ITERATION_CHILDREN_Z_INDEX, @@ -16,9 +16,9 @@ import { } from '../constants' import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants' import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants' -import type { IterationNodeType } from '../nodes/iteration/types' -import type { LoopNodeType } from '../nodes/loop/types' -import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants' +import { + BlockEnum, +} from '../types' export function generateNewNode({ data, position, id, zIndex, type, ...rest }: Omit<Node, 'id'> & { id?: string }): { newNode: Node diff --git a/web/app/components/workflow/utils/tool.ts b/web/app/components/workflow/utils/tool.ts index 1c3b792d9e..6740cf9be4 100644 --- a/web/app/components/workflow/utils/tool.ts +++ b/web/app/components/workflow/utils/tool.ts @@ -1,13 +1,13 @@ +import type { ToolNodeType } from '../nodes/tool/types' import type { InputVar, ToolWithProvider, } from '../types' -import type { ToolNodeType } from '../nodes/tool/types' +import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' import { CollectionType } from '@/app/components/tools/types' import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' -import { canFindTool } from '@/utils' -import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' import { Type } from '@/app/components/workflow/nodes/llm/types' +import { canFindTool } from '@/utils' export const getToolCheckParams = ( toolData: ToolNodeType, diff --git a/web/app/components/workflow/utils/variable.ts b/web/app/components/workflow/utils/variable.ts index f73f92e371..8c40a469d1 100644 --- a/web/app/components/workflow/utils/variable.ts +++ b/web/app/components/workflow/utils/variable.ts @@ -1,14 +1,12 @@ -import type { - ValueSelector, -} from '../types' import type { BlockEnum, + ValueSelector, } from '../types' import { hasErrorHandleNode } from '.' export const variableTransformer = (v: ValueSelector | string) => { if (typeof v === 'string') - return v.replace(/^{{#|#}}$/g, '').split('.') + return v.replace(/^\{\{#|#\}\}$/g, '').split('.') return `{{#${v.join('.')}#}}` } diff --git a/web/app/components/workflow/utils/workflow-entry.ts b/web/app/components/workflow/utils/workflow-entry.ts index 724a68a85b..bb24fa1466 100644 --- a/web/app/components/workflow/utils/workflow-entry.ts +++ b/web/app/components/workflow/utils/workflow-entry.ts @@ -1,4 +1,5 @@ -import { BlockEnum, type Node, isTriggerNode } from '../types' +import type { Node } from '../types' +import { BlockEnum, isTriggerNode } from '../types' /** * Get the workflow entry node @@ -6,7 +7,8 @@ import { BlockEnum, type Node, isTriggerNode } from '../types' */ export function getWorkflowEntryNode(nodes: Node[]): Node | undefined { const triggerNode = nodes.find(node => isTriggerNode(node.data.type)) - if (triggerNode) return triggerNode + if (triggerNode) + return triggerNode return nodes.find(node => node.data.type === BlockEnum.Start) } diff --git a/web/app/components/workflow/utils/workflow-init.spec.ts b/web/app/components/workflow/utils/workflow-init.spec.ts index 8b7bdfaa92..8dfcbeb30d 100644 --- a/web/app/components/workflow/utils/workflow-init.spec.ts +++ b/web/app/components/workflow/utils/workflow-init.spec.ts @@ -1,9 +1,9 @@ -import { preprocessNodesAndEdges } from './workflow-init' -import { BlockEnum } from '@/app/components/workflow/types' import type { Node, } from '@/app/components/workflow/types' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' +import { BlockEnum } from '@/app/components/workflow/types' +import { preprocessNodesAndEdges } from './workflow-init' describe('preprocessNodesAndEdges', () => { it('process nodes without iteration node or loop node should return origin nodes and edges.', () => { diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 08d0d82e79..18ba643d30 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -1,17 +1,23 @@ -import { - getConnectedEdges, -} from 'reactflow' -import { - cloneDeep, -} from 'lodash-es' +import type { IfElseNodeType } from '../nodes/if-else/types' +import type { IterationNodeType } from '../nodes/iteration/types' +import type { LoopNodeType } from '../nodes/loop/types' +import type { QuestionClassifierNodeType } from '../nodes/question-classifier/types' +import type { ToolNodeType } from '../nodes/tool/types' import type { Edge, Node, } from '../types' import { - BlockEnum, - ErrorHandleMode, -} from '../types' + cloneDeep, +} from 'lodash-es' +import { + getConnectedEdges, +} from 'reactflow' +import { correctModelProvider } from '@/utils' +import { + getIterationStartNode, + getLoopStartNode, +} from '.' import { CUSTOM_NODE, DEFAULT_RETRY_INTERVAL, @@ -21,19 +27,13 @@ import { NODE_WIDTH_X_OFFSET, START_INITIAL_POSITION, } from '../constants' +import { branchNameCorrect } from '../nodes/if-else/utils' import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants' import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants' -import type { QuestionClassifierNodeType } from '../nodes/question-classifier/types' -import type { IfElseNodeType } from '../nodes/if-else/types' -import { branchNameCorrect } from '../nodes/if-else/utils' -import type { IterationNodeType } from '../nodes/iteration/types' -import type { LoopNodeType } from '../nodes/loop/types' -import type { ToolNodeType } from '../nodes/tool/types' import { - getIterationStartNode, - getLoopStartNode, -} from '.' -import { correctModelProvider } from '@/utils' + BlockEnum, + ErrorHandleMode, +} from '../types' const WHITE = 'WHITE' const GRAY = 'GRAY' @@ -216,7 +216,7 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { acc[node.parentId] = [{ nodeId: node.id, nodeType: node.data.type }] } return acc - }, {} as Record<string, { nodeId: string; nodeType: BlockEnum }[]>) + }, {} as Record<string, { nodeId: string, nodeType: BlockEnum }[]>) return nodes.map((node) => { if (!node.type) diff --git a/web/app/components/workflow/utils/workflow.ts b/web/app/components/workflow/utils/workflow.ts index 14b1eb87d5..43fbd687c1 100644 --- a/web/app/components/workflow/utils/workflow.ts +++ b/web/app/components/workflow/utils/workflow.ts @@ -1,21 +1,21 @@ -import { - getOutgoers, -} from 'reactflow' -import { v4 as uuid4 } from 'uuid' -import { - uniqBy, -} from 'lodash-es' import type { Edge, Node, } from '../types' +import { + uniqBy, +} from 'lodash-es' +import { + getOutgoers, +} from 'reactflow' +import { v4 as uuid4 } from 'uuid' import { BlockEnum, } from '../types' export const canRunBySingle = (nodeType: BlockEnum, isChildNode: boolean) => { // child node means in iteration or loop. Set value to iteration(or loop) may cause variable not exit problem in backend. - if(isChildNode && nodeType === BlockEnum.Assigner) + if (isChildNode && nodeType === BlockEnum.Assigner) return false return nodeType === BlockEnum.LLM || nodeType === BlockEnum.KnowledgeRetrieval diff --git a/web/app/components/workflow/variable-inspect/display-content.tsx b/web/app/components/workflow/variable-inspect/display-content.tsx index 249f948719..901c0fa6dc 100644 --- a/web/app/components/workflow/variable-inspect/display-content.tsx +++ b/web/app/components/workflow/variable-inspect/display-content.tsx @@ -1,17 +1,17 @@ -import React, { useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { RiBracesLine, RiEyeLine } from '@remixicon/react' -import Textarea from '@/app/components/base/textarea' -import { Markdown } from '@/app/components/base/markdown' -import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' -import { SegmentedControl } from '@/app/components/base/segmented-control' -import { cn } from '@/utils/classnames' -import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list' +import type { VarType } from '../types' import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list/types' import type { ParentMode } from '@/models/datasets' +import { RiBracesLine, RiEyeLine } from '@remixicon/react' +import React, { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Markdown } from '@/app/components/base/markdown' +import { SegmentedControl } from '@/app/components/base/segmented-control' +import Textarea from '@/app/components/base/textarea' +import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list' +import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' import { ChunkingMode } from '@/models/datasets' +import { cn } from '@/utils/classnames' import { PreviewType, ViewMode } from './types' -import type { VarType } from '../types' type DisplayContentProps = { previewType: PreviewType @@ -52,15 +52,16 @@ const DisplayContent = (props: DisplayContentProps) => { return ( <div className={cn('flex h-full flex-col rounded-[10px] bg-components-input-bg-normal', isFocused && 'bg-components-input-bg-active outline outline-1 outline-components-input-border-active', className)}> - <div className='flex shrink-0 items-center justify-end p-1'> + <div className="flex shrink-0 items-center justify-end p-1"> {previewType === PreviewType.Markdown && ( - <div className='system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary'> + <div className="system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary"> {previewType.toUpperCase()} </div> )} {previewType === PreviewType.Chunks && ( - <div className='system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary'> - {varType.toUpperCase()}{schemaType ? `(${schemaType})` : ''} + <div className="system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary"> + {varType.toUpperCase()} + {schemaType ? `(${schemaType})` : ''} </div> )} <SegmentedControl @@ -70,43 +71,49 @@ const DisplayContent = (props: DisplayContentProps) => { ]} value={viewMode} onChange={setViewMode} - size='small' - padding='with' - activeClassName='!text-text-accent-light-mode-only' - btnClassName='!pl-1.5 !pr-0.5 gap-[3px]' - className='shrink-0' + size="small" + padding="with" + activeClassName="!text-text-accent-light-mode-only" + btnClassName="!pl-1.5 !pr-0.5 gap-[3px]" + className="shrink-0" /> </div> - <div className='flex flex-1 overflow-auto rounded-b-[10px] pl-3 pr-1'> + <div className="flex flex-1 overflow-auto rounded-b-[10px] pl-3 pr-1"> {viewMode === ViewMode.Code && ( previewType === PreviewType.Markdown - ? <Textarea - readOnly={readonly} - disabled={readonly} - className='h-full border-none bg-transparent p-0 text-text-secondary hover:bg-transparent focus:bg-transparent focus:shadow-none' - value={mdString as any} - onChange={e => handleTextChange?.(e.target.value)} - onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} - /> - : <SchemaEditor - readonly={readonly} - className='overflow-y-auto bg-transparent' - hideTopMenu - schema={jsonString!} - onUpdate={handleEditorChange!} - onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} - /> + ? ( + <Textarea + readOnly={readonly} + disabled={readonly} + className="h-full border-none bg-transparent p-0 text-text-secondary hover:bg-transparent focus:bg-transparent focus:shadow-none" + value={mdString as any} + onChange={e => handleTextChange?.(e.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + ) + : ( + <SchemaEditor + readonly={readonly} + className="overflow-y-auto bg-transparent" + hideTopMenu + schema={jsonString!} + onUpdate={handleEditorChange!} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + /> + ) )} {viewMode === ViewMode.Preview && ( previewType === PreviewType.Markdown - ? <Markdown className='grow overflow-auto rounded-lg px-4 py-3' content={(mdString ?? '') as string} /> - : <ChunkCardList - chunkType={chunkType!} - parentMode={parentMode} - chunkInfo={JSON.parse(jsonString!) as ChunkInfo} - /> + ? <Markdown className="grow overflow-auto rounded-lg px-4 py-3" content={(mdString ?? '') as string} /> + : ( + <ChunkCardList + chunkType={chunkType!} + parentMode={parentMode} + chunkInfo={JSON.parse(jsonString!) as ChunkInfo} + /> + ) )} </div> </div> diff --git a/web/app/components/workflow/variable-inspect/empty.tsx b/web/app/components/workflow/variable-inspect/empty.tsx index 38df10f6e3..e4e4b895f1 100644 --- a/web/app/components/workflow/variable-inspect/empty.tsx +++ b/web/app/components/workflow/variable-inspect/empty.tsx @@ -6,18 +6,19 @@ const Empty: FC = () => { const { t } = useTranslation() return ( - <div className='flex h-full flex-col gap-3 rounded-xl bg-background-section p-8'> - <div className='flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur-sm'> - <Variable02 className='h-5 w-5 text-text-accent' /> + <div className="flex h-full flex-col gap-3 rounded-xl bg-background-section p-8"> + <div className="flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur-sm"> + <Variable02 className="h-5 w-5 text-text-accent" /> </div> - <div className='flex flex-col gap-1'> - <div className='system-sm-semibold text-text-secondary'>{t('workflow.debug.variableInspect.title')}</div> - <div className='system-xs-regular text-text-tertiary'>{t('workflow.debug.variableInspect.emptyTip')}</div> + <div className="flex flex-col gap-1"> + <div className="system-sm-semibold text-text-secondary">{t('workflow.debug.variableInspect.title')}</div> + <div className="system-xs-regular text-text-tertiary">{t('workflow.debug.variableInspect.emptyTip')}</div> <a - className='system-xs-regular cursor-pointer text-text-accent' - href='https://docs.dify.ai/en/guides/workflow/debug-and-preview/variable-inspect' - target='_blank' - rel='noopener noreferrer'> + className="system-xs-regular cursor-pointer text-text-accent" + href="https://docs.dify.ai/en/guides/workflow/debug-and-preview/variable-inspect" + target="_blank" + rel="noopener noreferrer" + > {t('workflow.debug.variableInspect.emptyLink')} </a> </div> diff --git a/web/app/components/workflow/variable-inspect/group.tsx b/web/app/components/workflow/variable-inspect/group.tsx index 0887a3823d..bcee333e28 100644 --- a/web/app/components/workflow/variable-inspect/group.tsx +++ b/web/app/components/workflow/variable-inspect/group.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { currentVarType } from './panel' +import type { NodeWithVar, VarInInspect } from '@/types/workflow' import { RiArrowRightSLine, RiDeleteBinLine, @@ -7,16 +7,16 @@ import { RiLoader2Line, // RiErrorWarningFill, } from '@remixicon/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' // import Button from '@/app/components/base/button' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' -import type { currentVarType } from './panel' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' import { VarInInspectType } from '@/types/workflow' -import type { NodeWithVar, VarInInspect } from '@/types/workflow' import { cn } from '@/utils/classnames' import { useToolIcon } from '../hooks' -import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' type Props = { nodeData?: NodeWithVar @@ -86,7 +86,8 @@ const Group = ({ }) return } - if (!nodeData) return + if (!nodeData) + return handleSelect({ nodeId: nodeData.nodeId, nodeType: nodeData.nodeType, @@ -96,31 +97,31 @@ const Group = ({ } return ( - <div className='p-0.5'> + <div className="p-0.5"> {/* node item */} - <div className='group flex h-6 items-center gap-0.5'> - <div className='h-3 w-3 shrink-0'> + <div className="group flex h-6 items-center gap-0.5"> + <div className="h-3 w-3 shrink-0"> {nodeData?.isSingRunRunning && ( - <RiLoader2Line className='h-3 w-3 animate-spin text-text-accent' /> + <RiLoader2Line className="h-3 w-3 animate-spin text-text-accent" /> )} {(!nodeData || !nodeData.isSingRunRunning) && visibleVarList.length > 0 && ( <RiArrowRightSLine className={cn('h-3 w-3 text-text-tertiary', !isCollapsed && 'rotate-90')} onClick={() => setIsCollapsed(!isCollapsed)} /> )} </div> - <div className='flex grow cursor-pointer items-center gap-1' onClick={() => setIsCollapsed(!isCollapsed)}> + <div className="flex grow cursor-pointer items-center gap-1" onClick={() => setIsCollapsed(!isCollapsed)}> {nodeData && ( <> <BlockIcon - className='shrink-0' + className="shrink-0" type={nodeData.nodeType} toolIcon={toolIcon || ''} - size='xs' + size="xs" /> - <div className='system-xs-medium-uppercase truncate text-text-tertiary'>{nodeData.title}</div> + <div className="system-xs-medium-uppercase truncate text-text-tertiary">{nodeData.title}</div> </> )} {!nodeData && ( - <div className='system-xs-medium-uppercase truncate text-text-tertiary'> + <div className="system-xs-medium-uppercase truncate text-text-tertiary"> {isEnv && t('workflow.debug.variableInspect.envNode')} {isChatVar && t('workflow.debug.variableInspect.chatNode')} {isSystem && t('workflow.debug.variableInspect.systemNode')} @@ -128,15 +129,15 @@ const Group = ({ )} </div> {nodeData && !nodeData.isSingRunRunning && ( - <div className='hidden shrink-0 items-center group-hover:flex'> + <div className="hidden shrink-0 items-center group-hover:flex"> <Tooltip popupContent={t('workflow.debug.variableInspect.view')}> <ActionButton onClick={handleView}> - <RiFileList3Line className='h-4 w-4' /> + <RiFileList3Line className="h-4 w-4" /> </ActionButton> </Tooltip> <Tooltip popupContent={t('workflow.debug.variableInspect.clearNode')}> <ActionButton onClick={handleClear}> - <RiDeleteBinLine className='h-4 w-4' /> + <RiDeleteBinLine className="h-4 w-4" /> </ActionButton> </Tooltip> </div> @@ -144,7 +145,7 @@ const Group = ({ </div> {/* var item list */} {!isCollapsed && !nodeData?.isSingRunRunning && ( - <div className='px-0.5'> + <div className="px-0.5"> {visibleVarList.length > 0 && visibleVarList.map(varItem => ( <div key={varItem.id} @@ -157,10 +158,10 @@ const Group = ({ <VariableIconWithColor variableCategory={varType} isExceptionVariable={['error_type', 'error_message'].includes(varItem.name)} - className='size-4' + className="size-4" /> - <div className='system-sm-medium grow truncate text-text-secondary'>{varItem.name}</div> - <div className='system-xs-regular shrink-0 text-text-tertiary'>{varItem.value_type}</div> + <div className="system-sm-medium grow truncate text-text-secondary">{varItem.name}</div> + <div className="system-xs-regular shrink-0 text-text-tertiary">{varItem.value_type}</div> </div> ))} </div> diff --git a/web/app/components/workflow/variable-inspect/index.tsx b/web/app/components/workflow/variable-inspect/index.tsx index 64466ee312..ced7861e00 100644 --- a/web/app/components/workflow/variable-inspect/index.tsx +++ b/web/app/components/workflow/variable-inspect/index.tsx @@ -1,13 +1,13 @@ import type { FC } from 'react' +import { debounce } from 'lodash-es' import { useCallback, useMemo, } from 'react' -import { debounce } from 'lodash-es' -import { useStore } from '../store' -import { useResizePanel } from '../nodes/_base/hooks/use-resize-panel' -import Panel from './panel' import { cn } from '@/utils/classnames' +import { useResizePanel } from '../nodes/_base/hooks/use-resize-panel' +import { useStore } from '../store' +import Panel from './panel' const VariableInspectPanel: FC = () => { const showVariableInspectPanel = useStore(s => s.showVariableInspectPanel) @@ -44,8 +44,9 @@ const VariableInspectPanel: FC = () => { <div className={cn('relative pb-1')}> <div ref={triggerRef} - className='absolute -top-1 left-0 flex h-1 w-full cursor-row-resize resize-y items-center justify-center'> - <div className='h-0.5 w-10 rounded-sm bg-state-base-handle hover:w-full hover:bg-state-accent-solid active:w-full active:bg-state-accent-solid'></div> + className="absolute -top-1 left-0 flex h-1 w-full cursor-row-resize resize-y items-center justify-center" + > + <div className="h-0.5 w-10 rounded-sm bg-state-base-handle hover:w-full hover:bg-state-accent-solid active:w-full active:bg-state-accent-solid"></div> </div> <div ref={containerRef} diff --git a/web/app/components/workflow/variable-inspect/large-data-alert.tsx b/web/app/components/workflow/variable-inspect/large-data-alert.tsx index a6e00bf591..a2750c82e7 100644 --- a/web/app/components/workflow/variable-inspect/large-data-alert.tsx +++ b/web/app/components/workflow/variable-inspect/large-data-alert.tsx @@ -1,9 +1,9 @@ 'use client' -import { RiInformation2Fill } from '@remixicon/react' import type { FC } from 'react' +import { RiInformation2Fill } from '@remixicon/react' import React from 'react' -import { cn } from '@/utils/classnames' import { useTranslation } from 'react-i18next' +import { cn } from '@/utils/classnames' type Props = { textHasNoExport?: boolean @@ -20,12 +20,12 @@ const LargeDataAlert: FC<Props> = ({ const text = textHasNoExport ? t('workflow.debug.variableInspect.largeDataNoExport') : t('workflow.debug.variableInspect.largeData') return ( <div className={cn('flex h-8 items-center justify-between rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-2 shadow-xs', className)}> - <div className='flex h-full w-0 grow items-center space-x-1'> - <RiInformation2Fill className='size-4 shrink-0 text-text-accent' /> - <div className='system-xs-regular w-0 grow truncate text-text-primary'>{text}</div> + <div className="flex h-full w-0 grow items-center space-x-1"> + <RiInformation2Fill className="size-4 shrink-0 text-text-accent" /> + <div className="system-xs-regular w-0 grow truncate text-text-primary">{text}</div> </div> {downloadUrl && ( - <div className='system-xs-medium-uppercase ml-1 shrink-0 cursor-pointer text-text-accent'>{t('workflow.debug.variableInspect.export')}</div> + <div className="system-xs-medium-uppercase ml-1 shrink-0 cursor-pointer text-text-accent">{t('workflow.debug.variableInspect.export')}</div> )} </div> ) diff --git a/web/app/components/workflow/variable-inspect/left.tsx b/web/app/components/workflow/variable-inspect/left.tsx index b177440b2f..12e0bb8745 100644 --- a/web/app/components/workflow/variable-inspect/left.tsx +++ b/web/app/components/workflow/variable-inspect/left.tsx @@ -1,17 +1,17 @@ +import type { currentVarType } from './panel' + +import type { VarInInspect } from '@/types/workflow' // import { useState } from 'react' import { useTranslation } from 'react-i18next' - -import { useStore } from '../store' import Button from '@/app/components/base/button' +import { VarInInspectType } from '@/types/workflow' +import { cn } from '@/utils/classnames' +import useCurrentVars from '../hooks/use-inspect-vars-crud' +import { useNodesInteractions } from '../hooks/use-nodes-interactions' +import { useStore } from '../store' // import ActionButton from '@/app/components/base/action-button' // import Tooltip from '@/app/components/base/tooltip' import Group from './group' -import useCurrentVars from '../hooks/use-inspect-vars-crud' -import { useNodesInteractions } from '../hooks/use-nodes-interactions' -import type { currentVarType } from './panel' -import type { VarInInspect } from '@/types/workflow' -import { VarInInspectType } from '@/types/workflow' -import { cn } from '@/utils/classnames' type Props = { currentNodeVar?: currentVarType @@ -51,12 +51,12 @@ const Left = ({ return ( <div className={cn('flex h-full flex-col')}> {/* header */} - <div className='flex shrink-0 items-center justify-between gap-1 pl-4 pr-1 pt-2'> - <div className='system-sm-semibold-uppercase truncate text-text-primary'>{t('workflow.debug.variableInspect.title')}</div> - <Button variant='ghost' size='small' className='shrink-0' onClick={handleClearAll}>{t('workflow.debug.variableInspect.clearAll')}</Button> + <div className="flex shrink-0 items-center justify-between gap-1 pl-4 pr-1 pt-2"> + <div className="system-sm-semibold-uppercase truncate text-text-primary">{t('workflow.debug.variableInspect.title')}</div> + <Button variant="ghost" size="small" className="shrink-0" onClick={handleClearAll}>{t('workflow.debug.variableInspect.clearAll')}</Button> </div> {/* content */} - <div className='grow overflow-y-auto py-1'> + <div className="grow overflow-y-auto py-1"> {/* group ENV */} {environmentVariables.length > 0 && ( <Group @@ -86,8 +86,8 @@ const Left = ({ )} {/* divider */} {showDivider && ( - <div className='px-4 py-1'> - <div className='h-px bg-divider-subtle'></div> + <div className="px-4 py-1"> + <div className="h-px bg-divider-subtle"></div> </div> )} {/* group nodes */} diff --git a/web/app/components/workflow/variable-inspect/listening.tsx b/web/app/components/workflow/variable-inspect/listening.tsx index 1f2577f150..8b2c55fcc4 100644 --- a/web/app/components/workflow/variable-inspect/listening.tsx +++ b/web/app/components/workflow/variable-inspect/listening.tsx @@ -1,18 +1,20 @@ -import { type FC, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { type Node, useStoreApi } from 'reactflow' -import Button from '@/app/components/base/button' -import BlockIcon from '@/app/components/workflow/block-icon' -import { BlockEnum } from '@/app/components/workflow/types' -import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' -import { useStore } from '../store' -import { useGetToolIcon } from '@/app/components/workflow/hooks/use-tool-icon' import type { TFunction } from 'i18next' -import { getNextExecutionTime } from '@/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator' +import type { FC } from 'react' +import type { Node } from 'reactflow' import type { ScheduleTriggerNodeType } from '@/app/components/workflow/nodes/trigger-schedule/types' import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types' -import Tooltip from '@/app/components/base/tooltip' import copy from 'copy-to-clipboard' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useStoreApi } from 'reactflow' +import Button from '@/app/components/base/button' +import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' +import Tooltip from '@/app/components/base/tooltip' +import BlockIcon from '@/app/components/workflow/block-icon' +import { useGetToolIcon } from '@/app/components/workflow/hooks/use-tool-icon' +import { getNextExecutionTime } from '@/app/components/workflow/nodes/trigger-schedule/utils/execution-time-calculator' +import { BlockEnum } from '@/app/components/workflow/types' +import { useStore } from '../store' const resolveListeningDescription = ( message: string | undefined, @@ -32,7 +34,7 @@ const resolveListeningDescription = ( } if (triggerType === BlockEnum.TriggerPlugin) { - const pluginName = (triggerNode?.data as { provider_name?: string; title?: string })?.provider_name + const pluginName = (triggerNode?.data as { provider_name?: string, title?: string })?.provider_name || (triggerNode?.data as { title?: string })?.title || t('workflow.debug.variableInspect.listening.defaultPluginName') return t('workflow.debug.variableInspect.listening.tipPlugin', { pluginName }) @@ -155,8 +157,8 @@ const Listening: FC<ListeningProps> = ({ : resolveListeningDescription(message, triggerNode, triggerType, t) return ( - <div className='flex h-full flex-col gap-4 rounded-xl bg-background-section p-8'> - <div className='flex flex-row flex-wrap items-center gap-3'> + <div className="flex h-full flex-col gap-4 rounded-xl bg-background-section p-8"> + <div className="flex flex-row flex-wrap items-center gap-3"> {iconsToRender.map(icon => ( <BlockIcon key={icon.key} @@ -167,13 +169,13 @@ const Listening: FC<ListeningProps> = ({ /> ))} </div> - <div className='flex flex-col gap-1'> - <div className='system-sm-semibold text-text-secondary'>{t('workflow.debug.variableInspect.listening.title')}</div> - <div className='system-xs-regular whitespace-pre-line text-text-tertiary'>{description}</div> + <div className="flex flex-col gap-1"> + <div className="system-sm-semibold text-text-secondary">{t('workflow.debug.variableInspect.listening.title')}</div> + <div className="system-xs-regular whitespace-pre-line text-text-tertiary">{description}</div> </div> {webhookDebugUrl && ( - <div className='flex items-center gap-2'> - <div className='system-xs-regular shrink-0 whitespace-pre-line text-text-tertiary'> + <div className="flex items-center gap-2"> + <div className="system-xs-regular shrink-0 whitespace-pre-line text-text-tertiary"> {t('workflow.nodes.triggerWebhook.debugUrlTitle')} </div> <Tooltip @@ -186,7 +188,7 @@ const Listening: FC<ListeningProps> = ({ needsDelay={true} > <button - type='button' + type="button" aria-label={t('workflow.nodes.triggerWebhook.debugUrlCopy') || ''} className={`inline-flex items-center rounded-[6px] border border-divider-regular bg-components-badge-white-to-dark px-1.5 py-[2px] font-mono text-[13px] leading-[18px] text-text-secondary transition-colors hover:bg-components-panel-on-panel-item-bg-hover focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-components-panel-border ${debugUrlCopied ? 'bg-components-panel-on-panel-item-bg-hover text-text-primary' : ''}`} onClick={() => { @@ -194,7 +196,7 @@ const Listening: FC<ListeningProps> = ({ setDebugUrlCopied(true) }} > - <span className='whitespace-nowrap text-text-primary'> + <span className="whitespace-nowrap text-text-primary"> {webhookDebugUrl} </span> </button> @@ -203,12 +205,12 @@ const Listening: FC<ListeningProps> = ({ )} <div> <Button - size='medium' - className='px-3' - variant='primary' + size="medium" + className="px-3" + variant="primary" onClick={onStop} > - <StopCircle className='mr-1 size-4' /> + <StopCircle className="mr-1 size-4" /> {t('workflow.debug.variableInspect.listening.stopButton')} </Button> </div> diff --git a/web/app/components/workflow/variable-inspect/panel.tsx b/web/app/components/workflow/variable-inspect/panel.tsx index 047bede826..bccc2ae3e3 100644 --- a/web/app/components/workflow/variable-inspect/panel.tsx +++ b/web/app/components/workflow/variable-inspect/panel.tsx @@ -1,24 +1,24 @@ import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { NodeProps } from '../types' +import type { VarInInspect } from '@/types/workflow' import { RiCloseLine, } from '@remixicon/react' -import { useStore } from '../store' -import useCurrentVars from '../hooks/use-inspect-vars-crud' -import Empty from './empty' -import Listening from './listening' -import Left from './left' -import Right from './right' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' -import type { VarInInspect } from '@/types/workflow' -import { VarInInspectType } from '@/types/workflow' - -import { cn } from '@/utils/classnames' -import type { NodeProps } from '../types' -import useMatchSchemaType from '../nodes/_base/components/variable/use-match-schema-type' -import { useEventEmitterContextContext } from '@/context/event-emitter' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' +import { useEventEmitterContextContext } from '@/context/event-emitter' +import { VarInInspectType } from '@/types/workflow' +import { cn } from '@/utils/classnames' +import useCurrentVars from '../hooks/use-inspect-vars-crud' +import useMatchSchemaType from '../nodes/_base/components/variable/use-match-schema-type' + +import { useStore } from '../store' +import Empty from './empty' +import Left from './left' +import Listening from './listening' +import Right from './right' export type currentVarType = { nodeId: string @@ -55,7 +55,8 @@ const Panel: FC = () => { }, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) const currentNodeInfo = useMemo(() => { - if (!currentFocusNodeId) return + if (!currentFocusNodeId) + return if (currentFocusNodeId === VarInInspectType.environment) { const currentVar = environmentVariables.find(v => v.id === currentVarId) const res = { @@ -113,7 +114,8 @@ const Panel: FC = () => { return res } const targetNode = nodesWithInspectVars.find(node => node.nodeId === currentFocusNodeId) - if (!targetNode) return + if (!targetNode) + return const currentVar = targetNode.vars.find(v => v.id === currentVarId) return { nodeId: targetNode.nodeId, @@ -127,9 +129,11 @@ const Panel: FC = () => { }, [currentFocusNodeId, currentVarId, environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) const isCurrentNodeVarValueFetching = useMemo(() => { - if (!currentNodeInfo) return false + if (!currentNodeInfo) + return false const targetNode = nodesWithInspectVars.find(node => node.nodeId === currentNodeInfo.nodeId) - if (!targetNode) return false + if (!targetNode) + return false return !targetNode.isValueFetched }, [currentNodeInfo, nodesWithInspectVars]) @@ -156,13 +160,13 @@ const Panel: FC = () => { if (isListening) { return ( <div className={cn('flex h-full flex-col')}> - <div className='flex shrink-0 items-center justify-between pl-4 pr-2 pt-2'> - <div className='system-sm-semibold-uppercase text-text-primary'>{t('workflow.debug.variableInspect.title')}</div> + <div className="flex shrink-0 items-center justify-between pl-4 pr-2 pt-2"> + <div className="system-sm-semibold-uppercase text-text-primary">{t('workflow.debug.variableInspect.title')}</div> <ActionButton onClick={() => setShowVariableInspectPanel(false)}> - <RiCloseLine className='h-4 w-4' /> + <RiCloseLine className="h-4 w-4" /> </ActionButton> </div> - <div className='grow p-2'> + <div className="grow p-2"> <Listening onStop={handleStopListening} /> @@ -174,13 +178,13 @@ const Panel: FC = () => { if (isEmpty) { return ( <div className={cn('flex h-full flex-col')}> - <div className='flex shrink-0 items-center justify-between pl-4 pr-2 pt-2'> - <div className='system-sm-semibold-uppercase text-text-primary'>{t('workflow.debug.variableInspect.title')}</div> + <div className="flex shrink-0 items-center justify-between pl-4 pr-2 pt-2"> + <div className="system-sm-semibold-uppercase text-text-primary">{t('workflow.debug.variableInspect.title')}</div> <ActionButton onClick={() => setShowVariableInspectPanel(false)}> - <RiCloseLine className='h-4 w-4' /> + <RiCloseLine className="h-4 w-4" /> </ActionButton> </div> - <div className='grow p-2'> + <div className="grow p-2"> <Empty /> </div> </div> @@ -190,7 +194,7 @@ const Panel: FC = () => { return ( <div className={cn('relative flex h-full')}> {/* left */} - {bottomPanelWidth < 488 && showLeftPanel && <div className='absolute left-0 top-0 h-full w-full' onClick={() => setShowLeftPanel(false)}></div>} + {bottomPanelWidth < 488 && showLeftPanel && <div className="absolute left-0 top-0 h-full w-full" onClick={() => setShowLeftPanel(false)}></div>} <div className={cn( 'w-60 shrink-0 border-r border-divider-burn', @@ -207,7 +211,7 @@ const Panel: FC = () => { /> </div> {/* right */} - <div className='w-0 grow'> + <div className="w-0 grow"> <Right nodeId={currentFocusNodeId!} isValueFetching={isCurrentNodeVarValueFetching} diff --git a/web/app/components/workflow/variable-inspect/right.tsx b/web/app/components/workflow/variable-inspect/right.tsx index 9fbf18dd64..e451837eca 100644 --- a/web/app/components/workflow/variable-inspect/right.tsx +++ b/web/app/components/workflow/variable-inspect/right.tsx @@ -1,4 +1,5 @@ -import { useTranslation } from 'react-i18next' +import type { currentVarType } from './panel' +import type { GenRes } from '@/service/debug' import { RiArrowGoBackLine, RiCloseLine, @@ -6,35 +7,34 @@ import { RiMenuLine, RiSparklingFill, } from '@remixicon/react' -import { useStore } from '../store' -import { BlockEnum } from '../types' -import useCurrentVars from '../hooks/use-inspect-vars-crud' -import Empty from './empty' -import ValueContent from './value-content' +import { useBoolean } from 'ahooks' +import { produce } from 'immer' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' import CopyFeedback from '@/app/components/base/copy-feedback' +import Loading from '@/app/components/base/loading' import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' -import Loading from '@/app/components/base/loading' -import type { currentVarType } from './panel' +import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { useEventEmitterContextContext } from '@/context/event-emitter' +import { AppModeEnum } from '@/types/app' import { VarInInspectType } from '@/types/workflow' import { cn } from '@/utils/classnames' -import useNodeInfo from '../nodes/_base/hooks/use-node-info' -import { useBoolean } from 'ahooks' -import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' import GetCodeGeneratorResModal from '../../app/configuration/config/code-generator/get-code-generator-res' -import { AppModeEnum } from '@/types/app' -import { useHooksStore } from '../hooks-store' -import { useCallback, useMemo } from 'react' -import { useNodesInteractions, useToolIcon } from '../hooks' -import { CodeLanguage } from '../nodes/code/types' -import useNodeCrud from '../nodes/_base/hooks/use-node-crud' -import type { GenRes } from '@/service/debug' -import { produce } from 'immer' import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '../../base/prompt-editor/plugins/update-block' -import { useEventEmitterContextContext } from '@/context/event-emitter' -import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' +import { useNodesInteractions, useToolIcon } from '../hooks' +import { useHooksStore } from '../hooks-store' +import useCurrentVars from '../hooks/use-inspect-vars-crud' +import useNodeCrud from '../nodes/_base/hooks/use-node-crud' +import useNodeInfo from '../nodes/_base/hooks/use-node-info' +import { CodeLanguage } from '../nodes/code/types' +import { useStore } from '../store' +import { BlockEnum } from '../types' +import Empty from './empty' +import ValueContent from './value-content' type Props = { nodeId: string @@ -64,12 +64,14 @@ const Right = ({ } = useCurrentVars() const handleValueChange = (varId: string, value: any) => { - if (!currentNodeVar) return + if (!currentNodeVar) + return editInspectVarValue(currentNodeVar.nodeId, varId, value) } const resetValue = () => { - if (!currentNodeVar) return + if (!currentNodeVar) + return resetToLastRunVar(currentNodeVar.nodeId, currentNodeVar.var.id) } @@ -79,7 +81,8 @@ const Right = ({ } const handleClear = () => { - if (!currentNodeVar) return + if (!currentNodeVar) + return resetConversationVar(currentNodeVar.var.id) } @@ -161,20 +164,20 @@ const Right = ({ return ( <div className={cn('flex h-full flex-col')}> {/* header */} - <div className='flex shrink-0 items-center justify-between gap-1 px-2 pt-2'> + <div className="flex shrink-0 items-center justify-between gap-1 px-2 pt-2"> {bottomPanelWidth < 488 && ( - <ActionButton className='shrink-0' onClick={handleOpenMenu}> - <RiMenuLine className='h-4 w-4' /> + <ActionButton className="shrink-0" onClick={handleOpenMenu}> + <RiMenuLine className="h-4 w-4" /> </ActionButton> )} - <div className='flex w-0 grow items-center gap-1'> + <div className="flex w-0 grow items-center gap-1"> {currentNodeVar?.var && ( <> { [VarInInspectType.environment, VarInInspectType.conversation, VarInInspectType.system].includes(currentNodeVar.nodeType as VarInInspectType) && ( <VariableIconWithColor variableCategory={currentNodeVar.nodeType as VarInInspectType} - className='size-4' + className="size-4" /> ) } @@ -184,22 +187,25 @@ const Right = ({ && ( <> <BlockIcon - className='shrink-0' + className="shrink-0" type={currentNodeVar.nodeType as BlockEnum} - size='xs' + size="xs" toolIcon={toolIcon} /> - <div className='system-sm-regular shrink-0 text-text-secondary'>{currentNodeVar.title}</div> - <div className='system-sm-regular shrink-0 text-text-quaternary'>/</div> + <div className="system-sm-regular shrink-0 text-text-secondary">{currentNodeVar.title}</div> + <div className="system-sm-regular shrink-0 text-text-quaternary">/</div> </> )} - <div title={currentNodeVar.var.name} className='system-sm-semibold truncate text-text-secondary'>{currentNodeVar.var.name}</div> - <div className='system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary'> + <div title={currentNodeVar.var.name} className="system-sm-semibold truncate text-text-secondary">{currentNodeVar.var.name}</div> + <div className="system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary"> <span>{`${currentNodeVar.var.value_type}${displaySchemaType}`}</span> {isTruncated && ( <> <span>·</span> - <span>{((fullContent?.size_bytes || 0) / 1024 / 1024).toFixed(1)}MB</span> + <span> + {((fullContent?.size_bytes || 0) / 1024 / 1024).toFixed(1)} + MB + </span> </> )} </div> @@ -207,16 +213,16 @@ const Right = ({ </> )} </div> - <div className='flex shrink-0 items-center gap-1'> + <div className="flex shrink-0 items-center gap-1"> {currentNodeVar && ( <> {canShowPromptGenerator && ( <Tooltip popupContent={t('appDebug.generate.optimizePromptTooltip')}> <div - className='cursor-pointer rounded-md p-1 hover:bg-state-accent-active' + className="cursor-pointer rounded-md p-1 hover:bg-state-accent-active" onClick={handleShowPromptGenerator} > - <RiSparklingFill className='size-4 text-components-input-border-active-prompt-1' /> + <RiSparklingFill className="size-4 text-components-input-border-active-prompt-1" /> </div> </Tooltip> )} @@ -225,30 +231,30 @@ const Right = ({ <ActionButton> <a href={fullContent?.download_url} - target='_blank' + target="_blank" > - <RiFileDownloadFill className='size-4' /> + <RiFileDownloadFill className="size-4" /> </a> </ActionButton> </Tooltip> )} {!isTruncated && currentNodeVar.var.edited && ( <Badge> - <span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span> - <span className='system-2xs-semibold-uupercase'>{t('workflow.debug.variableInspect.edited')}</span> + <span className="ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary"></span> + <span className="system-2xs-semibold-uupercase">{t('workflow.debug.variableInspect.edited')}</span> </Badge> )} {!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type !== VarInInspectType.conversation && ( <Tooltip popupContent={t('workflow.debug.variableInspect.reset')}> <ActionButton onClick={resetValue}> - <RiArrowGoBackLine className='h-4 w-4' /> + <RiArrowGoBackLine className="h-4 w-4" /> </ActionButton> </Tooltip> )} {!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type === VarInInspectType.conversation && ( <Tooltip popupContent={t('workflow.debug.variableInspect.resetConversationVar')}> <ActionButton onClick={handleClear}> - <RiArrowGoBackLine className='h-4 w-4' /> + <RiArrowGoBackLine className="h-4 w-4" /> </ActionButton> </Tooltip> )} @@ -258,15 +264,15 @@ const Right = ({ </> )} <ActionButton onClick={handleClose}> - <RiCloseLine className='h-4 w-4' /> + <RiCloseLine className="h-4 w-4" /> </ActionButton> </div> </div> {/* content */} - <div className='grow p-2'> + <div className="grow p-2"> {!currentNodeVar?.var && <Empty />} {isValueFetching && ( - <div className='flex h-full items-center justify-center'> + <div className="flex h-full items-center justify-center"> <Loading /> </div> )} @@ -281,25 +287,29 @@ const Right = ({ </div> {isShowPromptGenerator && ( isCodeBlock - ? <GetCodeGeneratorResModal - isShow - mode={AppModeEnum.CHAT} - onClose={handleHidePromptGenerator} - flowId={configsMap?.flowId || ''} - nodeId={nodeId} - currentCode={currentPrompt} - codeLanguages={node?.data?.code_languages || CodeLanguage.python3} - onFinished={handleUpdatePrompt} - /> - : <GetAutomaticResModal - mode={AppModeEnum.CHAT} - isShow - onClose={handleHidePromptGenerator} - onFinished={handleUpdatePrompt} - flowId={configsMap?.flowId || ''} - nodeId={nodeId} - currentPrompt={currentPrompt} - /> + ? ( + <GetCodeGeneratorResModal + isShow + mode={AppModeEnum.CHAT} + onClose={handleHidePromptGenerator} + flowId={configsMap?.flowId || ''} + nodeId={nodeId} + currentCode={currentPrompt} + codeLanguages={node?.data?.code_languages || CodeLanguage.python3} + onFinished={handleUpdatePrompt} + /> + ) + : ( + <GetAutomaticResModal + mode={AppModeEnum.CHAT} + isShow + onClose={handleHidePromptGenerator} + onFinished={handleUpdatePrompt} + flowId={configsMap?.flowId || ''} + nodeId={nodeId} + currentPrompt={currentPrompt} + /> + ) )} </div> ) diff --git a/web/app/components/workflow/variable-inspect/trigger.tsx b/web/app/components/workflow/variable-inspect/trigger.tsx index 33161a6c5e..e354a6db67 100644 --- a/web/app/components/workflow/variable-inspect/trigger.tsx +++ b/web/app/components/workflow/variable-inspect/trigger.tsx @@ -1,18 +1,17 @@ import type { FC } from 'react' -import { useMemo } from 'react' -import { useNodes } from 'reactflow' -import { useTranslation } from 'react-i18next' -import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react' -import Tooltip from '@/app/components/base/tooltip' -import { useStore } from '../store' -import useCurrentVars from '../hooks/use-inspect-vars-crud' -import { WorkflowRunningStatus } from '@/app/components/workflow/types' -import { NodeRunningStatus } from '@/app/components/workflow/types' import type { CommonNodeType } from '@/app/components/workflow/types' -import { useEventEmitterContextContext } from '@/context/event-emitter' +import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react' +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useNodes } from 'reactflow' +import Tooltip from '@/app/components/base/tooltip' +import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types' import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types' +import { useEventEmitterContextContext } from '@/context/event-emitter' import { cn } from '@/utils/classnames' +import useCurrentVars from '../hooks/use-inspect-vars-crud' import { useNodesReadOnly } from '../hooks/use-workflow' +import { useStore } from '../store' const VariableInspectTrigger: FC = () => { const { t } = useTranslation() @@ -65,9 +64,7 @@ const VariableInspectTrigger: FC = () => { <div className={cn('flex items-center gap-1')}> {!isRunning && !currentVars.length && ( <div - className={cn('system-2xs-semibold-uppercase flex h-5 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-tertiary shadow-lg backdrop-blur-sm hover:bg-background-default-hover', - nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', - )} + className={cn('system-2xs-semibold-uppercase flex h-5 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-tertiary shadow-lg backdrop-blur-sm hover:bg-background-default-hover', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} onClick={() => { if (getNodesReadOnly()) return @@ -80,9 +77,7 @@ const VariableInspectTrigger: FC = () => { {!isRunning && currentVars.length > 0 && ( <> <div - className={cn('system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent', - nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', - )} + className={cn('system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} onClick={() => { if (getNodesReadOnly()) return @@ -92,9 +87,7 @@ const VariableInspectTrigger: FC = () => { {t('workflow.debug.variableInspect.trigger.cached')} </div> <div - className={cn('system-xs-medium flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 text-text-tertiary shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent hover:text-text-accent', - nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled', - )} + className={cn('system-xs-medium flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 text-text-tertiary shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent hover:text-text-accent', nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled')} onClick={() => { if (getNodesReadOnly()) return @@ -108,21 +101,21 @@ const VariableInspectTrigger: FC = () => { {isRunning && ( <> <div - className='system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent' + className="system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 text-text-accent shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent" onClick={() => setShowVariableInspectPanel(true)} > - <RiLoader2Line className='h-4 w-4 animate-spin' /> - <span className='text-text-accent'>{t('workflow.debug.variableInspect.trigger.running')}</span> + <RiLoader2Line className="h-4 w-4 animate-spin" /> + <span className="text-text-accent">{t('workflow.debug.variableInspect.trigger.running')}</span> </div> {isPreviewRunning && ( <Tooltip popupContent={t('workflow.debug.variableInspect.trigger.stop')} > <div - className='flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent' + className="flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 shadow-lg backdrop-blur-sm hover:bg-components-actionbar-bg-accent" onClick={handleStop} > - <RiStopCircleFill className='h-4 w-4 text-text-accent' /> + <RiStopCircleFill className="h-4 w-4 text-text-accent" /> </div> </Tooltip> )} diff --git a/web/app/components/workflow/variable-inspect/value-content.tsx b/web/app/components/workflow/variable-inspect/value-content.tsx index 0009fb4580..6d6a04434a 100644 --- a/web/app/components/workflow/variable-inspect/value-content.tsx +++ b/web/app/components/workflow/variable-inspect/value-content.tsx @@ -1,30 +1,30 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' +import type { VarInInspect } from '@/types/workflow' import { useDebounceFn } from 'ahooks' -import Textarea from '@/app/components/base/textarea' -import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' +import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' +import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' +import Textarea from '@/app/components/base/textarea' import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message' +import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' import { checkJsonSchemaDepth, getValidationErrorMessage, validateSchemaAgainstDraft7, } from '@/app/components/workflow/nodes/llm/utils' +import { useStore } from '@/app/components/workflow/store' +import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { validateJSONSchema, } from '@/app/components/workflow/variable-inspect/utils' -import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import { TransferMethod } from '@/types/app' -import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import { SupportUploadFileTypes } from '@/app/components/workflow/types' -import type { VarInInspect } from '@/types/workflow' import { VarInInspectType } from '@/types/workflow' import { cn } from '@/utils/classnames' -import LargeDataAlert from './large-data-alert' -import BoolValue from '../panel/chat-variable-panel/components/bool-value' -import { useStore } from '@/app/components/workflow/store' import { PreviewMode } from '../../base/features/types' +import BoolValue from '../panel/chat-variable-panel/components/bool-value' import DisplayContent from './display-content' +import LargeDataAlert from './large-data-alert' import { CHUNK_SCHEMA_TYPES, PreviewType } from './types' type Props = { @@ -184,36 +184,38 @@ const ValueContent = ({ return ( <div ref={contentContainerRef} - className='flex h-full flex-col' + className="flex h-full flex-col" > <div className={cn('relative grow')} style={{ height: `${editorHeight}px` }}> {showTextEditor && ( <> - {isTruncated && <LargeDataAlert className='absolute left-3 right-3 top-1' />} + {isTruncated && <LargeDataAlert className="absolute left-3 right-3 top-1" />} { - currentVar.value_type === 'string' ? ( - <DisplayContent - previewType={PreviewType.Markdown} - varType={currentVar.value_type} - mdString={value as any} - readonly={textEditorDisabled} - handleTextChange={handleTextChange} - className={cn(isTruncated && 'pt-[36px]')} - /> - ) : ( - <Textarea - readOnly={textEditorDisabled} - disabled={textEditorDisabled || isTruncated} - className={cn('h-full', isTruncated && 'pt-[48px]')} - value={value as any} - onChange={e => handleTextChange(e.target.value)} - /> - ) + currentVar.value_type === 'string' + ? ( + <DisplayContent + previewType={PreviewType.Markdown} + varType={currentVar.value_type} + mdString={value as any} + readonly={textEditorDisabled} + handleTextChange={handleTextChange} + className={cn(isTruncated && 'pt-[36px]')} + /> + ) + : ( + <Textarea + readOnly={textEditorDisabled} + disabled={textEditorDisabled || isTruncated} + className={cn('h-full', isTruncated && 'pt-[48px]')} + value={value as any} + onChange={e => handleTextChange(e.target.value)} + /> + ) } </> )} {showBoolEditor && ( - <div className='w-[295px]'> + <div className="w-[295px]"> <BoolValue value={currentVar.value as boolean} onChange={(newValue) => { @@ -225,7 +227,7 @@ const ValueContent = ({ )} { showBoolArrayEditor && ( - <div className='w-[295px] space-y-1'> + <div className="w-[295px] space-y-1"> {currentVar.value.map((v: boolean, i: number) => ( <BoolValue key={i} @@ -244,28 +246,28 @@ const ValueContent = ({ {showJSONEditor && ( hasChunks ? ( - <DisplayContent - previewType={PreviewType.Chunks} - varType={currentVar.value_type} - schemaType={currentVar.schemaType ?? ''} - jsonString={json ?? '{}'} - readonly={JSONEditorDisabled} - handleEditorChange={handleEditorChange} - /> - ) + <DisplayContent + previewType={PreviewType.Chunks} + varType={currentVar.value_type} + schemaType={currentVar.schemaType ?? ''} + jsonString={json ?? '{}'} + readonly={JSONEditorDisabled} + handleEditorChange={handleEditorChange} + /> + ) : ( - <SchemaEditor - readonly={JSONEditorDisabled || isTruncated} - className='overflow-y-auto' - hideTopMenu - schema={json} - onUpdate={handleEditorChange} - isTruncated={isTruncated} - /> - ) + <SchemaEditor + readonly={JSONEditorDisabled || isTruncated} + className="overflow-y-auto" + hideTopMenu + schema={json} + onUpdate={handleEditorChange} + isTruncated={isTruncated} + /> + ) )} {showFileEditor && ( - <div className='max-w-[460px]'> + <div className="max-w-[460px]"> <FileUploaderInAttachmentWrapper value={fileValue} onChange={files => handleFileChange(getProcessedFiles(files))} @@ -295,11 +297,11 @@ const ValueContent = ({ </div> )} </div> - <div ref={errorMessageRef} className='shrink-0'> - {parseError && <ErrorMessage className='mt-1' message={parseError.message} />} - {validationError && <ErrorMessage className='mt-1' message={validationError} />} + <div ref={errorMessageRef} className="shrink-0"> + {parseError && <ErrorMessage className="mt-1" message={parseError.message} />} + {validationError && <ErrorMessage className="mt-1" message={validationError} />} </div> - </div > + </div> ) } diff --git a/web/app/components/workflow/workflow-history-store.tsx b/web/app/components/workflow/workflow-history-store.tsx index 96e87f4fd4..502cb733cb 100644 --- a/web/app/components/workflow/workflow-history-store.tsx +++ b/web/app/components/workflow/workflow-history-store.tsx @@ -1,10 +1,13 @@ -import { type ReactNode, createContext, useContext, useMemo, useState } from 'react' -import { type StoreApi, create } from 'zustand' -import { type TemporalState, temporal } from 'zundo' -import isDeepEqual from 'fast-deep-equal' -import type { Edge, Node } from './types' +import type { ReactNode } from 'react' +import type { TemporalState } from 'zundo' +import type { StoreApi } from 'zustand' import type { WorkflowHistoryEventT } from './hooks' +import type { Edge, Node } from './types' +import isDeepEqual from 'fast-deep-equal' import { noop } from 'lodash-es' +import { createContext, useContext, useMemo, useState } from 'react' +import { temporal } from 'zundo' +import { create } from 'zustand' export const WorkflowHistoryStoreContext = createContext<WorkflowHistoryStoreContextType>({ store: null, shortcutsEnabled: true, setShortcutsEnabled: noop }) export const Provider = WorkflowHistoryStoreContext.Provider diff --git a/web/app/components/workflow/workflow-preview/components/custom-edge.tsx b/web/app/components/workflow/workflow-preview/components/custom-edge.tsx index eb660fb7b8..6294d3f88a 100644 --- a/web/app/components/workflow/workflow-preview/components/custom-edge.tsx +++ b/web/app/components/workflow/workflow-preview/components/custom-edge.tsx @@ -1,17 +1,17 @@ +import type { EdgeProps } from 'reactflow' import { memo, useMemo, } from 'react' -import type { EdgeProps } from 'reactflow' import { BaseEdge, - Position, getBezierPath, + Position, } from 'reactflow' -import { NodeRunningStatus } from '@/app/components/workflow/types' -import { getEdgeColor } from '@/app/components/workflow/utils' import CustomEdgeLinearGradientRender from '@/app/components/workflow/custom-edge-linear-gradient-render' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import { NodeRunningStatus } from '@/app/components/workflow/types' +import { getEdgeColor } from '@/app/components/workflow/utils' const CustomEdge = ({ id, @@ -51,8 +51,9 @@ const CustomEdge = ({ || _targetRunningStatus === NodeRunningStatus.Exception || _targetRunningStatus === NodeRunningStatus.Running ) - ) + ) { return id + } }, [_sourceRunningStatus, _targetRunningStatus, id]) const stroke = useMemo(() => { diff --git a/web/app/components/workflow/workflow-preview/components/error-handle-on-node.tsx b/web/app/components/workflow/workflow-preview/components/error-handle-on-node.tsx index 03ee1c7f47..73b4d01a2b 100644 --- a/web/app/components/workflow/workflow-preview/components/error-handle-on-node.tsx +++ b/web/app/components/workflow/workflow-preview/components/error-handle-on-node.tsx @@ -1,11 +1,11 @@ +import type { Node } from '@/app/components/workflow/types' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useUpdateNodeInternals } from 'reactflow' -import { NodeSourceHandle } from './node-handle' import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' -import type { Node } from '@/app/components/workflow/types' import { NodeRunningStatus } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' +import { NodeSourceHandle } from './node-handle' type ErrorHandleOnNodeProps = Pick<Node, 'id' | 'data'> const ErrorHandleOnNode = ({ @@ -25,18 +25,20 @@ const ErrorHandleOnNode = ({ return null return ( - <div className='relative px-3 pb-2 pt-1'> + <div className="relative px-3 pb-2 pt-1"> <div className={cn( 'relative flex h-6 items-center justify-between rounded-md bg-workflow-block-parma-bg px-[5px]', data._runningStatus === NodeRunningStatus.Exception && 'border-[0.5px] border-components-badge-status-light-warning-halo bg-state-warning-hover', - )}> - <div className='system-xs-medium-uppercase text-text-tertiary'> + )} + > + <div className="system-xs-medium-uppercase text-text-tertiary"> {t('workflow.common.onFailure')} </div> <div className={cn( 'system-xs-medium text-text-secondary', data._runningStatus === NodeRunningStatus.Exception && 'text-text-warning', - )}> + )} + > { error_strategy === ErrorHandleTypeEnum.defaultValue && ( t('workflow.nodes.common.errorHandle.defaultValue.output') @@ -54,7 +56,7 @@ const ErrorHandleOnNode = ({ id={id} data={data} handleId={ErrorHandleTypeEnum.failBranch} - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg' + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg" /> ) } diff --git a/web/app/components/workflow/workflow-preview/components/node-handle.tsx b/web/app/components/workflow/workflow-preview/components/node-handle.tsx index dd44daf1e4..f63dbba20b 100644 --- a/web/app/components/workflow/workflow-preview/components/node-handle.tsx +++ b/web/app/components/workflow/workflow-preview/components/node-handle.tsx @@ -1,3 +1,4 @@ +import type { Node } from '@/app/components/workflow/types' import { memo, } from 'react' @@ -8,7 +9,6 @@ import { import { BlockEnum, } from '@/app/components/workflow/types' -import type { Node } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' type NodeHandleProps = { @@ -27,7 +27,7 @@ export const NodeTargetHandle = memo(({ <> <Handle id={handleId} - type='target' + type="target" position={Position.Left} className={cn( 'z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none', @@ -35,9 +35,9 @@ export const NodeTargetHandle = memo(({ 'transition-all hover:scale-125', !connected && 'after:opacity-0', (data.type === BlockEnum.Start - || data.type === BlockEnum.TriggerWebhook - || data.type === BlockEnum.TriggerSchedule - || data.type === BlockEnum.TriggerPlugin) && 'opacity-0', + || data.type === BlockEnum.TriggerWebhook + || data.type === BlockEnum.TriggerSchedule + || data.type === BlockEnum.TriggerPlugin) && 'opacity-0', handleClassName, )} > @@ -57,7 +57,7 @@ export const NodeSourceHandle = memo(({ return ( <Handle id={handleId} - type='source' + type="source" position={Position.Right} className={cn( 'group/handle z-[1] !h-4 !w-4 !rounded-none !border-none !bg-transparent !outline-none', diff --git a/web/app/components/workflow/workflow-preview/components/nodes/base.tsx b/web/app/components/workflow/workflow-preview/components/nodes/base.tsx index 58df273917..07eed87d24 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/base.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/base.tsx @@ -1,27 +1,27 @@ import type { ReactElement, } from 'react' +import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' +import type { + NodeProps, +} from '@/app/components/workflow/types' import { cloneElement, memo, } from 'react' import { useTranslation } from 'react-i18next' -import { cn } from '@/utils/classnames' +import Tooltip from '@/app/components/base/tooltip' import BlockIcon from '@/app/components/workflow/block-icon' -import type { - NodeProps, -} from '@/app/components/workflow/types' import { BlockEnum, } from '@/app/components/workflow/types' import { hasErrorHandleNode } from '@/app/components/workflow/utils' -import Tooltip from '@/app/components/base/tooltip' -import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' +import { cn } from '@/utils/classnames' +import ErrorHandleOnNode from '../error-handle-on-node' import { NodeSourceHandle, NodeTargetHandle, } from '../node-handle' -import ErrorHandleOnNode from '../error-handle-on-node' type NodeChildElement = ReactElement<Partial<NodeProps>> @@ -59,46 +59,48 @@ const BaseCard = ({ > <div className={cn( 'flex items-center rounded-t-2xl px-3 pb-2 pt-3', - )}> + )} + > <NodeTargetHandle id={id} data={data} - handleClassName='!top-4 !-left-[9px] !translate-y-0' - handleId='target' + handleClassName="!top-4 !-left-[9px] !translate-y-0" + handleId="target" /> { data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && ( <NodeSourceHandle id={id} data={data} - handleClassName='!top-4 !-right-[9px] !translate-y-0' - handleId='source' + handleClassName="!top-4 !-right-[9px] !translate-y-0" + handleId="source" /> ) } <BlockIcon - className='mr-2 shrink-0' + className="mr-2 shrink-0" type={data.type} - size='md' + size="md" /> <div title={data.title} - className='system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary' + className="system-sm-semibold-uppercase mr-1 flex grow items-center truncate text-text-primary" > <div> {data.title} </div> { data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && ( - <Tooltip popupContent={ - <div className='w-[180px]'> - <div className='font-extrabold'> + <Tooltip popupContent={( + <div className="w-[180px]"> + <div className="font-extrabold"> {t('workflow.nodes.iteration.parallelModeEnableTitle')} </div> {t('workflow.nodes.iteration.parallelModeEnableDesc')} - </div>} + </div> + )} > - <div className='system-2xs-medium-uppercase ml-1 flex items-center justify-center rounded-[5px] border-[1px] border-text-warning px-[5px] py-[3px] text-text-warning '> + <div className="system-2xs-medium-uppercase ml-1 flex items-center justify-center rounded-[5px] border-[1px] border-text-warning px-[5px] py-[3px] text-text-warning "> {t('workflow.nodes.iteration.parallelModeUpper')} </div> </Tooltip> @@ -113,7 +115,7 @@ const BaseCard = ({ } { (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) && children && ( - <div className='h-[calc(100%-42px)] w-full grow pb-1 pl-1 pr-1'> + <div className="h-[calc(100%-42px)] w-full grow pb-1 pl-1 pr-1"> {cloneElement(children, { id, data })} </div> ) @@ -128,7 +130,7 @@ const BaseCard = ({ } { data.desc && data.type !== BlockEnum.Iteration && data.type !== BlockEnum.Loop && ( - <div className='system-xs-regular whitespace-pre-line break-words px-3 pb-2 pt-1 text-text-tertiary'> + <div className="system-xs-regular whitespace-pre-line break-words px-3 pb-2 pt-1 text-text-tertiary"> {data.desc} </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/nodes/constants.ts b/web/app/components/workflow/workflow-preview/components/nodes/constants.ts index 2a6b01d561..dedca308fc 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/constants.ts +++ b/web/app/components/workflow/workflow-preview/components/nodes/constants.ts @@ -1,8 +1,8 @@ import { BlockEnum } from '@/app/components/workflow/types' -import QuestionClassifierNode from './question-classifier/node' import IfElseNode from './if-else/node' import IterationNode from './iteration/node' import LoopNode from './loop/node' +import QuestionClassifierNode from './question-classifier/node' export const NodeComponentMap: Record<string, any> = { [BlockEnum.QuestionClassifier]: QuestionClassifierNode, diff --git a/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx index 8d9189fcc1..b0a9e6903f 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx @@ -1,12 +1,13 @@ import type { FC } from 'react' +import type { NodeProps } from 'reactflow' +import type { Condition, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from 'reactflow' -import { NodeSourceHandle } from '../../node-handle' -import { isEmptyRelatedOperator } from '@/app/components/workflow/nodes/if-else/utils' -import type { Condition, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types' -import ConditionValue from '@/app/components/workflow/nodes/if-else/components/condition-value' import ConditionFilesListValue from '@/app/components/workflow/nodes/if-else/components/condition-files-list-value' +import ConditionValue from '@/app/components/workflow/nodes/if-else/components/condition-value' +import { isEmptyRelatedOperator } from '@/app/components/workflow/nodes/if-else/utils' +import { NodeSourceHandle } from '../../node-handle' + const i18nPrefix = 'workflow.nodes.ifElse' const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { @@ -37,50 +38,53 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { return !!condition.value } }, []) - const conditionNotSet = (<div className='flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'> - {t(`${i18nPrefix}.conditionNotSetup`)} - </div>) + const conditionNotSet = ( + <div className="flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary"> + {t(`${i18nPrefix}.conditionNotSetup`)} + </div> + ) return ( - <div className='px-3'> + <div className="px-3"> { cases.map((caseItem, index) => ( <div key={caseItem.case_id}> - <div className='relative flex h-6 items-center px-1'> - <div className='flex w-full items-center justify-between'> - <div className='text-[10px] font-semibold text-text-tertiary'> + <div className="relative flex h-6 items-center px-1"> + <div className="flex w-full items-center justify-between"> + <div className="text-[10px] font-semibold text-text-tertiary"> {casesLength > 1 && `CASE ${index + 1}`} </div> - <div className='text-[12px] font-semibold text-text-secondary'>{index === 0 ? 'IF' : 'ELIF'}</div> + <div className="text-[12px] font-semibold text-text-secondary">{index === 0 ? 'IF' : 'ELIF'}</div> </div> <NodeSourceHandle {...props} handleId={caseItem.case_id} - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2' + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2" /> </div> - <div className='space-y-0.5'> + <div className="space-y-0.5"> {caseItem.conditions.map((condition, i) => ( - <div key={condition.id} className='relative'> + <div key={condition.id} className="relative"> { checkIsConditionSet(condition) ? ( - (!isEmptyRelatedOperator(condition.comparison_operator!) && condition.sub_variable_condition) - ? ( - <ConditionFilesListValue condition={condition} /> - ) - : ( - <ConditionValue - variableSelector={condition.variable_selector!} - operator={condition.comparison_operator!} - value={condition.value} - /> - ) + (!isEmptyRelatedOperator(condition.comparison_operator!) && condition.sub_variable_condition) + ? ( + <ConditionFilesListValue condition={condition} /> + ) + : ( + <ConditionValue + variableSelector={condition.variable_selector!} + operator={condition.comparison_operator!} + value={condition.value} + /> + ) - ) - : conditionNotSet} + ) + : conditionNotSet + } {i !== caseItem.conditions.length - 1 && ( - <div className='absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent'>{t(`${i18nPrefix}.${caseItem.logical_operator}`)}</div> + <div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${caseItem.logical_operator}`)}</div> )} </div> ))} @@ -88,12 +92,12 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => { </div> )) } - <div className='relative flex h-6 items-center px-1'> - <div className='w-full text-right text-xs font-semibold text-text-secondary'>ELSE</div> + <div className="relative flex h-6 items-center px-1"> + <div className="w-full text-right text-xs font-semibold text-text-secondary">ELSE</div> <NodeSourceHandle {...props} - handleId='false' - handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2' + handleId="false" + handleClassName="!top-1/2 !-right-[21px] !-translate-y-1/2" /> </div> </div> diff --git a/web/app/components/workflow/workflow-preview/components/nodes/index.tsx b/web/app/components/workflow/workflow-preview/components/nodes/index.tsx index e496d8440d..9521493446 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/index.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/index.tsx @@ -8,7 +8,7 @@ const CustomNode = (props: NodeProps) => { return ( <> - <BaseNode { ...props }> + <BaseNode {...props}> { NodeComponent && <NodeComponent /> } </BaseNode> </> diff --git a/web/app/components/workflow/workflow-preview/components/nodes/iteration-start/index.tsx b/web/app/components/workflow/workflow-preview/components/nodes/iteration-start/index.tsx index e4c7b42fca..5b24fe913c 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/iteration-start/index.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/iteration-start/index.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' import type { NodeProps } from 'reactflow' import { RiHome5Fill } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { NodeSourceHandle } from '../../node-handle' @@ -9,17 +9,17 @@ const IterationStartNode = ({ id, data }: NodeProps) => { const { t } = useTranslation() return ( - <div className='nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs'> + <div className="nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg shadow-xs"> <Tooltip popupContent={t('workflow.blocks.iteration-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> <NodeSourceHandle id={id} data={data} - handleClassName='!top-1/2 !-right-[9px] !-translate-y-1/2' - handleId='source' + handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2" + handleId="source" /> </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/nodes/iteration/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/iteration/node.tsx index 5f1ba79810..524bf8aaf7 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/iteration/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/iteration/node.tsx @@ -1,4 +1,6 @@ import type { FC } from 'react' +import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' +import type { NodeProps } from '@/app/components/workflow/types' import { memo, } from 'react' @@ -6,9 +8,7 @@ import { Background, useViewport, } from 'reactflow' -import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types' import { cn } from '@/utils/classnames' -import type { NodeProps } from '@/app/components/workflow/types' const Node: FC<NodeProps<IterationNodeType>> = ({ id, @@ -18,13 +18,14 @@ const Node: FC<NodeProps<IterationNodeType>> = ({ return ( <div className={cn( 'relative h-full min-h-[90px] w-full min-w-[240px] rounded-2xl bg-workflow-canvas-workflow-bg', - )}> + )} + > <Background id={`iteration-background-${id}`} - className='!z-0 rounded-2xl' + className="!z-0 rounded-2xl" gap={[14 / zoom, 14 / zoom]} size={2 / zoom} - color='var(--color-workflow-canvas-workflow-dot-color)' + color="var(--color-workflow-canvas-workflow-dot-color)" /> </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/nodes/loop-start/index.tsx b/web/app/components/workflow/workflow-preview/components/nodes/loop-start/index.tsx index d9dce9b0ee..9bf5261f2b 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/loop-start/index.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/loop-start/index.tsx @@ -1,7 +1,7 @@ -import { memo } from 'react' -import { useTranslation } from 'react-i18next' import type { NodeProps } from 'reactflow' import { RiHome5Fill } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { NodeSourceHandle } from '../../node-handle' @@ -9,17 +9,17 @@ const LoopStartNode = ({ id, data }: NodeProps) => { const { t } = useTranslation() return ( - <div className='nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg'> + <div className="nodrag group mt-1 flex h-11 w-11 items-center justify-center rounded-2xl border border-workflow-block-border bg-workflow-block-bg"> <Tooltip popupContent={t('workflow.blocks.loop-start')} asChild={false}> - <div className='flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500'> - <RiHome5Fill className='h-3 w-3 text-text-primary-on-surface' /> + <div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500"> + <RiHome5Fill className="h-3 w-3 text-text-primary-on-surface" /> </div> </Tooltip> <NodeSourceHandle id={id} data={data} - handleClassName='!top-1/2 !-right-[9px] !-translate-y-1/2' - handleId='source' + handleClassName="!top-1/2 !-right-[9px] !-translate-y-1/2" + handleId="source" /> </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/nodes/loop/hooks.ts b/web/app/components/workflow/workflow-preview/components/nodes/loop/hooks.ts index af252ce2f3..fd518c72c7 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/loop/hooks.ts +++ b/web/app/components/workflow/workflow-preview/components/nodes/loop/hooks.ts @@ -1,9 +1,9 @@ -import { useCallback } from 'react' -import { produce } from 'immer' -import { useStoreApi } from 'reactflow' import type { Node, } from '@/app/components/workflow/types' +import { produce } from 'immer' +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' import { LOOP_PADDING, } from '@/app/components/workflow/constants' diff --git a/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx index 9cd3eec716..2c6a05f092 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/loop/node.tsx @@ -1,4 +1,6 @@ import type { FC } from 'react' +import type { LoopNodeType } from '@/app/components/workflow/nodes/loop/types' +import type { NodeProps } from '@/app/components/workflow/types' import { memo, useEffect, @@ -8,9 +10,7 @@ import { useNodesInitialized, useViewport, } from 'reactflow' -import type { LoopNodeType } from '@/app/components/workflow/nodes/loop/types' import { cn } from '@/utils/classnames' -import type { NodeProps } from '@/app/components/workflow/types' import { useNodeLoopInteractions } from './hooks' const Node: FC<NodeProps<LoopNodeType>> = ({ @@ -36,10 +36,10 @@ const Node: FC<NodeProps<LoopNodeType>> = ({ > <Background id={`loop-background-${id}`} - className='!z-0 rounded-2xl' + className="!z-0 rounded-2xl" gap={[14 / zoom, 14 / zoom]} size={2 / zoom} - color='var(--color-workflow-canvas-workflow-dot-color)' + color="var(--color-workflow-canvas-workflow-dot-color)" /> </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx index 7c24ff54c3..c164d624e4 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' +import type { NodeProps } from 'reactflow' +import type { QuestionClassifierNodeType } from '@/app/components/workflow/nodes/question-classifier/types' import React from 'react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from 'reactflow' import InfoPanel from '@/app/components/workflow/nodes/_base/components/info-panel' -import type { QuestionClassifierNodeType } from '@/app/components/workflow/nodes/question-classifier/types' import { NodeSourceHandle } from '../../node-handle' const i18nPrefix = 'workflow.nodes.questionClassifiers' @@ -14,23 +14,23 @@ const Node: FC<NodeProps<QuestionClassifierNodeType>> = (props) => { const topics = data.classes return ( - <div className='mb-1 px-3 py-1'> + <div className="mb-1 px-3 py-1"> { !!topics.length && ( - <div className='mt-2 space-y-0.5'> + <div className="mt-2 space-y-0.5"> {topics.map((topic, index) => ( <div key={index} - className='relative' + className="relative" > <InfoPanel title={`${t(`${i18nPrefix}.class`)} ${index + 1}`} - content={''} + content="" /> <NodeSourceHandle {...props} handleId={topic.id} - handleClassName='!top-1/2 !-translate-y-1/2 !-right-[21px]' + handleClassName="!top-1/2 !-translate-y-1/2 !-right-[21px]" /> </div> ))} diff --git a/web/app/components/workflow/workflow-preview/components/note-node/index.tsx b/web/app/components/workflow/workflow-preview/components/note-node/index.tsx index 48390efb9c..feee0c7f2c 100644 --- a/web/app/components/workflow/workflow-preview/components/note-node/index.tsx +++ b/web/app/components/workflow/workflow-preview/components/note-node/index.tsx @@ -1,15 +1,15 @@ +import type { NodeProps } from 'reactflow' +import type { NoteNodeType } from '@/app/components/workflow/note-node/types' import { memo, useRef, } from 'react' import { useTranslation } from 'react-i18next' -import type { NodeProps } from 'reactflow' +import { THEME_MAP } from '@/app/components/workflow/note-node/constants' import { NoteEditor, NoteEditorContextProvider, } from '@/app/components/workflow/note-node/note-editor' -import { THEME_MAP } from '@/app/components/workflow/note-node/constants' -import type { NoteNodeType } from '@/app/components/workflow/note-node/types' import { cn } from '@/utils/classnames' const NoteNode = ({ @@ -41,11 +41,14 @@ const NoteNode = ({ className={cn( 'h-2 shrink-0 rounded-t-md opacity-50', THEME_MAP[theme].title, - )}></div> - <div className='grow overflow-y-auto px-3 py-2.5'> + )} + > + </div> + <div className="grow overflow-y-auto px-3 py-2.5"> <div className={cn( data.selected && 'nodrag nopan nowheel cursor-text', - )}> + )} + > <NoteEditor containerElement={ref.current} placeholder={t('workflow.nodes.note.editor.placeholder') || ''} @@ -54,7 +57,7 @@ const NoteNode = ({ </div> { data.showAuthor && ( - <div className='p-3 pt-0 text-xs text-text-tertiary'> + <div className="p-3 pt-0 text-xs text-text-tertiary"> {data.author} </div> ) diff --git a/web/app/components/workflow/workflow-preview/components/zoom-in-out.tsx b/web/app/components/workflow/workflow-preview/components/zoom-in-out.tsx index 2322db25ee..089d42422e 100644 --- a/web/app/components/workflow/workflow-preview/components/zoom-in-out.tsx +++ b/web/app/components/workflow/workflow-preview/components/zoom-in-out.tsx @@ -1,28 +1,28 @@ import type { FC } from 'react' +import { + RiZoomInLine, + RiZoomOutLine, +} from '@remixicon/react' import { Fragment, memo, useCallback, useState, } from 'react' -import { - RiZoomInLine, - RiZoomOutLine, -} from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useReactFlow, useViewport, } from 'reactflow' -import ShortcutsName from '@/app/components/workflow/shortcuts-name' import Divider from '@/app/components/base/divider' -import TipPopup from '@/app/components/workflow/operator/tip-popup' -import { cn } from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import TipPopup from '@/app/components/workflow/operator/tip-popup' +import ShortcutsName from '@/app/components/workflow/shortcuts-name' +import { cn } from '@/utils/classnames' enum ZoomType { zoomIn = 'zoomIn', @@ -103,7 +103,7 @@ const ZoomInOut: FC = () => { return ( <PortalToFollowElem - placement='top-start' + placement="top-start" open={open} onOpenChange={setOpen} offset={{ @@ -121,7 +121,8 @@ const ZoomInOut: FC = () => { > <div className={cn( 'flex h-8 w-[98px] items-center justify-between rounded-lg', - )}> + )} + > <TipPopup title={t('workflow.operator.zoomOut')} shortcuts={['ctrl', '-']} @@ -136,10 +137,13 @@ const ZoomInOut: FC = () => { zoomOut() }} > - <RiZoomOutLine className='h-4 w-4 text-text-tertiary hover:text-text-secondary' /> + <RiZoomOutLine className="h-4 w-4 text-text-tertiary hover:text-text-secondary" /> </div> </TipPopup> - <div onClick={handleTrigger} className={cn('system-sm-medium w-[34px] text-text-tertiary hover:text-text-secondary')}>{Number.parseFloat(`${zoom * 100}`).toFixed(0)}%</div> + <div onClick={handleTrigger} className={cn('system-sm-medium w-[34px] text-text-tertiary hover:text-text-secondary')}> + {Number.parseFloat(`${zoom * 100}`).toFixed(0)} + % + </div> <TipPopup title={t('workflow.operator.zoomIn')} shortcuts={['ctrl', '+']} @@ -154,32 +158,32 @@ const ZoomInOut: FC = () => { zoomIn() }} > - <RiZoomInLine className='h-4 w-4 text-text-tertiary hover:text-text-secondary' /> + <RiZoomInLine className="h-4 w-4 text-text-tertiary hover:text-text-secondary" /> </div> </TipPopup> </div> </div> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-10'> - <div className='w-[145px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]'> + <PortalToFollowElemContent className="z-10"> + <div className="w-[145px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-[5px]"> { ZOOM_IN_OUT_OPTIONS.map((options, i) => ( <Fragment key={i}> { i !== 0 && ( - <Divider className='m-0' /> + <Divider className="m-0" /> ) } - <div className='p-1'> + <div className="p-1"> { options.map(option => ( <div key={option.key} - className='system-md-regular flex h-8 cursor-pointer items-center justify-between space-x-1 rounded-lg py-1.5 pl-3 pr-2 text-text-secondary hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center justify-between space-x-1 rounded-lg py-1.5 pl-3 pr-2 text-text-secondary hover:bg-state-base-hover" onClick={() => handleZoom(option.key)} > <span>{option.text}</span> - <div className='flex items-center space-x-0.5'> + <div className="flex items-center space-x-0.5"> { option.key === ZoomType.zoomToFit && ( <ShortcutsName keys={['ctrl', '1']} /> diff --git a/web/app/components/workflow/workflow-preview/index.tsx b/web/app/components/workflow/workflow-preview/index.tsx index 5ce85a8804..8f61c2cfb6 100644 --- a/web/app/components/workflow/workflow-preview/index.tsx +++ b/web/app/components/workflow/workflow-preview/index.tsx @@ -1,49 +1,49 @@ 'use client' -import { - useCallback, - useState, -} from 'react' -import ReactFlow, { - Background, - MiniMap, - ReactFlowProvider, - SelectionMode, - applyEdgeChanges, - applyNodeChanges, -} from 'reactflow' import type { EdgeChange, NodeChange, Viewport, } from 'reactflow' -import 'reactflow/dist/style.css' -import '../style.css' -import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' -import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' -import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants' -import CustomConnectionLine from '@/app/components/workflow/custom-connection-line' +import type { + Edge, + Node, +} from '@/app/components/workflow/types' +import { + useCallback, + useState, +} from 'react' +import ReactFlow, { + applyEdgeChanges, + applyNodeChanges, + Background, + MiniMap, + ReactFlowProvider, + SelectionMode, +} from 'reactflow' import { CUSTOM_EDGE, CUSTOM_NODE, ITERATION_CHILDREN_Z_INDEX, } from '@/app/components/workflow/constants' -import { cn } from '@/utils/classnames' +import CustomConnectionLine from '@/app/components/workflow/custom-connection-line' +import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' +import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' +import { CUSTOM_NOTE_NODE } from '@/app/components/workflow/note-node/constants' +import { CUSTOM_SIMPLE_NODE } from '@/app/components/workflow/simple-node/constants' import { initialEdges, initialNodes, } from '@/app/components/workflow/utils/workflow-init' -import type { - Edge, - Node, -} from '@/app/components/workflow/types' -import { CUSTOM_NOTE_NODE } from '@/app/components/workflow/note-node/constants' -import CustomNode from './components/nodes' +import { cn } from '@/utils/classnames' import CustomEdge from './components/custom-edge' -import ZoomInOut from './components/zoom-in-out' +import CustomNode from './components/nodes' import IterationStartNode from './components/nodes/iteration-start' import LoopStartNode from './components/nodes/loop-start' import CustomNoteNode from './components/note-node' +import ZoomInOut from './components/zoom-in-out' +import 'reactflow/dist/style.css' +import '../style.css' const nodeTypes = { [CUSTOM_NODE]: CustomNode, @@ -82,7 +82,7 @@ const WorkflowPreview = ({ return ( <div - id='workflow-container' + id="workflow-container" className={cn( 'relative h-full w-full', className, @@ -96,11 +96,11 @@ const WorkflowPreview = ({ width: 102, height: 72, }} - maskColor='var(--color-workflow-minimap-bg)' - className='!absolute !bottom-14 !left-4 z-[9] !m-0 !h-[72px] !w-[102px] !rounded-lg !border-[0.5px] - !border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5' + maskColor="var(--color-workflow-minimap-bg)" + className="!absolute !bottom-14 !left-4 z-[9] !m-0 !h-[72px] !w-[102px] !rounded-lg !border-[0.5px] + !border-divider-subtle !bg-background-default-subtle !shadow-md !shadow-shadow-shadow-5" /> - <div className='absolute bottom-4 left-4 z-[9] mt-1 flex items-center gap-2'> + <div className="absolute bottom-4 left-4 z-[9] mt-1 flex items-center gap-2"> <ZoomInOut /> </div> </> @@ -128,8 +128,8 @@ const WorkflowPreview = ({ <Background gap={[14, 14]} size={2} - className='bg-workflow-canvas-workflow-bg' - color='var(--color-workflow-canvas-workflow-dot-color)' + className="bg-workflow-canvas-workflow-bg" + color="var(--color-workflow-canvas-workflow-dot-color)" /> </ReactFlow> </div> diff --git a/web/app/dev-preview/page.tsx b/web/app/dev-preview/page.tsx index e06f10d795..90090acdd0 100644 --- a/web/app/dev-preview/page.tsx +++ b/web/app/dev-preview/page.tsx @@ -4,8 +4,8 @@ import { BaseFieldType } from '../components/base/form/form-scenarios/base/types export default function Page() { return ( - <div className='flex h-screen w-full items-center justify-center p-20'> - <div className='w-[400px] rounded-lg border border-components-panel-border bg-components-panel-bg'> + <div className="flex h-screen w-full items-center justify-center p-20"> + <div className="w-[400px] rounded-lg border border-components-panel-border bg-components-panel-bg"> <BaseForm initialData={{ type: 'option_1', diff --git a/web/app/education-apply/education-apply-page.tsx b/web/app/education-apply/education-apply-page.tsx index 208b20278c..5f2446352e 100644 --- a/web/app/education-apply/education-apply-page.tsx +++ b/web/app/education-apply/education-apply-page.tsx @@ -1,30 +1,31 @@ 'use client' -import { - useState, -} from 'react' -import { useTranslation } from 'react-i18next' import { RiExternalLinkLine } from '@remixicon/react' +import { noop } from 'lodash-es' import { useRouter, useSearchParams, } from 'next/navigation' -import UserInfo from './user-info' -import SearchInput from './search-input' -import RoleSelector from './role-selector' -import Confirm from './verify-state-modal' +import { + useState, +} from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' +import { useToastContext } from '@/app/components/base/toast' +import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants' +import { useDocLink } from '@/context/i18n' +import { useProviderContext } from '@/context/provider-context' import { useEducationAdd, useInvalidateEducationStatus, } from '@/service/use-education' -import { useProviderContext } from '@/context/provider-context' -import { useToastContext } from '@/app/components/base/toast' -import { EDUCATION_VERIFYING_LOCALSTORAGE_ITEM } from '@/app/education-apply/constants' -import { noop } from 'lodash-es' import DifyLogo from '../components/base/logo/dify-logo' -import { useDocLink } from '@/context/i18n' +import RoleSelector from './role-selector' +import SearchInput from './search-input' +import UserInfo from './user-info' +import Confirm from './verify-state-modal' + const EducationApplyAge = () => { const { t } = useTranslation() const [schoolName, setSchoolName] = useState('') @@ -35,7 +36,7 @@ const EducationApplyAge = () => { isPending, mutateAsync: educationAdd, } = useEducationAdd({ onSuccess: noop }) - const [modalShow, setShowModal] = useState<undefined | { title: string; desc: string; onConfirm?: () => void }>(undefined) + const [modalShow, setShowModal] = useState<undefined | { title: string, desc: string, onConfirm?: () => void }>(undefined) const { onPlanInfoChanged } = useProviderContext() const updateEducationStatus = useInvalidateEducationStatus() const { notify } = useToastContext() @@ -75,8 +76,8 @@ const EducationApplyAge = () => { } return ( - <div className='fixed inset-0 z-[31] overflow-y-auto bg-background-body p-6'> - <div className='mx-auto w-full max-w-[1408px] rounded-2xl border border-effects-highlight bg-background-default-subtle'> + <div className="fixed inset-0 z-[31] overflow-y-auto bg-background-body p-6"> + <div className="mx-auto w-full max-w-[1408px] rounded-2xl border border-effects-highlight bg-background-default-subtle"> <div className="h-[349px] w-full overflow-hidden rounded-t-2xl bg-cover bg-center bg-no-repeat" style={{ @@ -84,23 +85,25 @@ const EducationApplyAge = () => { }} > </div> - <div className='mt-[-349px] box-content flex h-7 items-center justify-between p-6'> - <DifyLogo size='large' style='monochromeWhite' /> + <div className="mt-[-349px] box-content flex h-7 items-center justify-between p-6"> + <DifyLogo size="large" style="monochromeWhite" /> </div> - <div className='mx-auto max-w-[720px] px-8 pb-[180px]'> - <div className='mb-2 flex h-[192px] flex-col justify-end pb-4 pt-3 text-text-primary-on-surface'> - <div className='title-5xl-bold mb-2 shadow-xs'>{t('education.toVerified')}</div> - <div className='system-md-medium shadow-xs'> - {t('education.toVerifiedTip.front')}  - <span className='system-md-semibold underline'>{t('education.toVerifiedTip.coupon')}</span>  + <div className="mx-auto max-w-[720px] px-8 pb-[180px]"> + <div className="mb-2 flex h-[192px] flex-col justify-end pb-4 pt-3 text-text-primary-on-surface"> + <div className="title-5xl-bold mb-2 shadow-xs">{t('education.toVerified')}</div> + <div className="system-md-medium shadow-xs"> + {t('education.toVerifiedTip.front')} +  + <span className="system-md-semibold underline">{t('education.toVerifiedTip.coupon')}</span> +  {t('education.toVerifiedTip.end')} </div> </div> - <div className='mb-7'> + <div className="mb-7"> <UserInfo /> </div> - <div className='mb-7'> - <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'> + <div className="mb-7"> + <div className="system-md-semibold mb-1 flex h-6 items-center text-text-secondary"> {t('education.form.schoolName.title')} </div> <SearchInput @@ -108,8 +111,8 @@ const EducationApplyAge = () => { onChange={setSchoolName} /> </div> - <div className='mb-7'> - <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'> + <div className="mb-7"> + <div className="system-md-semibold mb-1 flex h-6 items-center text-text-secondary"> {t('education.form.schoolRole.title')} </div> <RoleSelector @@ -117,29 +120,32 @@ const EducationApplyAge = () => { onChange={setRole} /> </div> - <div className='mb-7'> - <div className='system-md-semibold mb-1 flex h-6 items-center text-text-secondary'> + <div className="mb-7"> + <div className="system-md-semibold mb-1 flex h-6 items-center text-text-secondary"> {t('education.form.terms.title')} </div> - <div className='system-md-regular mb-1 text-text-tertiary'> - {t('education.form.terms.desc.front')}  - <a href='https://dify.ai/terms' target='_blank' className='text-text-secondary hover:underline'>{t('education.form.terms.desc.termsOfService')}</a>  - {t('education.form.terms.desc.and')}  - <a href='https://dify.ai/privacy' target='_blank' className='text-text-secondary hover:underline'>{t('education.form.terms.desc.privacyPolicy')}</a> + <div className="system-md-regular mb-1 text-text-tertiary"> + {t('education.form.terms.desc.front')} +  + <a href="https://dify.ai/terms" target="_blank" className="text-text-secondary hover:underline">{t('education.form.terms.desc.termsOfService')}</a> +  + {t('education.form.terms.desc.and')} +  + <a href="https://dify.ai/privacy" target="_blank" className="text-text-secondary hover:underline">{t('education.form.terms.desc.privacyPolicy')}</a> {t('education.form.terms.desc.end')} </div> - <div className='system-md-regular py-2 text-text-primary'> - <div className='mb-2 flex'> + <div className="system-md-regular py-2 text-text-primary"> + <div className="mb-2 flex"> <Checkbox - className='mr-2 shrink-0' + className="mr-2 shrink-0" checked={ageChecked} onCheck={() => setAgeChecked(!ageChecked)} /> {t('education.form.terms.option.age')} </div> - <div className='flex'> + <div className="flex"> <Checkbox - className='mr-2 shrink-0' + className="mr-2 shrink-0" checked={inSchoolChecked} onCheck={() => setInSchoolChecked(!inSchoolChecked)} /> @@ -148,20 +154,20 @@ const EducationApplyAge = () => { </div> </div> <Button - variant='primary' + variant="primary" disabled={!ageChecked || !inSchoolChecked || !schoolName || !role || isPending} onClick={handleSubmit} > {t('education.submit')} </Button> - <div className='mb-4 mt-5 h-px bg-gradient-to-r from-[rgba(16,24,40,0.08)]'></div> + <div className="mb-4 mt-5 h-px bg-gradient-to-r from-[rgba(16,24,40,0.08)]"></div> <a - className='system-xs-regular flex items-center text-text-accent' + className="system-xs-regular flex items-center text-text-accent" href={docLink('/getting-started/dify-for-education')} - target='_blank' + target="_blank" > {t('education.learn')} - <RiExternalLinkLine className='ml-1 h-3 w-3' /> + <RiExternalLinkLine className="ml-1 h-3 w-3" /> </a> </div> </div> diff --git a/web/app/education-apply/expire-notice-modal.tsx b/web/app/education-apply/expire-notice-modal.tsx index abafd61aa2..51a3ba66b1 100644 --- a/web/app/education-apply/expire-notice-modal.tsx +++ b/web/app/education-apply/expire-notice-modal.tsx @@ -1,16 +1,16 @@ 'use client' +import { RiExternalLinkLine } from '@remixicon/react' +import Link from 'next/link' +import { useRouter } from 'next/navigation' import React from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import { useDocLink } from '@/context/i18n' -import Link from 'next/link' -import { useTranslation } from 'react-i18next' -import { RiExternalLinkLine } from '@remixicon/react' -import { SparklesSoftAccent } from '../components/base/icons/src/public/common' -import useTimestamp from '@/hooks/use-timestamp' import { useModalContextSelector } from '@/context/modal-context' +import useTimestamp from '@/hooks/use-timestamp' import { useEducationVerify } from '@/service/use-education' -import { useRouter } from 'next/navigation' +import { SparklesSoftAccent } from '../components/base/icons/src/public/common' export type ExpireNoticeModalPayloadProps = { expireAt: number @@ -46,45 +46,53 @@ const ExpireNoticeModal: React.FC<Props> = ({ expireAt, expired, onClose }) => { onClose={onClose} title={expired ? t(`${i18nPrefix}.expired.title`) : t(`${i18nPrefix}.isAboutToExpire.title`, { date: formatTime(expireAt, t(`${i18nPrefix}.dateFormat`) as string), interpolation: { escapeValue: false } })} closable - className='max-w-[600px]' + className="max-w-[600px]" > - <div className='body-md-regular mt-5 space-y-5 text-text-secondary'> + <div className="body-md-regular mt-5 space-y-5 text-text-secondary"> <div> - {expired ? (<> - <div>{t(`${i18nPrefix}.expired.summary.line1`)}</div> - <div>{t(`${i18nPrefix}.expired.summary.line2`)}</div> - </> - ) : t(`${i18nPrefix}.isAboutToExpire.summary`)} + {expired + ? ( + <> + <div>{t(`${i18nPrefix}.expired.summary.line1`)}</div> + <div>{t(`${i18nPrefix}.expired.summary.line2`)}</div> + </> + ) + : t(`${i18nPrefix}.isAboutToExpire.summary`)} </div> <div> - <strong className='title-md-semi-bold block'>{t(`${i18nPrefix}.stillInEducation.title`)}</strong> + <strong className="title-md-semi-bold block">{t(`${i18nPrefix}.stillInEducation.title`)}</strong> {t(`${i18nPrefix}.stillInEducation.${expired ? 'expired' : 'isAboutToExpire'}`)} </div> <div> - <strong className='title-md-semi-bold block'>{t(`${i18nPrefix}.alreadyGraduated.title`)}</strong> + <strong className="title-md-semi-bold block">{t(`${i18nPrefix}.alreadyGraduated.title`)}</strong> {t(`${i18nPrefix}.alreadyGraduated.${expired ? 'expired' : 'isAboutToExpire'}`)} </div> </div> <div className="mt-7 flex items-center justify-between space-x-2"> - <Link className='system-xs-regular flex items-center space-x-1 text-text-accent' href={eduDocLink} target="_blank" rel="noopener noreferrer"> + <Link className="system-xs-regular flex items-center space-x-1 text-text-accent" href={eduDocLink} target="_blank" rel="noopener noreferrer"> <div>{t('education.learn')}</div> - <RiExternalLinkLine className='size-3' /> + <RiExternalLinkLine className="size-3" /> </Link> - <div className='flex space-x-2'> - {expired ? ( - <Button onClick={() => { - onClose() - setShowPricingModal() - }} className='flex items-center space-x-1'> - <SparklesSoftAccent className='size-4' /> - <div className='text-components-button-secondary-accent-text'>{t(`${i18nPrefix}.action.upgrade`)}</div> - </Button> - ) : ( - <Button onClick={onClose}> - {t(`${i18nPrefix}.action.dismiss`)} - </Button> - )} - <Button variant='primary' onClick={handleConfirm}> + <div className="flex space-x-2"> + {expired + ? ( + <Button + onClick={() => { + onClose() + setShowPricingModal() + }} + className="flex items-center space-x-1" + > + <SparklesSoftAccent className="size-4" /> + <div className="text-components-button-secondary-accent-text">{t(`${i18nPrefix}.action.upgrade`)}</div> + </Button> + ) + : ( + <Button onClick={onClose}> + {t(`${i18nPrefix}.action.dismiss`)} + </Button> + )} + <Button variant="primary" onClick={handleConfirm}> {t(`${i18nPrefix}.action.reVerify`)} </Button> </div> diff --git a/web/app/education-apply/hooks.ts b/web/app/education-apply/hooks.ts index 9d45a5ee69..27895b89be 100644 --- a/web/app/education-apply/hooks.ts +++ b/web/app/education-apply/hooks.ts @@ -1,26 +1,25 @@ +import type { SearchParams } from './types' +import { useDebounceFn, useLocalStorageState } from 'ahooks' +import dayjs from 'dayjs' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useEffect, useState, } from 'react' -import { useDebounceFn, useLocalStorageState } from 'ahooks' -import { useSearchParams } from 'next/navigation' -import type { SearchParams } from './types' +import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' +import { useAppContext } from '@/context/app-context' +import { useModalContextSelector } from '@/context/modal-context' +import { useProviderContext } from '@/context/provider-context' +import { useEducationAutocomplete, useEducationVerify } from '@/service/use-education' import { EDUCATION_PRICING_SHOW_ACTION, EDUCATION_RE_VERIFY_ACTION, - EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION, + EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, } from './constants' -import { useEducationAutocomplete, useEducationVerify } from '@/service/use-education' -import { useModalContextSelector } from '@/context/modal-context' -import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' -import timezone from 'dayjs/plugin/timezone' -import { useAppContext } from '@/context/app-context' -import { useRouter } from 'next/navigation' -import { useProviderContext } from '@/context/provider-context' -import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' dayjs.extend(utc) dayjs.extend(timezone) diff --git a/web/app/education-apply/role-selector.tsx b/web/app/education-apply/role-selector.tsx index e6d6a67b89..1f8b3e3851 100644 --- a/web/app/education-apply/role-selector.tsx +++ b/web/app/education-apply/role-selector.tsx @@ -27,12 +27,12 @@ const RoleSelector = ({ ] return ( - <div className='flex'> + <div className="flex"> { options.map(option => ( <div key={option.key} - className='system-md-regular mr-6 flex h-5 cursor-pointer items-center text-text-primary' + className="system-md-regular mr-6 flex h-5 cursor-pointer items-center text-text-primary" onClick={() => onChange(option.key)} > <div diff --git a/web/app/education-apply/search-input.tsx b/web/app/education-apply/search-input.tsx index 63a393b326..f1be0cca30 100644 --- a/web/app/education-apply/search-input.tsx +++ b/web/app/education-apply/search-input.tsx @@ -5,13 +5,13 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useEducation } from './hooks' import Input from '@/app/components/base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import { useEducation } from './hooks' type SearchInputProps = { value?: string @@ -77,30 +77,30 @@ const SearchInput = ({ <PortalToFollowElem open={open} onOpenChange={setOpen} - placement='bottom' + placement="bottom" offset={4} triggerPopupSameWidth > - <PortalToFollowElemTrigger className='block w-full'> + <PortalToFollowElemTrigger className="block w-full"> <Input - className='w-full' + className="w-full" placeholder={t('education.form.schoolName.placeholder')} value={value} onChange={handleValueChange} /> </PortalToFollowElemTrigger> - <PortalToFollowElemContent className='z-[32]'> + <PortalToFollowElemContent className="z-[32]"> { !!schools.length && value && ( <div - className='max-h-[330px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1' + className="max-h-[330px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1" onScroll={handleScroll as any} > { schools.map((school, index) => ( <div key={index} - className='system-md-regular flex h-8 cursor-pointer items-center truncate rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover' + className="system-md-regular flex h-8 cursor-pointer items-center truncate rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover" title={school} onClick={() => { onChange(school) diff --git a/web/app/education-apply/user-info.tsx b/web/app/education-apply/user-info.tsx index 96ff1aaae6..4baa494c89 100644 --- a/web/app/education-apply/user-info.tsx +++ b/web/app/education-apply/user-info.tsx @@ -1,9 +1,9 @@ -import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import Button from '@/app/components/base/button' -import { useAppContext } from '@/context/app-context' +import { useTranslation } from 'react-i18next' import Avatar from '@/app/components/base/avatar' +import Button from '@/app/components/base/button' import { Triangle } from '@/app/components/base/icons/src/public/education' +import { useAppContext } from '@/context/app-context' import { useLogout } from '@/service/use-common' const UserInfo = () => { @@ -22,31 +22,31 @@ const UserInfo = () => { } return ( - <div className='relative flex items-center justify-between rounded-xl border-[4px] border-components-panel-on-panel-item-bg bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 pb-6 pl-6 pr-8 pt-9 shadow-shadow-shadow-5'> - <div className='absolute left-0 top-0 flex items-center'> - <div className='system-2xs-semibold-uppercase flex h-[22px] items-center bg-components-panel-on-panel-item-bg pl-2 pt-1 text-text-accent-light-mode-only'> + <div className="relative flex items-center justify-between rounded-xl border-[4px] border-components-panel-on-panel-item-bg bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 pb-6 pl-6 pr-8 pt-9 shadow-shadow-shadow-5"> + <div className="absolute left-0 top-0 flex items-center"> + <div className="system-2xs-semibold-uppercase flex h-[22px] items-center bg-components-panel-on-panel-item-bg pl-2 pt-1 text-text-accent-light-mode-only"> {t('education.currentSigned')} </div> - <Triangle className='h-[22px] w-4 text-components-panel-on-panel-item-bg' /> + <Triangle className="h-[22px] w-4 text-components-panel-on-panel-item-bg" /> </div> - <div className='flex items-center'> + <div className="flex items-center"> <Avatar - className='mr-4' + className="mr-4" avatar={userProfile.avatar_url} name={userProfile.name} size={48} /> - <div className='pt-1.5'> - <div className='system-md-semibold text-text-primary'> + <div className="pt-1.5"> + <div className="system-md-semibold text-text-primary"> {userProfile.name} </div> - <div className='system-sm-regular text-text-secondary'> + <div className="system-sm-regular text-text-secondary"> {userProfile.email} </div> </div> </div> <Button - variant='secondary' + variant="secondary" onClick={handleLogout} > {t('common.userProfile.logout')} diff --git a/web/app/education-apply/verify-state-modal.tsx b/web/app/education-apply/verify-state-modal.tsx index 2ea2fe5bae..e4a5cd9bbe 100644 --- a/web/app/education-apply/verify-state-modal.tsx +++ b/web/app/education-apply/verify-state-modal.tsx @@ -1,9 +1,9 @@ -import React, { useEffect, useRef, useState } from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' import { RiExternalLinkLine, } from '@remixicon/react' +import React, { useEffect, useRef, useState } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useDocLink } from '@/context/i18n' @@ -77,38 +77,40 @@ function Confirm({ return null return createPortal( - <div className={'fixed inset-0 z-[10000000] flex items-center justify-center bg-background-overlay'} + <div + className="fixed inset-0 z-[10000000] flex items-center justify-center bg-background-overlay" onClick={(e) => { e.preventDefault() e.stopPropagation() }} > - <div ref={dialogRef} className={'relative w-full max-w-[481px] overflow-hidden'}> - <div className='shadows-shadow-lg flex max-w-full flex-col items-start rounded-2xl border-[0.5px] border-solid border-components-panel-border bg-components-panel-bg'> - <div className='flex flex-col items-start gap-2 self-stretch pb-4 pl-6 pr-6 pt-6'> - <div className='title-2xl-semi-bold text-text-primary'>{title}</div> - <div className='system-md-regular w-full text-text-tertiary'>{content}</div> + <div ref={dialogRef} className="relative w-full max-w-[481px] overflow-hidden"> + <div className="shadows-shadow-lg flex max-w-full flex-col items-start rounded-2xl border-[0.5px] border-solid border-components-panel-border bg-components-panel-bg"> + <div className="flex flex-col items-start gap-2 self-stretch pb-4 pl-6 pr-6 pt-6"> + <div className="title-2xl-semi-bold text-text-primary">{title}</div> + <div className="system-md-regular w-full text-text-tertiary">{content}</div> </div> {email && ( - <div className='w-full space-y-1 px-6 py-3'> - <div className='system-sm-semibold py-1 text-text-secondary'>{t('education.emailLabel')}</div> - <div className='system-sm-regular rounded-lg bg-components-input-bg-disabled px-3 py-2 text-components-input-text-filled-disabled'>{email}</div> + <div className="w-full space-y-1 px-6 py-3"> + <div className="system-sm-semibold py-1 text-text-secondary">{t('education.emailLabel')}</div> + <div className="system-sm-regular rounded-lg bg-components-input-bg-disabled px-3 py-2 text-components-input-text-filled-disabled">{email}</div> </div> )} - <div className='flex items-center justify-between gap-2 self-stretch p-6'> - <div className='flex items-center gap-1'> + <div className="flex items-center justify-between gap-2 self-stretch p-6"> + <div className="flex items-center gap-1"> {showLink && ( <> - <a onClick={handleClick} href={eduDocLink} target='_blank' className='system-xs-regular cursor-pointer text-text-accent'>{t('education.learn')}</a> - <RiExternalLinkLine className='h-3 w-3 text-text-accent' /> + <a onClick={handleClick} href={eduDocLink} target="_blank" className="system-xs-regular cursor-pointer text-text-accent">{t('education.learn')}</a> + <RiExternalLinkLine className="h-3 w-3 text-text-accent" /> </> )} </div> - <Button variant='primary' className='!w-20' onClick={onConfirm}>{t('common.operation.ok')}</Button> + <Button variant="primary" className="!w-20" onClick={onConfirm}>{t('common.operation.ok')}</Button> </div> </div> </div> - </div>, document.body, + </div>, + document.body, ) } diff --git a/web/app/forgot-password/ChangePasswordForm.tsx b/web/app/forgot-password/ChangePasswordForm.tsx index 66119ea691..95362ac64b 100644 --- a/web/app/forgot-password/ChangePasswordForm.tsx +++ b/web/app/forgot-password/ChangePasswordForm.tsx @@ -1,17 +1,17 @@ 'use client' +import { CheckCircleIcon } from '@heroicons/react/24/solid' +import { useSearchParams } from 'next/navigation' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useSearchParams } from 'next/navigation' -import { basePath } from '@/utils/var' -import { cn } from '@/utils/classnames' -import { CheckCircleIcon } from '@heroicons/react/24/solid' -import Input from '../components/base/input' import Button from '@/app/components/base/button' -import { changePasswordWithToken } from '@/service/common' -import Toast from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' import { validPassword } from '@/config' +import { changePasswordWithToken } from '@/service/common' import { useVerifyForgotPasswordToken } from '@/service/use-common' +import { cn } from '@/utils/classnames' +import { basePath } from '@/utils/var' +import Input from '../components/base/input' const ChangePasswordForm = () => { const { t } = useTranslation() @@ -79,7 +79,8 @@ const ChangePasswordForm = () => { 'px-6', 'md:px-[108px]', ) - }> + } + > {!isTokenMissing && !verifyTokenRes && <Loading />} {(isTokenMissing || (verifyTokenRes && !verifyTokenRes.is_valid)) && ( <div className="flex flex-col md:w-[400px]"> @@ -88,19 +89,19 @@ const ChangePasswordForm = () => { <h2 className="text-[32px] font-bold text-text-primary">{t('login.invalid')}</h2> </div> <div className="mx-auto mt-6 w-full"> - <Button variant='primary' className='w-full !text-sm'> + <Button variant="primary" className="w-full !text-sm"> <a href="https://dify.ai">{t('login.explore')}</a> </Button> </div> </div> )} {verifyTokenRes && verifyTokenRes.is_valid && !showSuccess && ( - <div className='flex flex-col md:w-[400px]'> + <div className="flex flex-col md:w-[400px]"> <div className="mx-auto w-full"> <h2 className="text-[32px] font-bold text-text-primary"> {t('login.changePassword')} </h2> - <p className='mt-1 text-sm text-text-secondary'> + <p className="mt-1 text-sm text-text-secondary"> {t('login.changePasswordTip')} </p> </div> @@ -108,38 +109,38 @@ const ChangePasswordForm = () => { <div className="mx-auto mt-6 w-full"> <div className="relative"> {/* Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> {t('common.account.newPassword')} </label> <Input id="password" - type='password' + type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder={t('login.passwordPlaceholder') || ''} - className='mt-1' + className="mt-1" /> - <div className='mt-1 text-xs text-text-secondary'>{t('login.error.passwordInvalid')}</div> + <div className="mt-1 text-xs text-text-secondary">{t('login.error.passwordInvalid')}</div> </div> {/* Confirm Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="confirmPassword" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> {t('common.account.confirmPassword')} </label> <Input id="confirmPassword" - type='password' + type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} placeholder={t('login.confirmPasswordPlaceholder') || ''} - className='mt-1' + className="mt-1" /> </div> <div> <Button - variant='primary' - className='w-full !text-sm' + variant="primary" + className="w-full !text-sm" onClick={handleChangePassword} > {t('common.operation.reset')} @@ -153,14 +154,14 @@ const ChangePasswordForm = () => { <div className="flex flex-col md:w-[400px]"> <div className="mx-auto w-full"> <div className="mb-3 flex h-20 w-20 items-center justify-center rounded-[20px] border border-divider-regular bg-components-option-card-option-bg p-5 text-[40px] font-bold shadow-lg"> - <CheckCircleIcon className='h-10 w-10 text-[#039855]' /> + <CheckCircleIcon className="h-10 w-10 text-[#039855]" /> </div> <h2 className="text-[32px] font-bold text-text-primary"> {t('login.passwordChangedTip')} </h2> </div> <div className="mx-auto mt-6 w-full"> - <Button variant='primary' className='w-full'> + <Button variant="primary" className="w-full"> <a href={`${basePath}/signin`}>{t('login.passwordChanged')}</a> </Button> </div> diff --git a/web/app/forgot-password/ForgotPasswordForm.tsx b/web/app/forgot-password/ForgotPasswordForm.tsx index 37e6de7d5d..43aa1006d6 100644 --- a/web/app/forgot-password/ForgotPasswordForm.tsx +++ b/web/app/forgot-password/ForgotPasswordForm.tsx @@ -1,23 +1,23 @@ 'use client' -import React, { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { InitValidateStatusResponse } from '@/models/common' +import { zodResolver } from '@hookform/resolvers/zod' import { useRouter } from 'next/navigation' +import React, { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { z } from 'zod' -import { zodResolver } from '@hookform/resolvers/zod' -import Loading from '../components/base/loading' -import Input from '../components/base/input' import Button from '@/app/components/base/button' -import { basePath } from '@/utils/var' - import { fetchInitValidateStatus, fetchSetupStatus, sendForgotPasswordEmail, } from '@/service/common' -import type { InitValidateStatusResponse } from '@/models/common' +import { basePath } from '@/utils/var' + +import Input from '../components/base/input' +import Loading from '../components/base/loading' const accountFormSchema = z.object({ email: z @@ -81,42 +81,46 @@ const ForgotPasswordForm = () => { return ( loading ? <Loading /> - : <> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - <h2 className="text-[32px] font-bold text-text-primary"> - {isEmailSent ? t('login.resetLinkSent') : t('login.forgotPassword')} - </h2> - <p className='mt-1 text-sm text-text-secondary'> - {isEmailSent ? t('login.checkEmailForResetLink') : t('login.forgotPasswordDesc')} - </p> - </div> - <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md"> - <div className="relative"> - <form> - {!isEmailSent && ( - <div className='mb-5'> - <label htmlFor="email" - className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> - {t('login.email')} - </label> - <div className="mt-1"> - <Input - {...register('email')} - placeholder={t('login.emailPlaceholder') || ''} - /> - {errors.email && <span className='text-sm text-red-400'>{t(`${errors.email?.message}`)}</span>} + : ( + <> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + <h2 className="text-[32px] font-bold text-text-primary"> + {isEmailSent ? t('login.resetLinkSent') : t('login.forgotPassword')} + </h2> + <p className="mt-1 text-sm text-text-secondary"> + {isEmailSent ? t('login.checkEmailForResetLink') : t('login.forgotPasswordDesc')} + </p> + </div> + <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md"> + <div className="relative"> + <form> + {!isEmailSent && ( + <div className="mb-5"> + <label + htmlFor="email" + className="my-2 flex items-center justify-between text-sm font-medium text-text-primary" + > + {t('login.email')} + </label> + <div className="mt-1"> + <Input + {...register('email')} + placeholder={t('login.emailPlaceholder') || ''} + /> + {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}`)}</span>} + </div> + </div> + )} + <div> + <Button variant="primary" className="w-full" onClick={handleSendResetPasswordClick}> + {isEmailSent ? t('login.backToSignIn') : t('login.sendResetLink')} + </Button> </div> - </div> - )} - <div> - <Button variant='primary' className='w-full' onClick={handleSendResetPasswordClick}> - {isEmailSent ? t('login.backToSignIn') : t('login.sendResetLink')} - </Button> + </form> </div> - </form> - </div> - </div> - </> + </div> + </> + ) ) } diff --git a/web/app/forgot-password/page.tsx b/web/app/forgot-password/page.tsx index 3c78f734ad..4c37e096ca 100644 --- a/web/app/forgot-password/page.tsx +++ b/web/app/forgot-password/page.tsx @@ -1,12 +1,12 @@ 'use client' -import React from 'react' -import { cn } from '@/utils/classnames' import { useSearchParams } from 'next/navigation' +import React from 'react' +import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' +import { useGlobalPublicStore } from '@/context/global-public-context' +import useDocumentTitle from '@/hooks/use-document-title' +import { cn } from '@/utils/classnames' import Header from '../signin/_header' import ForgotPasswordForm from './ForgotPasswordForm' -import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' -import useDocumentTitle from '@/hooks/use-document-title' -import { useGlobalPublicStore } from '@/context/global-public-context' const ForgotPassword = () => { useDocumentTitle('') @@ -19,9 +19,15 @@ const ForgotPassword = () => { <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> <Header /> {token ? <ChangePasswordForm /> : <ForgotPasswordForm />} - {!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> - © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div>} + {!systemFeatures.branding.enabled && ( + <div className="px-8 py-6 text-sm font-normal text-text-tertiary"> + © + {' '} + {new Date().getFullYear()} + {' '} + LangGenius, Inc. All rights reserved. + </div> + )} </div> </div> ) diff --git a/web/app/init/InitPasswordPopup.tsx b/web/app/init/InitPasswordPopup.tsx index 6c0e9e3078..8ab6dcd0fd 100644 --- a/web/app/init/InitPasswordPopup.tsx +++ b/web/app/init/InitPasswordPopup.tsx @@ -1,14 +1,14 @@ 'use client' +import type { InitValidateStatusResponse } from '@/models/common' +import { useRouter } from 'next/navigation' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useRouter } from 'next/navigation' -import Toast from '../components/base/toast' -import Loading from '../components/base/loading' import Button from '@/app/components/base/button' -import { basePath } from '@/utils/var' -import { fetchInitValidateStatus, initValidate } from '@/service/common' -import type { InitValidateStatusResponse } from '@/models/common' import useDocumentTitle from '@/hooks/use-document-title' +import { fetchInitValidateStatus, initValidate } from '@/service/common' +import { basePath } from '@/utils/var' +import Loading from '../components/base/loading' +import Toast from '../components/base/toast' const InitPasswordPopup = () => { useDocumentTitle('') @@ -53,32 +53,34 @@ const InitPasswordPopup = () => { return ( loading ? <Loading /> - : <div> - {!validated && ( - <div className="mx-12 block min-w-28"> - <div className="mb-4"> - <label htmlFor="password" className="block text-sm font-medium text-text-secondary"> - {t('login.adminInitPassword')} + : ( + <div> + {!validated && ( + <div className="mx-12 block min-w-28"> + <div className="mb-4"> + <label htmlFor="password" className="block text-sm font-medium text-text-secondary"> + {t('login.adminInitPassword')} - </label> - <div className="relative mt-1 rounded-md shadow-sm"> - <input - id="password" - type="password" - value={password} - onChange={e => setPassword(e.target.value)} - className="block w-full appearance-none rounded-md border border-divider-regular px-3 py-2 shadow-sm placeholder:text-text-quaternary focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" - /> + </label> + <div className="relative mt-1 rounded-md shadow-sm"> + <input + id="password" + type="password" + value={password} + onChange={e => setPassword(e.target.value)} + className="block w-full appearance-none rounded-md border border-divider-regular px-3 py-2 shadow-sm placeholder:text-text-quaternary focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" + /> + </div> + </div> + <div className="flex flex-row flex-wrap justify-stretch p-0"> + <Button variant="primary" onClick={handleValidation} className="min-w-28 basis-full"> + {t('login.validate')} + </Button> + </div> </div> - </div> - <div className="flex flex-row flex-wrap justify-stretch p-0"> - <Button variant="primary" onClick={handleValidation} className="min-w-28 basis-full"> - {t('login.validate')} - </Button> - </div> + )} </div> - )} - </div> + ) ) } diff --git a/web/app/init/page.tsx b/web/app/init/page.tsx index 2842f3a739..c61457f984 100644 --- a/web/app/init/page.tsx +++ b/web/app/init/page.tsx @@ -1,6 +1,6 @@ import React from 'react' -import InitPasswordPopup from './InitPasswordPopup' import { cn } from '@/utils/classnames' +import InitPasswordPopup from './InitPasswordPopup' const Install = () => { return ( diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index 48956888fc..c3d9c1dfa6 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -1,25 +1,25 @@ 'use client' -import React, { useCallback, useEffect } from 'react' -import { useTranslation } from 'react-i18next' -import { useDebounceFn } from 'ahooks' - -import Link from 'next/link' -import { useRouter } from 'next/navigation' - import type { SubmitHandler } from 'react-hook-form' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { zodResolver } from '@hookform/resolvers/zod' -import Loading from '../components/base/loading' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' - -import { fetchInitValidateStatus, fetchSetupStatus, login, setup } from '@/service/common' import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common' -import useDocumentTitle from '@/hooks/use-document-title' -import { useDocLink } from '@/context/i18n' +import { zodResolver } from '@hookform/resolvers/zod' + +import { useDebounceFn } from 'ahooks' +import Link from 'next/link' + +import { useRouter } from 'next/navigation' +import React, { useCallback, useEffect } from 'react' +import { useForm } from 'react-hook-form' +import { useTranslation } from 'react-i18next' +import { z } from 'zod' +import Button from '@/app/components/base/button' import { validPassword } from '@/config' +import { useDocLink } from '@/context/i18n' +import useDocumentTitle from '@/hooks/use-document-title' +import { fetchInitValidateStatus, fetchSetupStatus, login, setup } from '@/service/common' +import { cn } from '@/utils/classnames' +import Loading from '../components/base/loading' + const accountFormSchema = z.object({ email: z .string() @@ -82,7 +82,8 @@ const InstallForm = () => { } const handleSetting = async () => { - if (isSubmitting) return + if (isSubmitting) + return handleSubmit(onSubmit)() } @@ -117,89 +118,97 @@ const InstallForm = () => { return ( loading ? <Loading /> - : <> - <div className="sm:mx-auto sm:w-full sm:max-w-md"> - <h2 className="text-[32px] font-bold text-text-primary">{t('login.setAdminAccount')}</h2> - <p className='mt-1 text-sm text-text-secondary'>{t('login.setAdminAccountDesc')}</p> - </div> - <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md"> - <div className="relative"> - <form onSubmit={handleSubmit(onSubmit)} onKeyDown={handleKeyDown}> - <div className='mb-5'> - <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> - {t('login.email')} - </label> - <div className="mt-1 rounded-md shadow-sm"> - <input - {...register('email')} - placeholder={t('login.emailPlaceholder') || ''} - className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'} - /> - {errors.email && <span className='text-sm text-red-400'>{t(`${errors.email?.message}`)}</span>} - </div> - - </div> - - <div className='mb-5'> - <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> - {t('login.name')} - </label> - <div className="relative mt-1 rounded-md shadow-sm"> - <input - {...register('name')} - placeholder={t('login.namePlaceholder') || ''} - className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'} - /> - </div> - {errors.name && <span className='text-sm text-red-400'>{t(`${errors.name.message}`)}</span>} - </div> - - <div className='mb-5'> - <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> - {t('login.password')} - </label> - <div className="relative mt-1 rounded-md shadow-sm"> - <input - {...register('password')} - type={showPassword ? 'text' : 'password'} - placeholder={t('login.passwordPlaceholder') || ''} - className={'system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs'} - /> - - <div className="absolute inset-y-0 right-0 flex items-center pr-3"> - <button - type="button" - onClick={() => setShowPassword(!showPassword)} - className="text-text-quaternary hover:text-text-tertiary focus:text-text-tertiary focus:outline-none" - > - {showPassword ? '👀' : '😝'} - </button> - </div> - </div> - - <div className={cn('mt-1 text-xs text-text-secondary', { - 'text-red-400 !text-sm': errors.password, - })}>{t('login.error.passwordInvalid')}</div> - </div> - - <div> - <Button variant='primary' className='w-full' onClick={handleSetting}> - {t('login.installBtn')} - </Button> - </div> - </form> - <div className="mt-2 block w-full text-xs text-text-secondary"> - {t('login.license.tip')} -   - <Link - className='text-text-accent' - target='_blank' rel='noopener noreferrer' - href={docLink('/policies/open-source')} - >{t('login.license.link')}</Link> + : ( + <> + <div className="sm:mx-auto sm:w-full sm:max-w-md"> + <h2 className="text-[32px] font-bold text-text-primary">{t('login.setAdminAccount')}</h2> + <p className="mt-1 text-sm text-text-secondary">{t('login.setAdminAccountDesc')}</p> </div> - </div> - </div> - </> + <div className="mt-8 grow sm:mx-auto sm:w-full sm:max-w-md"> + <div className="relative"> + <form onSubmit={handleSubmit(onSubmit)} onKeyDown={handleKeyDown}> + <div className="mb-5"> + <label htmlFor="email" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> + {t('login.email')} + </label> + <div className="mt-1 rounded-md shadow-sm"> + <input + {...register('email')} + placeholder={t('login.emailPlaceholder') || ''} + className="system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" + /> + {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}`)}</span>} + </div> + + </div> + + <div className="mb-5"> + <label htmlFor="name" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> + {t('login.name')} + </label> + <div className="relative mt-1 rounded-md shadow-sm"> + <input + {...register('name')} + placeholder={t('login.namePlaceholder') || ''} + className="system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" + /> + </div> + {errors.name && <span className="text-sm text-red-400">{t(`${errors.name.message}`)}</span>} + </div> + + <div className="mb-5"> + <label htmlFor="password" className="my-2 flex items-center justify-between text-sm font-medium text-text-primary"> + {t('login.password')} + </label> + <div className="relative mt-1 rounded-md shadow-sm"> + <input + {...register('password')} + type={showPassword ? 'text' : 'password'} + placeholder={t('login.passwordPlaceholder') || ''} + className="system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" + /> + + <div className="absolute inset-y-0 right-0 flex items-center pr-3"> + <button + type="button" + onClick={() => setShowPassword(!showPassword)} + className="text-text-quaternary hover:text-text-tertiary focus:text-text-tertiary focus:outline-none" + > + {showPassword ? '👀' : '😝'} + </button> + </div> + </div> + + <div className={cn('mt-1 text-xs text-text-secondary', { + 'text-red-400 !text-sm': errors.password, + })} + > + {t('login.error.passwordInvalid')} + </div> + </div> + + <div> + <Button variant="primary" className="w-full" onClick={handleSetting}> + {t('login.installBtn')} + </Button> + </div> + </form> + <div className="mt-2 block w-full text-xs text-text-secondary"> + {t('login.license.tip')} +   + <Link + className="text-text-accent" + target="_blank" + rel="noopener noreferrer" + href={docLink('/policies/open-source')} + > + {t('login.license.link')} + </Link> + </div> + </div> + </div> + </> + ) ) } diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index b3cdeb5ca4..b9a770405f 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -1,9 +1,9 @@ 'use client' import React from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { cn } from '@/utils/classnames' import Header from '../signin/_header' import InstallForm from './installForm' -import { cn } from '@/utils/classnames' -import { useGlobalPublicStore } from '@/context/global-public-context' const Install = () => { const { systemFeatures } = useGlobalPublicStore() @@ -12,9 +12,15 @@ const Install = () => { <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> <Header /> <InstallForm /> - {!systemFeatures.branding.enabled && <div className='px-8 py-6 text-sm font-normal text-text-tertiary'> - © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div>} + {!systemFeatures.branding.enabled && ( + <div className="px-8 py-6 text-sm font-normal text-text-tertiary"> + © + {' '} + {new Date().getFullYear()} + {' '} + LangGenius, Inc. All rights reserved. + </div> + )} </div> </div> ) diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 94a26eb776..25752c54a5 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,18 +1,18 @@ -import { ReactScan } from './components/react-scan' -import RoutePrefixHandle from './routePrefixHandle' import type { Viewport } from 'next' -import I18nServer from './components/i18n-server' -import BrowserInitializer from './components/browser-initializer' -import SentryInitializer from './components/sentry-initializer' -import { getLocaleOnServer } from '@/i18n-config/server' -import { TanstackQueryInitializer } from '@/context/query-client' import { ThemeProvider } from 'next-themes' +import { Instrument_Serif } from 'next/font/google' +import GlobalPublicStoreProvider from '@/context/global-public-context' +import { TanstackQueryInitializer } from '@/context/query-client' +import { getLocaleOnServer } from '@/i18n-config/server' +import { DatasetAttr } from '@/types/feature' +import { cn } from '@/utils/classnames' +import BrowserInitializer from './components/browser-initializer' +import I18nServer from './components/i18n-server' +import { ReactScan } from './components/react-scan' +import SentryInitializer from './components/sentry-initializer' +import RoutePrefixHandle from './routePrefixHandle' import './styles/globals.css' import './styles/markdown.scss' -import GlobalPublicStoreProvider from '@/context/global-public-context' -import { DatasetAttr } from '@/types/feature' -import { Instrument_Serif } from 'next/font/google' -import { cn } from '@/utils/classnames' export const viewport: Viewport = { width: 'device-width', @@ -85,13 +85,13 @@ const LocaleLayout = async ({ <meta name="msapplication-config" content="/browserconfig.xml" /> </head> <body - className='color-scheme h-full select-auto' + className="color-scheme h-full select-auto" {...datasetMap} > <ReactScan /> <ThemeProvider - attribute='data-theme' - defaultTheme='system' + attribute="data-theme" + defaultTheme="system" enableSystem disableTransitionOnChange enableColorScheme={false} diff --git a/web/app/page.tsx b/web/app/page.tsx index c5f5a9580b..117d6c838d 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -6,9 +6,9 @@ const Home = async () => { <div className="flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8"> <div className="sm:mx-auto sm:w-full sm:max-w-md"> - <Loading type='area' /> + <Loading type="area" /> <div className="mt-10 text-center"> - <Link href='/apps'>🚀</Link> + <Link href="/apps">🚀</Link> </div> </div> </div> diff --git a/web/app/repos/[owner]/[repo]/releases/route.ts b/web/app/repos/[owner]/[repo]/releases/route.ts index 29b604d94b..cc62e9d86c 100644 --- a/web/app/repos/[owner]/[repo]/releases/route.ts +++ b/web/app/repos/[owner]/[repo]/releases/route.ts @@ -1,11 +1,12 @@ -import { type NextRequest, NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' import { Octokit } from '@octokit/core' import { RequestError } from '@octokit/request-error' +import { NextResponse } from 'next/server' import { GITHUB_ACCESS_TOKEN } from '@/config' type Params = { - owner: string, - repo: string, + owner: string + repo: string } const octokit = new Octokit({ diff --git a/web/app/reset-password/check-code/page.tsx b/web/app/reset-password/check-code/page.tsx index 1d7597bf2a..fa10aec0c1 100644 --- a/web/app/reset-password/check-code/page.tsx +++ b/web/app/reset-password/check-code/page.tsx @@ -1,15 +1,15 @@ 'use client' import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import Countdown from '@/app/components/signin/countdown' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' -import { sendResetPasswordCode, verifyResetPasswordCode } from '@/service/common' +import Countdown from '@/app/components/signin/countdown' import I18NContext from '@/context/i18n' +import { sendResetPasswordCode, verifyResetPasswordCode } from '@/service/common' export default function CheckCode() { const { t } = useTranslation() @@ -63,37 +63,39 @@ export default function CheckCode() { catch (error) { console.error(error) } } - return <div className='flex flex-col gap-3'> - <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge text-text-accent-light-mode-only shadow-lg'> - <RiMailSendFill className='h-6 w-6 text-2xl' /> - </div> - <div className='pb-4 pt-2'> - <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.checkCode.checkYourEmail')}</h2> - <p className='body-md-regular mt-2 text-text-secondary'> - <span> - {t('login.checkCode.tipsPrefix')} - <strong>{email}</strong> - </span> - <br /> - {t('login.checkCode.validTime')} - </p> - </div> - - <form action=""> - <input type='text' className='hidden' /> - <label htmlFor="code" className='system-md-semibold mb-1 text-text-secondary'>{t('login.checkCode.verificationCode')}</label> - <Input value={code} onChange={e => setVerifyCode(e.target.value)} maxLength={6} className='mt-1' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> - <Button loading={loading} disabled={loading} className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button> - <Countdown onResend={resendCode} /> - </form> - <div className='py-2'> - <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> - </div> - <div onClick={() => router.back()} className='flex h-9 cursor-pointer items-center justify-center text-text-tertiary'> - <div className='inline-block rounded-full bg-background-default-dimmed p-1'> - <RiArrowLeftLine size={12} /> + return ( + <div className="flex flex-col gap-3"> + <div className="inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge text-text-accent-light-mode-only shadow-lg"> + <RiMailSendFill className="h-6 w-6 text-2xl" /> + </div> + <div className="pb-4 pt-2"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.checkCode.checkYourEmail')}</h2> + <p className="body-md-regular mt-2 text-text-secondary"> + <span> + {t('login.checkCode.tipsPrefix')} + <strong>{email}</strong> + </span> + <br /> + {t('login.checkCode.validTime')} + </p> + </div> + + <form action=""> + <input type="text" className="hidden" /> + <label htmlFor="code" className="system-md-semibold mb-1 text-text-secondary">{t('login.checkCode.verificationCode')}</label> + <Input value={code} onChange={e => setVerifyCode(e.target.value)} maxLength={6} className="mt-1" placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> + <Button loading={loading} disabled={loading} className="my-3 w-full" variant="primary" onClick={verify}>{t('login.checkCode.verify')}</Button> + <Countdown onResend={resendCode} /> + </form> + <div className="py-2"> + <div className="h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent"></div> + </div> + <div onClick={() => router.back()} className="flex h-9 cursor-pointer items-center justify-center text-text-tertiary"> + <div className="inline-block rounded-full bg-background-default-dimmed p-1"> + <RiArrowLeftLine size={12} /> + </div> + <span className="system-xs-regular ml-2">{t('login.back')}</span> </div> - <span className='system-xs-regular ml-2'>{t('login.back')}</span> </div> - </div> + ) } diff --git a/web/app/reset-password/layout.tsx b/web/app/reset-password/layout.tsx index 724873f30f..d9b665501d 100644 --- a/web/app/reset-password/layout.tsx +++ b/web/app/reset-password/layout.tsx @@ -1,30 +1,39 @@ 'use client' -import Header from '../signin/_header' +import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' -import { useGlobalPublicStore } from '@/context/global-public-context' +import Header from '../signin/_header' export default function SignInLayout({ children }: any) { const { systemFeatures } = useGlobalPublicStore() - return <> - <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> - <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> - <Header /> - <div className={ - cn( - 'flex w-full grow flex-col items-center justify-center', - 'px-6', - 'md:px-[108px]', - ) - }> - <div className='flex flex-col md:w-[400px]'> - {children} + return ( + <> + <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> + <div className={cn('flex w-full shrink-0 flex-col rounded-2xl border border-effects-highlight bg-background-default-subtle')}> + <Header /> + <div className={ + cn( + 'flex w-full grow flex-col items-center justify-center', + 'px-6', + 'md:px-[108px]', + ) + } + > + <div className="flex flex-col md:w-[400px]"> + {children} + </div> </div> + {!systemFeatures.branding.enabled && ( + <div className="system-xs-regular px-8 py-6 text-text-tertiary"> + © + {' '} + {new Date().getFullYear()} + {' '} + LangGenius, Inc. All rights reserved. + </div> + )} </div> - {!systemFeatures.branding.enabled && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> - © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div>} </div> - </div> - </> + </> + ) } diff --git a/web/app/reset-password/page.tsx b/web/app/reset-password/page.tsx index 11dfd07e5c..c7e15f8b3f 100644 --- a/web/app/reset-password/page.tsx +++ b/web/app/reset-password/page.tsx @@ -1,19 +1,19 @@ 'use client' -import Link from 'next/link' import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useState } from 'react' +import { noop } from 'lodash-es' +import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '../components/signin/countdown' -import { emailRegex } from '@/config' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' -import { sendResetPasswordCode } from '@/service/common' +import { emailRegex } from '@/config' import I18NContext from '@/context/i18n' -import { noop } from 'lodash-es' import useDocumentTitle from '@/hooks/use-document-title' +import { sendResetPasswordCode } from '@/service/common' +import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '../components/signin/countdown' export default function CheckCode() { const { t } = useTranslation() @@ -62,37 +62,39 @@ export default function CheckCode() { } } - return <div className='flex flex-col gap-3'> - <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> - <RiLockPasswordLine className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> - </div> - <div className='pb-4 pt-2'> - <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.resetPassword')}</h2> - <p className='body-md-regular mt-2 text-text-secondary'> - {t('login.resetPasswordDesc')} - </p> - </div> + return ( + <div className="flex flex-col gap-3"> + <div className="inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg"> + <RiLockPasswordLine className="h-6 w-6 text-2xl text-text-accent-light-mode-only" /> + </div> + <div className="pb-4 pt-2"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.resetPassword')}</h2> + <p className="body-md-regular mt-2 text-text-secondary"> + {t('login.resetPasswordDesc')} + </p> + </div> - <form onSubmit={noop}> - <input type='text' className='hidden' /> - <div className='mb-2'> - <label htmlFor="email" className='system-md-semibold my-2 text-text-secondary'>{t('login.email')}</label> - <div className='mt-1'> - <Input id='email' type="email" disabled={loading} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> - </div> - <div className='mt-3'> - <Button loading={loading} disabled={loading} variant='primary' className='w-full' onClick={handleGetEMailVerificationCode}>{t('login.sendVerificationCode')}</Button> + <form onSubmit={noop}> + <input type="text" className="hidden" /> + <div className="mb-2"> + <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary">{t('login.email')}</label> + <div className="mt-1"> + <Input id="email" type="email" disabled={loading} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> + </div> + <div className="mt-3"> + <Button loading={loading} disabled={loading} variant="primary" className="w-full" onClick={handleGetEMailVerificationCode}>{t('login.sendVerificationCode')}</Button> + </div> </div> + </form> + <div className="py-2"> + <div className="h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent"></div> </div> - </form> - <div className='py-2'> - <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + <Link href={`/signin?${searchParams.toString()}`} className="flex h-9 items-center justify-center text-text-tertiary hover:text-text-primary"> + <div className="inline-block rounded-full bg-background-default-dimmed p-1"> + <RiArrowLeftLine size={12} /> + </div> + <span className="system-xs-regular ml-2">{t('login.backToLogin')}</span> + </Link> </div> - <Link href={`/signin?${searchParams.toString()}`} className='flex h-9 items-center justify-center text-text-tertiary hover:text-text-primary'> - <div className='inline-block rounded-full bg-background-default-dimmed p-1'> - <RiArrowLeftLine size={12} /> - </div> - <span className='system-xs-regular ml-2'>{t('login.backToLogin')}</span> - </Link> - </div> + ) } diff --git a/web/app/reset-password/set-password/page.tsx b/web/app/reset-password/set-password/page.tsx index 30950a3dff..827bb24f73 100644 --- a/web/app/reset-password/set-password/page.tsx +++ b/web/app/reset-password/set-password/page.tsx @@ -1,15 +1,15 @@ 'use client' -import { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useRouter, useSearchParams } from 'next/navigation' -import { cn } from '@/utils/classnames' import { RiCheckboxCircleFill } from '@remixicon/react' import { useCountDown } from 'ahooks' +import { useRouter, useSearchParams } from 'next/navigation' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import { changePasswordWithToken } from '@/service/common' -import Toast from '@/app/components/base/toast' import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' import { validPassword } from '@/config' +import { changePasswordWithToken } from '@/service/common' +import { cn } from '@/utils/classnames' const ChangePasswordForm = () => { const { t } = useTranslation() @@ -91,14 +91,15 @@ const ChangePasswordForm = () => { 'px-6', 'md:px-[108px]', ) - }> + } + > {!showSuccess && ( - <div className='flex flex-col md:w-[400px]'> + <div className="flex flex-col md:w-[400px]"> <div className="mx-auto w-full"> <h2 className="title-4xl-semi-bold text-text-primary"> {t('login.changePassword')} </h2> - <p className='body-md-regular mt-2 text-text-secondary'> + <p className="body-md-regular mt-2 text-text-secondary"> {t('login.changePasswordTip')} </p> </div> @@ -106,13 +107,14 @@ const ChangePasswordForm = () => { <div className="mx-auto mt-6 w-full"> <div> {/* Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> {t('common.account.newPassword')} </label> - <div className='relative mt-1'> + <div className="relative mt-1"> <Input - id="password" type={showPassword ? 'text' : 'password'} + id="password" + type={showPassword ? 'text' : 'password'} value={password} onChange={e => setPassword(e.target.value)} placeholder={t('login.passwordPlaceholder') || ''} @@ -121,21 +123,21 @@ const ChangePasswordForm = () => { <div className="absolute inset-y-0 right-0 flex items-center"> <Button type="button" - variant='ghost' + variant="ghost" onClick={() => setShowPassword(!showPassword)} > {showPassword ? '👀' : '😝'} </Button> </div> </div> - <div className='body-xs-regular mt-1 text-text-secondary'>{t('login.error.passwordInvalid')}</div> + <div className="body-xs-regular mt-1 text-text-secondary">{t('login.error.passwordInvalid')}</div> </div> {/* Confirm Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="confirmPassword" className="system-md-semibold my-2 text-text-secondary"> {t('common.account.confirmPassword')} </label> - <div className='relative mt-1'> + <div className="relative mt-1"> <Input id="confirmPassword" type={showConfirmPassword ? 'text' : 'password'} @@ -146,7 +148,7 @@ const ChangePasswordForm = () => { <div className="absolute inset-y-0 right-0 flex items-center"> <Button type="button" - variant='ghost' + variant="ghost" onClick={() => setShowConfirmPassword(!showConfirmPassword)} > {showConfirmPassword ? '👀' : '😝'} @@ -156,8 +158,8 @@ const ChangePasswordForm = () => { </div> <div> <Button - variant='primary' - className='w-full' + variant="primary" + className="w-full" onClick={handleChangePassword} > {t('login.changePasswordBtn')} @@ -171,17 +173,28 @@ const ChangePasswordForm = () => { <div className="flex flex-col md:w-[400px]"> <div className="mx-auto w-full"> <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle font-bold shadow-lg"> - <RiCheckboxCircleFill className='h-6 w-6 text-text-success' /> + <RiCheckboxCircleFill className="h-6 w-6 text-text-success" /> </div> <h2 className="title-4xl-semi-bold text-text-primary"> {t('login.passwordChangedTip')} </h2> </div> <div className="mx-auto mt-6 w-full"> - <Button variant='primary' className='w-full' onClick={() => { - setLeftTime(undefined) - router.replace(getSignInUrl()) - }}>{t('login.passwordChanged')} ({Math.round(countdown / 1000)}) </Button> + <Button + variant="primary" + className="w-full" + onClick={() => { + setLeftTime(undefined) + router.replace(getSignInUrl()) + }} + > + {t('login.passwordChanged')} + {' '} + ( + {Math.round(countdown / 1000)} + ) + {' '} + </Button> </div> </div> )} diff --git a/web/app/routePrefixHandle.tsx b/web/app/routePrefixHandle.tsx index 464910782d..d3a36a51fc 100644 --- a/web/app/routePrefixHandle.tsx +++ b/web/app/routePrefixHandle.tsx @@ -1,8 +1,8 @@ 'use client' -import { basePath } from '@/utils/var' -import { useEffect } from 'react' import { usePathname } from 'next/navigation' +import { useEffect } from 'react' +import { basePath } from '@/utils/var' export default function RoutePrefixHandle() { const pathname = usePathname() diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index 731a229b8e..5ef24cd03e 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -1,22 +1,22 @@ 'use client' +import type { Locale } from '@/i18n-config' +import dynamic from 'next/dynamic' import React from 'react' import { useContext } from 'use-context-selector' -import LocaleSigninSelect from '@/app/components/base/select/locale-signin' import Divider from '@/app/components/base/divider' -import { languages } from '@/i18n-config/language' -import type { Locale } from '@/i18n-config' -import I18n from '@/context/i18n' -import dynamic from 'next/dynamic' +import LocaleSigninSelect from '@/app/components/base/select/locale-signin' import { useGlobalPublicStore } from '@/context/global-public-context' +import I18n from '@/context/i18n' +import { languages } from '@/i18n-config/language' // Avoid rendering the logo and theme selector on the server const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { ssr: false, - loading: () => <div className='h-7 w-16 bg-transparent' />, + loading: () => <div className="h-7 w-16 bg-transparent" />, }) const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector'), { ssr: false, - loading: () => <div className='size-8 bg-transparent' />, + loading: () => <div className="size-8 bg-transparent" />, }) const Header = () => { @@ -24,15 +24,17 @@ const Header = () => { const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return ( - <div className='flex w-full items-center justify-between p-6'> + <div className="flex w-full items-center justify-between p-6"> {systemFeatures.branding.enabled && systemFeatures.branding.login_page_logo - ? <img - src={systemFeatures.branding.login_page_logo} - className='block h-7 w-auto object-contain' - alt='logo' - /> - : <DifyLogo size='large' />} - <div className='flex items-center gap-1'> + ? ( + <img + src={systemFeatures.branding.login_page_logo} + className="block h-7 w-auto object-contain" + alt="logo" + /> + ) + : <DifyLogo size="large" />} + <div className="flex items-center gap-1"> <LocaleSigninSelect value={locale} items={languages.filter(item => item.supported)} @@ -40,7 +42,7 @@ const Header = () => { setLocaleOnClient(value as Locale) }} /> - <Divider type='vertical' className='mx-0 ml-2 h-4' /> + <Divider type="vertical" className="mx-0 ml-2 h-4" /> <ThemeSelector /> </div> </div> diff --git a/web/app/signin/check-code/page.tsx b/web/app/signin/check-code/page.tsx index 36c3c67a58..f0842e8c0d 100644 --- a/web/app/signin/check-code/page.tsx +++ b/web/app/signin/check-code/page.tsx @@ -1,18 +1,19 @@ 'use client' +import type { FormEvent } from 'react' import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { type FormEvent, useEffect, useRef, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' +import { useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import Countdown from '@/app/components/signin/countdown' +import { trackEvent } from '@/app/components/base/amplitude' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' -import { emailLoginWithCode, sendEMailLoginCode } from '@/service/common' +import Countdown from '@/app/components/signin/countdown' import I18NContext from '@/context/i18n' -import { resolvePostLoginRedirect } from '../utils/post-login-redirect' -import { trackEvent } from '@/app/components/base/amplitude' +import { emailLoginWithCode, sendEMailLoginCode } from '@/service/common' import { encryptVerificationCode } from '@/utils/encryption' +import { resolvePostLoginRedirect } from '../utils/post-login-redirect' export default function CheckCode() { const { t, i18n } = useTranslation() @@ -88,44 +89,46 @@ export default function CheckCode() { catch (error) { console.error(error) } } - return <div className='flex flex-col gap-3'> - <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> - <RiMailSendFill className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> - </div> - <div className='pb-4 pt-2'> - <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.checkCode.checkYourEmail')}</h2> - <p className='body-md-regular mt-2 text-text-secondary'> - <span> - {t('login.checkCode.tipsPrefix')} - <strong>{email}</strong> - </span> - <br /> - {t('login.checkCode.validTime')} - </p> - </div> - - <form onSubmit={handleSubmit}> - <label htmlFor="code" className='system-md-semibold mb-1 text-text-secondary'>{t('login.checkCode.verificationCode')}</label> - <Input - ref={codeInputRef} - id='code' - value={code} - onChange={e => setVerifyCode(e.target.value)} - maxLength={6} - className='mt-1' - placeholder={t('login.checkCode.verificationCodePlaceholder') as string} - /> - <Button type='submit' loading={loading} disabled={loading} className='my-3 w-full' variant='primary'>{t('login.checkCode.verify')}</Button> - <Countdown onResend={resendCode} /> - </form> - <div className='py-2'> - <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> - </div> - <div onClick={() => router.back()} className='flex h-9 cursor-pointer items-center justify-center text-text-tertiary'> - <div className='inline-block rounded-full bg-background-default-dimmed p-1'> - <RiArrowLeftLine size={12} /> + return ( + <div className="flex flex-col gap-3"> + <div className="inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg"> + <RiMailSendFill className="h-6 w-6 text-2xl text-text-accent-light-mode-only" /> + </div> + <div className="pb-4 pt-2"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.checkCode.checkYourEmail')}</h2> + <p className="body-md-regular mt-2 text-text-secondary"> + <span> + {t('login.checkCode.tipsPrefix')} + <strong>{email}</strong> + </span> + <br /> + {t('login.checkCode.validTime')} + </p> + </div> + + <form onSubmit={handleSubmit}> + <label htmlFor="code" className="system-md-semibold mb-1 text-text-secondary">{t('login.checkCode.verificationCode')}</label> + <Input + ref={codeInputRef} + id="code" + value={code} + onChange={e => setVerifyCode(e.target.value)} + maxLength={6} + className="mt-1" + placeholder={t('login.checkCode.verificationCodePlaceholder') as string} + /> + <Button type="submit" loading={loading} disabled={loading} className="my-3 w-full" variant="primary">{t('login.checkCode.verify')}</Button> + <Countdown onResend={resendCode} /> + </form> + <div className="py-2"> + <div className="h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent"></div> + </div> + <div onClick={() => router.back()} className="flex h-9 cursor-pointer items-center justify-center text-text-tertiary"> + <div className="inline-block rounded-full bg-background-default-dimmed p-1"> + <RiArrowLeftLine size={12} /> + </div> + <span className="system-xs-regular ml-2">{t('login.back')}</span> </div> - <span className='system-xs-regular ml-2'>{t('login.back')}</span> </div> - </div> + ) } diff --git a/web/app/signin/components/mail-and-code-auth.tsx b/web/app/signin/components/mail-and-code-auth.tsx index 002aaaf4ad..64ebc43a73 100644 --- a/web/app/signin/components/mail-and-code-auth.tsx +++ b/web/app/signin/components/mail-and-code-auth.tsx @@ -1,14 +1,15 @@ -import { type FormEvent, useState } from 'react' -import { useTranslation } from 'react-i18next' +import type { FormEvent } from 'react' import { useRouter, useSearchParams } from 'next/navigation' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import Input from '@/app/components/base/input' import Button from '@/app/components/base/button' -import { emailRegex } from '@/config' +import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' -import { sendEMailLoginCode } from '@/service/common' import { COUNT_DOWN_KEY, COUNT_DOWN_TIME_MS } from '@/app/components/signin/countdown' +import { emailRegex } from '@/config' import I18NContext from '@/context/i18n' +import { sendEMailLoginCode } from '@/service/common' type MailAndCodeAuthProps = { isInvite: boolean @@ -60,17 +61,18 @@ export default function MailAndCodeAuth({ isInvite }: MailAndCodeAuthProps) { handleGetEMailVerificationCode() } - return (<form onSubmit={handleSubmit}> - <input type='text' className='hidden' /> - <div className='mb-2'> - <label htmlFor="email" className='system-md-semibold my-2 text-text-secondary'>{t('login.email')}</label> - <div className='mt-1'> - <Input id='email' type="email" disabled={isInvite} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> + return ( + <form onSubmit={handleSubmit}> + <input type="text" className="hidden" /> + <div className="mb-2"> + <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary">{t('login.email')}</label> + <div className="mt-1"> + <Input id="email" type="email" disabled={isInvite} value={email} placeholder={t('login.emailPlaceholder') as string} onChange={e => setEmail(e.target.value)} /> + </div> + <div className="mt-3"> + <Button type="submit" loading={loading} disabled={loading || !email} variant="primary" className="w-full">{t('login.signup.verifyMail')}</Button> + </div> </div> - <div className='mt-3'> - <Button type='submit' loading={loading} disabled={loading || !email} variant='primary' className='w-full'>{t('login.signup.verifyMail')}</Button> - </div> - </div> - </form> + </form> ) } diff --git a/web/app/signin/components/mail-and-password-auth.tsx b/web/app/signin/components/mail-and-password-auth.tsx index 27c37e3e26..9ab2d9314c 100644 --- a/web/app/signin/components/mail-and-password-auth.tsx +++ b/web/app/signin/components/mail-and-password-auth.tsx @@ -1,19 +1,19 @@ +import type { ResponseError } from '@/service/fetch' +import { noop } from 'lodash-es' import Link from 'next/link' +import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { useRouter, useSearchParams } from 'next/navigation' import { useContext } from 'use-context-selector' +import { trackEvent } from '@/app/components/base/amplitude' import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' import { emailRegex } from '@/config' -import { login } from '@/service/common' -import Input from '@/app/components/base/input' import I18NContext from '@/context/i18n' -import { noop } from 'lodash-es' -import { resolvePostLoginRedirect } from '../utils/post-login-redirect' -import type { ResponseError } from '@/service/fetch' -import { trackEvent } from '@/app/components/base/amplitude' +import { login } from '@/service/common' import { encryptPassword } from '@/utils/encryption' +import { resolvePostLoginRedirect } from '../utils/post-login-redirect' type MailAndPasswordAuthProps = { isInvite: boolean @@ -99,71 +99,75 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis } } - return <form onSubmit={noop}> - <div className='mb-3'> - <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary"> - {t('login.email')} - </label> - <div className="mt-1"> - <Input - value={email} - onChange={e => setEmail(e.target.value)} - disabled={isInvite} - id="email" - type="email" - autoComplete="email" - placeholder={t('login.emailPlaceholder') || ''} - tabIndex={1} - /> - </div> - </div> - - <div className='mb-3'> - <label htmlFor="password" className="my-2 flex items-center justify-between"> - <span className='system-md-semibold text-text-secondary'>{t('login.password')}</span> - <Link - href={`/reset-password?${searchParams.toString()}`} - className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`} - tabIndex={isEmailSetup ? 0 : -1} - aria-disabled={!isEmailSetup} - > - {t('login.forget')} - </Link> - </label> - <div className="relative mt-1"> - <Input - id="password" - value={password} - onChange={e => setPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') - handleEmailPasswordLogin() - }} - type={showPassword ? 'text' : 'password'} - autoComplete="current-password" - placeholder={t('login.passwordPlaceholder') || ''} - tabIndex={2} - /> - <div className="absolute inset-y-0 right-0 flex items-center"> - <Button - type="button" - variant='ghost' - onClick={() => setShowPassword(!showPassword)} - > - {showPassword ? '👀' : '😝'} - </Button> + return ( + <form onSubmit={noop}> + <div className="mb-3"> + <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary"> + {t('login.email')} + </label> + <div className="mt-1"> + <Input + value={email} + onChange={e => setEmail(e.target.value)} + disabled={isInvite} + id="email" + type="email" + autoComplete="email" + placeholder={t('login.emailPlaceholder') || ''} + tabIndex={1} + /> </div> </div> - </div> - <div className='mb-2'> - <Button - tabIndex={2} - variant='primary' - onClick={handleEmailPasswordLogin} - disabled={isLoading || !email || !password} - className="w-full" - >{t('login.signBtn')}</Button> - </div> - </form> + <div className="mb-3"> + <label htmlFor="password" className="my-2 flex items-center justify-between"> + <span className="system-md-semibold text-text-secondary">{t('login.password')}</span> + <Link + href={`/reset-password?${searchParams.toString()}`} + className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`} + tabIndex={isEmailSetup ? 0 : -1} + aria-disabled={!isEmailSetup} + > + {t('login.forget')} + </Link> + </label> + <div className="relative mt-1"> + <Input + id="password" + value={password} + onChange={e => setPassword(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') + handleEmailPasswordLogin() + }} + type={showPassword ? 'text' : 'password'} + autoComplete="current-password" + placeholder={t('login.passwordPlaceholder') || ''} + tabIndex={2} + /> + <div className="absolute inset-y-0 right-0 flex items-center"> + <Button + type="button" + variant="ghost" + onClick={() => setShowPassword(!showPassword)} + > + {showPassword ? '👀' : '😝'} + </Button> + </div> + </div> + </div> + + <div className="mb-2"> + <Button + tabIndex={2} + variant="primary" + onClick={handleEmailPasswordLogin} + disabled={isLoading || !email || !password} + className="w-full" + > + {t('login.signBtn')} + </Button> + </div> + </form> + ) } diff --git a/web/app/signin/components/social-auth.tsx b/web/app/signin/components/social-auth.tsx index 1afac0809b..a1393e1fa5 100644 --- a/web/app/signin/components/social-auth.tsx +++ b/web/app/signin/components/social-auth.tsx @@ -1,10 +1,10 @@ -import { useTranslation } from 'react-i18next' import { useSearchParams } from 'next/navigation' -import style from '../page.module.css' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { API_PREFIX } from '@/config' -import { cn } from '@/utils/classnames' import { getPurifyHref } from '@/utils' +import { cn } from '@/utils/classnames' +import style from '../page.module.css' type SocialAuthProps = { disabled?: boolean @@ -21,38 +21,40 @@ export default function SocialAuth(props: SocialAuthProps) { return url } - return <> - <div className='w-full'> - <a href={getOAuthLink('/oauth/login/github')}> - <Button - disabled={props.disabled} - className='w-full' - > - <> - <span className={ - cn(style.githubIcon, - 'mr-2 h-5 w-5') - } /> - <span className="truncate leading-normal">{t('login.withGitHub')}</span> - </> - </Button> - </a> - </div> - <div className='w-full'> - <a href={getOAuthLink('/oauth/login/google')}> - <Button - disabled={props.disabled} - className='w-full' - > - <> - <span className={ - cn(style.googleIcon, - 'mr-2 h-5 w-5') - } /> - <span className="truncate leading-normal">{t('login.withGoogle')}</span> - </> - </Button> - </a> - </div> - </> + return ( + <> + <div className="w-full"> + <a href={getOAuthLink('/oauth/login/github')}> + <Button + disabled={props.disabled} + className="w-full" + > + <> + <span className={ + cn(style.githubIcon, 'mr-2 h-5 w-5') + } + /> + <span className="truncate leading-normal">{t('login.withGitHub')}</span> + </> + </Button> + </a> + </div> + <div className="w-full"> + <a href={getOAuthLink('/oauth/login/google')}> + <Button + disabled={props.disabled} + className="w-full" + > + <> + <span className={ + cn(style.googleIcon, 'mr-2 h-5 w-5') + } + /> + <span className="truncate leading-normal">{t('login.withGoogle')}</span> + </> + </Button> + </a> + </div> + </> + ) } diff --git a/web/app/signin/components/sso-auth.tsx b/web/app/signin/components/sso-auth.tsx index bb98eb2878..6b4c553f77 100644 --- a/web/app/signin/components/sso-auth.tsx +++ b/web/app/signin/components/sso-auth.tsx @@ -1,12 +1,12 @@ 'use client' -import { useRouter, useSearchParams } from 'next/navigation' import type { FC } from 'react' +import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import Button from '@/app/components/base/button' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' import Toast from '@/app/components/base/toast' import { getUserOAuth2SSOUrl, getUserOIDCSSOUrl, getUserSAMLSSOUrl } from '@/service/sso' -import Button from '@/app/components/base/button' import { SSOProtocol } from '@/types/feature' type SSOAuthProps = { @@ -64,7 +64,7 @@ const SSOAuth: FC<SSOAuthProps> = ({ disabled={isLoading} className="w-full" > - <Lock01 className='mr-2 h-5 w-5 text-text-accent-light-mode-only' /> + <Lock01 className="mr-2 h-5 w-5 text-text-accent-light-mode-only" /> <span className="truncate">{t('login.withSSO')}</span> </Button> ) diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index de8d6c60ea..9abd4366e1 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -1,24 +1,23 @@ 'use client' -import { useTranslation } from 'react-i18next' -import { useDocLink } from '@/context/i18n' -import { useCallback, useState } from 'react' -import Link from 'next/link' -import { useContext } from 'use-context-selector' -import { useRouter, useSearchParams } from 'next/navigation' import { RiAccountCircleLine } from '@remixicon/react' -import Input from '@/app/components/base/input' -import { SimpleSelect } from '@/app/components/base/select' -import Button from '@/app/components/base/button' -import { timezones } from '@/utils/timezone' -import { LanguagesSupported, languages } from '@/i18n-config/language' -import I18n from '@/context/i18n' -import { activateMember } from '@/service/common' -import Loading from '@/app/components/base/loading' -import Toast from '@/app/components/base/toast' import { noop } from 'lodash-es' +import Link from 'next/link' +import { useRouter, useSearchParams } from 'next/navigation' +import { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Loading from '@/app/components/base/loading' +import { SimpleSelect } from '@/app/components/base/select' +import Toast from '@/app/components/base/toast' import { useGlobalPublicStore } from '@/context/global-public-context' -import { resolvePostLoginRedirect } from '../utils/post-login-redirect' +import I18n, { useDocLink } from '@/context/i18n' +import { languages, LanguagesSupported } from '@/i18n-config/language' +import { activateMember } from '@/service/common' import { useInvitationCheck } from '@/service/use-common' +import { timezones } from '@/utils/timezone' +import { resolvePostLoginRedirect } from '../utils/post-login-redirect' export default function InviteSettingsPage() { const { t } = useTranslation() @@ -70,95 +69,104 @@ export default function InviteSettingsPage() { if (!checkRes) return <Loading /> if (!checkRes.is_valid) { - return <div className="flex flex-col md:w-[400px]"> - <div className="mx-auto w-full"> - <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle text-2xl font-bold shadow-lg">🤷‍♂️</div> - <h2 className="title-4xl-semi-bold text-text-primary">{t('login.invalid')}</h2> + return ( + <div className="flex flex-col md:w-[400px]"> + <div className="mx-auto w-full"> + <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle text-2xl font-bold shadow-lg">🤷‍♂️</div> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.invalid')}</h2> + </div> + <div className="mx-auto mt-6 w-full"> + <Button variant="primary" className="w-full !text-sm"> + <a href="https://dify.ai">{t('login.explore')}</a> + </Button> + </div> </div> - <div className="mx-auto mt-6 w-full"> - <Button variant='primary' className='w-full !text-sm'> - <a href="https://dify.ai">{t('login.explore')}</a> - </Button> - </div> - </div> + ) } - return <div className='flex flex-col gap-3'> - <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> - <RiAccountCircleLine className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> - </div> - <div className='pb-4 pt-2'> - <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.setYourAccount')}</h2> - </div> - <form onSubmit={noop}> - <div className='mb-5'> - <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary"> - {t('login.name')} - </label> - <div className="mt-1"> - <Input - id="name" - type="text" - value={name} - onChange={e => setName(e.target.value)} - placeholder={t('login.namePlaceholder') || ''} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault() - e.stopPropagation() - handleActivate() - } - }} - /> + return ( + <div className="flex flex-col gap-3"> + <div className="inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg"> + <RiAccountCircleLine className="h-6 w-6 text-2xl text-text-accent-light-mode-only" /> + </div> + <div className="pb-4 pt-2"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.setYourAccount')}</h2> + </div> + <form onSubmit={noop}> + <div className="mb-5"> + <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary"> + {t('login.name')} + </label> + <div className="mt-1"> + <Input + id="name" + type="text" + value={name} + onChange={e => setName(e.target.value)} + placeholder={t('login.namePlaceholder') || ''} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault() + e.stopPropagation() + handleActivate() + } + }} + /> + </div> </div> - </div> - <div className='mb-5'> - <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary"> - {t('login.interfaceLanguage')} - </label> - <div className="mt-1"> - <SimpleSelect - defaultValue={LanguagesSupported[0]} - items={languages.filter(item => item.supported)} - onSelect={(item) => { - setLanguage(item.value as string) - }} - /> + <div className="mb-5"> + <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary"> + {t('login.interfaceLanguage')} + </label> + <div className="mt-1"> + <SimpleSelect + defaultValue={LanguagesSupported[0]} + items={languages.filter(item => item.supported)} + onSelect={(item) => { + setLanguage(item.value as string) + }} + /> + </div> </div> - </div> - {/* timezone */} - <div className='mb-5'> - <label htmlFor="timezone" className="system-md-semibold text-text-secondary"> - {t('login.timezone')} - </label> - <div className="mt-1"> - <SimpleSelect - defaultValue={timezone} - items={timezones} - onSelect={(item) => { - setTimezone(item.value as string) - }} - /> + {/* timezone */} + <div className="mb-5"> + <label htmlFor="timezone" className="system-md-semibold text-text-secondary"> + {t('login.timezone')} + </label> + <div className="mt-1"> + <SimpleSelect + defaultValue={timezone} + items={timezones} + onSelect={(item) => { + setTimezone(item.value as string) + }} + /> + </div> </div> - </div> - <div> - <Button - variant='primary' - className='w-full' - onClick={handleActivate} - > - {`${t('login.join')} ${checkRes?.data?.workspace_name}`} - </Button> - </div> - </form> - {!systemFeatures.branding.enabled && <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> - {t('login.license.tip')} + <div> + <Button + variant="primary" + className="w-full" + onClick={handleActivate} + > + {`${t('login.join')} ${checkRes?.data?.workspace_name}`} + </Button> + </div> + </form> + {!systemFeatures.branding.enabled && ( + <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> + {t('login.license.tip')}   - <Link - className='system-xs-medium text-text-accent-secondary' - target='_blank' rel='noopener noreferrer' - href={docLink('/policies/open-source')} - >{t('login.license.link')}</Link> - </div>} - </div> + <Link + className="system-xs-medium text-text-accent-secondary" + target="_blank" + rel="noopener noreferrer" + href={docLink('/policies/open-source')} + > + {t('login.license.link')} + </Link> + </div> + )} + </div> + ) } diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index 17922f7892..4a1a2f4f58 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -1,26 +1,34 @@ 'use client' -import Header from './_header' - -import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' + import useDocumentTitle from '@/hooks/use-document-title' +import { cn } from '@/utils/classnames' +import Header from './_header' export default function SignInLayout({ children }: any) { const { systemFeatures } = useGlobalPublicStore() useDocumentTitle('') - return <> - <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> - <div className={cn('flex w-full shrink-0 flex-col items-center rounded-2xl border border-effects-highlight bg-background-default-subtle')}> - <Header /> - <div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}> - <div className='flex flex-col md:w-[400px]'> - {children} + return ( + <> + <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> + <div className={cn('flex w-full shrink-0 flex-col items-center rounded-2xl border border-effects-highlight bg-background-default-subtle')}> + <Header /> + <div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}> + <div className="flex flex-col md:w-[400px]"> + {children} + </div> </div> + {systemFeatures.branding.enabled === false && ( + <div className="system-xs-regular px-8 py-6 text-text-tertiary"> + © + {' '} + {new Date().getFullYear()} + {' '} + LangGenius, Inc. All rights reserved. + </div> + )} </div> - {systemFeatures.branding.enabled === false && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> - © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div>} </div> - </div> - </> + </> + ) } diff --git a/web/app/signin/normal-form.tsx b/web/app/signin/normal-form.tsx index 260eb8c0cb..6bc37e6dd3 100644 --- a/web/app/signin/normal-form.tsx +++ b/web/app/signin/normal-form.tsx @@ -1,22 +1,22 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' +import { RiContractLine, RiDoorLockLine, RiErrorWarningFill } from '@remixicon/react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import { RiContractLine, RiDoorLockLine, RiErrorWarningFill } from '@remixicon/react' +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Toast from '@/app/components/base/toast' +import { IS_CE_EDITION } from '@/config' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { invitationCheck } from '@/service/common' +import { useIsLogin } from '@/service/use-common' +import { LicenseStatus } from '@/types/feature' +import { cn } from '@/utils/classnames' import Loading from '../components/base/loading' import MailAndCodeAuth from './components/mail-and-code-auth' import MailAndPasswordAuth from './components/mail-and-password-auth' import SocialAuth from './components/social-auth' import SSOAuth from './components/sso-auth' -import { cn } from '@/utils/classnames' -import { invitationCheck } from '@/service/common' -import { LicenseStatus } from '@/types/feature' -import Toast from '@/app/components/base/toast' -import { IS_CE_EDITION } from '@/config' -import { useGlobalPublicStore } from '@/context/global-public-context' -import { resolvePostLoginRedirect } from './utils/post-login-redirect' import Split from './split' -import { useIsLogin } from '@/service/use-common' +import { resolvePostLoginRedirect } from './utils/post-login-redirect' const NormalForm = () => { const { t } = useTranslation() @@ -73,152 +73,204 @@ const NormalForm = () => { init() }, [init]) if (isLoading) { - return <div className={ - cn( - 'flex w-full grow flex-col items-center justify-center', - 'px-6', - 'md:px-[108px]', - ) - }> - <Loading type='area' /> - </div> + return ( + <div className={ + cn( + 'flex w-full grow flex-col items-center justify-center', + 'px-6', + 'md:px-[108px]', + ) + } + > + <Loading type="area" /> + </div> + ) } if (systemFeatures.license?.status === LicenseStatus.LOST) { - return <div className='mx-auto mt-8 w-full'> - <div className='relative'> - <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> - <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> - <RiContractLine className='h-5 w-5' /> - <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + return ( + <div className="mx-auto mt-8 w-full"> + <div className="relative"> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow"> + <RiContractLine className="h-5 w-5" /> + <RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" /> + </div> + <p className="system-sm-medium text-text-primary">{t('login.licenseLost')}</p> + <p className="system-xs-regular mt-1 text-text-tertiary">{t('login.licenseLostTip')}</p> </div> - <p className='system-sm-medium text-text-primary'>{t('login.licenseLost')}</p> - <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseLostTip')}</p> </div> </div> - </div> + ) } if (systemFeatures.license?.status === LicenseStatus.EXPIRED) { - return <div className='mx-auto mt-8 w-full'> - <div className='relative'> - <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> - <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> - <RiContractLine className='h-5 w-5' /> - <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + return ( + <div className="mx-auto mt-8 w-full"> + <div className="relative"> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow"> + <RiContractLine className="h-5 w-5" /> + <RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" /> + </div> + <p className="system-sm-medium text-text-primary">{t('login.licenseExpired')}</p> + <p className="system-xs-regular mt-1 text-text-tertiary">{t('login.licenseExpiredTip')}</p> </div> - <p className='system-sm-medium text-text-primary'>{t('login.licenseExpired')}</p> - <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseExpiredTip')}</p> </div> </div> - </div> + ) } if (systemFeatures.license?.status === LicenseStatus.INACTIVE) { - return <div className='mx-auto mt-8 w-full'> - <div className='relative'> - <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> - <div className='shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> - <RiContractLine className='h-5 w-5' /> - <RiErrorWarningFill className='absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary' /> + return ( + <div className="mx-auto mt-8 w-full"> + <div className="relative"> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow"> + <RiContractLine className="h-5 w-5" /> + <RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" /> + </div> + <p className="system-sm-medium text-text-primary">{t('login.licenseInactive')}</p> + <p className="system-xs-regular mt-1 text-text-tertiary">{t('login.licenseInactiveTip')}</p> </div> - <p className='system-sm-medium text-text-primary'>{t('login.licenseInactive')}</p> - <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.licenseInactiveTip')}</p> </div> </div> - </div> + ) } return ( <> <div className="mx-auto mt-8 w-full"> {isInviteLink - ? <div className="mx-auto w-full"> - <h2 className="title-4xl-semi-bold text-text-primary">{t('login.join')}{workspaceName}</h2> - {!systemFeatures.branding.enabled && <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.joinTipStart')}{workspaceName}{t('login.joinTipEnd')}</p>} - </div> - : <div className="mx-auto w-full"> - <h2 className="title-4xl-semi-bold text-text-primary">{systemFeatures.branding.enabled ? t('login.pageTitleForE') : t('login.pageTitle')}</h2> - <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.welcome')}</p> - </div>} + ? ( + <div className="mx-auto w-full"> + <h2 className="title-4xl-semi-bold text-text-primary"> + {t('login.join')} + {workspaceName} + </h2> + {!systemFeatures.branding.enabled && ( + <p className="body-md-regular mt-2 text-text-tertiary"> + {t('login.joinTipStart')} + {workspaceName} + {t('login.joinTipEnd')} + </p> + )} + </div> + ) + : ( + <div className="mx-auto w-full"> + <h2 className="title-4xl-semi-bold text-text-primary">{systemFeatures.branding.enabled ? t('login.pageTitleForE') : t('login.pageTitle')}</h2> + <p className="body-md-regular mt-2 text-text-tertiary">{t('login.welcome')}</p> + </div> + )} <div className="relative"> <div className="mt-6 flex flex-col gap-3"> {systemFeatures.enable_social_oauth_login && <SocialAuth />} - {systemFeatures.sso_enforced_for_signin && <div className='w-full'> - <SSOAuth protocol={systemFeatures.sso_enforced_for_signin_protocol} /> - </div>} + {systemFeatures.sso_enforced_for_signin && ( + <div className="w-full"> + <SSOAuth protocol={systemFeatures.sso_enforced_for_signin_protocol} /> + </div> + )} </div> - {showORLine && <div className="relative mt-6"> - <div className="flex items-center"> - <div className="h-px flex-1 bg-gradient-to-r from-background-gradient-mask-transparent to-divider-regular"></div> - <span className="system-xs-medium-uppercase px-3 text-text-tertiary">{t('login.or')}</span> - <div className="h-px flex-1 bg-gradient-to-l from-background-gradient-mask-transparent to-divider-regular"></div> + {showORLine && ( + <div className="relative mt-6"> + <div className="flex items-center"> + <div className="h-px flex-1 bg-gradient-to-r from-background-gradient-mask-transparent to-divider-regular"></div> + <span className="system-xs-medium-uppercase px-3 text-text-tertiary">{t('login.or')}</span> + <div className="h-px flex-1 bg-gradient-to-l from-background-gradient-mask-transparent to-divider-regular"></div> + </div> </div> - </div>} + )} { - (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login) && <> - {systemFeatures.enable_email_code_login && authType === 'code' && <> - <MailAndCodeAuth isInvite={isInviteLink} /> - {systemFeatures.enable_email_password_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('password') }}> - <span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.usePassword')}</span> - </div>} - </>} - {systemFeatures.enable_email_password_login && authType === 'password' && <> - <MailAndPasswordAuth isInvite={isInviteLink} isEmailSetup={systemFeatures.is_email_setup} allowRegistration={systemFeatures.is_allow_register} /> - {systemFeatures.enable_email_code_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('code') }}> - <span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.useVerificationCode')}</span> - </div>} - </>} - <Split className='mb-5 mt-4' /> - </> + (systemFeatures.enable_email_code_login || systemFeatures.enable_email_password_login) && ( + <> + {systemFeatures.enable_email_code_login && authType === 'code' && ( + <> + <MailAndCodeAuth isInvite={isInviteLink} /> + {systemFeatures.enable_email_password_login && ( + <div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('password') }}> + <span className="system-xs-medium text-components-button-secondary-accent-text">{t('login.usePassword')}</span> + </div> + )} + </> + )} + {systemFeatures.enable_email_password_login && authType === 'password' && ( + <> + <MailAndPasswordAuth isInvite={isInviteLink} isEmailSetup={systemFeatures.is_email_setup} allowRegistration={systemFeatures.is_allow_register} /> + {systemFeatures.enable_email_code_login && ( + <div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('code') }}> + <span className="system-xs-medium text-components-button-secondary-accent-text">{t('login.useVerificationCode')}</span> + </div> + )} + </> + )} + <Split className="mb-5 mt-4" /> + </> + ) } {systemFeatures.is_allow_register && authType === 'password' && ( - <div className='mb-3 text-[13px] font-medium leading-4 text-text-secondary'> + <div className="mb-3 text-[13px] font-medium leading-4 text-text-secondary"> <span>{t('login.signup.noAccount')}</span> <Link - className='text-text-accent' - href='/signup' - >{t('login.signup.signUp')}</Link> + className="text-text-accent" + href="/signup" + > + {t('login.signup.signUp')} + </Link> </div> )} - {allMethodsAreDisabled && <> - <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> - <div className='shadows-shadow-lg mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow'> - <RiDoorLockLine className='h-5 w-5' /> + {allMethodsAreDisabled && ( + <> + <div className="rounded-lg bg-gradient-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4"> + <div className="shadows-shadow-lg mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow"> + <RiDoorLockLine className="h-5 w-5" /> + </div> + <p className="system-sm-medium text-text-primary">{t('login.noLoginMethod')}</p> + <p className="system-xs-regular mt-1 text-text-tertiary">{t('login.noLoginMethodTip')}</p> </div> - <p className='system-sm-medium text-text-primary'>{t('login.noLoginMethod')}</p> - <p className='system-xs-regular mt-1 text-text-tertiary'>{t('login.noLoginMethodTip')}</p> - </div> - <div className="relative my-2 py-2"> - <div className="absolute inset-0 flex items-center" aria-hidden="true"> - <div className='h-px w-full bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> + <div className="relative my-2 py-2"> + <div className="absolute inset-0 flex items-center" aria-hidden="true"> + <div className="h-px w-full bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent"></div> + </div> </div> - </div> - </>} - {!systemFeatures.branding.enabled && <> - <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> - {t('login.tosDesc')} + </> + )} + {!systemFeatures.branding.enabled && ( + <> + <div className="system-xs-regular mt-2 block w-full text-text-tertiary"> + {t('login.tosDesc')}   - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/terms' - >{t('login.tos')}</Link> + <Link + className="system-xs-medium text-text-secondary hover:underline" + target="_blank" + rel="noopener noreferrer" + href="https://dify.ai/terms" + > + {t('login.tos')} + </Link>  &  - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/privacy' - >{t('login.pp')}</Link> - </div> - {IS_CE_EDITION && <div className="w-hull system-xs-regular mt-2 block text-text-tertiary"> - {t('login.goToInit')} + <Link + className="system-xs-medium text-text-secondary hover:underline" + target="_blank" + rel="noopener noreferrer" + href="https://dify.ai/privacy" + > + {t('login.pp')} + </Link> + </div> + {IS_CE_EDITION && ( + <div className="w-hull system-xs-regular mt-2 block text-text-tertiary"> + {t('login.goToInit')}   - <Link - className='system-xs-medium text-text-secondary hover:underline' - href='/install' - >{t('login.setAdminAccount')}</Link> - </div>} - </>} + <Link + className="system-xs-medium text-text-secondary hover:underline" + href="/install" + > + {t('login.setAdminAccount')} + </Link> + </div> + )} + </> + )} </div> </div> </> diff --git a/web/app/signin/one-more-step.tsx b/web/app/signin/one-more-step.tsx index 4b20f85681..80013e622f 100644 --- a/web/app/signin/one-more-step.tsx +++ b/web/app/signin/one-more-step.tsx @@ -1,17 +1,18 @@ 'use client' -import React, { type Reducer, useReducer } from 'react' -import { useTranslation } from 'react-i18next' +import type { Reducer } from 'react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import Input from '../components/base/input' +import React, { useReducer } from 'react' +import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' -import Tooltip from '@/app/components/base/tooltip' import { SimpleSelect } from '@/app/components/base/select' -import { timezones } from '@/utils/timezone' -import { LanguagesSupported, languages } from '@/i18n-config/language' import Toast from '@/app/components/base/toast' +import Tooltip from '@/app/components/base/tooltip' import { useDocLink } from '@/context/i18n' +import { languages, LanguagesSupported } from '@/i18n-config/language' import { useOneMoreStep } from '@/service/use-common' +import { timezones } from '@/utils/timezone' +import Input from '../components/base/input' type IState = { invitation_code: string @@ -21,9 +22,9 @@ type IState = { type IAction = | { type: 'failed', payload: null } - | { type: 'invitation_code', value: string } - | { type: 'interface_language', value: string } - | { type: 'timezone', value: string } + | { type: 'invitation_code', value: string } + | { type: 'interface_language', value: string } + | { type: 'timezone', value: string } const reducer: Reducer<IState, IAction> = (state: IState, action: IAction) => { switch (action.type) { @@ -79,7 +80,7 @@ const OneMoreStep = () => { <> <div className="mx-auto w-full"> <h2 className="title-4xl-semi-bold text-text-secondary">{t('login.oneMoreStep')}</h2> - <p className='body-md-regular mt-1 text-text-tertiary'>{t('login.createSample')}</p> + <p className="body-md-regular mt-1 text-text-tertiary">{t('login.createSample')}</p> </div> <div className="mx-auto mt-6 w-full"> @@ -88,16 +89,16 @@ const OneMoreStep = () => { <label className="system-md-semibold my-2 flex items-center justify-between text-text-secondary"> {t('login.invitationCode')} <Tooltip - popupContent={ - <div className='w-[256px] text-xs font-medium'> - <div className='font-medium'>{t('login.sendUsMail')}</div> - <div className='cursor-pointer text-xs font-medium text-text-accent-secondary'> + popupContent={( + <div className="w-[256px] text-xs font-medium"> + <div className="font-medium">{t('login.sendUsMail')}</div> + <div className="cursor-pointer text-xs font-medium text-text-accent-secondary"> <a href="mailto:request-invitation@langgenius.ai">request-invitation@langgenius.ai</a> </div> </div> - } + )} > - <span className='cursor-pointer text-text-accent-secondary'>{t('login.dontHave')}</span> + <span className="cursor-pointer text-text-accent-secondary">{t('login.dontHave')}</span> </Tooltip> </label> <div className="mt-1"> @@ -112,7 +113,7 @@ const OneMoreStep = () => { /> </div> </div> - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="name" className="system-md-semibold my-2 text-text-secondary"> {t('login.interfaceLanguage')} </label> @@ -126,7 +127,7 @@ const OneMoreStep = () => { /> </div> </div> - <div className='mb-4'> + <div className="mb-4"> <label htmlFor="timezone" className="system-md-semibold text-text-tertiary"> {t('login.timezone')} </label> @@ -142,8 +143,8 @@ const OneMoreStep = () => { </div> <div> <Button - variant='primary' - className='w-full' + variant="primary" + className="w-full" disabled={isPending} onClick={handleSubmit} > @@ -154,10 +155,13 @@ const OneMoreStep = () => { {t('login.license.tip')}   <Link - className='system-xs-medium text-text-accent-secondary' - target='_blank' rel='noopener noreferrer' + className="system-xs-medium text-text-accent-secondary" + target="_blank" + rel="noopener noreferrer" href={docLink('/policies/agreement/README')} - >{t('login.license.link')}</Link> + > + {t('login.license.link')} + </Link> </div> </div> </div> diff --git a/web/app/signin/page.tsx b/web/app/signin/page.tsx index 01c790c760..6f3632393c 100644 --- a/web/app/signin/page.tsx +++ b/web/app/signin/page.tsx @@ -1,9 +1,9 @@ 'use client' import { useSearchParams } from 'next/navigation' -import OneMoreStep from './one-more-step' -import NormalForm from './normal-form' import { useEffect } from 'react' import usePSInfo from '../components/billing/partner-stack/use-ps-info' +import NormalForm from './normal-form' +import OneMoreStep from './one-more-step' const SignIn = () => { const searchParams = useSearchParams() diff --git a/web/app/signin/split.tsx b/web/app/signin/split.tsx index 8fd6fefc15..b6e848357c 100644 --- a/web/app/signin/split.tsx +++ b/web/app/signin/split.tsx @@ -12,7 +12,8 @@ const Split: FC<Props> = ({ }) => { return ( <div - className={cn('h-px w-[400px] bg-[linear-gradient(90deg,rgba(255,255,255,0.01)_0%,rgba(16,24,40,0.08)_50.5%,rgba(255,255,255,0.01)_100%)]', className)}> + className={cn('h-px w-[400px] bg-[linear-gradient(90deg,rgba(255,255,255,0.01)_0%,rgba(16,24,40,0.08)_50.5%,rgba(255,255,255,0.01)_100%)]', className)} + > </div> ) } diff --git a/web/app/signin/utils/post-login-redirect.ts b/web/app/signin/utils/post-login-redirect.ts index 45e2c55941..b548a1bac9 100644 --- a/web/app/signin/utils/post-login-redirect.ts +++ b/web/app/signin/utils/post-login-redirect.ts @@ -1,6 +1,6 @@ -import { OAUTH_AUTHORIZE_PENDING_KEY, REDIRECT_URL_KEY } from '@/app/account/oauth/authorize/constants' -import dayjs from 'dayjs' import type { ReadonlyURLSearchParams } from 'next/navigation' +import dayjs from 'dayjs' +import { OAUTH_AUTHORIZE_PENDING_KEY, REDIRECT_URL_KEY } from '@/app/account/oauth/authorize/constants' function getItemWithExpiry(key: string): string | null { const itemStr = localStorage.getItem(key) @@ -10,7 +10,8 @@ function getItemWithExpiry(key: string): string | null { try { const item = JSON.parse(itemStr) localStorage.removeItem(key) - if (!item?.value) return null + if (!item?.value) + return null return dayjs().unix() > item.expiry ? null : item.value } diff --git a/web/app/signup/check-code/page.tsx b/web/app/signup/check-code/page.tsx index 35c5e78a45..3a4ff403fb 100644 --- a/web/app/signup/check-code/page.tsx +++ b/web/app/signup/check-code/page.tsx @@ -1,15 +1,15 @@ 'use client' +import type { MailSendResponse, MailValidityResponse } from '@/service/use-common' import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import Countdown from '@/app/components/signin/countdown' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' +import Countdown from '@/app/components/signin/countdown' import I18NContext from '@/context/i18n' -import type { MailSendResponse, MailValidityResponse } from '@/service/use-common' import { useMailValidity, useSendMail } from '@/service/use-common' export default function CheckCode() { @@ -74,36 +74,38 @@ export default function CheckCode() { catch (error) { console.error(error) } } - return <div className='flex flex-col gap-3'> - <div className='inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg'> - <RiMailSendFill className='h-6 w-6 text-2xl text-text-accent-light-mode-only' /> - </div> - <div className='pb-4 pt-2'> - <h2 className='title-4xl-semi-bold text-text-primary'>{t('login.checkCode.checkYourEmail')}</h2> - <p className='body-md-regular mt-2 text-text-secondary'> - <span> - {t('login.checkCode.tipsPrefix')} - <strong>{email}</strong> - </span> - <br /> - {t('login.checkCode.validTime')} - </p> - </div> - - <form action=""> - <label htmlFor="code" className='system-md-semibold mb-1 text-text-secondary'>{t('login.checkCode.verificationCode')}</label> - <Input value={code} onChange={e => setVerifyCode(e.target.value)} maxLength={6} className='mt-1' placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> - <Button loading={loading} disabled={loading} className='my-3 w-full' variant='primary' onClick={verify}>{t('login.checkCode.verify')}</Button> - <Countdown onResend={resendCode} /> - </form> - <div className='py-2'> - <div className='h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent'></div> - </div> - <div onClick={() => router.back()} className='flex h-9 cursor-pointer items-center justify-center text-text-tertiary'> - <div className='bg-background-default-dimm inline-block rounded-full p-1'> - <RiArrowLeftLine size={12} /> + return ( + <div className="flex flex-col gap-3"> + <div className="inline-flex h-14 w-14 items-center justify-center rounded-2xl border border-components-panel-border-subtle bg-background-default-dodge shadow-lg"> + <RiMailSendFill className="h-6 w-6 text-2xl text-text-accent-light-mode-only" /> + </div> + <div className="pb-4 pt-2"> + <h2 className="title-4xl-semi-bold text-text-primary">{t('login.checkCode.checkYourEmail')}</h2> + <p className="body-md-regular mt-2 text-text-secondary"> + <span> + {t('login.checkCode.tipsPrefix')} + <strong>{email}</strong> + </span> + <br /> + {t('login.checkCode.validTime')} + </p> + </div> + + <form action=""> + <label htmlFor="code" className="system-md-semibold mb-1 text-text-secondary">{t('login.checkCode.verificationCode')}</label> + <Input value={code} onChange={e => setVerifyCode(e.target.value)} maxLength={6} className="mt-1" placeholder={t('login.checkCode.verificationCodePlaceholder') as string} /> + <Button loading={loading} disabled={loading} className="my-3 w-full" variant="primary" onClick={verify}>{t('login.checkCode.verify')}</Button> + <Countdown onResend={resendCode} /> + </form> + <div className="py-2"> + <div className="h-px bg-gradient-to-r from-background-gradient-mask-transparent via-divider-regular to-background-gradient-mask-transparent"></div> + </div> + <div onClick={() => router.back()} className="flex h-9 cursor-pointer items-center justify-center text-text-tertiary"> + <div className="bg-background-default-dimm inline-block rounded-full p-1"> + <RiArrowLeftLine size={12} /> + </div> + <span className="system-xs-regular ml-2">{t('login.back')}</span> </div> - <span className='system-xs-regular ml-2'>{t('login.back')}</span> </div> - </div> + ) } diff --git a/web/app/signup/components/input-mail.tsx b/web/app/signup/components/input-mail.tsx index d2e7bca65b..b001e1f8b0 100644 --- a/web/app/signup/components/input-mail.tsx +++ b/web/app/signup/components/input-mail.tsx @@ -1,18 +1,18 @@ 'use client' +import type { MailSendResponse } from '@/service/use-common' import { noop } from 'lodash-es' -import Input from '@/app/components/base/input' +import Link from 'next/link' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import { useCallback, useState } from 'react' import Button from '@/app/components/base/button' -import { emailRegex } from '@/config' +import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' -import type { MailSendResponse } from '@/service/use-common' -import { useSendMail } from '@/service/use-common' -import I18n from '@/context/i18n' import Split from '@/app/signin/split' -import Link from 'next/link' +import { emailRegex } from '@/config' import { useGlobalPublicStore } from '@/context/global-public-context' +import I18n from '@/context/i18n' +import { useSendMail } from '@/service/use-common' type Props = { onSuccess: (email: string, payload: string) => void @@ -40,63 +40,77 @@ export default function Form({ return } const res = await submitMail({ email, language: locale }) - if((res as MailSendResponse).result === 'success') + if ((res as MailSendResponse).result === 'success') onSuccess(email, (res as MailSendResponse).data) }, [email, locale, submitMail, t]) - return <form onSubmit={noop}> - <div className='mb-3'> - <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary"> - {t('login.email')} - </label> - <div className="mt-1"> - <Input - value={email} - onChange={e => setEmail(e.target.value)} - id="email" - type="email" - autoComplete="email" - placeholder={t('login.emailPlaceholder') || ''} - tabIndex={1} - /> + return ( + <form onSubmit={noop}> + <div className="mb-3"> + <label htmlFor="email" className="system-md-semibold my-2 text-text-secondary"> + {t('login.email')} + </label> + <div className="mt-1"> + <Input + value={email} + onChange={e => setEmail(e.target.value)} + id="email" + type="email" + autoComplete="email" + placeholder={t('login.emailPlaceholder') || ''} + tabIndex={1} + /> + </div> </div> - </div> - <div className='mb-2'> - <Button - tabIndex={2} - variant='primary' - onClick={handleSubmit} - disabled={isPending || !email} - className="w-full" - >{t('login.signup.verifyMail')}</Button> - </div> - <Split className='mb-5 mt-4' /> + <div className="mb-2"> + <Button + tabIndex={2} + variant="primary" + onClick={handleSubmit} + disabled={isPending || !email} + className="w-full" + > + {t('login.signup.verifyMail')} + </Button> + </div> + <Split className="mb-5 mt-4" /> - <div className='text-[13px] font-medium leading-4 text-text-secondary'> - <span>{t('login.signup.haveAccount')}</span> - <Link - className='text-text-accent' - href='/signin' - >{t('login.signup.signIn')}</Link> - </div> + <div className="text-[13px] font-medium leading-4 text-text-secondary"> + <span>{t('login.signup.haveAccount')}</span> + <Link + className="text-text-accent" + href="/signin" + > + {t('login.signup.signIn')} + </Link> + </div> - {!systemFeatures.branding.enabled && <> - <div className="system-xs-regular mt-3 block w-full text-text-tertiary"> - {t('login.tosDesc')} + {!systemFeatures.branding.enabled && ( + <> + <div className="system-xs-regular mt-3 block w-full text-text-tertiary"> + {t('login.tosDesc')}   - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/terms' - >{t('login.tos')}</Link> + <Link + className="system-xs-medium text-text-secondary hover:underline" + target="_blank" + rel="noopener noreferrer" + href="https://dify.ai/terms" + > + {t('login.tos')} + </Link>  &  - <Link - className='system-xs-medium text-text-secondary hover:underline' - target='_blank' rel='noopener noreferrer' - href='https://dify.ai/privacy' - >{t('login.pp')}</Link> - </div> - </>} + <Link + className="system-xs-medium text-text-secondary hover:underline" + target="_blank" + rel="noopener noreferrer" + href="https://dify.ai/privacy" + > + {t('login.pp')} + </Link> + </div> + </> + )} - </form> + </form> + ) } diff --git a/web/app/signup/layout.tsx b/web/app/signup/layout.tsx index e6b9c36411..6728b66115 100644 --- a/web/app/signup/layout.tsx +++ b/web/app/signup/layout.tsx @@ -1,26 +1,34 @@ 'use client' import Header from '@/app/signin/_header' -import { cn } from '@/utils/classnames' import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' +import { cn } from '@/utils/classnames' export default function RegisterLayout({ children }: any) { const { systemFeatures } = useGlobalPublicStore() useDocumentTitle('') - return <> - <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> - <div className={cn('flex w-full shrink-0 flex-col items-center rounded-2xl border border-effects-highlight bg-background-default-subtle')}> - <Header /> - <div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}> - <div className='flex flex-col md:w-[400px]'> - {children} + return ( + <> + <div className={cn('flex min-h-screen w-full justify-center bg-background-default-burn p-6')}> + <div className={cn('flex w-full shrink-0 flex-col items-center rounded-2xl border border-effects-highlight bg-background-default-subtle')}> + <Header /> + <div className={cn('flex w-full grow flex-col items-center justify-center px-6 md:px-[108px]')}> + <div className="flex flex-col md:w-[400px]"> + {children} + </div> </div> + {systemFeatures.branding.enabled === false && ( + <div className="system-xs-regular px-8 py-6 text-text-tertiary"> + © + {' '} + {new Date().getFullYear()} + {' '} + LangGenius, Inc. All rights reserved. + </div> + )} </div> - {systemFeatures.branding.enabled === false && <div className='system-xs-regular px-8 py-6 text-text-tertiary'> - © {new Date().getFullYear()} LangGenius, Inc. All rights reserved. - </div>} </div> - </div> - </> + </> + ) } diff --git a/web/app/signup/page.tsx b/web/app/signup/page.tsx index d410f5c085..4b75445b2c 100644 --- a/web/app/signup/page.tsx +++ b/web/app/signup/page.tsx @@ -1,8 +1,8 @@ 'use client' -import { useCallback } from 'react' -import MailForm from './components/input-mail' import { useRouter, useSearchParams } from 'next/navigation' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import MailForm from './components/input-mail' const Signup = () => { const router = useRouter() @@ -20,7 +20,7 @@ const Signup = () => { <div className="mx-auto mt-8 w-full"> <div className="mx-auto mb-10 w-full"> <h2 className="title-4xl-semi-bold text-text-primary">{t('login.signup.createAccount')}</h2> - <p className='body-md-regular mt-2 text-text-tertiary'>{t('login.signup.welcome')}</p> + <p className="body-md-regular mt-2 text-text-tertiary">{t('login.signup.welcome')}</p> </div> <MailForm onSuccess={handleInputMailSubmitted} /> </div> diff --git a/web/app/signup/set-password/page.tsx b/web/app/signup/set-password/page.tsx index f75dff24d9..a4b6708ce8 100644 --- a/web/app/signup/set-password/page.tsx +++ b/web/app/signup/set-password/page.tsx @@ -1,15 +1,15 @@ 'use client' +import type { MailRegisterResponse } from '@/service/use-common' +import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useRouter, useSearchParams } from 'next/navigation' -import { cn } from '@/utils/classnames' -import Button from '@/app/components/base/button' -import Toast from '@/app/components/base/toast' -import Input from '@/app/components/base/input' -import { validPassword } from '@/config' -import type { MailRegisterResponse } from '@/service/use-common' -import { useMailRegister } from '@/service/use-common' import { trackEvent } from '@/app/components/base/amplitude' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Toast from '@/app/components/base/toast' +import { validPassword } from '@/config' +import { useMailRegister } from '@/service/use-common' +import { cn } from '@/utils/classnames' const ChangePasswordForm = () => { const { t } = useTranslation() @@ -79,13 +79,14 @@ const ChangePasswordForm = () => { 'px-6', 'md:px-[108px]', ) - }> - <div className='flex flex-col md:w-[400px]'> + } + > + <div className="flex flex-col md:w-[400px]"> <div className="mx-auto w-full"> <h2 className="title-4xl-semi-bold text-text-primary"> {t('login.changePassword')} </h2> - <p className='body-md-regular mt-2 text-text-secondary'> + <p className="body-md-regular mt-2 text-text-secondary"> {t('login.changePasswordTip')} </p> </div> @@ -93,31 +94,31 @@ const ChangePasswordForm = () => { <div className="mx-auto mt-6 w-full"> <div> {/* Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="password" className="system-md-semibold my-2 text-text-secondary"> {t('common.account.newPassword')} </label> - <div className='relative mt-1'> + <div className="relative mt-1"> <Input id="password" - type='password' + type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder={t('login.passwordPlaceholder') || ''} /> </div> - <div className='body-xs-regular mt-1 text-text-secondary'>{t('login.error.passwordInvalid')}</div> + <div className="body-xs-regular mt-1 text-text-secondary">{t('login.error.passwordInvalid')}</div> </div> {/* Confirm Password */} - <div className='mb-5'> + <div className="mb-5"> <label htmlFor="confirmPassword" className="system-md-semibold my-2 text-text-secondary"> {t('common.account.confirmPassword')} </label> - <div className='relative mt-1'> + <div className="relative mt-1"> <Input id="confirmPassword" - type='password' + type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} placeholder={t('login.confirmPasswordPlaceholder') || ''} @@ -126,8 +127,8 @@ const ChangePasswordForm = () => { </div> <div> <Button - variant='primary' - className='w-full' + variant="primary" + className="w-full" onClick={handleSubmit} disabled={isPending || !password || !confirmPassword} > diff --git a/web/config/index.spec.ts b/web/config/index.spec.ts index fd6b541006..7b1d91186d 100644 --- a/web/config/index.spec.ts +++ b/web/config/index.spec.ts @@ -1,11 +1,10 @@ -import { validPassword } from './index' -import { VAR_REGEX, resetReg } from './index' +import { resetReg, validPassword, VAR_REGEX } from './index' describe('config test', () => { const passwordRegex = validPassword // Valid passwords - test('Valid passwords: contains letter+digit, length ≥8', () => { + it('Valid passwords: contains letter+digit, length ≥8', () => { expect(passwordRegex.test('password1')).toBe(true) expect(passwordRegex.test('PASSWORD1')).toBe(true) expect(passwordRegex.test('12345678a')).toBe(true) @@ -15,40 +14,40 @@ describe('config test', () => { }) // Missing letter - test('Invalid passwords: missing letter', () => { + it('Invalid passwords: missing letter', () => { expect(passwordRegex.test('12345678')).toBe(false) expect(passwordRegex.test('!@#$%^&*123')).toBe(false) }) // Missing digit - test('Invalid passwords: missing digit', () => { + it('Invalid passwords: missing digit', () => { expect(passwordRegex.test('password')).toBe(false) expect(passwordRegex.test('PASSWORD')).toBe(false) expect(passwordRegex.test('AbCdEfGh')).toBe(false) }) // Too short - test('Invalid passwords: less than 8 characters', () => { + it('Invalid passwords: less than 8 characters', () => { expect(passwordRegex.test('pass1')).toBe(false) expect(passwordRegex.test('abc123')).toBe(false) expect(passwordRegex.test('1a')).toBe(false) }) // Boundary test - test('Boundary test: exactly 8 characters', () => { + it('Boundary test: exactly 8 characters', () => { expect(passwordRegex.test('abc12345')).toBe(true) expect(passwordRegex.test('1abcdefg')).toBe(true) }) // Special characters - test('Special characters: non-whitespace special chars allowed', () => { + it('Special characters: non-whitespace special chars allowed', () => { expect(passwordRegex.test('pass@123')).toBe(true) expect(passwordRegex.test('p@$$w0rd')).toBe(true) expect(passwordRegex.test('!1aBcDeF')).toBe(true) }) // Contains whitespace - test('Invalid passwords: contains whitespace', () => { + it('Invalid passwords: contains whitespace', () => { expect(passwordRegex.test('pass word1')).toBe(false) expect(passwordRegex.test('password1 ')).toBe(false) expect(passwordRegex.test(' password1')).toBe(false) diff --git a/web/config/index.ts b/web/config/index.ts index 508a94f3f0..96e0f7bc4a 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -1,19 +1,21 @@ +import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' import { InputVarType } from '@/app/components/workflow/types' -import { AgentStrategy } from '@/types/app' import { PromptRole } from '@/models/debug' import { PipelineInputVarType } from '@/models/pipeline' +import { AgentStrategy } from '@/types/app' import { DatasetAttr } from '@/types/feature' import pkg from '../package.json' -import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' const getBooleanConfig = ( envVar: string | undefined, dataAttrKey: DatasetAttr, defaultValue: boolean = true, ) => { - if (envVar !== undefined && envVar !== '') return envVar === 'true' + if (envVar !== undefined && envVar !== '') + return envVar === 'true' const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey) - if (attrValue !== undefined && attrValue !== '') return attrValue === 'true' + if (attrValue !== undefined && attrValue !== '') + return attrValue === 'true' return defaultValue } @@ -24,13 +26,15 @@ const getNumberConfig = ( ) => { if (envVar) { const parsed = Number.parseInt(envVar) - if (!Number.isNaN(parsed) && parsed > 0) return parsed + if (!Number.isNaN(parsed) && parsed > 0) + return parsed } const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey) if (attrValue) { const parsed = Number.parseInt(attrValue) - if (!Number.isNaN(parsed) && parsed > 0) return parsed + if (!Number.isNaN(parsed) && parsed > 0) + return parsed } return defaultValue } @@ -40,10 +44,12 @@ const getStringConfig = ( dataAttrKey: DatasetAttr, defaultValue: string, ) => { - if (envVar) return envVar + if (envVar) + return envVar const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey) - if (attrValue) return attrValue + if (attrValue) + return attrValue return defaultValue } @@ -159,7 +165,8 @@ const COOKIE_DOMAIN = getStringConfig( '', ).trim() export const CSRF_COOKIE_NAME = () => { - if (COOKIE_DOMAIN) return 'csrf_token' + if (COOKIE_DOMAIN) + return 'csrf_token' const isSecure = API_PREFIX.startsWith('https://') return isSecure ? '__Host-csrf_token' : 'csrf_token' } @@ -179,7 +186,8 @@ export const emailRegex = /^[\w.!#$%&'*+\-/=?^{|}~]+@([\w-]+\.)+[\w-]{2,}$/m const MAX_ZN_VAR_NAME_LENGTH = 8 const MAX_EN_VAR_VALUE_LENGTH = 30 export const getMaxVarNameLength = (value: string) => { - if (zhRegex.test(value)) return MAX_ZN_VAR_NAME_LENGTH + if (zhRegex.test(value)) + return MAX_ZN_VAR_NAME_LENGTH return MAX_EN_VAR_VALUE_LENGTH } @@ -324,7 +332,7 @@ Thought: {{agent_scratchpad}} } export const VAR_REGEX - = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.\d+)?(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi + = /\{\{(#[\w-]{1,50}(\.\d+)?(\.[a-z_]\w{0,29}){1,10}#)\}\}/gi export const resetReg = () => (VAR_REGEX.lastIndex = 0) @@ -398,7 +406,7 @@ export const ENABLE_SINGLE_DOLLAR_LATEX = getBooleanConfig( export const VALUE_SELECTOR_DELIMITER = '@@@' -export const validPassword = /^(?=.*[a-zA-Z])(?=.*\d)\S{8,}$/ +export const validPassword = /^(?=.*[a-z])(?=.*\d)\S{8,}$/i export const ZENDESK_WIDGET_KEY = getStringConfig( process.env.NEXT_PUBLIC_ZENDESK_WIDGET_KEY, diff --git a/web/context/access-control-store.ts b/web/context/access-control-store.ts index 3a80d7c865..1cb8eb9848 100644 --- a/web/context/access-control-store.ts +++ b/web/context/access-control-store.ts @@ -1,7 +1,7 @@ -import { create } from 'zustand' import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control' -import { AccessMode } from '@/models/access-control' import type { App } from '@/types/app' +import { create } from 'zustand' +import { AccessMode } from '@/models/access-control' type AccessControlStore = { appId: App['id'] diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index 48d67c3611..b7a47048f3 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -1,21 +1,21 @@ 'use client' +import type { FC, ReactNode } from 'react' +import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' +import { useQueryClient } from '@tanstack/react-query' +import { noop } from 'lodash-es' import { useCallback, useEffect, useMemo } from 'react' import { createContext, useContext, useContextSelector } from 'use-context-selector' -import type { FC, ReactNode } from 'react' -import { useQueryClient } from '@tanstack/react-query' +import { setUserId, setUserProperties } from '@/app/components/base/amplitude' +import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' +import MaintenanceNotice from '@/app/components/header/maintenance-notice' +import { ZENDESK_FIELD_IDS } from '@/config' import { useCurrentWorkspace, useLangGeniusVersion, useUserProfile, } from '@/service/use-common' -import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' -import MaintenanceNotice from '@/app/components/header/maintenance-notice' -import { noop } from 'lodash-es' -import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' -import { ZENDESK_FIELD_IDS } from '@/config' import { useGlobalPublicStore } from './global-public-context' -import { setUserId, setUserProperties } from '@/app/components/base/amplitude' export type AppContextValue = { userProfile: UserProfileResponse @@ -195,10 +195,11 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) => isCurrentWorkspaceDatasetOperator, mutateCurrentWorkspace, isLoadingCurrentWorkspace, - }}> - <div className='flex h-full flex-col overflow-y-auto'> + }} + > + <div className="flex h-full flex-col overflow-y-auto"> {globalThis.document?.body?.getAttribute('data-public-maintenance-notice') && <MaintenanceNotice />} - <div className='relative flex grow flex-col overflow-y-auto overflow-x-hidden bg-background-body'> + <div className="relative flex grow flex-col overflow-y-auto overflow-x-hidden bg-background-body"> {children} </div> </div> diff --git a/web/context/dataset-detail.ts b/web/context/dataset-detail.ts index 81213bb14b..3aa88c584d 100644 --- a/web/context/dataset-detail.ts +++ b/web/context/dataset-detail.ts @@ -1,7 +1,7 @@ -import { createContext, useContext, useContextSelector } from 'use-context-selector' -import type { DataSet } from '@/models/datasets' -import type { IndexingType } from '@/app/components/datasets/create/step-two' import type { QueryObserverResult, RefetchOptions } from '@tanstack/react-query' +import type { IndexingType } from '@/app/components/datasets/create/step-two' +import type { DataSet } from '@/models/datasets' +import { createContext, useContext, useContextSelector } from 'use-context-selector' type DatasetDetailContextValue = { indexingTechnique?: IndexingType diff --git a/web/context/datasets-context.tsx b/web/context/datasets-context.tsx index e3dc38d78d..4ca7ad311e 100644 --- a/web/context/datasets-context.tsx +++ b/web/context/datasets-context.tsx @@ -1,8 +1,8 @@ 'use client' -import { createContext, useContext } from 'use-context-selector' import type { DataSet } from '@/models/datasets' import { noop } from 'lodash-es' +import { createContext, useContext } from 'use-context-selector' export type DatasetsContextValue = { datasets: DataSet[] diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts index 5301835f12..51ba4ab626 100644 --- a/web/context/debug-configuration.ts +++ b/web/context/debug-configuration.ts @@ -1,6 +1,8 @@ import type { RefObject } from 'react' -import { createContext, useContext } from 'use-context-selector' -import { PromptMode } from '@/models/debug' +import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { Collection } from '@/app/components/tools/types' +import type { ExternalDataTool } from '@/models/common' +import type { DataSet } from '@/models/datasets' import type { AnnotationReplyConfig, BlockStatus, @@ -19,15 +21,12 @@ import type { SuggestedQuestionsAfterAnswerConfig, TextToSpeechConfig, } from '@/models/debug' -import type { ExternalDataTool } from '@/models/common' -import type { DataSet } from '@/models/datasets' import type { VisionSettings } from '@/types/app' -import { AppModeEnum } from '@/types/app' -import { ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app' -import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' -import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { Collection } from '@/app/components/tools/types' import { noop } from 'lodash-es' +import { createContext, useContext } from 'use-context-selector' +import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' +import { PromptMode } from '@/models/debug' +import { AppModeEnum, ModelModeType, Resolution, RETRIEVE_TYPE, TransferMethod } from '@/types/app' type IDebugConfiguration = { appId: string diff --git a/web/context/event-emitter.tsx b/web/context/event-emitter.tsx index d31e32e8aa..61a605cabf 100644 --- a/web/context/event-emitter.tsx +++ b/web/context/event-emitter.tsx @@ -1,8 +1,8 @@ 'use client' -import { createContext, useContext } from 'use-context-selector' -import { useEventEmitter } from 'ahooks' import type { EventEmitter } from 'ahooks/lib/useEventEmitter' +import { useEventEmitter } from 'ahooks' +import { createContext, useContext } from 'use-context-selector' const EventEmitterContext = createContext<{ eventEmitter: EventEmitter<string> | null }>({ eventEmitter: null, diff --git a/web/context/explore-context.ts b/web/context/explore-context.ts index d8d64fb34c..688b9036f9 100644 --- a/web/context/explore-context.ts +++ b/web/context/explore-context.ts @@ -1,6 +1,6 @@ -import { createContext } from 'use-context-selector' import type { InstalledApp } from '@/models/explore' import { noop } from 'lodash-es' +import { createContext } from 'use-context-selector' type IExplore = { controlUpdateInstalledApps: number diff --git a/web/context/external-knowledge-api-context.tsx b/web/context/external-knowledge-api-context.tsx index 9bf6ece70b..b137e8ca5e 100644 --- a/web/context/external-knowledge-api-context.tsx +++ b/web/context/external-knowledge-api-context.tsx @@ -1,8 +1,8 @@ 'use client' -import { createContext, useCallback, useContext, useMemo } from 'react' import type { FC, ReactNode } from 'react' import type { ExternalAPIItem, ExternalAPIListResponse } from '@/models/datasets' +import { createContext, useCallback, useContext, useMemo } from 'react' import { useExternalKnowledgeApiList } from '@/service/knowledge/use-dataset' type ExternalKnowledgeApiContextType = { diff --git a/web/context/global-public-context.tsx b/web/context/global-public-context.tsx index 324ac019c8..c2742bb7a9 100644 --- a/web/context/global-public-context.tsx +++ b/web/context/global-public-context.tsx @@ -1,12 +1,12 @@ 'use client' -import { create } from 'zustand' -import { useQuery } from '@tanstack/react-query' import type { FC, PropsWithChildren } from 'react' -import { useEffect } from 'react' import type { SystemFeatures } from '@/types/feature' -import { defaultSystemFeatures } from '@/types/feature' -import { getSystemFeatures } from '@/service/common' +import { useQuery } from '@tanstack/react-query' +import { useEffect } from 'react' +import { create } from 'zustand' import Loading from '@/app/components/base/loading' +import { getSystemFeatures } from '@/service/common' +import { defaultSystemFeatures } from '@/types/feature' type GlobalPublicStore = { isGlobalPending: boolean @@ -40,7 +40,7 @@ const GlobalPublicStoreProvider: FC<PropsWithChildren> = ({ }, [isPending, setIsPending]) if (isPending) - return <div className='flex h-screen w-screen items-center justify-center'><Loading /></div> + return <div className="flex h-screen w-screen items-center justify-center"><Loading /></div> return <>{children}</> } export default GlobalPublicStoreProvider diff --git a/web/context/hooks/use-trigger-events-limit-modal.ts b/web/context/hooks/use-trigger-events-limit-modal.ts index ac02acc025..403df58378 100644 --- a/web/context/hooks/use-trigger-events-limit-modal.ts +++ b/web/context/hooks/use-trigger-events-limit-modal.ts @@ -1,9 +1,10 @@ -import { type Dispatch, type SetStateAction, useCallback, useEffect, useRef, useState } from 'react' +import type { Dispatch, SetStateAction } from 'react' +import type { ModalState } from '../modal-context' import dayjs from 'dayjs' +import { useCallback, useEffect, useRef, useState } from 'react' import { NUM_INFINITE } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { IS_CLOUD_EDITION } from '@/config' -import type { ModalState } from '../modal-context' export type TriggerEventsLimitModalPayload = { usage: number diff --git a/web/context/i18n.ts b/web/context/i18n.ts index 6364cbf219..773569fa21 100644 --- a/web/context/i18n.ts +++ b/web/context/i18n.ts @@ -1,10 +1,10 @@ +import type { Locale } from '@/i18n-config' +import { noop } from 'lodash-es' import { createContext, useContext, } from 'use-context-selector' -import type { Locale } from '@/i18n-config' import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language' -import { noop } from 'lodash-es' type II18NContext = { locale: Locale diff --git a/web/context/mitt-context.tsx b/web/context/mitt-context.tsx index 2b437b0c30..6c6209b5a5 100644 --- a/web/context/mitt-context.tsx +++ b/web/context/mitt-context.tsx @@ -1,6 +1,6 @@ +import { noop } from 'lodash-es' import { createContext, useContext, useContextSelector } from 'use-context-selector' import { useMitt } from '@/hooks/use-mitt' -import { noop } from 'lodash-es' type ContextValueType = ReturnType<typeof useMitt> export const MittContext = createContext<ContextValueType>({ diff --git a/web/context/modal-context.test.tsx b/web/context/modal-context.test.tsx index 5ea8422030..07a82939a0 100644 --- a/web/context/modal-context.test.tsx +++ b/web/context/modal-context.test.tsx @@ -1,8 +1,8 @@ -import React from 'react' import { act, render, screen, waitFor } from '@testing-library/react' -import { ModalContextProvider } from '@/context/modal-context' -import { Plan } from '@/app/components/billing/type' +import React from 'react' import { defaultPlan } from '@/app/components/billing/config' +import { Plan } from '@/app/components/billing/type' +import { ModalContextProvider } from '@/context/modal-context' vi.mock('@/config', async (importOriginal) => { const actual = await importOriginal<typeof import('@/config')>() diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 7f08045993..2afd1b7b2f 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -1,45 +1,46 @@ 'use client' import type { Dispatch, SetStateAction } from 'react' -import { useCallback, useEffect, useState } from 'react' -import { createContext, useContext, useContextSelector } from 'use-context-selector' -import { useSearchParams } from 'next/navigation' +import type { TriggerEventsLimitModalPayload } from './hooks/use-trigger-events-limit-modal' +import type { OpeningStatement } from '@/app/components/base/features/types' +import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations' +import type { AccountSettingTab } from '@/app/components/header/account-setting/constants' import type { ConfigurationMethodEnum, Credential, CustomConfigurationModelFixedFields, CustomModel, + ModelModalModeEnum, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { - EDUCATION_PRICING_SHOW_ACTION, - EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, -} from '@/app/education-apply/constants' -import type { AccountSettingTab } from '@/app/components/header/account-setting/constants' +import type { ModelLoadBalancingModalProps } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal' +import type { UpdatePluginPayload } from '@/app/components/plugins/types' +import type { InputVar } from '@/app/components/workflow/types' +import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal' +import type { + ApiBasedExtension, + ExternalDataTool, +} from '@/models/common' +import type { ModerationConfig, PromptVariable } from '@/models/debug' +import { noop } from 'lodash-es' +import dynamic from 'next/dynamic' +import { useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useState } from 'react' +import { createContext, useContext, useContextSelector } from 'use-context-selector' import { ACCOUNT_SETTING_MODAL_ACTION, DEFAULT_ACCOUNT_SETTING_TAB, isValidAccountSettingTab, } from '@/app/components/header/account-setting/constants' -import type { ModerationConfig, PromptVariable } from '@/models/debug' -import type { - ApiBasedExtension, - ExternalDataTool, -} from '@/models/common' -import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations' -import type { ModelLoadBalancingModalProps } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal' -import type { OpeningStatement } from '@/app/components/base/features/types' -import type { InputVar } from '@/app/components/workflow/types' -import type { UpdatePluginPayload } from '@/app/components/plugins/types' -import { removeSpecificQueryParam } from '@/utils' -import { noop } from 'lodash-es' -import dynamic from 'next/dynamic' -import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal' -import type { ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useProviderContext } from '@/context/provider-context' -import { useAppContext } from '@/context/app-context' import { - type TriggerEventsLimitModalPayload, + EDUCATION_PRICING_SHOW_ACTION, + EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, +} from '@/app/education-apply/constants' +import { useAppContext } from '@/context/app-context' +import { useProviderContext } from '@/context/provider-context' +import { removeSpecificQueryParam } from '@/utils' +import { + useTriggerEventsLimitModal, } from './hooks/use-trigger-events-limit-modal' @@ -92,7 +93,7 @@ export type ModalState<T> = { onEditCallback?: (newPayload: T) => void onValidateBeforeSaveCallback?: (newPayload: T) => boolean isEditMode?: boolean - datasetBindings?: { id: string; name: string }[] + datasetBindings?: { id: string, name: string }[] } export type ModelModalType = { @@ -358,7 +359,8 @@ export const ModalContextProvider = ({ setShowUpdatePluginModal, setShowEducationExpireNoticeModal, setShowTriggerEventsLimitModal, - }}> + }} + > <> {children} { @@ -410,7 +412,8 @@ export const ModalContextProvider = ({ showAnnotationFullModal && ( <AnnotationFullModal show={showAnnotationFullModal} - onHide={() => setShowAnnotationFullModal(false)} /> + onHide={() => setShowAnnotationFullModal(false)} + /> ) } { @@ -478,7 +481,8 @@ export const ModalContextProvider = ({ {...showEducationExpireNoticeModal.payload} onClose={() => setShowEducationExpireNoticeModal(null)} /> - )} + ) + } { !!showTriggerEventsLimitModal && ( <TriggerEventsLimitModal @@ -496,7 +500,8 @@ export const ModalContextProvider = ({ handleShowPricingModal() }} /> - )} + ) + } </> </ModalContext.Provider> ) diff --git a/web/context/provider-context-mock.spec.tsx b/web/context/provider-context-mock.spec.tsx index ae2d634a5d..5b5f71c972 100644 --- a/web/context/provider-context-mock.spec.tsx +++ b/web/context/provider-context-mock.spec.tsx @@ -1,8 +1,8 @@ -import { render } from '@testing-library/react' import type { UsagePlanInfo } from '@/app/components/billing/type' +import { render } from '@testing-library/react' +import { createMockPlan, createMockPlanReset, createMockPlanTotal, createMockPlanUsage } from '@/__mocks__/provider-context' import { Plan } from '@/app/components/billing/type' import ProviderContextMock from './provider-context-mock' -import { createMockPlan, createMockPlanReset, createMockPlanTotal, createMockPlanUsage } from '@/__mocks__/provider-context' let mockPlan: Plan = Plan.sandbox const usage: UsagePlanInfo = { diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index e1739853c6..eb2a034f3b 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -1,34 +1,33 @@ 'use client' -import { createContext, useContext, useContextSelector } from 'use-context-selector' -import { useEffect, useState } from 'react' -import dayjs from 'dayjs' -import { useTranslation } from 'react-i18next' +import type { Plan, UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type' +import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { RETRIEVE_METHOD } from '@/types/app' import { useQueryClient } from '@tanstack/react-query' +import dayjs from 'dayjs' +import { noop } from 'lodash-es' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { createContext, useContext, useContextSelector } from 'use-context-selector' +import Toast from '@/app/components/base/toast' +import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' +import { defaultPlan } from '@/app/components/billing/config' +import { parseCurrentPlan } from '@/app/components/billing/utils' +import { + CurrentSystemQuotaTypeEnum, + ModelStatusEnum, + ModelTypeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' +import { ZENDESK_FIELD_IDS } from '@/config' +import { fetchCurrentPlanInfo } from '@/service/billing' import { useModelListByType, useModelProviders, useSupportRetrievalMethods, } from '@/service/use-common' -import { - CurrentSystemQuotaTypeEnum, - ModelStatusEnum, - ModelTypeEnum, -} from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { Model, ModelProvider } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { RETRIEVE_METHOD } from '@/types/app' -import type { Plan, UsageResetInfo } from '@/app/components/billing/type' -import type { UsagePlanInfo } from '@/app/components/billing/type' -import { fetchCurrentPlanInfo } from '@/service/billing' -import { parseCurrentPlan } from '@/app/components/billing/utils' -import { defaultPlan } from '@/app/components/billing/config' -import Toast from '@/app/components/base/toast' import { useEducationStatus, } from '@/service/use-education' -import { noop } from 'lodash-es' -import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils' -import { ZENDESK_FIELD_IDS } from '@/config' export type ProviderContextState = { modelProviders: ModelProvider[] @@ -61,7 +60,7 @@ export type ProviderContextState = { size: number limit: number } - }, + } refreshLicenseLimit: () => void isAllowTransferWorkspace: boolean isAllowPublishAsCustomKnowledgePipelineTemplate: boolean @@ -251,7 +250,8 @@ export const ProviderContextProvider = ({ refreshLicenseLimit: fetchPlan, isAllowTransferWorkspace, isAllowPublishAsCustomKnowledgePipelineTemplate, - }}> + }} + > {children} </ProviderContext.Provider> ) diff --git a/web/context/query-client.tsx b/web/context/query-client.tsx index 3deccba439..da95491630 100644 --- a/web/context/query-client.tsx +++ b/web/context/query-client.tsx @@ -16,8 +16,10 @@ const client = new QueryClient({ export const TanstackQueryInitializer: FC<PropsWithChildren> = (props) => { const { children } = props - return <QueryClientProvider client={client}> - {children} - <ReactQueryDevtools initialIsOpen={false} /> - </QueryClientProvider> + return ( + <QueryClientProvider client={client}> + {children} + <ReactQueryDevtools initialIsOpen={false} /> + </QueryClientProvider> + ) } diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx index 1b189cd452..e6680c95a5 100644 --- a/web/context/web-app-context.tsx +++ b/web/context/web-app-context.tsx @@ -1,15 +1,15 @@ 'use client' -import type { ChatConfig } from '@/app/components/base/chat/types' -import Loading from '@/app/components/base/loading' -import { AccessMode } from '@/models/access-control' -import type { AppData, AppMeta } from '@/models/share' -import { useGetWebAppAccessModeByCode } from '@/service/use-share' -import { usePathname, useSearchParams } from 'next/navigation' import type { FC, PropsWithChildren } from 'react' +import type { ChatConfig } from '@/app/components/base/chat/types' +import type { AppData, AppMeta } from '@/models/share' +import { usePathname, useSearchParams } from 'next/navigation' import { useEffect } from 'react' import { create } from 'zustand' import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils' +import Loading from '@/app/components/base/loading' +import { AccessMode } from '@/models/access-control' +import { useGetWebAppAccessModeByCode } from '@/service/use-share' import { useGlobalPublicStore } from './global-public-context' type WebAppStore = { @@ -112,9 +112,11 @@ const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => { }, [accessModeResult, updateWebAppAccessMode, shareCode]) if (isGlobalPending || isLoading) { - return <div className='flex h-full w-full items-center justify-center'> - <Loading /> - </div> + return ( + <div className="flex h-full w-full items-center justify-center"> + <Loading /> + </div> + ) } return ( <> diff --git a/web/context/workspace-context.tsx b/web/context/workspace-context.tsx index da7dcf5a50..3834641bc1 100644 --- a/web/context/workspace-context.tsx +++ b/web/context/workspace-context.tsx @@ -1,8 +1,8 @@ 'use client' +import type { IWorkspace } from '@/models/common' import { createContext, useContext } from 'use-context-selector' import { useWorkspaces } from '@/service/use-common' -import type { IWorkspace } from '@/models/common' export type WorkspacesContextValue = { workspaces: IWorkspace[] @@ -24,7 +24,8 @@ export const WorkspaceProvider = ({ return ( <WorkspacesContext.Provider value={{ workspaces: data?.workspaces || [], - }}> + }} + > {children} </WorkspacesContext.Provider> ) diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index ea2c961ad0..da425efb62 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -1,149 +1,65 @@ -import { - GLOB_TESTS, combine, javascript, node, - stylistic, typescript, unicorn, -} from '@antfu/eslint-config' -import globals from 'globals' -import storybook from 'eslint-plugin-storybook' -// import { fixupConfigRules } from '@eslint/compat' -import tailwind from 'eslint-plugin-tailwindcss' -import reactHooks from 'eslint-plugin-react-hooks' +// @ts-check +import antfu from '@antfu/eslint-config' import sonar from 'eslint-plugin-sonarjs' -import oxlint from 'eslint-plugin-oxlint' -import next from '@next/eslint-plugin-next' +import storybook from 'eslint-plugin-storybook' +import tailwind from 'eslint-plugin-tailwindcss' -// import reactRefresh from 'eslint-plugin-react-refresh' - -export default combine( - stylistic({ - lessOpinionated: true, - // original @antfu/eslint-config does not support jsx - jsx: false, - semi: false, - quotes: 'single', - overrides: { - // original config - 'style/indent': ['error', 2], - 'style/quotes': ['error', 'single'], - 'curly': ['error', 'multi-or-nest', 'consistent'], - 'style/comma-spacing': ['error', { before: false, after: true }], - 'style/quote-props': ['warn', 'consistent-as-needed'], - - // these options does not exist in old version - // maybe useless - 'style/indent-binary-ops': 'off', - 'style/multiline-ternary': 'off', - 'antfu/top-level-function': 'off', - 'antfu/curly': 'off', - 'antfu/consistent-chaining': 'off', - - // copy from eslint-config-antfu 0.36.0 - 'style/brace-style': ['error', 'stroustrup', { allowSingleLine: true }], - 'style/dot-location': ['error', 'property'], - 'style/object-curly-newline': ['error', { consistent: true, multiline: true }], - 'style/template-curly-spacing': ['error', 'never'], - 'style/keyword-spacing': 'off', - - // not exist in old version, and big change - 'style/member-delimiter-style': 'off', - }, - }), - javascript({ - overrides: { - // handled by unused-imports/no-unused-vars - 'no-unused-vars': 'off', - }, - }), - typescript({ - overrides: { - // original config - 'ts/consistent-type-definitions': ['warn', 'type'], - - // useful, but big change - 'ts/no-empty-object-type': 'off', - }, - }), - unicorn(), - node(), - // Next.js configuration +export default antfu( { - plugins: { - '@next/next': next, + react: { + overrides: { + 'react/no-context-provider': 'off', + 'react/no-forward-ref': 'off', + 'react/no-use-context': 'off', + }, }, - rules: { - ...next.configs.recommended.rules, - ...next.configs['core-web-vitals'].rules, - // performance issue, and not used. - '@next/next/no-html-link-for-pages': 'off', + nextjs: true, + ignores: ['public'], + typescript: { + overrides: { + 'ts/consistent-type-definitions': ['error', 'type'], + }, }, - }, - { - ignores: [ - 'storybook-static/**', - '**/node_modules/*', - '**/dist/', - '**/build/', - '**/out/', - '**/.next/', - '**/public/*', - '**/*.json', - '**/*.js', - ], - }, - { - // orignal config - rules: { - // orignal ts/no-var-requires - 'ts/no-require-imports': 'off', - 'no-console': 'off', - 'react/display-name': 'off', - 'array-callback-return': ['error', { - allowImplicit: false, - checkForEach: false, - }], - - // copy from eslint-config-antfu 0.36.0 - 'camelcase': 'off', - 'default-case-last': 'error', - - // antfu use eslint-plugin-perfectionist to replace this - // will cause big change, so keep the original sort-imports - 'sort-imports': [ - 'error', - { - ignoreCase: false, - ignoreDeclarationSort: true, - ignoreMemberSort: false, - memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], - allowSeparatedGroups: false, - }, - ], - - // antfu migrate to eslint-plugin-unused-imports - 'unused-imports/no-unused-vars': 'warn', - 'unused-imports/no-unused-imports': 'warn', - - // We use `import { noop } from 'lodash-es'` across `web` project - 'no-empty-function': 'error', + test: { + overrides: { + 'test/prefer-lowercase-title': 'off', + }, }, - - languageOptions: { - globals: { - ...globals.browser, - ...globals.es2025, - ...globals.node, - React: 'readable', - JSX: 'readable', + stylistic: { + overrides: { + 'antfu/top-level-function': 'off', }, }, }, - storybook.configs['flat/recommended'], - // reactRefresh.configs.recommended, + // downgrade some rules from error to warn for gradual adoption + // we should fix these in following pull requests { - rules: reactHooks.configs.recommended.rules, - plugins: { - 'react-hooks': reactHooks, + // @keep-sorted + rules: { + 'next/inline-script-id': 'warn', + 'no-console': 'warn', + 'no-irregular-whitespace': 'warn', + 'no-unused-vars': 'warn', + 'node/prefer-global/buffer': 'warn', + 'node/prefer-global/process': 'warn', + 'react/no-create-ref': 'warn', + 'react/no-missing-key': 'warn', + 'react/no-nested-component-definitions': 'warn', + 'regexp/no-dupe-disjunctions': 'warn', + 'regexp/no-super-linear-backtracking': 'warn', + 'regexp/no-unused-capturing-group': 'warn', + 'regexp/no-useless-assertions': 'warn', + 'regexp/no-useless-quantifier': 'warn', + 'style/multiline-ternary': 'warn', + 'test/no-identical-title': 'warn', + 'test/prefer-hooks-in-order': 'warn', + 'ts/no-empty-object-type': 'warn', + 'ts/no-require-imports': 'warn', + 'unicorn/prefer-number-properties': 'warn', + 'unused-imports/no-unused-vars': 'warn', }, }, + storybook.configs['flat/recommended'], // sonar { rules: { @@ -180,6 +96,14 @@ export default combine( // others 'sonarjs/todo-tag': 'warn', 'sonarjs/table-header': 'off', + + // new from this update + 'sonarjs/unused-import': 'off', + 'sonarjs/use-type-alias': 'warn', + 'sonarjs/single-character-alternation': 'warn', + 'sonarjs/no-os-command-from-path': 'warn', + 'sonarjs/class-name': 'off', + 'sonarjs/no-redundant-jump': 'warn', }, plugins: { sonarjs: sonar, @@ -193,38 +117,6 @@ export default combine( 'max-lines': 'off', }, }, - // need further research - { - rules: { - // not exist in old version - 'antfu/consistent-list-newline': 'off', - 'node/prefer-global/process': 'off', - 'node/prefer-global/buffer': 'off', - 'node/no-callback-literal': 'off', - 'eslint-comments/no-unused-disable': 'off', - 'tailwindcss/no-arbitrary-value': 'off', - 'tailwindcss/classnames-order': 'off', - 'style/indent': ['error', 2, { - SwitchCase: 1, - ignoreComments: true, - - }], - // useful, but big change - 'unicorn/prefer-number-properties': 'warn', - 'unicorn/no-new-array': 'warn', - }, - }, - // suppress error for `no-undef` rule - { - files: GLOB_TESTS, - languageOptions: { - globals: { - ...globals.browser, - ...globals.es2021, - ...globals.node, - }, - }, - }, tailwind.configs['flat/recommended'], { settings: { @@ -263,5 +155,4 @@ export default combine( 'tailwindcss/migration-from-tailwind-2': 'warn', }, }, - ...oxlint.buildFromOxlintConfigFile('./.oxlintrc.json'), ) diff --git a/web/hooks/use-app-favicon.ts b/web/hooks/use-app-favicon.ts index e8a0173371..32b8f6893a 100644 --- a/web/hooks/use-app-favicon.ts +++ b/web/hooks/use-app-favicon.ts @@ -1,7 +1,7 @@ +import type { AppIconType } from '@/types/app' import { useAsyncEffect } from 'ahooks' import { appDefaultIconBackground } from '@/config' import { searchEmoji } from '@/utils/emoji' -import type { AppIconType } from '@/types/app' type UseAppFaviconOptions = { enable?: boolean @@ -31,11 +31,11 @@ export function useAppFavicon(options: UseAppFaviconOptions) { link.href = isValidImageIcon ? icon_url : 'data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>' - + `<rect width=%22100%25%22 height=%22100%25%22 fill=%22${encodeURIComponent(icon_background || appDefaultIconBackground)}%22 rx=%2230%22 ry=%2230%22 />` - + `<text x=%2212.5%22 y=%221em%22 font-size=%2275%22>${ - icon ? await searchEmoji(icon) : '🤖' - }</text>` - + '</svg>' + + `<rect width=%22100%25%22 height=%22100%25%22 fill=%22${encodeURIComponent(icon_background || appDefaultIconBackground)}%22 rx=%2230%22 ry=%2230%22 />` + + `<text x=%2212.5%22 y=%221em%22 font-size=%2275%22>${ + icon ? await searchEmoji(icon) : '🤖' + }</text>` + + '</svg>' link.rel = 'shortcut icon' link.type = 'image/svg' diff --git a/web/hooks/use-document-title.spec.ts b/web/hooks/use-document-title.spec.ts index 27bfe6d3fe..3909978591 100644 --- a/web/hooks/use-document-title.spec.ts +++ b/web/hooks/use-document-title.spec.ts @@ -1,3 +1,5 @@ +import { act, renderHook } from '@testing-library/react' +import { useGlobalPublicStore } from '@/context/global-public-context' /** * Test suite for useDocumentTitle hook * @@ -11,9 +13,7 @@ * If no page title: "[Brand Name]" */ import { defaultSystemFeatures } from '@/types/feature' -import { act, renderHook } from '@testing-library/react' import useDocumentTitle from './use-document-title' -import { useGlobalPublicStore } from '@/context/global-public-context' vi.mock('@/service/common', () => ({ getSystemFeatures: vi.fn(() => ({ ...defaultSystemFeatures })), diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts index 10a167a9ea..bb69aeb20f 100644 --- a/web/hooks/use-document-title.ts +++ b/web/hooks/use-document-title.ts @@ -1,8 +1,8 @@ 'use client' -import { useGlobalPublicStore } from '@/context/global-public-context' import { useFavicon, useTitle } from 'ahooks' -import { basePath } from '@/utils/var' import { useEffect } from 'react' +import { useGlobalPublicStore } from '@/context/global-public-context' +import { basePath } from '@/utils/var' export default function useDocumentTitle(title: string) { const isPending = useGlobalPublicStore(s => s.isGlobalPending) diff --git a/web/hooks/use-format-time-from-now.spec.ts b/web/hooks/use-format-time-from-now.spec.ts index 87e33b6467..c5236dfbe6 100644 --- a/web/hooks/use-format-time-from-now.spec.ts +++ b/web/hooks/use-format-time-from-now.spec.ts @@ -13,6 +13,9 @@ import type { Mock } from 'vitest' * - Returns human-readable relative time strings */ import { renderHook } from '@testing-library/react' +// Import after mock to get the mocked version +import { useI18N } from '@/context/i18n' + import { useFormatTimeFromNow } from './use-format-time-from-now' // Mock the i18n context @@ -22,9 +25,6 @@ vi.mock('@/context/i18n', () => ({ })), })) -// Import after mock to get the mocked version -import { useI18N } from '@/context/i18n' - describe('useFormatTimeFromNow', () => { beforeEach(() => { vi.clearAllMocks() @@ -315,10 +315,27 @@ describe('useFormatTimeFromNow', () => { */ it('should handle all mapped locales', () => { const locales = [ - 'en-US', 'zh-Hans', 'zh-Hant', 'pt-BR', 'es-ES', 'fr-FR', - 'de-DE', 'ja-JP', 'ko-KR', 'ru-RU', 'it-IT', 'th-TH', - 'id-ID', 'uk-UA', 'vi-VN', 'ro-RO', 'pl-PL', 'hi-IN', - 'tr-TR', 'fa-IR', 'sl-SI', + 'en-US', + 'zh-Hans', + 'zh-Hant', + 'pt-BR', + 'es-ES', + 'fr-FR', + 'de-DE', + 'ja-JP', + 'ko-KR', + 'ru-RU', + 'it-IT', + 'th-TH', + 'id-ID', + 'uk-UA', + 'vi-VN', + 'ro-RO', + 'pl-PL', + 'hi-IN', + 'tr-TR', + 'fa-IR', + 'sl-SI', ] const now = Date.now() diff --git a/web/hooks/use-format-time-from-now.ts b/web/hooks/use-format-time-from-now.ts index db3be93df2..09d8db7321 100644 --- a/web/hooks/use-format-time-from-now.ts +++ b/web/hooks/use-format-time-from-now.ts @@ -1,8 +1,8 @@ +import type { Locale } from '@/i18n-config' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { useCallback } from 'react' import { useI18N } from '@/context/i18n' -import type { Locale } from '@/i18n-config' import 'dayjs/locale/de' import 'dayjs/locale/es' import 'dayjs/locale/fa' diff --git a/web/hooks/use-import-dsl.ts b/web/hooks/use-import-dsl.ts index e5fafb1e75..35aa701fbd 100644 --- a/web/hooks/use-import-dsl.ts +++ b/web/hooks/use-import-dsl.ts @@ -1,25 +1,25 @@ +import type { + DSLImportMode, + DSLImportResponse, +} from '@/models/app' +import type { AppIconType } from '@/types/app' +import { useRouter } from 'next/navigation' import { useCallback, useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useRouter } from 'next/navigation' -import type { - DSLImportMode, - DSLImportResponse, -} from '@/models/app' +import { useToastContext } from '@/app/components/base/toast' +import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' +import { useSelector } from '@/context/app-context' import { DSLImportStatus } from '@/models/app' import { importDSL, importDSLConfirm, } from '@/service/apps' -import type { AppIconType } from '@/types/app' -import { useToastContext } from '@/app/components/base/toast' -import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' import { getRedirection } from '@/utils/app-redirection' -import { useSelector } from '@/context/app-context' -import { NEED_REFRESH_APP_LIST_KEY } from '@/config' type DSLPayload = { mode: DSLImportMode @@ -43,7 +43,7 @@ export const useImportDSL = () => { const { handleCheckPluginDependencies } = usePluginDependencies() const isCurrentWorkspaceEditor = useSelector(s => s.isCurrentWorkspaceEditor) const { push } = useRouter() - const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() + const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>() const importIdRef = useRef<string>('') const handleImportDSL = useCallback(async ( diff --git a/web/hooks/use-metadata.ts b/web/hooks/use-metadata.ts index 436747c86e..a51e6b150e 100644 --- a/web/hooks/use-metadata.ts +++ b/web/hooks/use-metadata.ts @@ -1,8 +1,9 @@ 'use client' +import type { DocType } from '@/models/datasets' import { useTranslation } from 'react-i18next' -import { formatFileSize, formatNumber, formatTime } from '@/utils/format' -import { ChunkingMode, type DocType } from '@/models/datasets' import useTimestamp from '@/hooks/use-timestamp' +import { ChunkingMode } from '@/models/datasets' +import { formatFileSize, formatNumber, formatTime } from '@/utils/format' export type inputType = 'input' | 'select' | 'textarea' export type metadataType = DocType | 'originInfo' | 'technicalParameters' diff --git a/web/hooks/use-mitt.ts b/web/hooks/use-mitt.ts index 584636c8a6..e29396aefb 100644 --- a/web/hooks/use-mitt.ts +++ b/web/hooks/use-mitt.ts @@ -12,10 +12,10 @@ export type _Events = Record<EventType, unknown> export type UseSubscribeOption = { /** - * Whether the subscription is enabled. - * @default true - */ - enabled: boolean; + * Whether the subscription is enabled. + * @default true + */ + enabled: boolean } export type ExtendedOn<Events extends _Events> = { @@ -23,17 +23,17 @@ export type ExtendedOn<Events extends _Events> = { type: Key, handler: Handler<Events[Key]>, options?: UseSubscribeOption, - ): void; + ): void ( type: '*', handler: WildcardHandler<Events>, option?: UseSubscribeOption, - ): void; + ): void } export type UseMittReturn<Events extends _Events> = { - useSubscribe: ExtendedOn<Events>; - emit: Emitter<Events>['emit']; + useSubscribe: ExtendedOn<Events> + emit: Emitter<Events>['emit'] } const defaultSubscribeOption: UseSubscribeOption = { diff --git a/web/hooks/use-moderate.ts b/web/hooks/use-moderate.ts index 11b078fbd9..e42441e58e 100644 --- a/web/hooks/use-moderate.ts +++ b/web/hooks/use-moderate.ts @@ -1,5 +1,5 @@ -import { useEffect, useRef, useState } from 'react' import type { ModerationService } from '@/models/common' +import { useEffect, useRef, useState } from 'react' function splitStringByLength(inputString: string, chunkLength: number) { const resultArray = [] diff --git a/web/hooks/use-pay.tsx b/web/hooks/use-pay.tsx index 3812949dec..486fa299a7 100644 --- a/web/hooks/use-pay.tsx +++ b/web/hooks/use-pay.tsx @@ -1,9 +1,9 @@ 'use client' -import { useCallback, useEffect, useState } from 'react' -import { useRouter, useSearchParams } from 'next/navigation' -import { useTranslation } from 'react-i18next' import type { IConfirm } from '@/app/components/base/confirm' +import { useRouter, useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' import { useNotionBinding } from '@/service/use-common' @@ -102,7 +102,7 @@ export const CheckModal = () => { onCancel={handleCancelShowPayStatusModal} onConfirm={handleCancelShowPayStatusModal} showCancel={false} - type={confirmInfo.type === 'info' ? 'info' : 'warning' } + type={confirmInfo.type === 'info' ? 'info' : 'warning'} title={confirmInfo.title} content={(confirmInfo as unknown as { desc: string }).desc || ''} confirmText={(confirmInfo.type === 'info' && t('common.operation.ok')) || ''} diff --git a/web/hooks/use-tab-searchparams.spec.ts b/web/hooks/use-tab-searchparams.spec.ts index 424f17d909..e724f323af 100644 --- a/web/hooks/use-tab-searchparams.spec.ts +++ b/web/hooks/use-tab-searchparams.spec.ts @@ -12,6 +12,9 @@ import type { Mock } from 'vitest' * navigation persistent and shareable across sessions. */ import { act, renderHook } from '@testing-library/react' +// Import after mocks +import { usePathname } from 'next/navigation' + import { useTabSearchParams } from './use-tab-searchparams' // Mock Next.js navigation hooks @@ -29,9 +32,6 @@ vi.mock('next/navigation', () => ({ useSearchParams: vi.fn(() => mockSearchParams), })) -// Import after mocks -import { usePathname } from 'next/navigation' - describe('useTabSearchParams', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/web/hooks/use-theme.ts b/web/hooks/use-theme.ts index c814c7d9de..c9c2bdea55 100644 --- a/web/hooks/use-theme.ts +++ b/web/hooks/use-theme.ts @@ -1,5 +1,5 @@ -import { Theme } from '@/types/app' import { useTheme as useBaseTheme } from 'next-themes' +import { Theme } from '@/types/app' const useTheme = () => { const { theme, resolvedTheme, ...rest } = useBaseTheme() diff --git a/web/hooks/use-timestamp.ts b/web/hooks/use-timestamp.ts index 5242eb565a..05afa8e178 100644 --- a/web/hooks/use-timestamp.ts +++ b/web/hooks/use-timestamp.ts @@ -1,8 +1,8 @@ 'use client' -import { useCallback } from 'react' import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import { useCallback } from 'react' import { useAppContext } from '@/context/app-context' dayjs.extend(utc) diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md index fd4ef30833..0fe8922345 100644 --- a/web/i18n-config/README.md +++ b/web/i18n-config/README.md @@ -79,7 +79,6 @@ export type I18nText = { 4. Add the new language to the `language.json` file. ```typescript - export const languages = [ { value: 'en-US', @@ -172,7 +171,7 @@ export const languages = [ supported: true, }, // Add your language here 👇 - ... + // ... // Add your language here 👆 ] ``` diff --git a/web/i18n-config/auto-gen-i18n.js b/web/i18n-config/auto-gen-i18n.js index 3ab76c8ed4..561fa95869 100644 --- a/web/i18n-config/auto-gen-i18n.js +++ b/web/i18n-config/auto-gen-i18n.js @@ -1,11 +1,11 @@ import fs from 'node:fs' -import path from 'node:path' -import vm from 'node:vm' -import { fileURLToPath } from 'node:url' import { createRequire } from 'node:module' -import { transpile } from 'typescript' -import { parseModule, generateCode, loadFile } from 'magicast' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import vm from 'node:vm' import { translate } from 'bing-translate-api' +import { generateCode, loadFile, parseModule } from 'magicast' +import { transpile } from 'typescript' const require = createRequire(import.meta.url) const __filename = fileURLToPath(import.meta.url) @@ -42,7 +42,8 @@ function parseArgs(argv) { let cursor = startIndex + 1 while (cursor < argv.length && !argv[cursor].startsWith('--')) { const value = argv[cursor].trim() - if (value) values.push(value) + if (value) + values.push(value) cursor++ } return { values, nextIndex: cursor - 1 } @@ -127,7 +128,7 @@ function protectPlaceholders(text) { const patterns = [ /\{\{[^{}]+\}\}/g, // mustache /\$\{[^{}]+\}/g, // template expressions - /<[^>]+?>/g, // html-like tags + /<[^>]+>/g, // html-like tags ] patterns.forEach((pattern) => { @@ -160,7 +161,7 @@ async function translateText(source, toLanguage) { const { translation } = await translate(safeText, null, languageKeyMap[toLanguage]) return { value: restore(translation), skipped: false } } - catch (error) { + catch (error) { console.error(`❌ Error translating to ${toLanguage}:`, error.message) return { value: source, skipped: true, error: error.message } } @@ -310,7 +311,7 @@ export default translation } const { code } = generateCode(mod) - let res = `const translation =${code.replace('export default', '')} + const res = `const translation =${code.replace('export default', '')} export default translation `.replace(/,\n\n/g, ',\n').replace('};', '}') @@ -319,13 +320,13 @@ export default translation fs.writeFileSync(toGenLanguageFilePath, res) console.log(`💾 Saved translations to ${toGenLanguageFilePath}`) } - else { + else { console.log(`🔍 [DRY RUN] Would save translations to ${toGenLanguageFilePath}`) } return result } - catch (error) { + catch (error) { console.error(`Error processing file ${fullKeyFilePath}:`, error.message) throw error } diff --git a/web/i18n-config/check-i18n-sync.js b/web/i18n-config/check-i18n-sync.js index acf5ca7a20..af00d23875 100644 --- a/web/i18n-config/check-i18n-sync.js +++ b/web/i18n-config/check-i18n-sync.js @@ -1,12 +1,13 @@ #!/usr/bin/env node -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' import lodash from 'lodash' -const { camelCase } = lodash import ts from 'typescript' +const { camelCase } = lodash + const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) diff --git a/web/i18n-config/check-i18n.js b/web/i18n-config/check-i18n.js index 638a543610..096a4d7afc 100644 --- a/web/i18n-config/check-i18n.js +++ b/web/i18n-config/check-i18n.js @@ -1,8 +1,8 @@ import fs from 'node:fs' -import path from 'node:path' -import vm from 'node:vm' -import { fileURLToPath } from 'node:url' import { createRequire } from 'node:module' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import vm from 'node:vm' import { transpile } from 'typescript' const require = createRequire(import.meta.url) @@ -11,6 +11,7 @@ const __dirname = path.dirname(__filename) const targetLanguage = 'en-US' const data = require('./languages.json') + const languages = data.languages.filter(language => language.supported).map(language => language.value) function parseArgs(argv) { @@ -27,7 +28,8 @@ function parseArgs(argv) { let cursor = startIndex + 1 while (cursor < argv.length && !argv[cursor].startsWith('--')) { const value = argv[cursor].trim() - if (value) values.push(value) + if (value) + values.push(value) cursor++ } return { values, nextIndex: cursor - 1 } @@ -124,8 +126,7 @@ async function getKeysFromLanguage(language) { const filePath = path.join(folderPath, file) const fileName = file.replace(/\.[^/.]+$/, '') // Remove file extension const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) => - c.toUpperCase(), - ) // Convert to camel case + c.toUpperCase()) // Convert to camel case try { const content = fs.readFileSync(filePath, 'utf8') @@ -147,7 +148,7 @@ async function getKeysFromLanguage(language) { // Extract the translation object const translationObj = moduleExports.default || moduleExports - if(!translationObj || typeof translationObj !== 'object') { + if (!translationObj || typeof translationObj !== 'object') { console.error(`Error parsing file: ${filePath}`) reject(new Error(`Error parsing file: ${filePath}`)) return @@ -161,7 +162,7 @@ async function getKeysFromLanguage(language) { // This is an object (but not array), recurse into it but don't add it as a key iterateKeys(obj[key], nestedKey) } - else { + else { // This is a leaf node (string, number, boolean, array, etc.), add it as a key nestedKeys.push(nestedKey) } @@ -173,7 +174,7 @@ async function getKeysFromLanguage(language) { const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`) allKeys.push(...fileKeys) } - catch (error) { + catch (error) { console.error(`Error processing file ${filePath}:`, error.message) reject(error) } @@ -193,7 +194,7 @@ function removeKeysFromObject(obj, keysToRemove, prefix = '') { modified = true console.log(`🗑️ Removed key: ${fullKey}`) } - else if (typeof obj[key] === 'object' && obj[key] !== null) { + else if (typeof obj[key] === 'object' && obj[key] !== null) { const subModified = removeKeysFromObject(obj[key], keysToRemove, fullKey) modified = modified || subModified } @@ -246,7 +247,7 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { } } } - else { + else { // Nested key - need to find the exact path const currentPath = [] let braceDepth = 0 @@ -256,12 +257,12 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { const trimmedLine = line.trim() // Track current object path - const keyMatch = trimmedLine.match(/^(\w+)\s*:\s*{/) + const keyMatch = trimmedLine.match(/^(\w+)\s*:\s*\{/) if (keyMatch) { currentPath.push(keyMatch[1]) braceDepth++ } - else if (trimmedLine === '},' || trimmedLine === '}') { + else if (trimmedLine === '},' || trimmedLine === '}') { if (braceDepth > 0) { braceDepth-- currentPath.pop() @@ -316,11 +317,12 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { // Check if this line ends the value (ends with quote and comma/no comma) if ((trimmed.endsWith('\',') || trimmed.endsWith('",') || trimmed.endsWith('`,') - || trimmed.endsWith('\'') || trimmed.endsWith('"') || trimmed.endsWith('`')) - && !trimmed.startsWith('//')) + || trimmed.endsWith('\'') || trimmed.endsWith('"') || trimmed.endsWith('`')) + && !trimmed.startsWith('//')) { break + } } - else { + else { break } @@ -332,7 +334,7 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { console.log(`🗑️ Found key to remove: ${keyToRemove} at line ${targetLineIndex + 1}${linesToRemoveForKey.length > 1 ? ` (multiline, ${linesToRemoveForKey.length} lines)` : ''}`) modified = true } - else { + else { console.log(`⚠️ Could not find key: ${keyToRemove}`) } } @@ -365,7 +367,7 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { return false } - catch (error) { + catch (error) { console.error(`Error processing file ${filePath}:`, error.message) return false } @@ -439,7 +441,8 @@ async function main() { let totalRemoved = 0 for (const fileName of files) { const removed = await removeExtraKeysFromFile(language, fileName, extraKeys) - if (removed) totalRemoved++ + if (removed) + totalRemoved++ } console.log(`✅ Auto-removal completed for ${language}. Modified ${totalRemoved} files.`) diff --git a/web/i18n-config/generate-i18n-types.js b/web/i18n-config/generate-i18n-types.js index a4c2234b83..0b3c0195af 100644 --- a/web/i18n-config/generate-i18n-types.js +++ b/web/i18n-config/generate-i18n-types.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' import lodash from 'lodash' import ts from 'typescript' @@ -50,8 +50,8 @@ import 'react-i18next' // Extract types from translation files using typeof import pattern` // Generate individual type definitions - const typeDefinitions = namespaces.map(namespace => { - const typeName = camelCase(namespace).replace(/^\w/, c => c.toUpperCase()) + 'Messages' + const typeDefinitions = namespaces.map((namespace) => { + const typeName = `${camelCase(namespace).replace(/^\w/, c => c.toUpperCase())}Messages` return `type ${typeName} = typeof import('../i18n/en-US/${namespace}').default` }).join('\n') @@ -59,11 +59,11 @@ import 'react-i18next' const messagesInterface = ` // Complete type structure that matches i18next-config.ts camelCase conversion export type Messages = { -${namespaces.map(namespace => { - const camelCased = camelCase(namespace) - const typeName = camelCase(namespace).replace(/^\w/, c => c.toUpperCase()) + 'Messages' - return ` ${camelCased}: ${typeName};` - }).join('\n')} +${namespaces.map((namespace) => { + const camelCased = camelCase(namespace) + const typeName = `${camelCase(namespace).replace(/^\w/, c => c.toUpperCase())}Messages` + return ` ${camelCased}: ${typeName};` +}).join('\n')} }` const utilityTypes = ` @@ -133,13 +133,14 @@ function main() { } console.log('✅ Type definitions are in sync') - } else { + } + else { // Generate mode: write file fs.writeFileSync(outputPath, typeDefinitions) console.log(`✅ Generated type definitions: ${outputPath}`) } - - } catch (error) { + } + catch (error) { console.error('❌ Error:', error.message) process.exit(1) } diff --git a/web/i18n-config/i18next-config.ts b/web/i18n-config/i18next-config.ts index 37651ae191..b310d380e2 100644 --- a/web/i18n-config/i18next-config.ts +++ b/web/i18n-config/i18next-config.ts @@ -3,31 +3,31 @@ import i18n from 'i18next' import { camelCase } from 'lodash-es' import { initReactI18next } from 'react-i18next' +import app from '../i18n/en-US/app' // Static imports for en-US (fallback language) import appAnnotation from '../i18n/en-US/app-annotation' import appApi from '../i18n/en-US/app-api' import appDebug from '../i18n/en-US/app-debug' import appLog from '../i18n/en-US/app-log' import appOverview from '../i18n/en-US/app-overview' -import app from '../i18n/en-US/app' import billing from '../i18n/en-US/billing' import common from '../i18n/en-US/common' import custom from '../i18n/en-US/custom' +import dataset from '../i18n/en-US/dataset' import datasetCreation from '../i18n/en-US/dataset-creation' import datasetDocuments from '../i18n/en-US/dataset-documents' import datasetHitTesting from '../i18n/en-US/dataset-hit-testing' import datasetPipeline from '../i18n/en-US/dataset-pipeline' import datasetSettings from '../i18n/en-US/dataset-settings' -import dataset from '../i18n/en-US/dataset' import education from '../i18n/en-US/education' import explore from '../i18n/en-US/explore' import layout from '../i18n/en-US/layout' import login from '../i18n/en-US/login' import oauth from '../i18n/en-US/oauth' import pipeline from '../i18n/en-US/pipeline' +import plugin from '../i18n/en-US/plugin' import pluginTags from '../i18n/en-US/plugin-tags' import pluginTrigger from '../i18n/en-US/plugin-trigger' -import plugin from '../i18n/en-US/plugin' import register from '../i18n/en-US/register' import runLog from '../i18n/en-US/run-log' import share from '../i18n/en-US/share' @@ -141,7 +141,8 @@ if (!i18n.isInitialized) { } export const changeLanguage = async (lng?: string) => { - if (!lng) return + if (!lng) + return if (!i18n.hasResourceBundle(lng, 'translation')) { const resource = await loadLangResources(lng) i18n.addResourceBundle(lng, 'translation', resource, true, true) diff --git a/web/i18n-config/index.ts b/web/i18n-config/index.ts index b2b83fa76a..8a0f712f2a 100644 --- a/web/i18n-config/index.ts +++ b/web/i18n-config/index.ts @@ -1,7 +1,7 @@ import Cookies from 'js-cookie' -import { changeLanguage } from '@/i18n-config/i18next-config' import { LOCALE_COOKIE_NAME } from '@/config' +import { changeLanguage } from '@/i18n-config/i18next-config' import { LanguagesSupported } from '@/i18n-config/language' export const i18n = { @@ -23,8 +23,11 @@ export const getLocaleOnClient = (): Locale => { } export const renderI18nObject = (obj: Record<string, string>, language: string) => { - if (!obj) return '' - if (obj?.[language]) return obj[language] - if (obj?.en_US) return obj.en_US + if (!obj) + return '' + if (obj?.[language]) + return obj[language] + if (obj?.en_US) + return obj.en_US return Object.values(obj)[0] } diff --git a/web/i18n-config/language.ts b/web/i18n-config/language.ts index 20b3eb3ecc..a1fe6e790f 100644 --- a/web/i18n-config/language.ts +++ b/web/i18n-config/language.ts @@ -1,4 +1,5 @@ import data from './languages.json' + export type Item = { value: number | string name: string diff --git a/web/i18n-config/server.ts b/web/i18n-config/server.ts index 404a71cfaf..c4e008cf84 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n-config/server.ts @@ -1,12 +1,12 @@ -import { cookies, headers } from 'next/headers' -import Negotiator from 'negotiator' +import type { Locale } from '.' import { match } from '@formatjs/intl-localematcher' - import { createInstance } from 'i18next' + import resourcesToBackend from 'i18next-resources-to-backend' +import Negotiator from 'negotiator' +import { cookies, headers } from 'next/headers' import { initReactI18next } from 'react-i18next/initReactI18next' import { i18n } from '.' -import type { Locale } from '.' // https://locize.com/blog/next-13-app-dir-i18n/ const initI18next = async (lng: Locale, ns: string) => { diff --git a/web/models/access-control.ts b/web/models/access-control.ts index 911662b5c4..d0e58d645b 100644 --- a/web/models/access-control.ts +++ b/web/models/access-control.ts @@ -24,7 +24,7 @@ export type AccessControlAccount = { avatarUrl: 'string' } -export type SubjectGroup = { subjectId: string; subjectType: SubjectType; groupData: AccessControlGroup } -export type SubjectAccount = { subjectId: string; subjectType: SubjectType; accountData: AccessControlAccount } +export type SubjectGroup = { subjectId: string, subjectType: SubjectType, groupData: AccessControlGroup } +export type SubjectAccount = { subjectId: string, subjectType: SubjectType, accountData: AccessControlAccount } export type Subject = SubjectGroup | SubjectAccount diff --git a/web/models/app.ts b/web/models/app.ts index fa148511f0..473c68c62b 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -11,8 +11,8 @@ import type { TracingProvider, WeaveConfig, } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' -import type { App, AppModeEnum, AppTemplate, SiteConfig } from '@/types/app' import type { Dependency } from '@/app/components/plugins/types' +import type { App, AppModeEnum, AppTemplate, SiteConfig } from '@/types/app' export enum DSLImportMode { YAML_CONTENT = 'yaml-content', @@ -56,15 +56,15 @@ export type CreateAppResponse = App export type UpdateAppSiteCodeResponse = { app_id: string } & SiteConfig export type AppDailyMessagesResponse = { - data: Array<{ date: string; message_count: number }> + data: Array<{ date: string, message_count: number }> } export type AppDailyConversationsResponse = { - data: Array<{ date: string; conversation_count: number }> + data: Array<{ date: string, conversation_count: number }> } export type WorkflowDailyConversationsResponse = { - data: Array<{ date: string; runs: number }> + data: Array<{ date: string, runs: number }> } export type AppStatisticsResponse = { @@ -72,11 +72,11 @@ export type AppStatisticsResponse = { } export type AppDailyEndUsersResponse = { - data: Array<{ date: string; terminal_count: number }> + data: Array<{ date: string, terminal_count: number }> } export type AppTokenCostsResponse = { - data: Array<{ date: string; token_count: number; total_price: number; currency: number }> + data: Array<{ date: string, token_count: number, total_price: number, currency: number }> } export type UpdateAppModelConfigResponse = { result: string } diff --git a/web/models/common.ts b/web/models/common.ts index 5d1499dcd5..0e034ffa33 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -268,7 +268,7 @@ export type CodeBasedExtensionForm = { label: I18nText variable: string required: boolean - options: { label: I18nText; value: string }[] + options: { label: I18nText, value: string }[] default: string placeholder: string max_length?: number diff --git a/web/models/datasets.ts b/web/models/datasets.ts index fe4c568e46..ba61c95b64 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -1,12 +1,12 @@ import type { DataSourceNotionPage, DataSourceProvider } from './common' -import type { AppIconType, AppModeEnum, RetrievalConfig, TransferMethod } from '@/types/app' +import type { DatasourceType } from './pipeline' import type { Tag } from '@/app/components/base/tag-management/constant' import type { IndexingType } from '@/app/components/datasets/create/step-two' -import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import type { MetadataItemWithValue } from '@/app/components/datasets/metadata/types' +import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' +import type { AppIconType, AppModeEnum, RetrievalConfig, TransferMethod } from '@/types/app' import { ExternalKnowledgeBase, General, ParentChild, Qa } from '@/app/components/base/icons/src/public/knowledge/dataset-card' import { GeneralChunk, ParentChildChunk, QuestionAndAnswer } from '@/app/components/base/icons/src/vender/knowledge' -import type { DatasourceType } from './pipeline' export enum DataSourceType { FILE = 'upload_file', @@ -98,7 +98,7 @@ export type ExternalAPIItem = { endpoint: string api_key: string } - dataset_bindings: { id: string; name: string }[] + dataset_bindings: { id: string, name: string }[] created_by: string created_at: string } @@ -224,7 +224,7 @@ export type IndexingEstimateResponse = { total_price: number currency: string total_segments: number - preview: Array<{ content: string; child_chunks: string[] }> + preview: Array<{ content: string, child_chunks: string[] }> qa_preview?: QA[] } @@ -360,7 +360,7 @@ export type OnlineDocumentInfo = { page_name: string parent_id: string type: string - }, + } } export type OnlineDriveInfo = { @@ -590,7 +590,7 @@ export type SegmentsResponse = { export type Query = { content: string - content_type: 'text_query' | 'image_query', + content_type: 'text_query' | 'image_query' file_info: Attachment | null } diff --git a/web/models/debug.ts b/web/models/debug.ts index 90f79cbf8d..5290268fe9 100644 --- a/web/models/debug.ts +++ b/web/models/debug.ts @@ -1,8 +1,3 @@ -import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' -import type { - RerankingModeEnum, - WeightedScoreEnum, -} from '@/models/datasets' import type { FileUpload } from '@/app/components/base/features/types' import type { MetadataFilteringConditions, @@ -10,6 +5,12 @@ import type { } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import type { ModelConfig as NodeModelConfig } from '@/app/components/workflow/types' import type { ExternalDataTool } from '@/models/common' +import type { + RerankingModeEnum, + WeightedScoreEnum, +} from '@/models/datasets' +import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' + export type Inputs = Record<string, string | number | object | boolean> export enum PromptMode { diff --git a/web/models/explore.ts b/web/models/explore.ts index fbbd01837a..1d513e9b70 100644 --- a/web/models/explore.ts +++ b/web/models/explore.ts @@ -1,4 +1,5 @@ import type { AppIconType, AppModeEnum } from '@/types/app' + export type AppBasicInfo = { id: string mode: AppModeEnum diff --git a/web/models/log.ts b/web/models/log.ts index b9c91a7a3c..8c022ee6b2 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -1,10 +1,10 @@ import type { Viewport } from 'reactflow' -import type { VisionFile } from '@/types/app' +import type { Metadata } from '@/app/components/base/chat/chat/type' import type { Edge, Node, } from '@/app/components/workflow/types' -import type { Metadata } from '@/app/components/base/chat/chat/type' +import type { VisionFile } from '@/types/app' // Log type contains key:string conversation_id:string created_at:string question:string answer:string export type Conversation = { @@ -77,7 +77,7 @@ export type MessageContent = { conversation_id: string query: string inputs: Record<string, any> - message: { role: string; text: string; files?: VisionFile[] }[] + message: { role: string, text: string, files?: VisionFile[] }[] message_tokens: number answer_tokens: number answer: string diff --git a/web/models/pipeline.ts b/web/models/pipeline.ts index 1c2211b6d9..143bc61180 100644 --- a/web/models/pipeline.ts +++ b/web/models/pipeline.ts @@ -1,12 +1,12 @@ -import type { Edge, EnvironmentVariable, Node, SupportUploadFileTypes } from '@/app/components/workflow/types' +import type { Viewport } from 'reactflow' import type { DSLImportMode, DSLImportStatus } from './app' import type { ChunkingMode, DatasetPermission, DocumentIndexingStatus, FileIndexingEstimateResponse, IconInfo } from './datasets' -import type { Dependency } from '@/app/components/plugins/types' import type { AppIconSelection } from '@/app/components/base/app-icon-picker' -import type { Viewport } from 'reactflow' +import type { Dependency } from '@/app/components/plugins/types' +import type { Edge, EnvironmentVariable, Node, SupportUploadFileTypes } from '@/app/components/workflow/types' import type { TransferMethod } from '@/types/app' -import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' import type { NodeRunResult } from '@/types/workflow' +import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' export enum DatasourceType { localFile = 'local_file', @@ -190,7 +190,7 @@ export type PublishedPipelineInfoResponse = { id: string name: string email: string - }, + } environment_variables?: EnvironmentVariable[] rag_pipeline_variables?: RAGPipelineVariables version: string diff --git a/web/next.config.js b/web/next.config.js index b6d6fb5d6c..3414d09021 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -1,6 +1,6 @@ -import { codeInspectorPlugin } from 'code-inspector-plugin' import withBundleAnalyzerInit from '@next/bundle-analyzer' import createMDX from '@next/mdx' +import { codeInspectorPlugin } from 'code-inspector-plugin' import withPWAInit from 'next-pwa' const isDev = process.env.NODE_ENV === 'development' @@ -21,9 +21,9 @@ const withPWA = withPWAInit({ cacheName: 'google-fonts', expiration: { maxEntries: 4, - maxAgeSeconds: 365 * 24 * 60 * 60 // 1 year - } - } + maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year + }, + }, }, { urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, @@ -32,9 +32,9 @@ const withPWA = withPWAInit({ cacheName: 'google-fonts-webfonts', expiration: { maxEntries: 4, - maxAgeSeconds: 365 * 24 * 60 * 60 // 1 year - } - } + maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year + }, + }, }, { urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|avif)$/i, @@ -43,9 +43,9 @@ const withPWA = withPWAInit({ cacheName: 'images', expiration: { maxEntries: 64, - maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days - } - } + maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days + }, + }, }, { urlPattern: /\.(?:js|css)$/i, @@ -54,9 +54,9 @@ const withPWA = withPWAInit({ cacheName: 'static-resources', expiration: { maxEntries: 32, - maxAgeSeconds: 24 * 60 * 60 // 1 day - } - } + maxAgeSeconds: 24 * 60 * 60, // 1 day + }, + }, }, { urlPattern: /^\/api\/.*/i, @@ -66,11 +66,11 @@ const withPWA = withPWAInit({ networkTimeoutSeconds: 10, expiration: { maxEntries: 16, - maxAgeSeconds: 60 * 60 // 1 hour - } - } - } - ] + maxAgeSeconds: 60 * 60, // 1 hour + }, + }, + }, + ], }) const withMDX = createMDX({ extension: /\.mdx?$/, @@ -100,8 +100,8 @@ const nextConfig = { transpilePackages: ['echarts', 'zrender'], turbopack: { rules: codeInspectorPlugin({ - bundler: 'turbopack' - }) + bundler: 'turbopack', + }), }, productionBrowserSourceMaps: false, // enable browser source map generation during the production build // Configure pageExtensions to include md and mdx @@ -118,7 +118,7 @@ const nextConfig = { }, experimental: { optimizePackageImports: [ - '@heroicons/react' + '@heroicons/react', ], }, // fix all before production. Now it slow the develop speed. @@ -145,7 +145,7 @@ const nextConfig = { output: 'standalone', compiler: { removeConsole: isDev ? false : { exclude: ['warn', 'error'] }, - } + }, } export default withPWA(withBundleAnalyzer(withMDX(nextConfig))) diff --git a/web/package.json b/web/package.json index e75841379d..ce2e59e022 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { "name": "dify-web", - "version": "1.11.1", "type": "module", + "version": "1.11.1", "private": true, "packageManager": "pnpm@10.26.1+sha512.664074abc367d2c9324fdc18037097ce0a8f126034160f709928e9e9f95d98714347044e5c3164d65bd5da6c59c6be362b107546292a8eecb7999196e5ce58fa", "engines": { @@ -24,11 +24,10 @@ "build": "next build", "build:docker": "next build && node scripts/optimize-standalone.js", "start": "node ./scripts/copy-and-start.mjs", - "lint:oxlint": "oxlint --config .oxlintrc.json .", - "lint": "pnpm run lint:oxlint && eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache", - "lint:fix": "pnpm run lint:oxlint && eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix", - "lint:quiet": "pnpm run lint:oxlint && eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", - "lint:complexity": "pnpm run lint:oxlint && eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --rule 'complexity: [error, {max: 15}]' --quiet", + "lint": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache", + "lint:fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix", + "lint:quiet": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", + "lint:complexity": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --rule 'complexity: [error, {max: 15}]' --quiet", "type-check": "tsc --noEmit", "type-check:tsgo": "tsgo --noEmit", "prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky", @@ -149,10 +148,10 @@ "zustand": "^5.0.9" }, "devDependencies": { - "@antfu/eslint-config": "^5.4.1", + "@antfu/eslint-config": "^6.7.3", "@babel/core": "^7.28.4", "@chromatic-com/storybook": "^4.1.1", - "@eslint-react/eslint-plugin": "^1.53.1", + "@eslint-react/eslint-plugin": "^2.3.13", "@mdx-js/loader": "^3.1.1", "@mdx-js/react": "^3.1.1", "@next/bundle-analyzer": "15.5.9", @@ -183,7 +182,7 @@ "@types/semver": "^7.7.1", "@types/sortablejs": "^1.15.8", "@types/uuid": "^10.0.0", - "@typescript-eslint/parser": "^8.48.0", + "@typescript-eslint/parser": "^8.50.0", "@typescript/native-preview": "^7.0.0-dev", "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "4.0.16", @@ -192,14 +191,12 @@ "bing-translate-api": "^4.1.0", "code-inspector-plugin": "1.2.9", "cross-env": "^10.1.0", - "eslint": "^9.38.0", - "eslint-plugin-oxlint": "^1.23.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", + "eslint": "^9.39.2", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-sonarjs": "^3.0.5", - "eslint-plugin-storybook": "^9.1.13", + "eslint-plugin-storybook": "^10.1.10", "eslint-plugin-tailwindcss": "^3.18.2", - "globals": "^15.15.0", "husky": "^9.1.7", "istanbul-lib-coverage": "^3.2.2", "jsdom": "^27.3.0", @@ -209,7 +206,6 @@ "lodash": "^4.17.21", "magicast": "^0.3.5", "nock": "^14.0.10", - "oxlint": "^1.31.0", "postcss": "^8.5.6", "react-scan": "^0.4.3", "sass": "^1.93.2", @@ -223,36 +219,11 @@ "vitest": "^4.0.16", "vitest-localstorage-mock": "^0.1.2" }, - "resolutions": { - "@types/react": "~19.2.7", - "@types/react-dom": "~19.2.3", - "string-width": "~4.2.3", - "@eslint/plugin-kit": "~0.3", - "canvas": "^3.2.0", - "esbuild": "~0.25.0", - "pbkdf2": "~3.1.3", - "prismjs": "~1.30", - "brace-expansion": "~2.0" - }, - "lint-staged": { - "**/*.js?(x)": [ - "eslint --fix" - ], - "**/*.ts?(x)": [ - "oxlint --config .oxlintrc.json", - "eslint --fix" - ] - }, "pnpm": { "overrides": { - "@monaco-editor/loader": "1.5.0", "@eslint/plugin-kit@<0.3.4": "0.3.4", - "brace-expansion@<2.0.2": "2.0.2", - "devalue@<5.3.2": "5.3.2", - "esbuild@<0.25.0": "0.25.0", - "pbkdf2@<3.1.3": "3.1.3", - "prismjs@<1.30.0": "1.30.0", - "vite@<6.4.1": "6.4.1", + "@monaco-editor/loader": "1.5.0", + "@nolyfill/safe-buffer": "npm:safe-buffer@^5.2.1", "array-includes": "npm:@nolyfill/array-includes@^1", "array.prototype.findlast": "npm:@nolyfill/array.prototype.findlast@^1", "array.prototype.findlastindex": "npm:@nolyfill/array.prototype.findlastindex@^1", @@ -260,7 +231,10 @@ "array.prototype.flatmap": "npm:@nolyfill/array.prototype.flatmap@^1", "array.prototype.tosorted": "npm:@nolyfill/array.prototype.tosorted@^1", "assert": "npm:@nolyfill/assert@^1", + "brace-expansion@<2.0.2": "2.0.2", + "devalue@<5.3.2": "5.3.2", "es-iterator-helpers": "npm:@nolyfill/es-iterator-helpers@^1", + "esbuild@<0.25.0": "0.25.0", "hasown": "npm:@nolyfill/hasown@^1", "is-arguments": "npm:@nolyfill/is-arguments@^1", "is-core-module": "npm:@nolyfill/is-core-module@^1", @@ -272,8 +246,9 @@ "object.fromentries": "npm:@nolyfill/object.fromentries@^1", "object.groupby": "npm:@nolyfill/object.groupby@^1", "object.values": "npm:@nolyfill/object.values@^1", + "pbkdf2@<3.1.3": "3.1.3", + "prismjs@<1.30.0": "1.30.0", "safe-buffer": "^5.2.1", - "@nolyfill/safe-buffer": "npm:safe-buffer@^5.2.1", "safe-regex-test": "npm:@nolyfill/safe-regex-test@^1", "safer-buffer": "npm:@nolyfill/safer-buffer@^1", "side-channel": "npm:@nolyfill/side-channel@^1", @@ -282,6 +257,7 @@ "string.prototype.repeat": "npm:@nolyfill/string.prototype.repeat@^1", "string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1", "typed-array-buffer": "npm:@nolyfill/typed-array-buffer@^1", + "vite@<6.4.1": "6.4.1", "which-typed-array": "npm:@nolyfill/which-typed-array@^1" }, "ignoredBuiltDependencies": [ @@ -293,5 +269,19 @@ "esbuild", "sharp" ] + }, + "resolutions": { + "@eslint/plugin-kit": "~0.3", + "@types/react": "~19.2.7", + "@types/react-dom": "~19.2.3", + "brace-expansion": "~2.0", + "canvas": "^3.2.0", + "esbuild": "~0.25.0", + "pbkdf2": "~3.1.3", + "prismjs": "~1.30", + "string-width": "~4.2.3" + }, + "lint-staged": { + "*": "eslint --fix" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 93db0d0791..95d35c24d8 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -356,8 +356,8 @@ importers: version: 5.0.9(@types/react@19.2.7)(immer@11.1.0)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: '@antfu/eslint-config': - specifier: ^5.4.1 - version: 5.4.1(@eslint-react/eslint-plugin@1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3))(@next/eslint-plugin-next@15.5.9)(@vue/compiler-sfc@3.5.25)(eslint-plugin-react-hooks@5.2.0(eslint@9.39.1(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + specifier: ^6.7.3 + version: 6.7.3(@eslint-react/eslint-plugin@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@15.5.9)(@vue/compiler-sfc@3.5.25)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@1.21.7)))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) '@babel/core': specifier: ^7.28.4 version: 7.28.5 @@ -365,8 +365,8 @@ importers: specifier: ^4.1.1 version: 4.1.3(storybook@9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))) '@eslint-react/eslint-plugin': - specifier: ^1.53.1 - version: 1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3) + specifier: ^2.3.13 + version: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@mdx-js/loader': specifier: ^3.1.1 version: 3.1.1(webpack@5.103.0(esbuild@0.25.0)(uglify-js@3.19.3)) @@ -458,8 +458,8 @@ importers: specifier: ^10.0.0 version: 10.0.0 '@typescript-eslint/parser': - specifier: ^8.48.0 - version: 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + specifier: ^8.50.0 + version: 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript/native-preview': specifier: ^7.0.0-dev version: 7.0.0-dev.20251209.1 @@ -485,29 +485,23 @@ importers: specifier: ^10.1.0 version: 10.1.0 eslint: - specifier: ^9.38.0 - version: 9.39.1(jiti@1.21.7) - eslint-plugin-oxlint: - specifier: ^1.23.0 - version: 1.32.0 + specifier: ^9.39.2 + version: 9.39.2(jiti@1.21.7) eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.39.1(jiti@1.21.7)) + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@1.21.7)) eslint-plugin-react-refresh: - specifier: ^0.4.24 - version: 0.4.24(eslint@9.39.1(jiti@1.21.7)) + specifier: ^0.4.26 + version: 0.4.26(eslint@9.39.2(jiti@1.21.7)) eslint-plugin-sonarjs: specifier: ^3.0.5 - version: 3.0.5(eslint@9.39.1(jiti@1.21.7)) + version: 3.0.5(eslint@9.39.2(jiti@1.21.7)) eslint-plugin-storybook: - specifier: ^9.1.13 - version: 9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.9.3) + specifier: ^10.1.10 + version: 10.1.10(eslint@9.39.2(jiti@1.21.7))(storybook@9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.9.3) eslint-plugin-tailwindcss: specifier: ^3.18.2 version: 3.18.2(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.2)) - globals: - specifier: ^15.15.0 - version: 15.15.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -535,9 +529,6 @@ importers: nock: specifier: ^14.0.10 version: 14.0.10 - oxlint: - specifier: ^1.31.0 - version: 1.32.0 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -661,12 +652,12 @@ packages: '@amplitude/targeting@0.2.0': resolution: {integrity: sha512-/50ywTrC4hfcfJVBbh5DFbqMPPfaIOivZeb5Gb+OGM03QrA+lsUqdvtnKLNuWtceD4H6QQ2KFzPJ5aAJLyzVDA==} - '@antfu/eslint-config@5.4.1': - resolution: {integrity: sha512-x7BiNkxJRlXXs8tIvg0CgMuNo5IZVWkGLMJotCtCtzWUHW78Pmm8PvtXhvLBbTc8683GGBK616MMztWLh4RNjA==} + '@antfu/eslint-config@6.7.3': + resolution: {integrity: sha512-0tYYzY59uLnxWgbP9xpuxpvodTcWDacj439kTAJZB3sn7O0BnPfVxTnRvleGYaKCEALBZkzdC/wCho9FD7ICLw==} hasBin: true peerDependencies: - '@eslint-react/eslint-plugin': ^1.38.4 - '@next/eslint-plugin-next': ^15.4.0-canary.115 + '@eslint-react/eslint-plugin': ^2.0.1 + '@next/eslint-plugin-next': '>=15.0.0' '@prettier/plugin-xml': ^3.4.1 '@unocss/eslint-plugin': '>=0.50.0' astro-eslint-parser: ^1.0.2 @@ -674,7 +665,7 @@ packages: eslint-plugin-astro: ^1.2.0 eslint-plugin-format: '>=0.1.0' eslint-plugin-jsx-a11y: '>=6.10.2' - eslint-plugin-react-hooks: ^5.2.0 + eslint-plugin-react-hooks: ^7.0.0 eslint-plugin-react-refresh: ^0.4.19 eslint-plugin-solid: ^0.14.3 eslint-plugin-svelte: '>=2.35.1' @@ -1424,14 +1415,18 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@es-joy/jsdoccomment@0.50.2': - resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} - engines: {node: '>=18'} - - '@es-joy/jsdoccomment@0.58.0': - resolution: {integrity: sha512-smMc5pDht/UVsCD3hhw/a/e/p8m0RdRYiluXToVfd+d4yaQQh7nn9bACjkk6nXJvat7EWPAxuFkMEFfrxeGa3Q==} + '@es-joy/jsdoccomment@0.76.0': + resolution: {integrity: sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==} engines: {node: '>=20.11.0'} + '@es-joy/jsdoccomment@0.78.0': + resolution: {integrity: sha512-rQkU5u8hNAq2NVRzHnIUUvR6arbO0b6AOlvpTNS48CkiKSn/xtNfOzBK23JE4SiW89DgvU7GtxLVgV4Vn2HBAw==} + engines: {node: '>=20.11.0'} + + '@es-joy/resolve.exports@1.2.0': + resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} + engines: {node: '>=10'} + '@esbuild/aix-ppc64@0.25.0': resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} engines: {node: '>=18'} @@ -1758,39 +1753,44 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@1.53.1': - resolution: {integrity: sha512-qvUC99ewtriJp9quVEOvZ6+RHcsMLfVQ0OhZ4/LupZUDhjW7GiX1dxJsFaxHdJ9rLNLhQyLSPmbAToeqUrSruQ==} - engines: {node: '>=18.18.0'} - - '@eslint-react/core@1.53.1': - resolution: {integrity: sha512-8prroos5/Uvvh8Tjl1HHCpq4HWD3hV9tYkm7uXgKA6kqj0jHlgRcQzuO6ZPP7feBcK3uOeug7xrq03BuG8QKCA==} - engines: {node: '>=18.18.0'} - - '@eslint-react/eff@1.53.1': - resolution: {integrity: sha512-uq20lPRAmsWRjIZm+mAV/2kZsU2nDqn5IJslxGWe3Vfdw23hoyhEw3S1KKlxbftwbTvsZjKvVP0iw3bZo/NUpg==} - engines: {node: '>=18.18.0'} - - '@eslint-react/eslint-plugin@1.53.1': - resolution: {integrity: sha512-JZ2ciXNCC9CtBBAqYtwWH+Jy/7ZzLw+whei8atP4Fxsbh+Scs30MfEwBzuiEbNw6uF9eZFfPidchpr5RaEhqxg==} - engines: {node: '>=18.18.0'} + '@eslint-react/ast@2.3.13': + resolution: {integrity: sha512-OP2rOhHYLx2nfd9uA9uACKZJN9z9rX9uuAMx4PjT75JNOdYr1GgqWQZcYCepyJ+gmVNCyiXcLXuyhavqxCSM8Q==} + engines: {node: '>=20.19.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - '@eslint-react/kit@1.53.1': - resolution: {integrity: sha512-zOi2le9V4rMrJvQV4OeedGvMGvDT46OyFPOwXKs7m0tQu5vXVJ8qwIPaVQT1n/WIuvOg49OfmAVaHpGxK++xLQ==} - engines: {node: '>=18.18.0'} + '@eslint-react/core@2.3.13': + resolution: {integrity: sha512-4bWBE+1kApuxJKIrLJH2FuFtCbM4fXfDs6Ou8MNamGoX6hdynlntssvaMZTd/lk/L8dt01H/3btr7xBX4+4BNA==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' - '@eslint-react/shared@1.53.1': - resolution: {integrity: sha512-gomJQmFqQgQVI3Ra4vTMG/s6a4bx3JqeNiTBjxBJt4C9iGaBj458GkP4LJHX7TM6xUzX+fMSKOPX7eV3C/+UCw==} - engines: {node: '>=18.18.0'} + '@eslint-react/eff@2.3.13': + resolution: {integrity: sha512-byXsssozwh3VaiqcOonAKQgLXgpMVNSxBWFjdfbNhW7+NttorSt950qtiw+P7A9JoRab1OuGYk4MDY5UVBno8Q==} + engines: {node: '>=20.19.0'} - '@eslint-react/var@1.53.1': - resolution: {integrity: sha512-yzwopvPntcHU7mmDvWzRo1fb8QhjD8eDRRohD11rTV1u7nWO4QbJi0pOyugQakvte1/W11Y0Vr8Of0Ojk/A6zg==} - engines: {node: '>=18.18.0'} + '@eslint-react/eslint-plugin@2.3.13': + resolution: {integrity: sha512-gq0Z0wADAXvJS8Y/Wk3isK7WIEcfrQGGGdWvorAv0T7MxPd3d32TVwdc1Gx3hVLka3fYq1BBlQ5Fr8e1VgNuIg==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@eslint-react/shared@2.3.13': + resolution: {integrity: sha512-ESE7dVeOXtem3K6BD6k2wJaFt35kPtTT9SWCL99LFk7pym4OEGoMxPcyB2R7PMWiVudwl63BmiOgQOdaFYPONg==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@eslint-react/var@2.3.13': + resolution: {integrity: sha512-BozBfUZkzzobD6x/M8XERAnZQ3UvZPsD49zTGFKKU9M/bgsM78HwzxAPLkiu88W55v3sO/Kqf8fQTXT4VEeZ/g==} + engines: {node: '>=20.19.0'} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' '@eslint/compat@1.4.1': resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} @@ -1821,8 +1821,8 @@ packages: resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/markdown@7.5.1': @@ -1833,10 +1833,6 @@ packages: resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.4': - resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.5': resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2577,46 +2573,6 @@ packages: cpu: [x64] os: [win32] - '@oxlint/darwin-arm64@1.32.0': - resolution: {integrity: sha512-yrqPmZYu5Qb+49h0P5EXVIq8VxYkDDM6ZQrWzlh16+UGFcD8HOXs4oF3g9RyfaoAbShLCXooSQsM/Ifwx8E/eQ==} - cpu: [arm64] - os: [darwin] - - '@oxlint/darwin-x64@1.32.0': - resolution: {integrity: sha512-pQRZrJG/2nAKc3IuocFbaFFbTDlQsjz2WfivRsMn0hw65EEsSuM84WMFMiAfLpTGyTICeUtHZLHlrM5lzVr36A==} - cpu: [x64] - os: [darwin] - - '@oxlint/linux-arm64-gnu@1.32.0': - resolution: {integrity: sha512-tyomSmU2DzwcTmbaWFmStHgVfRmJDDvqcIvcw4fRB1YlL2Qg/XaM4NJ0m2bdTap38gxD5FSxSgCo0DkQ8GTolg==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-arm64-musl@1.32.0': - resolution: {integrity: sha512-0W46dRMaf71OGE4+Rd+GHfS1uF/UODl5Mef6871pMhN7opPGfTI2fKJxh9VzRhXeSYXW/Z1EuCq9yCfmIJq+5Q==} - cpu: [arm64] - os: [linux] - - '@oxlint/linux-x64-gnu@1.32.0': - resolution: {integrity: sha512-5+6myVCBOMvM62rDB9T3CARXUvIwhGqte6E+HoKRwYaqsxGUZ4bh3pItSgSFwHjLGPrvADS11qJUkk39eQQBzQ==} - cpu: [x64] - os: [linux] - - '@oxlint/linux-x64-musl@1.32.0': - resolution: {integrity: sha512-qwQlwYYgVIC6ScjpUwiKKNyVdUlJckrfwPVpIjC9mvglIQeIjKuuyaDxUZWIOc/rEzeCV/tW6tcbehLkfEzqsw==} - cpu: [x64] - os: [linux] - - '@oxlint/win32-arm64@1.32.0': - resolution: {integrity: sha512-7qYZF9CiXGtdv8Z/fBkgB5idD2Zokht67I5DKWH0fZS/2R232sDqW2JpWVkXltk0+9yFvmvJ0ouJgQRl9M3S2g==} - cpu: [arm64] - os: [win32] - - '@oxlint/win32-x64@1.32.0': - resolution: {integrity: sha512-XW1xqCj34MEGJlHteqasTZ/LmBrwYIgluhNW0aP+XWkn90+stKAq3W/40dvJKbMK9F7o09LPCuMVtUW7FIUuiA==} - cpu: [x64] - os: [win32] - '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -3215,6 +3171,10 @@ packages: peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x + '@sindresorhus/base62@1.0.0': + resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} + engines: {node: '>=18'} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -3726,16 +3686,16 @@ packages: '@types/zen-observable@0.8.3': resolution: {integrity: sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + '@typescript-eslint/eslint-plugin@8.50.0': + resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 + '@typescript-eslint/parser': ^8.50.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + '@typescript-eslint/parser@8.50.0': + resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3747,16 +3707,32 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.50.0': + resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.49.0': resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.50.0': + resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.49.0': resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.50.0': + resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.49.0': resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3764,16 +3740,33 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.50.0': + resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@8.49.0': resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.50.0': + resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.49.0': resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.50.0': + resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.49.0': resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3781,10 +3774,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.50.0': + resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@8.49.0': resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.50.0': + resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251209.1': resolution: {integrity: sha512-F1cnYi+ZeinYQnaTQKKIsbuoq8vip5iepBkSZXlB8PjbG62LW1edUdktd/nVEc+Q+SEysSQ3jRdk9eU766s5iw==} cpu: [arm64] @@ -3842,8 +3846,8 @@ packages: '@vitest/browser': optional: true - '@vitest/eslint-plugin@1.5.2': - resolution: {integrity: sha512-2t1F2iecXB/b1Ox4U137lhD3chihEE3dRVtu3qMD35tc6UqUjg1VGRJoS1AkFKwpT8zv8OQInzPQO06hrRkeqw==} + '@vitest/eslint-plugin@1.6.1': + resolution: {integrity: sha512-q4ZCihsURDxhJm6bEUtJjciXtT5k3ijWR4U+0f9XdCRAzAfML5NUUSwulsFoK1AFohBieh52akKWJEIFFMLn/g==} engines: {node: '>=18'} peerDependencies: eslint: '>=8.57.0' @@ -5145,8 +5149,8 @@ packages: peerDependencies: eslint: '*' - eslint-plugin-command@3.3.1: - resolution: {integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==} + eslint-plugin-command@3.4.0: + resolution: {integrity: sha512-EW4eg/a7TKEhG0s5IEti72kh3YOTlnhfFNuctq5WnB1fst37/IHTd5OkD+vnlRf3opTvUcSRihAateP6bT5ZcA==} peerDependencies: eslint: '*' @@ -5156,8 +5160,8 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-import-lite@0.3.0: - resolution: {integrity: sha512-dkNBAL6jcoCsXZsQ/Tt2yXmMDoNt5NaBh/U7yvccjiK8cai6Ay+MK77bMykmqQA2bTF6lngaLCDij6MTO3KkvA==} + eslint-plugin-import-lite@0.4.0: + resolution: {integrity: sha512-My0ReAg8WbHXYECIHVJkWB8UxrinZn3m72yonOYH6MFj40ZN1vHYQj16iq2Fd8Wrt/vRZJwDX2xm/BzDk1FzTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -5166,8 +5170,8 @@ packages: typescript: optional: true - eslint-plugin-jsdoc@59.1.0: - resolution: {integrity: sha512-sg9mzjjzfnMynyY4W8FDiQv3i8eFcKVEHDt4Xh7MLskP3QkMt2z6p7FuzSw7jJSKFues6RaK2GWvmkB1FLPxXg==} + eslint-plugin-jsdoc@61.5.0: + resolution: {integrity: sha512-PR81eOGq4S7diVnV9xzFSBE4CDENRQGP0Lckkek8AdHtbj+6Bm0cItwlFnxsLFriJHspiE3mpu8U20eODyToIg==} engines: {node: '>=20.11.0'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 @@ -5188,93 +5192,62 @@ packages: resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} - eslint-plugin-oxlint@1.32.0: - resolution: {integrity: sha512-CodKgz/9q3euGbCYrXVRyFxHfnrxn9Q4EywqE4V/VYegry2pJ9/hPQ0OUDTRzbl3/pPbVndkrUUm5tK8NTSgeg==} - eslint-plugin-perfectionist@4.15.1: resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: eslint: '>=8.45.0' - eslint-plugin-pnpm@1.4.2: - resolution: {integrity: sha512-em/HEUlud5G3G4VZe2dhgsLm2ey6CG+Y+Lq3fS/RsbnmKhi+D+LcLz31GphTJhizCoKl2oAVndMltOHbuBYe+A==} + eslint-plugin-pnpm@1.4.3: + resolution: {integrity: sha512-wdWrkWN5mxRgEADkQvxwv0xA+0++/hYDD5OyXTL6UqPLUPdcCFQJO61NO7IKhEqb3GclWs02OoFs1METN+a3zQ==} peerDependencies: eslint: ^9.0.0 - eslint-plugin-react-debug@1.53.1: - resolution: {integrity: sha512-WNOiQ6jhodJE88VjBU/IVDM+2Zr9gKHlBFDUSA3fQ0dMB5RiBVj5wMtxbxRuipK/GqNJbteqHcZoYEod7nfddg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-dom@2.3.13: + resolution: {integrity: sha512-O9jglTOnnuyfJcSxjeVc8lqIp5kuS9/0MLLCHlOTH8ZjIifHHxUr6GZ2fd4la9y0FsoEYXEO7DBIMjWx2vCwjg==} + engines: {node: '>=20.19.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - eslint-plugin-react-dom@1.53.1: - resolution: {integrity: sha512-UYrWJ2cS4HpJ1A5XBuf1HfMpPoLdfGil+27g/ldXfGemb4IXqlxHt4ANLyC8l2CWcE3SXGJW7mTslL34MG0qTQ==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-hooks-extra@2.3.13: + resolution: {integrity: sha512-NSnY8yvtrvu2FAALLuvc2xesIAkMqGyJgilpy8wEi1w/Nw6v0IwBEffoNKLq9OHW4v3nikud3aBTqWfWKOx67Q==} + engines: {node: '>=20.0.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - eslint-plugin-react-hooks-extra@1.53.1: - resolution: {integrity: sha512-fshTnMWNn9NjFLIuy7HzkRgGK29vKv4ZBO9UMr+kltVAfKLMeXXP6021qVKk66i/XhQjbktiS+vQsu1Rd3ZKvg==} - engines: {node: '>=18.18.0'} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true - - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-naming-convention@1.53.1: - resolution: {integrity: sha512-rvZ/B/CSVF8d34HQ4qIt90LRuxotVx+KUf3i1OMXAyhsagEFMRe4gAlPJiRufZ+h9lnuu279bEdd+NINsXOteA==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-naming-convention@2.3.13: + resolution: {integrity: sha512-2iler1ldFpB/PaNpN8WAVk6dKYKwKcoGm1j0JAAjdCrsfOTJ007ol2xTAyoHKAbMOvkZSi7qq90q+Q//RuhWwA==} + engines: {node: '>=20.19.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - eslint-plugin-react-refresh@0.4.24: - resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} peerDependencies: eslint: '>=8.40' - eslint-plugin-react-web-api@1.53.1: - resolution: {integrity: sha512-INVZ3Cbl9/b+sizyb43ChzEPXXYuDsBGU9BIg7OVTNPyDPloCXdI+dQFAcSlDocZhPrLxhPV3eT6+gXbygzYXg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-web-api@2.3.13: + resolution: {integrity: sha512-+UypRPHP9GFMulIENpsC/J+TygWywiyz2mb4qyUP6y/IwdcSilk1MyF9WquNYKB/4/FN4Rl1oRm6WMbfkbpMnQ==} + engines: {node: '>=20.19.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' - eslint-plugin-react-x@1.53.1: - resolution: {integrity: sha512-MwMNnVwiPem0U6SlejDF/ddA4h/lmP6imL1RDZ2m3pUBrcdcOwOx0gyiRVTA3ENnhRlWfHljHf5y7m8qDSxMEg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-x@2.3.13: + resolution: {integrity: sha512-+m+V/5VLMxgx0VsFUUyflMNLQG0WFYspsfv0XJFqx7me3A2b3P20QatNDHQCYswz0PRbRFqinTPukPRhZh68ag==} + engines: {node: '>=20.19.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - ts-api-utils: ^2.1.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - ts-api-utils: - optional: true - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' eslint-plugin-regexp@2.10.0: resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==} @@ -5287,12 +5260,11 @@ packages: peerDependencies: eslint: ^8.0.0 || ^9.0.0 - eslint-plugin-storybook@9.1.16: - resolution: {integrity: sha512-I8f3DXniPxFbcptVgOjtIHNvW6sDu1O2d1zNsxLKmeAvEaRLus1ij8iFHCgkNzMthrU5U2F4Wdo/aaSpz5kHjA==} - engines: {node: '>=20.0.0'} + eslint-plugin-storybook@10.1.10: + resolution: {integrity: sha512-ITr6Aq3buR/DuDATkq1BafUVJLybyo676fY+tj9Zjd1Ak+UXBAMQcQ++tiBVVHm1RqADwM3b1o6bnWHK2fPPKw==} peerDependencies: eslint: '>=8' - storybook: ^9.1.16 + storybook: ^10.1.10 eslint-plugin-tailwindcss@3.18.2: resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==} @@ -5306,11 +5278,11 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-plugin-unicorn@61.0.2: - resolution: {integrity: sha512-zLihukvneYT7f74GNbVJXfWIiNQmkc/a9vYBTE4qPkQZswolWNdu+Wsp9sIXno1JOzdn6OUwLPd19ekXVkahRA==} + eslint-plugin-unicorn@62.0.0: + resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: - eslint: '>=9.29.0' + eslint: '>=9.38.0' eslint-plugin-unused-imports@4.3.0: resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==} @@ -5335,8 +5307,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-yml@1.19.0: - resolution: {integrity: sha512-S+4GbcCWksFKAvFJtf0vpdiCkZZvDJCV4Zsi9ahmYkYOYcf+LRqqzvzkb/ST7vTYV6sFwXOvawzYyL/jFT2nQA==} + eslint-plugin-yml@1.19.1: + resolution: {integrity: sha512-bYkOxyEiXh9WxUhVYPELdSHxGG5pOjCSeJOVkfdIyj6tuiHDxrES2WAW1dBxn3iaZQey57XflwLtCYRcNPOiOg==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' @@ -5363,8 +5335,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -5780,6 +5752,12 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -6133,17 +6111,17 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsdoc-type-pratt-parser@4.1.0: - resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} - engines: {node: '>=12.0.0'} - jsdoc-type-pratt-parser@4.8.0: resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} engines: {node: '>=12.0.0'} - jsdoc-type-pratt-parser@5.4.0: - resolution: {integrity: sha512-F9GQ+F1ZU6qvSrZV8fNFpjDNf614YzR2eF6S0+XbDjAcUI28FSoXnYZFjQmb1kFx3rrJb5PnxUH3/Yti6fcM+g==} - engines: {node: '>=12.0.0'} + jsdoc-type-pratt-parser@6.10.0: + resolution: {integrity: sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==} + engines: {node: '>=20.0.0'} + + jsdoc-type-pratt-parser@7.0.0: + resolution: {integrity: sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==} + engines: {node: '>=20.0.0'} jsdom-testing-mocks@1.16.0: resolution: {integrity: sha512-wLrulXiLpjmcUYOYGEvz4XARkrmdVpyxzdBl9IAMbQ+ib2/UhUTRCn49McdNfXLff2ysGBUms49ZKX0LR1Q0gg==} @@ -6158,11 +6136,6 @@ packages: canvas: optional: true - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -6198,9 +6171,6 @@ packages: resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} @@ -6803,8 +6773,8 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-deep-merge@1.0.5: - resolution: {integrity: sha512-3DioFgOzetbxbeUq8pB2NunXo8V0n4EvqsWM/cJoI6IA9zghd7cl/2pBOuWRf4dlvA+fcg5ugFMZaN2/RuoaGg==} + object-deep-merge@2.0.0: + resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -6848,16 +6818,6 @@ packages: oxc-resolver@11.15.0: resolution: {integrity: sha512-Hk2J8QMYwmIO9XTCUiOH00+Xk2/+aBxRUnhrSlANDyCnLYc32R1WSIq1sU2yEdlqd53FfMpPEpnBYIKQMzliJw==} - oxlint@1.32.0: - resolution: {integrity: sha512-HYDQCga7flsdyLMUIxTgSnEx5KBxpP9VINB8NgO+UjV80xBiTQXyVsvjtneMT3ZBLMbL0SlG/Dm03XQAsEshMA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.8.1' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -7065,8 +7025,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - pnpm-workspace-yaml@1.4.2: - resolution: {integrity: sha512-L2EKuOeV8aSt3z0RNtdwkg96BHV4WRN9pN2oTHKkMQQRxVEHFXPTbB+nly6ip1qV+JQM6qBebSiMgPRBx8S0Vw==} + pnpm-workspace-yaml@1.4.3: + resolution: {integrity: sha512-Q8B3SWuuISy/Ciag4DFP7MCrJX07wfaekcqD2o/msdIj4x8Ql3bZ/NEKOXV7mTVh7m1YdiFWiMi9xH+0zuEGHw==} points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -7553,10 +7513,6 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.12.0: - resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} - hasBin: true - regjsparser@0.13.0: resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} hasBin: true @@ -7602,6 +7558,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reserved-identifiers@1.2.0: + resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} + engines: {node: '>=18'} + resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -8146,6 +8106,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + to-valid-identifier@1.0.0: + resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} + engines: {node: '>=20'} + toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} @@ -8851,6 +8815,12 @@ packages: zen-observable@0.8.15: resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -9047,50 +9017,50 @@ snapshots: idb: 8.0.0 tslib: 2.8.1 - '@antfu/eslint-config@5.4.1(@eslint-react/eslint-plugin@1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3))(@next/eslint-plugin-next@15.5.9)(@vue/compiler-sfc@3.5.25)(eslint-plugin-react-hooks@5.2.0(eslint@9.39.1(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@antfu/eslint-config@6.7.3(@eslint-react/eslint-plugin@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(@next/eslint-plugin-next@15.5.9)(@vue/compiler-sfc@3.5.25)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@1.21.7)))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 - '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.39.2(jiti@1.21.7)) '@eslint/markdown': 7.5.1 - '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.5.2(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.2(jiti@1.21.7)) + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.6.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) ansis: 4.2.0 cac: 6.7.14 - eslint: 9.39.1(jiti@1.21.7) - eslint-config-flat-gitignore: 2.1.0(eslint@9.39.1(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) + eslint-config-flat-gitignore: 2.1.0(eslint@9.39.2(jiti@1.21.7)) eslint-flat-config-utils: 2.1.4 - eslint-merge-processors: 2.0.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-antfu: 3.1.1(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-command: 3.3.1(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-import-lite: 0.3.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-jsdoc: 59.1.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-jsonc: 2.21.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-n: 17.23.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint-merge-processors: 2.0.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-antfu: 3.1.1(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-command: 3.4.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import-lite: 0.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-jsdoc: 61.5.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsonc: 2.21.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-n: 17.23.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 4.15.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-pnpm: 1.4.2(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-regexp: 2.10.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-toml: 0.12.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-unicorn: 61.0.2(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-vue: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))) - eslint-plugin-yml: 1.19.0(eslint@9.39.1(jiti@1.21.7)) - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-perfectionist: 4.15.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-pnpm: 1.4.3(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-regexp: 2.10.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-toml: 0.12.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-unicorn: 62.0.0(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-vue: 10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.2(jiti@1.21.7)))(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@1.21.7))) + eslint-plugin-yml: 1.19.1(eslint@9.39.2(jiti@1.21.7)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.2(jiti@1.21.7)) globals: 16.5.0 jsonc-eslint-parser: 2.4.2 local-pkg: 1.1.2 parse-gitignore: 2.0.0 toml-eslint-parser: 0.10.1 - vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@1.21.7)) + vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@1.21.7)) yaml-eslint-parser: 1.3.2 optionalDependencies: - '@eslint-react/eslint-plugin': 1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3) + '@eslint-react/eslint-plugin': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@next/eslint-plugin-next': 15.5.9 - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-react-refresh: 0.4.24(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-refresh: 0.4.26(eslint@9.39.2(jiti@1.21.7)) transitivePeerDependencies: - '@eslint/json' - '@vue/compiler-sfc' @@ -10032,21 +10002,23 @@ snapshots: '@epic-web/invariant@1.0.0': {} - '@es-joy/jsdoccomment@0.50.2': + '@es-joy/jsdoccomment@0.76.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.50.0 comment-parser: 1.4.1 esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.1.0 + jsdoc-type-pratt-parser: 6.10.0 - '@es-joy/jsdoccomment@0.58.0': + '@es-joy/jsdoccomment@0.78.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.50.0 comment-parser: 1.4.1 esquery: 1.6.0 - jsdoc-type-pratt-parser: 5.4.0 + jsdoc-type-pratt-parser: 7.0.0 + + '@es-joy/resolve.exports@1.2.0': {} '@esbuild/aix-ppc64@0.25.0': optional: true @@ -10201,118 +10173,99 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.1(jiti@1.21.7))': + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.2(jiti@1.21.7))': dependencies: escape-string-regexp: 4.0.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) ignore: 5.3.2 - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@1.21.7))': dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} '@eslint-community/regexpp@4.12.2': {} - '@eslint-react/ast@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/ast@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) string-ts: 2.3.1 - ts-pattern: 5.9.0 - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/core@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - birecord: 0.1.1 - ts-pattern: 5.9.0 - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/eff@1.53.1': {} - - '@eslint-react/eslint-plugin@1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) - eslint-plugin-react-debug: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-dom: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-hooks-extra: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-naming-convention: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-web-api: 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-react-x: 1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3) - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - - ts-api-utils - '@eslint-react/kit@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/core@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + birecord: 0.1.1 + eslint: 9.39.2(jiti@1.21.7) ts-pattern: 5.9.0 - zod: 4.1.13 + typescript: 5.9.3 transitivePeerDependencies: - - eslint - supports-color - - typescript - '@eslint-react/shared@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - ts-pattern: 5.9.0 - zod: 4.1.13 - transitivePeerDependencies: - - eslint - - supports-color - - typescript + '@eslint-react/eff@2.3.13': {} - '@eslint-react/var@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@eslint-react/eslint-plugin@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - string-ts: 2.3.1 - ts-pattern: 5.9.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-plugin-react-dom: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-hooks-extra: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-naming-convention: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-web-api: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-x: 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - - eslint - supports-color - - typescript - '@eslint/compat@1.4.1(eslint@9.39.1(jiti@1.21.7))': + '@eslint-react/shared@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-react/eff': 2.3.13 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + ts-pattern: 5.9.0 + typescript: 5.9.3 + zod: 4.1.13 + transitivePeerDependencies: + - supports-color + + '@eslint-react/var@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + ts-pattern: 5.9.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@eslint/compat@1.4.1(eslint@9.39.2(jiti@1.21.7))': dependencies: '@eslint/core': 0.17.0 optionalDependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) '@eslint/config-array@0.21.1': dependencies: @@ -10348,7 +10301,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.39.1': {} + '@eslint/js@9.39.2': {} '@eslint/markdown@7.5.1': dependencies: @@ -10366,11 +10319,6 @@ snapshots: '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.3.4': - dependencies: - '@eslint/core': 0.15.2 - levn: 0.4.1 - '@eslint/plugin-kit@0.3.5': dependencies: '@eslint/core': 0.15.2 @@ -11114,30 +11062,6 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.15.0': optional: true - '@oxlint/darwin-arm64@1.32.0': - optional: true - - '@oxlint/darwin-x64@1.32.0': - optional: true - - '@oxlint/linux-arm64-gnu@1.32.0': - optional: true - - '@oxlint/linux-arm64-musl@1.32.0': - optional: true - - '@oxlint/linux-x64-gnu@1.32.0': - optional: true - - '@oxlint/linux-x64-musl@1.32.0': - optional: true - - '@oxlint/win32-arm64@1.32.0': - optional: true - - '@oxlint/win32-x64@1.32.0': - optional: true - '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -11679,6 +11603,8 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 19.2.3 + '@sindresorhus/base62@1.0.0': {} + '@sindresorhus/is@4.6.0': {} '@standard-schema/spec@1.1.0': {} @@ -11872,11 +11798,11 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7))': + '@stylistic/eslint-plugin@5.6.1(eslint@9.39.2(jiti@1.21.7))': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@typescript-eslint/types': 8.49.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -12306,15 +12232,15 @@ snapshots: '@types/zen-observable@0.8.3': {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 + eslint: 9.39.2(jiti@1.21.7) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.9.3) @@ -12322,14 +12248,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12337,7 +12263,16 @@ snapshots: '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.50.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: @@ -12348,17 +12283,38 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager@8.50.0': + dependencies: + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -12366,6 +12322,8 @@ snapshots: '@typescript-eslint/types@8.49.0': {} + '@typescript-eslint/types@8.50.0': {} + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) @@ -12381,13 +12339,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/visitor-keys': 8.50.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.50.0 + '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12397,6 +12381,11 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.50.0': + dependencies: + '@typescript-eslint/types': 8.50.0 + eslint-visitor-keys: 4.2.1 + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251209.1': optional: true @@ -12459,11 +12448,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.5.2(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/eslint-plugin@1.6.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)(vitest@4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 vitest: 4.0.16(@types/node@18.15.0)(happy-dom@20.0.11)(jiti@1.21.7)(jsdom@27.3.0(canvas@3.2.0))(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2) @@ -13858,83 +13847,84 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.39.1(jiti@1.21.7)): + eslint-compat-utils@0.5.1(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) semver: 7.7.3 - eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@1.21.7)): + eslint-compat-utils@0.6.5(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) semver: 7.7.3 - eslint-config-flat-gitignore@2.1.0(eslint@9.39.1(jiti@1.21.7)): + eslint-config-flat-gitignore@2.1.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@eslint/compat': 1.4.1(eslint@9.39.1(jiti@1.21.7)) - eslint: 9.39.1(jiti@1.21.7) + '@eslint/compat': 1.4.1(eslint@9.39.2(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) eslint-flat-config-utils@2.1.4: dependencies: pathe: 2.0.3 - eslint-json-compat-utils@0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.2): + eslint-json-compat-utils@0.2.1(eslint@9.39.2(jiti@1.21.7))(jsonc-eslint-parser@2.4.2): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) esquery: 1.6.0 jsonc-eslint-parser: 2.4.2 - eslint-merge-processors@2.0.0(eslint@9.39.1(jiti@1.21.7)): + eslint-merge-processors@2.0.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) - eslint-plugin-antfu@3.1.1(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-antfu@3.1.1(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) - eslint-plugin-command@3.3.1(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-command@3.4.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@es-joy/jsdoccomment': 0.50.2 - eslint: 9.39.1(jiti@1.21.7) + '@es-joy/jsdoccomment': 0.78.0 + eslint: 9.39.2(jiti@1.21.7) - eslint-plugin-es-x@7.8.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-es-x@7.8.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 - eslint: 9.39.1(jiti@1.21.7) - eslint-compat-utils: 0.5.1(eslint@9.39.1(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) + eslint-compat-utils: 0.5.1(eslint@9.39.2(jiti@1.21.7)) - eslint-plugin-import-lite@0.3.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-import-lite@0.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/types': 8.49.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) optionalDependencies: typescript: 5.9.3 - eslint-plugin-jsdoc@59.1.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-jsdoc@61.5.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@es-joy/jsdoccomment': 0.58.0 + '@es-joy/jsdoccomment': 0.76.0 + '@es-joy/resolve.exports': 1.2.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) espree: 10.4.0 esquery: 1.6.0 - object-deep-merge: 1.0.5 + html-entities: 2.6.0 + object-deep-merge: 2.0.0 parse-imports-exports: 0.2.4 semver: 7.7.3 spdx-expression-parse: 4.0.0 + to-valid-identifier: 1.0.0 transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@2.21.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-jsonc@2.21.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) diff-sequences: 27.5.1 - eslint: 9.39.1(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) - eslint-json-compat-utils: 0.2.1(eslint@9.39.1(jiti@1.21.7))(jsonc-eslint-parser@2.4.2) + eslint: 9.39.2(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.39.2(jiti@1.21.7)) + eslint-json-compat-utils: 0.2.1(eslint@9.39.2(jiti@1.21.7))(jsonc-eslint-parser@2.4.2) espree: 10.4.0 graphemer: 1.4.0 jsonc-eslint-parser: 2.4.2 @@ -13943,12 +13933,12 @@ snapshots: transitivePeerDependencies: - '@eslint/json' - eslint-plugin-n@17.23.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-n@17.23.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) enhanced-resolve: 5.18.3 - eslint: 9.39.1(jiti@1.21.7) - eslint-plugin-es-x: 7.8.0(eslint@9.39.1(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) + eslint-plugin-es-x: 7.8.0(eslint@9.39.2(jiti@1.21.7)) get-tsconfig: 4.13.0 globals: 15.15.0 globrex: 0.1.2 @@ -13960,178 +13950,151 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-oxlint@1.32.0: - dependencies: - jsonc-parser: 3.3.1 - - eslint-plugin-perfectionist@4.15.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-perfectionist@4.15.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.4.2(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-pnpm@1.4.3(eslint@9.39.2(jiti@1.21.7)): dependencies: empathic: 2.0.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) jsonc-eslint-parser: 2.4.2 pathe: 2.0.3 - pnpm-workspace-yaml: 1.4.2 + pnpm-workspace-yaml: 1.4.3 tinyglobby: 0.2.15 yaml: 2.8.2 yaml-eslint-parser: 1.3.2 - eslint-plugin-react-debug@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-dom@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) - string-ts: 2.3.1 - ts-pattern: 5.9.0 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-dom@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-hooks-extra@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@5.2.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + eslint: 9.39.2(jiti@1.21.7) + hermes-parser: 0.25.1 + zod: 3.25.76 + zod-validation-error: 4.0.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color - eslint-plugin-react-naming-convention@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-naming-convention@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.4.24(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) - eslint-plugin-react-web-api@1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-react-web-api@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) string-ts: 2.3.1 ts-pattern: 5.9.0 - optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-x@1.53.1(eslint@9.39.1(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.9.3))(typescript@5.9.3): + eslint-plugin-react-x@2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/core': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/shared': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@eslint-react/var': 1.53.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/ast': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/core': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/eff': 2.3.13 + '@eslint-react/shared': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@eslint-react/var': 2.3.13(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) compare-versions: 6.1.1 - eslint: 9.39.1(jiti@1.21.7) - is-immutable-type: 5.0.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + is-immutable-type: 5.0.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) string-ts: 2.3.1 - ts-pattern: 5.9.0 - optionalDependencies: ts-api-utils: 2.1.0(typescript@5.9.3) + ts-pattern: 5.9.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-regexp@2.10.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-regexp@2.10.0(eslint@9.39.2(jiti@1.21.7)): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.1 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) jsdoc-type-pratt-parser: 4.8.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@3.0.5(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-sonarjs@3.0.5(eslint@9.39.2(jiti@1.21.7)): dependencies: '@eslint-community/regexpp': 4.12.1 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) functional-red-black-tree: 1.0.1 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 @@ -14140,10 +14103,10 @@ snapshots: semver: 7.7.2 typescript: 5.9.3 - eslint-plugin-storybook@9.1.16(eslint@9.39.1(jiti@1.21.7))(storybook@9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.9.3): + eslint-plugin-storybook@10.1.10(eslint@9.39.2(jiti@1.21.7))(storybook@9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) storybook: 9.1.17(@testing-library/dom@10.4.1)(vite@7.3.0(@types/node@18.15.0)(jiti@1.21.7)(sass@1.95.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -14155,26 +14118,26 @@ snapshots: postcss: 8.5.6 tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.2) - eslint-plugin-toml@0.12.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-toml@0.12.0(eslint@9.39.2(jiti@1.21.7)): dependencies: debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.39.2(jiti@1.21.7)) lodash: 4.17.21 toml-eslint-parser: 0.10.1 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@61.0.2(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@1.21.7)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@eslint/plugin-kit': 0.3.4 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) + '@eslint/plugin-kit': 0.3.5 change-case: 5.4.4 ci-info: 4.3.1 clean-regexp: 1.0.0 core-js-compat: 3.47.0 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) esquery: 1.6.0 find-up-simple: 1.0.1 globals: 16.5.0 @@ -14183,46 +14146,46 @@ snapshots: jsesc: 3.1.0 pluralize: 8.0.0 regexp-tree: 0.1.27 - regjsparser: 0.12.0 + regjsparser: 0.13.0 semver: 7.7.3 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7)): dependencies: - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@1.21.7)))(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7))): + eslint-plugin-vue@10.6.2(@stylistic/eslint-plugin@5.6.1(eslint@9.39.2(jiti@1.21.7)))(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@1.21.7))): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - eslint: 9.39.1(jiti@1.21.7) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 semver: 7.7.3 - vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@1.21.7)) + vue-eslint-parser: 10.2.0(eslint@9.39.2(jiti@1.21.7)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.2(jiti@1.21.7)) + '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) - eslint-plugin-yml@1.19.0(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-yml@1.19.1(eslint@9.39.2(jiti@1.21.7)): dependencies: debug: 4.4.3 diff-sequences: 27.5.1 escape-string-regexp: 4.0.0 - eslint: 9.39.1(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) + eslint: 9.39.2(jiti@1.21.7) + eslint-compat-utils: 0.6.5(eslint@9.39.2(jiti@1.21.7)) natural-compare: 1.4.0 yaml-eslint-parser: 1.3.2 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.1(jiti@1.21.7)): + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.25)(eslint@9.39.2(jiti@1.21.7)): dependencies: '@vue/compiler-sfc': 3.5.25 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) eslint-scope@5.1.1: dependencies: @@ -14238,15 +14201,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.1(jiti@1.21.7): + eslint@9.39.2(jiti@1.21.7): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@1.21.7)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.1 + '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 @@ -14802,6 +14765,12 @@ snapshots: he@1.2.0: {} + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + highlight.js@10.7.3: {} highlightjs-vue@1.0.0: {} @@ -15001,10 +14970,10 @@ snapshots: is-hexadecimal@2.0.1: {} - is-immutable-type@5.0.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + is-immutable-type@5.0.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.9.3) ts-declaration-location: 1.0.7(typescript@5.9.3) typescript: 5.9.3 @@ -15106,11 +15075,11 @@ snapshots: dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@4.1.0: {} - jsdoc-type-pratt-parser@4.8.0: {} - jsdoc-type-pratt-parser@5.4.0: {} + jsdoc-type-pratt-parser@6.10.0: {} + + jsdoc-type-pratt-parser@7.0.0: {} jsdom-testing-mocks@1.16.0: dependencies: @@ -15146,8 +15115,6 @@ snapshots: - supports-color - utf-8-validate - jsesc@3.0.2: {} - jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -15173,8 +15140,6 @@ snapshots: espree: 9.6.1 semver: 7.7.3 - jsonc-parser@3.3.1: {} - jsonfile@6.2.0: dependencies: universalify: 2.0.1 @@ -16118,9 +16083,7 @@ snapshots: object-assign@4.1.1: {} - object-deep-merge@1.0.5: - dependencies: - type-fest: 4.2.0 + object-deep-merge@2.0.0: {} object-hash@3.0.0: {} @@ -16184,17 +16147,6 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.15.0 '@oxc-resolver/binding-win32-x64-msvc': 11.15.0 - oxlint@1.32.0: - optionalDependencies: - '@oxlint/darwin-arm64': 1.32.0 - '@oxlint/darwin-x64': 1.32.0 - '@oxlint/linux-arm64-gnu': 1.32.0 - '@oxlint/linux-arm64-musl': 1.32.0 - '@oxlint/linux-x64-gnu': 1.32.0 - '@oxlint/linux-x64-musl': 1.32.0 - '@oxlint/win32-arm64': 1.32.0 - '@oxlint/win32-x64': 1.32.0 - p-cancelable@2.1.1: {} p-limit@2.3.0: @@ -16388,7 +16340,7 @@ snapshots: pluralize@8.0.0: {} - pnpm-workspace-yaml@1.4.2: + pnpm-workspace-yaml@1.4.3: dependencies: yaml: 2.8.2 @@ -16949,10 +16901,6 @@ snapshots: regjsgen@0.8.0: {} - regjsparser@0.12.0: - dependencies: - jsesc: 3.0.2 - regjsparser@0.13.0: dependencies: jsesc: 3.1.0 @@ -17049,6 +16997,8 @@ snapshots: require-from-string@2.0.2: {} + reserved-identifiers@1.2.0: {} + resize-observer-polyfill@1.5.1: {} resolve-alpn@1.2.1: {} @@ -17661,6 +17611,11 @@ snapshots: dependencies: is-number: 7.0.0 + to-valid-identifier@1.0.0: + dependencies: + '@sindresorhus/base62': 1.0.0 + reserved-identifiers: 1.2.0 + toggle-selection@1.0.6: {} toml-eslint-parser@0.10.1: @@ -17765,7 +17720,8 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.2.0: {} + type-fest@4.2.0: + optional: true typescript@5.9.3: {} @@ -18058,10 +18014,10 @@ snapshots: vscode-uri@3.0.8: {} - vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@1.21.7)): + vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@1.21.7)): dependencies: debug: 4.4.3 - eslint: 9.39.1(jiti@1.21.7) + eslint: 9.39.2(jiti@1.21.7) eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -18369,6 +18325,10 @@ snapshots: zen-observable@0.8.15: {} + zod-validation-error@4.0.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod@3.25.76: {} zod@4.1.13: {} diff --git a/web/scripts/copy-and-start.mjs b/web/scripts/copy-and-start.mjs index b23ce636a4..9525c6b45f 100644 --- a/web/scripts/copy-and-start.mjs +++ b/web/scripts/copy-and-start.mjs @@ -4,8 +4,8 @@ * It is intended to be used as a replacement for `next start`. */ -import { cp, mkdir, stat } from 'node:fs/promises' import { spawn } from 'node:child_process' +import { cp, mkdir, stat } from 'node:fs/promises' import path from 'node:path' // Configuration for directories to copy diff --git a/web/scripts/generate-icons.js b/web/scripts/generate-icons.js index b1b6f24435..979fbf059f 100644 --- a/web/scripts/generate-icons.js +++ b/web/scripts/generate-icons.js @@ -15,40 +15,41 @@ const sizes = [ { size: 128, name: 'icon-128x128.png' }, { size: 144, name: 'icon-144x144.png' }, { size: 152, name: 'icon-152x152.png' }, -]; +] -const inputPath = path.join(__dirname, '../public/icon.svg'); -const outputDir = path.join(__dirname, '../public'); +const inputPath = path.join(__dirname, '../public/icon.svg') +const outputDir = path.join(__dirname, '../public') // Generate icons async function generateIcons() { try { - console.log('Generating PWA icons...'); - + console.log('Generating PWA icons...') + for (const { size, name } of sizes) { - const outputPath = path.join(outputDir, name); - + const outputPath = path.join(outputDir, name) + await sharp(inputPath) .resize(size, size) .png() - .toFile(outputPath); - - console.log(`✓ Generated ${name} (${size}x${size})`); + .toFile(outputPath) + + console.log(`✓ Generated ${name} (${size}x${size})`) } - + // Generate apple-touch-icon await sharp(inputPath) .resize(180, 180) .png() - .toFile(path.join(outputDir, 'apple-touch-icon.png')); - - console.log('✓ Generated apple-touch-icon.png (180x180)'); - - console.log('\n✅ All icons generated successfully!'); - } catch (error) { - console.error('Error generating icons:', error); - process.exit(1); + .toFile(path.join(outputDir, 'apple-touch-icon.png')) + + console.log('✓ Generated apple-touch-icon.png (180x180)') + + console.log('\n✅ All icons generated successfully!') + } + catch (error) { + console.error('Error generating icons:', error) + process.exit(1) } } -generateIcons(); \ No newline at end of file +generateIcons() diff --git a/web/scripts/optimize-standalone.js b/web/scripts/optimize-standalone.js index c2f472bee1..b73667eac6 100644 --- a/web/scripts/optimize-standalone.js +++ b/web/scripts/optimize-standalone.js @@ -10,14 +10,14 @@ import { fileURLToPath } from 'node:url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -console.log('🔧 Optimizing standalone output...'); +console.log('🔧 Optimizing standalone output...') -const standaloneDir = path.join(__dirname, '..', '.next', 'standalone'); +const standaloneDir = path.join(__dirname, '..', '.next', 'standalone') // Check if standalone directory exists if (!fs.existsSync(standaloneDir)) { - console.error('❌ Standalone directory not found. Please run "next build" first.'); - process.exit(1); + console.error('❌ Standalone directory not found. Please run "next build" first.') + process.exit(1) } // List of paths to remove (relative to standalone directory) @@ -28,126 +28,136 @@ const pathsToRemove = [ 'node_modules/.pnpm/terser-webpack-plugin@*/node_modules/jest-worker', // Remove actual jest-worker packages (directories only, not symlinks) 'node_modules/.pnpm/jest-worker@*', -]; +] // Function to safely remove a path function removePath(basePath, relativePath) { - const fullPath = path.join(basePath, relativePath); + const fullPath = path.join(basePath, relativePath) // Handle wildcard patterns if (relativePath.includes('*')) { - const parts = relativePath.split('/'); - let currentPath = basePath; + const parts = relativePath.split('/') + let currentPath = basePath for (let i = 0; i < parts.length; i++) { - const part = parts[i]; + const part = parts[i] if (part.includes('*')) { // Find matching directories if (fs.existsSync(currentPath)) { - const entries = fs.readdirSync(currentPath); + const entries = fs.readdirSync(currentPath) // replace '*' with '.*' - const regexPattern = part.replace(/\*/g, '.*'); + const regexPattern = part.replace(/\*/g, '.*') - const regex = new RegExp(`^${regexPattern}$`); + const regex = new RegExp(`^${regexPattern}$`) for (const entry of entries) { if (regex.test(entry)) { - const remainingPath = parts.slice(i + 1).join('/'); - const matchedPath = path.join(currentPath, entry, remainingPath); + const remainingPath = parts.slice(i + 1).join('/') + const matchedPath = path.join(currentPath, entry, remainingPath) try { // Use lstatSync to check if path exists (works for both files and symlinks) - const stats = fs.lstatSync(matchedPath); + const stats = fs.lstatSync(matchedPath) if (stats.isSymbolicLink()) { // Remove symlink - fs.unlinkSync(matchedPath); - console.log(`✅ Removed symlink: ${path.relative(basePath, matchedPath)}`); - } else { - // Remove directory/file - fs.rmSync(matchedPath, { recursive: true, force: true }); - console.log(`✅ Removed: ${path.relative(basePath, matchedPath)}`); + fs.unlinkSync(matchedPath) + console.log(`✅ Removed symlink: ${path.relative(basePath, matchedPath)}`) } - } catch (error) { + else { + // Remove directory/file + fs.rmSync(matchedPath, { recursive: true, force: true }) + console.log(`✅ Removed: ${path.relative(basePath, matchedPath)}`) + } + } + catch (error) { // Silently ignore ENOENT (path not found) errors if (error.code !== 'ENOENT') { - console.error(`❌ Failed to remove ${matchedPath}: ${error.message}`); + console.error(`❌ Failed to remove ${matchedPath}: ${error.message}`) } } } } } - return; - } else { - currentPath = path.join(currentPath, part); + return + } + else { + currentPath = path.join(currentPath, part) } } - } else { + } + else { // Direct path removal if (fs.existsSync(fullPath)) { try { - fs.rmSync(fullPath, { recursive: true, force: true }); - console.log(`✅ Removed: ${relativePath}`); - } catch (error) { - console.error(`❌ Failed to remove ${fullPath}: ${error.message}`); + fs.rmSync(fullPath, { recursive: true, force: true }) + console.log(`✅ Removed: ${relativePath}`) + } + catch (error) { + console.error(`❌ Failed to remove ${fullPath}: ${error.message}`) } } } } // Remove unnecessary paths -console.log('🗑️ Removing unnecessary files...'); +console.log('🗑️ Removing unnecessary files...') for (const pathToRemove of pathsToRemove) { - removePath(standaloneDir, pathToRemove); + removePath(standaloneDir, pathToRemove) } // Calculate size reduction -console.log('\n📊 Optimization complete!'); +console.log('\n📊 Optimization complete!') // Optional: Display the size of remaining jest-related files (if any) const checkForJest = (dir) => { - const jestFiles = []; + const jestFiles = [] function walk(currentPath) { - if (!fs.existsSync(currentPath)) return; + if (!fs.existsSync(currentPath)) + return try { - const entries = fs.readdirSync(currentPath); + const entries = fs.readdirSync(currentPath) for (const entry of entries) { - const fullPath = path.join(currentPath, entry); + const fullPath = path.join(currentPath, entry) try { - const stat = fs.lstatSync(fullPath); // Use lstatSync to handle symlinks + const stat = fs.lstatSync(fullPath) // Use lstatSync to handle symlinks if (stat.isDirectory() && !stat.isSymbolicLink()) { // Skip node_modules subdirectories to avoid deep traversal if (entry === 'node_modules' && currentPath !== standaloneDir) { - continue; + continue } - walk(fullPath); - } else if (stat.isFile() && entry.includes('jest')) { - jestFiles.push(path.relative(standaloneDir, fullPath)); + walk(fullPath) } - } catch (err) { + else if (stat.isFile() && entry.includes('jest')) { + jestFiles.push(path.relative(standaloneDir, fullPath)) + } + } + catch (err) { // Skip files that can't be accessed - continue; + continue } } - } catch (err) { + } + catch (err) { // Skip directories that can't be read - return; + } } - walk(dir); - return jestFiles; -}; - -const remainingJestFiles = checkForJest(standaloneDir); -if (remainingJestFiles.length > 0) { - console.log('\n⚠️ Warning: Some jest-related files still remain:'); - remainingJestFiles.forEach(file => console.log(` - ${file}`)); -} else { - console.log('\n✨ No jest-related files found in standalone output!'); + walk(dir) + return jestFiles +} + +const remainingJestFiles = checkForJest(standaloneDir) +if (remainingJestFiles.length > 0) { + console.log('\n⚠️ Warning: Some jest-related files still remain:') + remainingJestFiles.forEach(file => console.log(` - ${file}`)) +} +else { + console.log('\n✨ No jest-related files found in standalone output!') } diff --git a/web/service/_tools_util.spec.ts b/web/service/_tools_util.spec.ts index 658c276df1..e3bec3efcf 100644 --- a/web/service/_tools_util.spec.ts +++ b/web/service/_tools_util.spec.ts @@ -1,16 +1,16 @@ import { buildProviderQuery } from './_tools_util' describe('makeProviderQuery', () => { - test('collectionName without special chars', () => { + it('collectionName without special chars', () => { expect(buildProviderQuery('ABC')).toBe('provider=ABC') }) - test('should escape &', () => { + it('should escape &', () => { expect(buildProviderQuery('ABC&DEF')).toBe('provider=ABC%26DEF') }) - test('should escape /', () => { + it('should escape /', () => { expect(buildProviderQuery('ABC/DEF')).toBe('provider=ABC%2FDEF') }) - test('should escape ?', () => { + it('should escape ?', () => { expect(buildProviderQuery('ABC?DEF')).toBe('provider=ABC%3FDEF') }) }) diff --git a/web/service/access-control.ts b/web/service/access-control.ts index 18dc9e0001..ad0c14fd0a 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -1,16 +1,16 @@ -import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { get, post } from './base' -import { getUserCanAccess } from './share' import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control' import type { App } from '@/types/app' +import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useGlobalPublicStore } from '@/context/global-public-context' +import { get, post } from './base' +import { getUserCanAccess } from './share' const NAME_SPACE = 'access-control' export const useAppWhiteListSubjects = (appId: string | undefined, enabled: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'app-whitelist-subjects', appId], - queryFn: () => get<{ groups: AccessControlGroup[]; members: AccessControlAccount[] }>(`/enterprise/webapp/app/subjects?appId=${appId}`), + queryFn: () => get<{ groups: AccessControlGroup[], members: AccessControlAccount[] }>(`/enterprise/webapp/app/subjects?appId=${appId}`), enabled: !!appId && enabled, staleTime: 0, gcTime: 0, @@ -24,7 +24,7 @@ type SearchResults = { hasMore: boolean } -export const useSearchForWhiteListCandidates = (query: { keyword?: string; groupId?: AccessControlGroup['id']; resultsPerPage?: number }, enabled: boolean) => { +export const useSearchForWhiteListCandidates = (query: { keyword?: string, groupId?: AccessControlGroup['id'], resultsPerPage?: number }, enabled: boolean) => { return useInfiniteQuery({ queryKey: [NAME_SPACE, 'app-whitelist-candidates', query], queryFn: ({ pageParam }) => { @@ -70,7 +70,7 @@ export const useUpdateAccessMode = () => { }) } -export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled }: { appId?: string; isInstalledApp?: boolean; enabled?: boolean }) => { +export const useGetUserCanAccessApp = ({ appId, isInstalledApp = true, enabled }: { appId?: string, isInstalledApp?: boolean, enabled?: boolean }) => { const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) return useQuery({ queryKey: [NAME_SPACE, 'user-can-access-app', appId], diff --git a/web/service/annotation.ts b/web/service/annotation.ts index af708fe174..acdb944386 100644 --- a/web/service/annotation.ts +++ b/web/service/annotation.ts @@ -1,7 +1,7 @@ import type { Fetcher } from 'swr' -import { del, get, post } from './base' import type { AnnotationCreateResponse, AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type' import { ANNOTATION_DEFAULT } from '@/config' +import { del, get, post } from './base' export const fetchAnnotationConfig = (appId: string) => { return get(`apps/${appId}/annotation-setting`) @@ -44,12 +44,12 @@ export const addAnnotation = (appId: string, body: AnnotationItemBasic) => { return post<AnnotationCreateResponse>(`apps/${appId}/annotations`, { body }) } -export const annotationBatchImport: Fetcher<{ job_id: string; job_status: string }, { url: string; body: FormData }> = ({ url, body }) => { - return post<{ job_id: string; job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true }) +export const annotationBatchImport: Fetcher<{ job_id: string, job_status: string }, { url: string, body: FormData }> = ({ url, body }) => { + return post<{ job_id: string, job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true }) } -export const checkAnnotationBatchImportProgress: Fetcher<{ job_id: string; job_status: string }, { jobID: string; appId: string }> = ({ jobID, appId }) => { - return get<{ job_id: string; job_status: string }>(`/apps/${appId}/annotations/batch-import-status/${jobID}`) +export const checkAnnotationBatchImportProgress: Fetcher<{ job_id: string, job_status: string }, { jobID: string, appId: string }> = ({ jobID, appId }) => { + return get<{ job_id: string, job_status: string }>(`/apps/${appId}/annotations/batch-import-status/${jobID}`) } export const editAnnotation = (appId: string, annotationId: string, body: AnnotationItemBasic) => { diff --git a/web/service/apps.ts b/web/service/apps.ts index 89001bffec..1e3c93a33a 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -1,19 +1,19 @@ -import { del, get, patch, post, put } from './base' +import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' import type { ApiKeysListResponse, AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppDetailResponse, AppListResponse, AppStatisticsResponse, AppTemplatesResponse, AppTokenCostsResponse, AppVoicesListResponse, CreateApiKeyResponse, DSLImportMode, DSLImportResponse, GenerationIntroductionResponse, TracingConfig, TracingStatus, UpdateAppModelConfigResponse, UpdateAppSiteCodeResponse, UpdateOpenAIKeyResponse, ValidateOpenAIKeyResponse, WebhookTriggerResponse, WorkflowDailyConversationsResponse } from '@/models/app' import type { CommonResponse } from '@/models/common' import type { AppIconType, AppModeEnum, ModelConfig } from '@/types/app' -import type { TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' +import { del, get, patch, post, put } from './base' -export const fetchAppList = ({ url, params }: { url: string; params?: Record<string, any> }): Promise<AppListResponse> => { +export const fetchAppList = ({ url, params }: { url: string, params?: Record<string, any> }): Promise<AppListResponse> => { return get<AppListResponse>(url, { params }) } -export const fetchAppDetail = ({ url, id }: { url: string; id: string }): Promise<AppDetailResponse> => { +export const fetchAppDetail = ({ url, id }: { url: string, id: string }): Promise<AppDetailResponse> => { return get<AppDetailResponse>(`${url}/${id}`) } // Direct API call function for non-SWR usage -export const fetchAppDetailDirect = async ({ url, id }: { url: string; id: string }): Promise<AppDetailResponse> => { +export const fetchAppDetailDirect = async ({ url, id }: { url: string, id: string }): Promise<AppDetailResponse> => { return get<AppDetailResponse>(`${url}/${id}`) } @@ -84,7 +84,7 @@ export const copyApp = ({ return post<AppDetailResponse>(`apps/${appID}/copy`, { body: { name, icon_type, icon, icon_background, mode, description } }) } -export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: string; include?: boolean; workflowID?: string }): Promise<{ data: string }> => { +export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: string, include?: boolean, workflowID?: string }): Promise<{ data: string }> => { const params = new URLSearchParams({ include_secret: include.toString(), }) @@ -93,7 +93,7 @@ export const exportAppConfig = ({ appID, include = false, workflowID }: { appID: return get<{ data: string }>(`apps/${appID}/export?${params.toString()}`) } -export const importDSL = ({ mode, yaml_content, yaml_url, app_id, name, description, icon_type, icon, icon_background }: { mode: DSLImportMode; yaml_content?: string; yaml_url?: string; app_id?: string; name?: string; description?: string; icon_type?: AppIconType; icon?: string; icon_background?: string }): Promise<DSLImportResponse> => { +export const importDSL = ({ mode, yaml_content, yaml_url, app_id, name, description, icon_type, icon, icon_background }: { mode: DSLImportMode, yaml_content?: string, yaml_url?: string, app_id?: string, name?: string, description?: string, icon_type?: AppIconType, icon?: string, icon_background?: string }): Promise<DSLImportResponse> => { return post<DSLImportResponse>('apps/imports', { body: { mode, yaml_content, yaml_url, app_id, name, description, icon, icon_type, icon_background } }) } @@ -101,7 +101,7 @@ export const importDSLConfirm = ({ import_id }: { import_id: string }): Promise< return post<DSLImportResponse>(`apps/imports/${import_id}/confirm`, { body: {} }) } -export const switchApp = ({ appID, name, icon_type, icon, icon_background }: { appID: string; name: string; icon_type: AppIconType; icon: string; icon_background?: string | null }): Promise<{ new_app_id: string }> => { +export const switchApp = ({ appID, name, icon_type, icon, icon_background }: { appID: string, name: string, icon_type: AppIconType, icon: string, icon_background?: string | null }): Promise<{ new_app_id: string }> => { return post<{ new_app_id: string }>(`apps/${appID}/convert-to-workflow`, { body: { name, icon_type, icon, icon_background } }) } @@ -109,16 +109,16 @@ export const deleteApp = (appID: string): Promise<CommonResponse> => { return del<CommonResponse>(`apps/${appID}`) } -export const updateAppSiteStatus = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => { +export const updateAppSiteStatus = ({ url, body }: { url: string, body: Record<string, any> }): Promise<AppDetailResponse> => { return post<AppDetailResponse>(url, { body }) } -export const updateAppApiStatus = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => { +export const updateAppApiStatus = ({ url, body }: { url: string, body: Record<string, any> }): Promise<AppDetailResponse> => { return post<AppDetailResponse>(url, { body }) } // path: /apps/{appId}/rate-limit -export const updateAppRateLimit = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => { +export const updateAppRateLimit = ({ url, body }: { url: string, body: Record<string, any> }): Promise<AppDetailResponse> => { return post<AppDetailResponse>(url, { body }) } @@ -126,68 +126,68 @@ export const updateAppSiteAccessToken = ({ url }: { url: string }): Promise<Upda return post<UpdateAppSiteCodeResponse>(url) } -export const updateAppSiteConfig = ({ url, body }: { url: string; body: Record<string, any> }): Promise<AppDetailResponse> => { +export const updateAppSiteConfig = ({ url, body }: { url: string, body: Record<string, any> }): Promise<AppDetailResponse> => { return post<AppDetailResponse>(url, { body }) } -export const getAppDailyMessages = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyMessagesResponse> => { +export const getAppDailyMessages = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppDailyMessagesResponse> => { return get<AppDailyMessagesResponse>(url, { params }) } -export const getAppDailyConversations = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyConversationsResponse> => { +export const getAppDailyConversations = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppDailyConversationsResponse> => { return get<AppDailyConversationsResponse>(url, { params }) } -export const getWorkflowDailyConversations = ({ url, params }: { url: string; params: Record<string, any> }): Promise<WorkflowDailyConversationsResponse> => { +export const getWorkflowDailyConversations = ({ url, params }: { url: string, params: Record<string, any> }): Promise<WorkflowDailyConversationsResponse> => { return get<WorkflowDailyConversationsResponse>(url, { params }) } -export const getAppStatistics = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppStatisticsResponse> => { +export const getAppStatistics = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppStatisticsResponse> => { return get<AppStatisticsResponse>(url, { params }) } -export const getAppDailyEndUsers = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppDailyEndUsersResponse> => { +export const getAppDailyEndUsers = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppDailyEndUsersResponse> => { return get<AppDailyEndUsersResponse>(url, { params }) } -export const getAppTokenCosts = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppTokenCostsResponse> => { +export const getAppTokenCosts = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppTokenCostsResponse> => { return get<AppTokenCostsResponse>(url, { params }) } -export const updateAppModelConfig = ({ url, body }: { url: string; body: Record<string, any> }): Promise<UpdateAppModelConfigResponse> => { +export const updateAppModelConfig = ({ url, body }: { url: string, body: Record<string, any> }): Promise<UpdateAppModelConfigResponse> => { return post<UpdateAppModelConfigResponse>(url, { body }) } // For temp testing -export const fetchAppListNoMock = ({ url, params }: { url: string; params: Record<string, any> }): Promise<AppListResponse> => { +export const fetchAppListNoMock = ({ url, params }: { url: string, params: Record<string, any> }): Promise<AppListResponse> => { return get<AppListResponse>(url, params) } -export const fetchApiKeysList = ({ url, params }: { url: string; params: Record<string, any> }): Promise<ApiKeysListResponse> => { +export const fetchApiKeysList = ({ url, params }: { url: string, params: Record<string, any> }): Promise<ApiKeysListResponse> => { return get<ApiKeysListResponse>(url, params) } -export const delApikey = ({ url, params }: { url: string; params: Record<string, any> }): Promise<CommonResponse> => { +export const delApikey = ({ url, params }: { url: string, params: Record<string, any> }): Promise<CommonResponse> => { return del<CommonResponse>(url, params) } -export const createApikey = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CreateApiKeyResponse> => { +export const createApikey = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CreateApiKeyResponse> => { return post<CreateApiKeyResponse>(url, body) } -export const validateOpenAIKey = ({ url, body }: { url: string; body: { token: string } }): Promise<ValidateOpenAIKeyResponse> => { +export const validateOpenAIKey = ({ url, body }: { url: string, body: { token: string } }): Promise<ValidateOpenAIKeyResponse> => { return post<ValidateOpenAIKeyResponse>(url, { body }) } -export const updateOpenAIKey = ({ url, body }: { url: string; body: { token: string } }): Promise<UpdateOpenAIKeyResponse> => { +export const updateOpenAIKey = ({ url, body }: { url: string, body: { token: string } }): Promise<UpdateOpenAIKeyResponse> => { return post<UpdateOpenAIKeyResponse>(url, { body }) } -export const generationIntroduction = ({ url, body }: { url: string; body: { prompt_template: string } }): Promise<GenerationIntroductionResponse> => { +export const generationIntroduction = ({ url, body }: { url: string, body: { prompt_template: string } }): Promise<GenerationIntroductionResponse> => { return post<GenerationIntroductionResponse>(url, { body }) } -export const fetchAppVoices = ({ appId, language }: { appId: string; language?: string }): Promise<AppVoicesListResponse> => { +export const fetchAppVoices = ({ appId, language }: { appId: string, language?: string }): Promise<AppVoicesListResponse> => { language = language || 'en-US' return get<AppVoicesListResponse>(`apps/${appId}/text-to-audio/voices?language=${language}`) } @@ -197,12 +197,12 @@ export const fetchTracingStatus = ({ appId }: { appId: string }): Promise<Tracin return get<TracingStatus>(`/apps/${appId}/trace`) } -export const updateTracingStatus = ({ appId, body }: { appId: string; body: Record<string, any> }): Promise<CommonResponse> => { +export const updateTracingStatus = ({ appId, body }: { appId: string, body: Record<string, any> }): Promise<CommonResponse> => { return post<CommonResponse>(`/apps/${appId}/trace`, { body }) } // Webhook Trigger -export const fetchWebhookUrl = ({ appId, nodeId }: { appId: string; nodeId: string }): Promise<WebhookTriggerResponse> => { +export const fetchWebhookUrl = ({ appId, nodeId }: { appId: string, nodeId: string }): Promise<WebhookTriggerResponse> => { return get<WebhookTriggerResponse>( `apps/${appId}/workflows/triggers/webhook`, { params: { node_id: nodeId } }, @@ -210,7 +210,7 @@ export const fetchWebhookUrl = ({ appId, nodeId }: { appId: string; nodeId: stri ) } -export const fetchTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<TracingConfig & { has_not_configured: true }> => { +export const fetchTracingConfig = ({ appId, provider }: { appId: string, provider: TracingProvider }): Promise<TracingConfig & { has_not_configured: true }> => { return get<TracingConfig & { has_not_configured: true }>(`/apps/${appId}/trace-config`, { params: { tracing_provider: provider, @@ -218,14 +218,14 @@ export const fetchTracingConfig = ({ appId, provider }: { appId: string; provide }) } -export const addTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => { +export const addTracingConfig = ({ appId, body }: { appId: string, body: TracingConfig }): Promise<CommonResponse> => { return post<CommonResponse>(`/apps/${appId}/trace-config`, { body }) } -export const updateTracingConfig = ({ appId, body }: { appId: string; body: TracingConfig }): Promise<CommonResponse> => { +export const updateTracingConfig = ({ appId, body }: { appId: string, body: TracingConfig }): Promise<CommonResponse> => { return patch<CommonResponse>(`/apps/${appId}/trace-config`, { body }) } -export const removeTracingConfig = ({ appId, provider }: { appId: string; provider: TracingProvider }): Promise<CommonResponse> => { +export const removeTracingConfig = ({ appId, provider }: { appId: string, provider: TracingProvider }): Promise<CommonResponse> => { return del<CommonResponse>(`/apps/${appId}/trace-config?tracing_provider=${provider}`) } diff --git a/web/service/base.ts b/web/service/base.ts index 91051dbf86..d9f3dba53a 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -1,9 +1,11 @@ -import { API_PREFIX, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_CE_EDITION, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config' -import { refreshAccessTokenOrRelogin } from './refresh-token' -import Toast from '@/app/components/base/toast' -import { basePath } from '@/utils/var' +import type { FetchOptionType, ResponseError } from './fetch' import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type' import type { VisionFile } from '@/types/app' +import type { + DataSourceNodeCompletedResponse, + DataSourceNodeErrorResponse, + DataSourceNodeProcessingResponse, +} from '@/types/pipeline' import type { AgentLogResponse, IterationFinishedResponse, @@ -21,16 +23,15 @@ import type { WorkflowFinishedResponse, WorkflowStartedResponse, } from '@/types/workflow' -import type { FetchOptionType, ResponseError } from './fetch' -import { ContentType, base, getBaseOptions } from './fetch' -import { asyncRunSafe } from '@/utils' -import type { - DataSourceNodeCompletedResponse, - DataSourceNodeErrorResponse, - DataSourceNodeProcessingResponse, -} from '@/types/pipeline' import Cookies from 'js-cookie' +import Toast from '@/app/components/base/toast' +import { API_PREFIX, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_CE_EDITION, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config' +import { asyncRunSafe } from '@/utils' +import { basePath } from '@/utils/var' +import { base, ContentType, getBaseOptions } from './fetch' +import { refreshAccessTokenOrRelogin } from './refresh-token' import { getWebAppPassport } from './webapp-auth' + const TIME_OUT = 100000 export type IOnDataMoreInfo = { @@ -464,7 +465,7 @@ export const ssePost = async ( if (!/^[23]\d{2}$/.test(String(res.status))) { if (res.status === 401) { if (isPublicAPI) { - res.json().then((data: { code?: string; message?: string }) => { + res.json().then((data: { code?: string, message?: string }) => { if (isPublicAPI) { if (data.code === 'web_app_access_denied') requiredWebSSOLogin(data.message, 403) @@ -532,7 +533,8 @@ export const ssePost = async ( onDataSourceNodeCompleted, onDataSourceNodeError, ) - }).catch((e) => { + }) + .catch((e) => { if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().includes('TypeError: Cannot assign to read only property')) Toast.notify({ type: 'error', message: e }) onError?.(e) diff --git a/web/service/billing.ts b/web/service/billing.ts index 979a888582..f06c4f06c6 100644 --- a/web/service/billing.ts +++ b/web/service/billing.ts @@ -1,5 +1,5 @@ -import { get, put } from './base' import type { CurrentPlanInfoBackend, SubscriptionUrlsBackend } from '@/app/components/billing/type' +import { get, put } from './base' export const fetchCurrentPlanInfo = () => { return get<CurrentPlanInfoBackend>('/features') diff --git a/web/service/common.ts b/web/service/common.ts index 1793675bc5..5fc4850d5f 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -1,4 +1,16 @@ -import { del, get, patch, post, put } from './base' +import type { + DefaultModelResponse, + Model, + ModelItem, + ModelLoadBalancingConfig, + ModelParameterRule, + ModelProvider, + ModelTypeEnum, +} from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { + UpdateOpenAIKeyResponse, + ValidateOpenAIKeyResponse, +} from '@/models/app' import type { AccountIntegrate, ApiBasedExtension, @@ -7,9 +19,9 @@ import type { DataSourceNotion, FileUploadConfigResponse, ICurrentWorkspace, - IWorkspace, InitValidateStatusResponse, InvitationResponse, + IWorkspace, LangGeniusVersionResponse, Member, ModerateResponse, @@ -21,21 +33,9 @@ import type { SetupStatusResponse, UserProfileOriginResponse, } from '@/models/common' -import type { - UpdateOpenAIKeyResponse, - ValidateOpenAIKeyResponse, -} from '@/models/app' -import type { - DefaultModelResponse, - Model, - ModelItem, - ModelLoadBalancingConfig, - ModelParameterRule, - ModelProvider, - ModelTypeEnum, -} from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RETRIEVE_METHOD } from '@/types/app' import type { SystemFeatures } from '@/types/feature' +import { del, get, patch, post, put } from './base' type LoginSuccess = { result: 'success' @@ -48,10 +48,10 @@ type LoginFail = { message: string } type LoginResponse = LoginSuccess | LoginFail -export const login = ({ url, body }: { url: string; body: Record<string, any> }): Promise<LoginResponse> => { +export const login = ({ url, body }: { url: string, body: Record<string, any> }): Promise<LoginResponse> => { return post<LoginResponse>(url, { body }) } -export const webAppLogin = ({ url, body }: { url: string; body: Record<string, any> }): Promise<LoginResponse> => { +export const webAppLogin = ({ url, body }: { url: string, body: Record<string, any> }): Promise<LoginResponse> => { return post<LoginResponse>(url, { body }, { isPublicAPI: true }) } @@ -71,50 +71,50 @@ export const fetchSetupStatus = (): Promise<SetupStatusResponse> => { return get<SetupStatusResponse>('/setup') } -export const fetchUserProfile = ({ url, params }: { url: string; params: Record<string, any> }): Promise<UserProfileOriginResponse> => { +export const fetchUserProfile = ({ url, params }: { url: string, params: Record<string, any> }): Promise<UserProfileOriginResponse> => { return get<UserProfileOriginResponse>(url, params, { needAllResponseContent: true }) } -export const updateUserProfile = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CommonResponse> => { +export const updateUserProfile = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } -export const fetchLangGeniusVersion = ({ url, params }: { url: string; params: Record<string, any> }): Promise<LangGeniusVersionResponse> => { +export const fetchLangGeniusVersion = ({ url, params }: { url: string, params: Record<string, any> }): Promise<LangGeniusVersionResponse> => { return get<LangGeniusVersionResponse>(url, { params }) } -export const oauth = ({ url, params }: { url: string; params: Record<string, any> }): Promise<OauthResponse> => { +export const oauth = ({ url, params }: { url: string, params: Record<string, any> }): Promise<OauthResponse> => { return get<OauthResponse>(url, { params }) } -export const oneMoreStep = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CommonResponse> => { +export const oneMoreStep = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } -export const fetchMembers = ({ url, params }: { url: string; params: Record<string, any> }): Promise<{ accounts: Member[] | null }> => { +export const fetchMembers = ({ url, params }: { url: string, params: Record<string, any> }): Promise<{ accounts: Member[] | null }> => { return get<{ accounts: Member[] | null }>(url, { params }) } -export const fetchProviders = ({ url, params }: { url: string; params: Record<string, any> }): Promise<Provider[] | null> => { +export const fetchProviders = ({ url, params }: { url: string, params: Record<string, any> }): Promise<Provider[] | null> => { return get<Provider[] | null>(url, { params }) } -export const validateProviderKey = ({ url, body }: { url: string; body: { token: string } }): Promise<ValidateOpenAIKeyResponse> => { +export const validateProviderKey = ({ url, body }: { url: string, body: { token: string } }): Promise<ValidateOpenAIKeyResponse> => { return post<ValidateOpenAIKeyResponse>(url, { body }) } -export const updateProviderAIKey = ({ url, body }: { url: string; body: { token: string | ProviderAzureToken | ProviderAnthropicToken } }): Promise<UpdateOpenAIKeyResponse> => { +export const updateProviderAIKey = ({ url, body }: { url: string, body: { token: string | ProviderAzureToken | ProviderAnthropicToken } }): Promise<UpdateOpenAIKeyResponse> => { return post<UpdateOpenAIKeyResponse>(url, { body }) } -export const fetchAccountIntegrates = ({ url, params }: { url: string; params: Record<string, any> }): Promise<{ data: AccountIntegrate[] | null }> => { +export const fetchAccountIntegrates = ({ url, params }: { url: string, params: Record<string, any> }): Promise<{ data: AccountIntegrate[] | null }> => { return get<{ data: AccountIntegrate[] | null }>(url, { params }) } -export const inviteMember = ({ url, body }: { url: string; body: Record<string, any> }): Promise<InvitationResponse> => { +export const inviteMember = ({ url, body }: { url: string, body: Record<string, any> }): Promise<InvitationResponse> => { return post<InvitationResponse>(url, { body }) } -export const updateMemberRole = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CommonResponse> => { +export const updateMemberRole = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CommonResponse> => { return put<CommonResponse>(url, { body }) } @@ -125,33 +125,33 @@ export const deleteMemberOrCancelInvitation = ({ url }: { url: string }): Promis export const sendOwnerEmail = (body: { language?: string }): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>('/workspaces/current/members/send-owner-transfer-confirm-email', { body }) -export const verifyOwnerEmail = (body: { code: string; token: string }): Promise<CommonResponse & { is_valid: boolean; email: string; token: string }> => - post<CommonResponse & { is_valid: boolean; email: string; token: string }>('/workspaces/current/members/owner-transfer-check', { body }) +export const verifyOwnerEmail = (body: { code: string, token: string }): Promise<CommonResponse & { is_valid: boolean, email: string, token: string }> => + post<CommonResponse & { is_valid: boolean, email: string, token: string }>('/workspaces/current/members/owner-transfer-check', { body }) -export const ownershipTransfer = (memberID: string, body: { token: string }): Promise<CommonResponse & { is_valid: boolean; email: string; token: string }> => - post<CommonResponse & { is_valid: boolean; email: string; token: string }>(`/workspaces/current/members/${memberID}/owner-transfer`, { body }) +export const ownershipTransfer = (memberID: string, body: { token: string }): Promise<CommonResponse & { is_valid: boolean, email: string, token: string }> => + post<CommonResponse & { is_valid: boolean, email: string, token: string }>(`/workspaces/current/members/${memberID}/owner-transfer`, { body }) export const fetchFilePreview = ({ fileID }: { fileID: string }): Promise<{ content: string }> => { return get<{ content: string }>(`/files/${fileID}/preview`) } -export const fetchCurrentWorkspace = ({ url, params }: { url: string; params: Record<string, any> }): Promise<ICurrentWorkspace> => { +export const fetchCurrentWorkspace = ({ url, params }: { url: string, params: Record<string, any> }): Promise<ICurrentWorkspace> => { return post<ICurrentWorkspace>(url, { body: params }) } -export const updateCurrentWorkspace = ({ url, body }: { url: string; body: Record<string, any> }): Promise<ICurrentWorkspace> => { +export const updateCurrentWorkspace = ({ url, body }: { url: string, body: Record<string, any> }): Promise<ICurrentWorkspace> => { return post<ICurrentWorkspace>(url, { body }) } -export const fetchWorkspaces = ({ url, params }: { url: string; params: Record<string, any> }): Promise<{ workspaces: IWorkspace[] }> => { +export const fetchWorkspaces = ({ url, params }: { url: string, params: Record<string, any> }): Promise<{ workspaces: IWorkspace[] }> => { return get<{ workspaces: IWorkspace[] }>(url, { params }) } -export const switchWorkspace = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CommonResponse & { new_tenant: IWorkspace }> => { +export const switchWorkspace = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CommonResponse & { new_tenant: IWorkspace }> => { return post<CommonResponse & { new_tenant: IWorkspace }>(url, { body }) } -export const updateWorkspaceInfo = ({ url, body }: { url: string; body: Record<string, any> }): Promise<ICurrentWorkspace> => { +export const updateWorkspaceInfo = ({ url, body }: { url: string, body: Record<string, any> }): Promise<ICurrentWorkspace> => { return post<ICurrentWorkspace>(url, { body }) } @@ -171,18 +171,18 @@ export const fetchPluginProviders = (url: string): Promise<PluginProvider[] | nu return get<PluginProvider[] | null>(url) } -export const validatePluginProviderKey = ({ url, body }: { url: string; body: { credentials: any } }): Promise<ValidateOpenAIKeyResponse> => { +export const validatePluginProviderKey = ({ url, body }: { url: string, body: { credentials: any } }): Promise<ValidateOpenAIKeyResponse> => { return post<ValidateOpenAIKeyResponse>(url, { body }) } -export const updatePluginProviderAIKey = ({ url, body }: { url: string; body: { credentials: any } }): Promise<UpdateOpenAIKeyResponse> => { +export const updatePluginProviderAIKey = ({ url, body }: { url: string, body: { credentials: any } }): Promise<UpdateOpenAIKeyResponse> => { return post<UpdateOpenAIKeyResponse>(url, { body }) } -export const invitationCheck = ({ url, params }: { url: string; params: { workspace_id?: string; email?: string; token: string } }): Promise<CommonResponse & { is_valid: boolean; data: { workspace_name: string; email: string; workspace_id: string } }> => { - return get<CommonResponse & { is_valid: boolean; data: { workspace_name: string; email: string; workspace_id: string } }>(url, { params }) +export const invitationCheck = ({ url, params }: { url: string, params: { workspace_id?: string, email?: string, token: string } }): Promise<CommonResponse & { is_valid: boolean, data: { workspace_name: string, email: string, workspace_id: string } }> => { + return get<CommonResponse & { is_valid: boolean, data: { workspace_name: string, email: string, workspace_id: string } }>(url, { params }) } -export const activateMember = ({ url, body }: { url: string; body: any }): Promise<LoginResponse> => { +export const activateMember = ({ url, body }: { url: string, body: any }): Promise<LoginResponse> => { return post<LoginResponse>(url, { body }) } @@ -216,27 +216,27 @@ export const fetchModelList = (url: string): Promise<{ data: Model[] }> => { return get<{ data: Model[] }>(url) } -export const validateModelProvider = ({ url, body }: { url: string; body: any }): Promise<ValidateOpenAIKeyResponse> => { +export const validateModelProvider = ({ url, body }: { url: string, body: any }): Promise<ValidateOpenAIKeyResponse> => { return post<ValidateOpenAIKeyResponse>(url, { body }) } -export const validateModelLoadBalancingCredentials = ({ url, body }: { url: string; body: any }): Promise<ValidateOpenAIKeyResponse> => { +export const validateModelLoadBalancingCredentials = ({ url, body }: { url: string, body: any }): Promise<ValidateOpenAIKeyResponse> => { return post<ValidateOpenAIKeyResponse>(url, { body }) } -export const setModelProvider = ({ url, body }: { url: string; body: any }): Promise<CommonResponse> => { +export const setModelProvider = ({ url, body }: { url: string, body: any }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } -export const deleteModelProvider = ({ url, body }: { url: string; body?: any }): Promise<CommonResponse> => { +export const deleteModelProvider = ({ url, body }: { url: string, body?: any }): Promise<CommonResponse> => { return del<CommonResponse>(url, { body }) } -export const changeModelProviderPriority = ({ url, body }: { url: string; body: any }): Promise<CommonResponse> => { +export const changeModelProviderPriority = ({ url, body }: { url: string, body: any }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } -export const setModelProviderModel = ({ url, body }: { url: string; body: any }): Promise<CommonResponse> => { +export const setModelProviderModel = ({ url, body }: { url: string, body: any }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } @@ -252,7 +252,7 @@ export const fetchDefaultModal = (url: string): Promise<{ data: DefaultModelResp return get<{ data: DefaultModelResponse }>(url) } -export const updateDefaultModel = ({ url, body }: { url: string; body: any }): Promise<CommonResponse> => { +export const updateDefaultModel = ({ url, body }: { url: string, body: any }): Promise<CommonResponse> => { return post<CommonResponse>(url, { body }) } @@ -280,11 +280,11 @@ export const fetchApiBasedExtensionDetail = (url: string): Promise<ApiBasedExten return get<ApiBasedExtension>(url) } -export const addApiBasedExtension = ({ url, body }: { url: string; body: ApiBasedExtension }): Promise<ApiBasedExtension> => { +export const addApiBasedExtension = ({ url, body }: { url: string, body: ApiBasedExtension }): Promise<ApiBasedExtension> => { return post<ApiBasedExtension>(url, { body }) } -export const updateApiBasedExtension = ({ url, body }: { url: string; body: ApiBasedExtension }): Promise<ApiBasedExtension> => { +export const updateApiBasedExtension = ({ url, body }: { url: string, body: ApiBasedExtension }): Promise<ApiBasedExtension> => { return post<ApiBasedExtension>(url, { body }) } @@ -296,7 +296,7 @@ export const fetchCodeBasedExtensionList = (url: string): Promise<CodeBasedExten return get<CodeBasedExtension>(url) } -export const moderate = (url: string, body: { app_id: string; text: string }): Promise<ModerateResponse> => { +export const moderate = (url: string, body: { app_id: string, text: string }): Promise<ModerateResponse> => { return post<ModerateResponse>(url, { body }) } @@ -311,79 +311,79 @@ export const getSystemFeatures = (): Promise<SystemFeatures> => { return get<SystemFeatures>('/system-features') } -export const enableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }): Promise<CommonResponse> => +export const enableModel = (url: string, body: { model: string, model_type: ModelTypeEnum }): Promise<CommonResponse> => patch<CommonResponse>(url, { body }) -export const disableModel = (url: string, body: { model: string; model_type: ModelTypeEnum }): Promise<CommonResponse> => +export const disableModel = (url: string, body: { model: string, model_type: ModelTypeEnum }): Promise<CommonResponse> => patch<CommonResponse>(url, { body }) -export const sendForgotPasswordEmail = ({ url, body }: { url: string; body: { email: string } }): Promise<CommonResponse & { data: string }> => +export const sendForgotPasswordEmail = ({ url, body }: { url: string, body: { email: string } }): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>(url, { body }) -export const verifyForgotPasswordToken = ({ url, body }: { url: string; body: { token: string } }): Promise<CommonResponse & { is_valid: boolean; email: string }> => { - return post<CommonResponse & { is_valid: boolean; email: string }>(url, { body }) +export const verifyForgotPasswordToken = ({ url, body }: { url: string, body: { token: string } }): Promise<CommonResponse & { is_valid: boolean, email: string }> => { + return post<CommonResponse & { is_valid: boolean, email: string }>(url, { body }) } -export const changePasswordWithToken = ({ url, body }: { url: string; body: { token: string; new_password: string; password_confirm: string } }): Promise<CommonResponse> => +export const changePasswordWithToken = ({ url, body }: { url: string, body: { token: string, new_password: string, password_confirm: string } }): Promise<CommonResponse> => post<CommonResponse>(url, { body }) -export const sendWebAppForgotPasswordEmail = ({ url, body }: { url: string; body: { email: string } }): Promise<CommonResponse & { data: string }> => +export const sendWebAppForgotPasswordEmail = ({ url, body }: { url: string, body: { email: string } }): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>(url, { body }, { isPublicAPI: true }) -export const verifyWebAppForgotPasswordToken = ({ url, body }: { url: string; body: { token: string } }): Promise<CommonResponse & { is_valid: boolean; email: string }> => { - return post<CommonResponse & { is_valid: boolean; email: string }>(url, { body }, { isPublicAPI: true }) +export const verifyWebAppForgotPasswordToken = ({ url, body }: { url: string, body: { token: string } }): Promise<CommonResponse & { is_valid: boolean, email: string }> => { + return post<CommonResponse & { is_valid: boolean, email: string }>(url, { body }, { isPublicAPI: true }) } -export const changeWebAppPasswordWithToken = ({ url, body }: { url: string; body: { token: string; new_password: string; password_confirm: string } }): Promise<CommonResponse> => +export const changeWebAppPasswordWithToken = ({ url, body }: { url: string, body: { token: string, new_password: string, password_confirm: string } }): Promise<CommonResponse> => post<CommonResponse>(url, { body }, { isPublicAPI: true }) -export const uploadRemoteFileInfo = (url: string, isPublic?: boolean, silent?: boolean): Promise<{ id: string; name: string; size: number; mime_type: string; url: string }> => { - return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic, silent }) +export const uploadRemoteFileInfo = (url: string, isPublic?: boolean, silent?: boolean): Promise<{ id: string, name: string, size: number, mime_type: string, url: string }> => { + return post<{ id: string, name: string, size: number, mime_type: string, url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic, silent }) } export const sendEMailLoginCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } }) -export const emailLoginWithCode = (data: { email: string; code: string; token: string; language: string }): Promise<LoginResponse> => +export const emailLoginWithCode = (data: { email: string, code: string, token: string, language: string }): Promise<LoginResponse> => post<LoginResponse>('/email-code-login/validity', { body: data }) -export const sendResetPasswordCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string; message?: string; code?: string }> => - post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } }) +export const sendResetPasswordCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string, message?: string, code?: string }> => + post<CommonResponse & { data: string, message?: string, code?: string }>('/forgot-password', { body: { email, language } }) -export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }): Promise<CommonResponse & { is_valid: boolean; token: string }> => - post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body }) +export const verifyResetPasswordCode = (body: { email: string, code: string, token: string }): Promise<CommonResponse & { is_valid: boolean, token: string }> => + post<CommonResponse & { is_valid: boolean, token: string }>('/forgot-password/validity', { body }) export const sendWebAppEMailLoginCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } }, { isPublicAPI: true }) -export const webAppEmailLoginWithCode = (data: { email: string; code: string; token: string }): Promise<LoginResponse> => +export const webAppEmailLoginWithCode = (data: { email: string, code: string, token: string }): Promise<LoginResponse> => post<LoginResponse>('/email-code-login/validity', { body: data }, { isPublicAPI: true }) -export const sendWebAppResetPasswordCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string; message?: string; code?: string }> => - post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } }, { isPublicAPI: true }) +export const sendWebAppResetPasswordCode = (email: string, language = 'en-US'): Promise<CommonResponse & { data: string, message?: string, code?: string }> => + post<CommonResponse & { data: string, message?: string, code?: string }>('/forgot-password', { body: { email, language } }, { isPublicAPI: true }) -export const verifyWebAppResetPasswordCode = (body: { email: string; code: string; token: string }): Promise<CommonResponse & { is_valid: boolean; token: string }> => - post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body }, { isPublicAPI: true }) +export const verifyWebAppResetPasswordCode = (body: { email: string, code: string, token: string }): Promise<CommonResponse & { is_valid: boolean, token: string }> => + post<CommonResponse & { is_valid: boolean, token: string }>('/forgot-password/validity', { body }, { isPublicAPI: true }) export const sendDeleteAccountCode = (): Promise<CommonResponse & { data: string }> => get<CommonResponse & { data: string }>('/account/delete/verify') -export const verifyDeleteAccountCode = (body: { code: string; token: string }): Promise<CommonResponse & { is_valid: boolean }> => +export const verifyDeleteAccountCode = (body: { code: string, token: string }): Promise<CommonResponse & { is_valid: boolean }> => post<CommonResponse & { is_valid: boolean }>('/account/delete', { body }) -export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }): Promise<CommonResponse> => +export const submitDeleteAccountFeedback = (body: { feedback: string, email: string }): Promise<CommonResponse> => post<CommonResponse>('/account/delete/feedback', { body }) export const getDocDownloadUrl = (doc_name: string): Promise<{ url: string }> => get<{ url: string }>('/compliance/download', { params: { doc_name } }, { silent: true }) -export const sendVerifyCode = (body: { email: string; phase: string; token?: string }): Promise<CommonResponse & { data: string }> => +export const sendVerifyCode = (body: { email: string, phase: string, token?: string }): Promise<CommonResponse & { data: string }> => post<CommonResponse & { data: string }>('/account/change-email', { body }) -export const verifyEmail = (body: { email: string; code: string; token: string }): Promise<CommonResponse & { is_valid: boolean; email: string; token: string }> => - post<CommonResponse & { is_valid: boolean; email: string; token: string }>('/account/change-email/validity', { body }) +export const verifyEmail = (body: { email: string, code: string, token: string }): Promise<CommonResponse & { is_valid: boolean, email: string, token: string }> => + post<CommonResponse & { is_valid: boolean, email: string, token: string }>('/account/change-email/validity', { body }) -export const resetEmail = (body: { new_email: string; token: string }): Promise<CommonResponse> => +export const resetEmail = (body: { new_email: string, token: string }): Promise<CommonResponse> => post<CommonResponse>('/account/change-email/reset', { body }) export const checkEmailExisted = (body: { email: string }): Promise<CommonResponse> => diff --git a/web/service/datasets.ts b/web/service/datasets.ts index 8791a61b7c..eb22af8446 100644 --- a/web/service/datasets.ts +++ b/web/service/datasets.ts @@ -1,7 +1,13 @@ -import qs from 'qs' -import { del, get, patch, post, put } from './base' +import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations' +import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' +import type { + ApiKeysListResponse, + CreateApiKeyResponse, +} from '@/models/app' +import type { CommonResponse, DataSourceNotionWorkspace } from '@/models/common' import type { CreateDocumentReq, + createDocumentResponse, DataSet, DataSetListResponse, ErrorDocsResponse, @@ -21,17 +27,11 @@ import type { IndexingStatusResponse, ProcessRuleResponse, RelatedAppResponse, - createDocumentResponse, } from '@/models/datasets' -import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' -import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations' -import type { CommonResponse, DataSourceNotionWorkspace } from '@/models/common' -import { DataSourceProvider } from '@/models/common' -import type { - ApiKeysListResponse, - CreateApiKeyResponse, -} from '@/models/app' import type { RetrievalConfig } from '@/types/app' +import qs from 'qs' +import { DataSourceProvider } from '@/models/common' +import { del, get, patch, post, put } from './base' // apis for documents in a dataset @@ -58,9 +58,7 @@ export const updateDatasetSetting = ({ body, }: { datasetId: string - body: Partial<Pick<DataSet, - 'name' | 'description' | 'permission' | 'partial_member_list' | 'indexing_technique' | 'retrieval_model' | 'embedding_model' | 'embedding_model_provider' | 'icon_info' | 'doc_form' - >> + body: Partial<Pick<DataSet, 'name' | 'description' | 'permission' | 'partial_member_list' | 'indexing_technique' | 'retrieval_model' | 'embedding_model' | 'embedding_model_provider' | 'icon_info' | 'doc_form'>> }): Promise<DataSet> => { return patch<DataSet>(`/datasets/${datasetId}`, { body }) } @@ -96,7 +94,7 @@ export const fetchExternalAPI = ({ apiTemplateId }: { apiTemplateId: string }): return get<ExternalAPIItem>(`/datasets/external-knowledge-api/${apiTemplateId}`) } -export const updateExternalAPI = ({ apiTemplateId, body }: { apiTemplateId: string; body: ExternalAPIItem }): Promise<ExternalAPIItem> => { +export const updateExternalAPI = ({ apiTemplateId, body }: { apiTemplateId: string, body: ExternalAPIItem }): Promise<ExternalAPIItem> => { return patch<ExternalAPIItem>(`/datasets/external-knowledge-api/${apiTemplateId}`, { body }) } @@ -127,7 +125,7 @@ export const createFirstDocument = ({ body }: { body: CreateDocumentReq }): Prom return post<createDocumentResponse>('/datasets/init', { body }) } -export const createDocument = ({ datasetId, body }: { datasetId: string; body: CreateDocumentReq }): Promise<createDocumentResponse> => { +export const createDocument = ({ datasetId, body }: { datasetId: string, body: CreateDocumentReq }): Promise<createDocumentResponse> => { return post<createDocumentResponse>(`/datasets/${datasetId}/documents`, { body }) } @@ -160,24 +158,24 @@ export const resumeDocIndexing = ({ datasetId, documentId }: CommonDocReq): Prom return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/processing/resume`) } -export const preImportNotionPages = ({ url, datasetId }: { url: string; datasetId?: string }): Promise<{ notion_info: DataSourceNotionWorkspace[] }> => { +export const preImportNotionPages = ({ url, datasetId }: { url: string, datasetId?: string }): Promise<{ notion_info: DataSourceNotionWorkspace[] }> => { return get<{ notion_info: DataSourceNotionWorkspace[] }>(url, { params: { dataset_id: datasetId } }) } -export const modifyDocMetadata = ({ datasetId, documentId, body }: CommonDocReq & { body: { doc_type: string; doc_metadata: Record<string, any> } }): Promise<CommonResponse> => { +export const modifyDocMetadata = ({ datasetId, documentId, body }: CommonDocReq & { body: { doc_type: string, doc_metadata: Record<string, any> } }): Promise<CommonResponse> => { return put<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/metadata`, { body }) } // hit testing -export const hitTesting = ({ datasetId, queryText, retrieval_model }: { datasetId: string; queryText: string; retrieval_model: RetrievalConfig }): Promise<HitTestingResponse> => { +export const hitTesting = ({ datasetId, queryText, retrieval_model }: { datasetId: string, queryText: string, retrieval_model: RetrievalConfig }): Promise<HitTestingResponse> => { return post<HitTestingResponse>(`/datasets/${datasetId}/hit-testing`, { body: { query: queryText, retrieval_model } }) } -export const externalKnowledgeBaseHitTesting = ({ datasetId, query, external_retrieval_model }: { datasetId: string; query: string; external_retrieval_model: { top_k: number; score_threshold: number; score_threshold_enabled: boolean } }): Promise<ExternalKnowledgeBaseHitTestingResponse> => { +export const externalKnowledgeBaseHitTesting = ({ datasetId, query, external_retrieval_model }: { datasetId: string, query: string, external_retrieval_model: { top_k: number, score_threshold: number, score_threshold_enabled: boolean } }): Promise<ExternalKnowledgeBaseHitTestingResponse> => { return post<ExternalKnowledgeBaseHitTestingResponse>(`/datasets/${datasetId}/external-hit-testing`, { body: { query, external_retrieval_model } }) } -export const fetchTestingRecords = ({ datasetId, params }: { datasetId: string; params: { page: number; limit: number } }): Promise<HitTestingRecordsResponse> => { +export const fetchTestingRecords = ({ datasetId, params }: { datasetId: string, params: { page: number, limit: number } }): Promise<HitTestingRecordsResponse> => { return get<HitTestingRecordsResponse>(`/datasets/${datasetId}/queries`, { params }) } @@ -185,7 +183,7 @@ export const fetchFileIndexingEstimate = (body: IndexingEstimateParams): Promise return post<FileIndexingEstimateResponse>('/datasets/indexing-estimate', { body }) } -export const fetchNotionPagePreview = ({ pageID, pageType, credentialID }: { pageID: string; pageType: string; credentialID: string }): Promise<{ content: string }> => { +export const fetchNotionPagePreview = ({ pageID, pageType, credentialID }: { pageID: string, pageType: string, credentialID: string }): Promise<{ content: string }> => { return get<{ content: string }>(`notion/pages/${pageID}/${pageType}/preview`, { params: { credential_id: credentialID, @@ -193,15 +191,15 @@ export const fetchNotionPagePreview = ({ pageID, pageType, credentialID }: { pag }) } -export const fetchApiKeysList = ({ url, params }: { url: string; params: Record<string, any> }): Promise<ApiKeysListResponse> => { +export const fetchApiKeysList = ({ url, params }: { url: string, params: Record<string, any> }): Promise<ApiKeysListResponse> => { return get<ApiKeysListResponse>(url, params) } -export const delApikey = ({ url, params }: { url: string; params: Record<string, any> }): Promise<CommonResponse> => { +export const delApikey = ({ url, params }: { url: string, params: Record<string, any> }): Promise<CommonResponse> => { return del<CommonResponse>(url, params) } -export const createApikey = ({ url, body }: { url: string; body: Record<string, any> }): Promise<CreateApiKeyResponse> => { +export const createApikey = ({ url, body }: { url: string, body: Record<string, any> }): Promise<CreateApiKeyResponse> => { return post<CreateApiKeyResponse>(url, body) } @@ -286,6 +284,6 @@ export const getErrorDocs = ({ datasetId }: { datasetId: string }): Promise<Erro return get<ErrorDocsResponse>(`/datasets/${datasetId}/error-docs`) } -export const retryErrorDocs = ({ datasetId, document_ids }: { datasetId: string; document_ids: string[] }): Promise<CommonResponse> => { +export const retryErrorDocs = ({ datasetId, document_ids }: { datasetId: string, document_ids: string[] }): Promise<CommonResponse> => { return post<CommonResponse>(`/datasets/${datasetId}/retry`, { body: { document_ids } }) } diff --git a/web/service/debug.ts b/web/service/debug.ts index 3f3abda2d2..850f3dfc24 100644 --- a/web/service/debug.ts +++ b/web/service/debug.ts @@ -1,8 +1,8 @@ -import { get, post, ssePost } from './base' import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnMessageEnd, IOnMessageReplace, IOnThought } from './base' +import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug' import type { AppModeEnum, ModelModeType } from '@/types/app' -import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { get, post, ssePost } from './base' export type BasicAppFirstRes = { prompt: string @@ -106,8 +106,8 @@ export const fetchPromptTemplate = ({ mode, modelName, hasSetDataSet, -}: { appMode: AppModeEnum; mode: ModelModeType; modelName: string; hasSetDataSet: boolean }) => { - return get<Promise<{ chat_prompt_config: ChatPromptConfig; completion_prompt_config: CompletionPromptConfig; stop: [] }>>('/app/prompt-templates', { +}: { appMode: AppModeEnum, mode: ModelModeType, modelName: string, hasSetDataSet: boolean }) => { + return get<Promise<{ chat_prompt_config: ChatPromptConfig, completion_prompt_config: CompletionPromptConfig, stop: [] }>>('/app/prompt-templates', { params: { app_mode: appMode, model_mode: mode, @@ -120,6 +120,6 @@ export const fetchPromptTemplate = ({ export const fetchTextGenerationMessage = ({ appId, messageId, -}: { appId: string; messageId: string }) => { +}: { appId: string, messageId: string }) => { return get<Promise<any>>(`/apps/${appId}/messages/${messageId}`) } diff --git a/web/service/demo/index.tsx b/web/service/demo/index.tsx index b0b76bfcff..d538d6fda2 100644 --- a/web/service/demo/index.tsx +++ b/web/service/demo/index.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' -import React from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { createApp, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps' +import React from 'react' import Loading from '@/app/components/base/loading' import { AppModeEnum } from '@/types/app' +import { createApp, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps' import { useAppDailyConversations, useAppDailyEndUsers, @@ -84,12 +84,16 @@ const Service: FC = () => { return ( <div> - <div className='flex flex-col gap-3'> + <div className="flex flex-col gap-3"> <div> <div>1.App list</div> <div> {appList.data.map(item => ( - <div key={item.id}>{item.id} {item.name}</div> + <div key={item.id}> + {item.id} + {' '} + {item.name} + </div> ))} </div> </div> diff --git a/web/service/explore.ts b/web/service/explore.ts index 6a440d7f5d..70d5de37f2 100644 --- a/web/service/explore.ts +++ b/web/service/explore.ts @@ -1,6 +1,6 @@ -import { del, get, patch, post } from './base' -import type { App, AppCategory } from '@/models/explore' import type { AccessMode } from '@/models/access-control' +import type { App, AppCategory } from '@/models/explore' +import { del, get, patch, post } from './base' export const fetchAppList = () => { return get<{ diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 030549bdab..d0af932d73 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -1,9 +1,9 @@ import type { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, Hooks } from 'ky' -import ky from 'ky' import type { IOtherOptions } from './base' +import Cookies from 'js-cookie' +import ky from 'ky' import Toast from '@/app/components/base/toast' import { API_PREFIX, APP_VERSION, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_MARKETPLACE, MARKETPLACE_API_PREFIX, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config' -import Cookies from 'js-cookie' import { getWebAppAccessToken, getWebAppPassport } from './webapp-auth' const TIME_OUT = 100000 @@ -24,7 +24,8 @@ export type FetchOptionType = Omit<RequestInit, 'body'> & { } const afterResponse204: AfterResponseHook = async (_request, _options, response) => { - if (response.status === 204) return Response.json({ result: 'success' }) + if (response.status === 204) + return Response.json({ result: 'success' }) } export type ResponseError = { @@ -209,8 +210,9 @@ async function base<T>(url: string, options: FetchOptionType = {}, otherOptions: if ( contentType && [ContentType.download, ContentType.audio, ContentType.downloadZip].includes(contentType) - ) + ) { return await res.blob() as T + } return await res.json() as T } diff --git a/web/service/knowledge/use-create-dataset.ts b/web/service/knowledge/use-create-dataset.ts index 2530188c7e..eb656c2994 100644 --- a/web/service/knowledge/use-create-dataset.ts +++ b/web/service/knowledge/use-create-dataset.ts @@ -1,8 +1,6 @@ -import groupBy from 'lodash-es/groupBy' import type { MutationOptions } from '@tanstack/react-query' -import { useMutation } from '@tanstack/react-query' -import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets' import type { IndexingType } from '@/app/components/datasets/create/step-two' +import type { DataSourceProvider, NotionPage } from '@/models/common' import type { ChunkingMode, CrawlOptions, @@ -10,6 +8,7 @@ import type { CreateDatasetReq, CreateDatasetResponse, CreateDocumentReq, + createDocumentResponse, CustomFile, DataSourceType, FileIndexingEstimateResponse, @@ -17,10 +16,11 @@ import type { NotionInfo, ProcessRule, ProcessRuleResponse, - createDocumentResponse, } from '@/models/datasets' -import type { DataSourceProvider, NotionPage } from '@/models/common' +import { useMutation } from '@tanstack/react-query' +import groupBy from 'lodash-es/groupBy' import { post } from '../base' +import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets' const NAME_SPACE = 'knowledge/create-dataset' diff --git a/web/service/knowledge/use-dataset.ts b/web/service/knowledge/use-dataset.ts index 2b0c78b249..87fc1f2aec 100644 --- a/web/service/knowledge/use-dataset.ts +++ b/web/service/knowledge/use-dataset.ts @@ -1,16 +1,10 @@ import type { MutationOptions } from '@tanstack/react-query' -import { - keepPreviousData, - useInfiniteQuery, - useMutation, - useQuery, - useQueryClient, -} from '@tanstack/react-query' -import qs from 'qs' +import type { ApiKeysListResponse } from '@/models/app' +import type { CommonResponse } from '@/models/common' import type { DataSet, - DataSetListResponse, DatasetListRequest, + DataSetListResponse, ErrorDocsResponse, ExternalAPIListResponse, FetchDatasetsParams, @@ -20,10 +14,16 @@ import type { ProcessRuleResponse, RelatedAppResponse, } from '@/models/datasets' -import type { ApiKeysListResponse } from '@/models/app' +import { + keepPreviousData, + useInfiniteQuery, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query' +import qs from 'qs' import { get, post } from '../base' import { useInvalid } from '../use-base' -import type { CommonResponse } from '@/models/common' const NAME_SPACE = 'dataset' @@ -199,7 +199,7 @@ export const useInvalidateExternalKnowledgeApiList = () => { export const useDatasetTestingRecords = ( datasetId?: string, - params?: { page: number; limit: number }, + params?: { page: number, limit: number }, ) => { return useQuery<HitTestingRecordsResponse>({ queryKey: [NAME_SPACE, 'testing-records', datasetId, params], diff --git a/web/service/knowledge/use-document.ts b/web/service/knowledge/use-document.ts index c3321b7a76..476f3534bc 100644 --- a/web/service/knowledge/use-document.ts +++ b/web/service/knowledge/use-document.ts @@ -1,15 +1,15 @@ +import type { MetadataType, SortType } from '../datasets' +import type { CommonResponse } from '@/models/common' +import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets' import { useMutation, useQuery, } from '@tanstack/react-query' -import { del, get, patch } from '../base' -import { useInvalid } from '../use-base' -import type { MetadataType, SortType } from '../datasets' -import { pauseDocIndexing, resumeDocIndexing } from '../datasets' -import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets' -import { DocumentActionType } from '@/models/datasets' -import type { CommonResponse } from '@/models/common' import { normalizeStatusForQuery } from '@/app/components/datasets/documents/status-filter' +import { DocumentActionType } from '@/models/datasets' +import { del, get, patch } from '../base' +import { pauseDocIndexing, resumeDocIndexing } from '../datasets' +import { useInvalid } from '../use-base' const NAME_SPACE = 'knowledge/document' @@ -22,7 +22,7 @@ export const useDocumentList = (payload: { limit: number sort?: SortType status?: string - }, + } refetchInterval?: number | false }) => { const { query, datasetId, refetchInterval } = payload diff --git a/web/service/knowledge/use-hit-testing.ts b/web/service/knowledge/use-hit-testing.ts index dfa030a01f..75c111483e 100644 --- a/web/service/knowledge/use-hit-testing.ts +++ b/web/service/knowledge/use-hit-testing.ts @@ -1,5 +1,3 @@ -import { useMutation, useQuery } from '@tanstack/react-query' -import { useInvalid } from '../use-base' import type { ExternalKnowledgeBaseHitTestingRequest, ExternalKnowledgeBaseHitTestingResponse, @@ -8,7 +6,9 @@ import type { HitTestingRequest, HitTestingResponse, } from '@/models/datasets' +import { useMutation, useQuery } from '@tanstack/react-query' import { get, post } from '../base' +import { useInvalid } from '../use-base' const NAME_SPACE = 'hit-testing' diff --git a/web/service/knowledge/use-import.ts b/web/service/knowledge/use-import.ts index 07579a065e..0fc7bc9dfa 100644 --- a/web/service/knowledge/use-import.ts +++ b/web/service/knowledge/use-import.ts @@ -1,6 +1,6 @@ +import type { DataSourceNotionWorkspace } from '@/models/common' import { useQuery, useQueryClient } from '@tanstack/react-query' import { get } from '../base' -import type { DataSourceNotionWorkspace } from '@/models/common' type PreImportNotionPagesParams = { datasetId: string diff --git a/web/service/knowledge/use-metadata.spec.tsx b/web/service/knowledge/use-metadata.spec.tsx index 11b68168e1..0ab4825482 100644 --- a/web/service/knowledge/use-metadata.spec.tsx +++ b/web/service/knowledge/use-metadata.spec.tsx @@ -1,6 +1,6 @@ -import { DataType } from '@/app/components/datasets/metadata/types' -import { act, renderHook } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { act, renderHook } from '@testing-library/react' +import { DataType } from '@/app/components/datasets/metadata/types' import { useBatchUpdateDocMetadata } from '@/service/knowledge/use-metadata' import { useDocumentListKey } from './use-document' diff --git a/web/service/knowledge/use-metadata.ts b/web/service/knowledge/use-metadata.ts index eb85142d9f..50b2c47ae3 100644 --- a/web/service/knowledge/use-metadata.ts +++ b/web/service/knowledge/use-metadata.ts @@ -1,9 +1,9 @@ import type { BuiltInMetadataItem, MetadataBatchEditToServer, MetadataItemWithValueLength } from '@/app/components/datasets/metadata/types' -import { del, get, patch, post } from '../base' -import { useDocumentListKey, useInvalidDocumentList } from './use-document' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useInvalid } from '../use-base' import type { DocumentDetailResponse } from '@/models/datasets' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { del, get, patch, post } from '../base' +import { useInvalid } from '../use-base' +import { useDocumentListKey, useInvalidDocumentList } from './use-document' const NAME_SPACE = 'dataset-metadata' diff --git a/web/service/knowledge/use-segment.ts b/web/service/knowledge/use-segment.ts index 8b3e939e73..1d0ce4b774 100644 --- a/web/service/knowledge/use-segment.ts +++ b/web/service/knowledge/use-segment.ts @@ -1,5 +1,3 @@ -import { useMutation, useQuery } from '@tanstack/react-query' -import { del, get, patch, post } from '../base' import type { CommonResponse } from '@/models/common' import type { BatchImportResponse, @@ -7,9 +5,11 @@ import type { ChildSegmentsResponse, ChunkingMode, SegmentDetailModel, - SegmentUpdater, SegmentsResponse, + SegmentUpdater, } from '@/models/datasets' +import { useMutation, useQuery } from '@tanstack/react-query' +import { del, get, patch, post } from '../base' const NAME_SPACE = 'segment' @@ -45,9 +45,9 @@ export const useSegmentList = ( export const useUpdateSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'update'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: SegmentUpdater }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentId: string, body: SegmentUpdater }) => { const { datasetId, documentId, segmentId, body } = payload - return patch<{ data: SegmentDetailModel; doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) + return patch<{ data: SegmentDetailModel, doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, { body }) }, }) } @@ -55,9 +55,9 @@ export const useUpdateSegment = () => { export const useAddSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'add'], - mutationFn: (payload: { datasetId: string; documentId: string; body: SegmentUpdater }) => { + mutationFn: (payload: { datasetId: string, documentId: string, body: SegmentUpdater }) => { const { datasetId, documentId, body } = payload - return post<{ data: SegmentDetailModel; doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segment`, { body }) + return post<{ data: SegmentDetailModel, doc_form: ChunkingMode }>(`/datasets/${datasetId}/documents/${documentId}/segment`, { body }) }, }) } @@ -65,7 +65,7 @@ export const useAddSegment = () => { export const useEnableSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'enable'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentIds: string[] }) => { const { datasetId, documentId, segmentIds } = payload const query = segmentIds.map(id => `segment_id=${id}`).join('&') return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/enable?${query}`) @@ -76,7 +76,7 @@ export const useEnableSegment = () => { export const useDisableSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'disable'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentIds: string[] }) => { const { datasetId, documentId, segmentIds } = payload const query = segmentIds.map(id => `segment_id=${id}`).join('&') return patch<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segment/disable?${query}`) @@ -87,7 +87,7 @@ export const useDisableSegment = () => { export const useDeleteSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'delete'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentIds: string[] }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentIds: string[] }) => { const { datasetId, documentId, segmentIds } = payload const query = segmentIds.map(id => `segment_id=${id}`).join('&') return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments?${query}`) @@ -124,7 +124,7 @@ export const useChildSegmentList = ( export const useDeleteChildSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'childChunk', 'delete'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentId: string, childChunkId: string }) => { const { datasetId, documentId, segmentId, childChunkId } = payload return del<CommonResponse>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`) }, @@ -134,7 +134,7 @@ export const useDeleteChildSegment = () => { export const useAddChildSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'childChunk', 'add'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; body: { content: string } }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentId: string, body: { content: string } }) => { const { datasetId, documentId, segmentId, body } = payload return post<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, { body }) }, @@ -144,7 +144,7 @@ export const useAddChildSegment = () => { export const useUpdateChildSegment = () => { return useMutation({ mutationKey: [NAME_SPACE, 'childChunk', 'update'], - mutationFn: (payload: { datasetId: string; documentId: string; segmentId: string; childChunkId: string; body: { content: string } }) => { + mutationFn: (payload: { datasetId: string, documentId: string, segmentId: string, childChunkId: string, body: { content: string } }) => { const { datasetId, documentId, segmentId, childChunkId, body } = payload return patch<{ data: ChildChunkDetail }>(`/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, { body }) }, @@ -154,7 +154,7 @@ export const useUpdateChildSegment = () => { export const useSegmentBatchImport = () => { return useMutation({ mutationKey: [NAME_SPACE, 'batchImport'], - mutationFn: (payload: { url: string; body: { upload_file_id: string } }) => { + mutationFn: (payload: { url: string, body: { upload_file_id: string } }) => { const { url, body } = payload return post<BatchImportResponse>(url, { body }) }, diff --git a/web/service/log.ts b/web/service/log.ts index f54e93630f..aa0be7ac3b 100644 --- a/web/service/log.ts +++ b/web/service/log.ts @@ -1,5 +1,4 @@ import type { Fetcher } from 'swr' -import { get, post } from './base' import type { AgentLogDetailRequest, AgentLogDetailResponse, @@ -21,13 +20,14 @@ import type { WorkflowRunDetailResponse, } from '@/models/log' import type { NodeTracingListResponse } from '@/types/workflow' +import { get, post } from './base' -export const fetchConversationList: Fetcher<ConversationListResponse, { name: string; appId: string; params?: Record<string, any> }> = ({ appId, params }) => { +export const fetchConversationList: Fetcher<ConversationListResponse, { name: string, appId: string, params?: Record<string, any> }> = ({ appId, params }) => { return get<ConversationListResponse>(`/console/api/apps/${appId}/messages`, params) } // (Text Generation Application) Session List -export const fetchCompletionConversations: Fetcher<CompletionConversationsResponse, { url: string; params?: CompletionConversationsRequest }> = ({ url, params }) => { +export const fetchCompletionConversations: Fetcher<CompletionConversationsResponse, { url: string, params?: CompletionConversationsRequest }> = ({ url, params }) => { return get<CompletionConversationsResponse>(url, { params }) } @@ -37,7 +37,7 @@ export const fetchCompletionConversationDetail: Fetcher<CompletionConversationFu } // (Chat Application) Session List -export const fetchChatConversations: Fetcher<ChatConversationsResponse, { url: string; params?: ChatConversationsRequest }> = ({ url, params }) => { +export const fetchChatConversations: Fetcher<ChatConversationsResponse, { url: string, params?: ChatConversationsRequest }> = ({ url, params }) => { return get<ChatConversationsResponse>(url, { params }) } @@ -47,15 +47,15 @@ export const fetchChatConversationDetail: Fetcher<ChatConversationFullDetailResp } // (Chat Application) Message list in one session -export const fetchChatMessages: Fetcher<ChatMessagesResponse, { url: string; params: ChatMessagesRequest }> = ({ url, params }) => { +export const fetchChatMessages: Fetcher<ChatMessagesResponse, { url: string, params: ChatMessagesRequest }> = ({ url, params }) => { return get<ChatMessagesResponse>(url, { params }) } -export const updateLogMessageFeedbacks: Fetcher<LogMessageFeedbacksResponse, { url: string; body: LogMessageFeedbacksRequest }> = ({ url, body }) => { +export const updateLogMessageFeedbacks: Fetcher<LogMessageFeedbacksResponse, { url: string, body: LogMessageFeedbacksRequest }> = ({ url, body }) => { return post<LogMessageFeedbacksResponse>(url, { body }) } -export const updateLogMessageAnnotations: Fetcher<LogMessageAnnotationsResponse, { url: string; body: LogMessageAnnotationsRequest }> = ({ url, body }) => { +export const updateLogMessageAnnotations: Fetcher<LogMessageAnnotationsResponse, { url: string, body: LogMessageAnnotationsRequest }> = ({ url, body }) => { return post<LogMessageAnnotationsResponse>(url, { body }) } @@ -63,7 +63,7 @@ export const fetchAnnotationsCount: Fetcher<AnnotationsCountResponse, { url: str return get<AnnotationsCountResponse>(url) } -export const fetchWorkflowLogs: Fetcher<WorkflowLogsResponse, { url: string; params: Record<string, any> }> = ({ url, params }) => { +export const fetchWorkflowLogs: Fetcher<WorkflowLogsResponse, { url: string, params: Record<string, any> }> = ({ url, params }) => { return get<WorkflowLogsResponse>(url, { params }) } @@ -75,6 +75,6 @@ export const fetchTracingList: Fetcher<NodeTracingListResponse, { url: string }> return get<NodeTracingListResponse>(url) } -export const fetchAgentLogDetail = ({ appID, params }: { appID: string; params: AgentLogDetailRequest }) => { +export const fetchAgentLogDetail = ({ appID, params }: { appID: string, params: AgentLogDetailRequest }) => { return get<AgentLogDetailResponse>(`/apps/${appID}/agent/logs`, { params }) } diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 0a880b865f..afaebf4fb5 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -1,5 +1,8 @@ import type { Fetcher } from 'swr' -import { get, getMarketplace, post, upload } from './base' +import type { + MarketplaceCollectionPluginsResponse, + MarketplaceCollectionsResponse, +} from '@/app/components/plugins/marketplace/types' import type { Dependency, InstallPackageResponse, @@ -13,10 +16,7 @@ import type { updatePackageResponse, uploadGitHubResponse, } from '@/app/components/plugins/types' -import type { - MarketplaceCollectionPluginsResponse, - MarketplaceCollectionsResponse, -} from '@/app/components/plugins/marketplace/types' +import { get, getMarketplace, post, upload } from './base' export const uploadFile = async (file: File, isBundle: boolean) => { const formData = new FormData() @@ -33,8 +33,7 @@ export const updateFromMarketPlace = async (body: Record<string, string>) => { }) } -export const updateFromGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string, - originalPlugin: string, newPlugin: string) => { +export const updateFromGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string, originalPlugin: string, newPlugin: string) => { return post<updatePackageResponse>('/workspaces/current/plugin/upgrade/github', { body: { repo: repoUrl, @@ -83,7 +82,7 @@ export const fetchPluginInfoFromMarketPlace = async ({ return getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${org}/${name}`) } -export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => { +export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string }> = ({ url }) => { return get<MarketplaceCollectionsResponse>(url) } diff --git a/web/service/share.ts b/web/service/share.ts index dffd3aecb7..203dc896db 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -13,28 +13,35 @@ import type { IOnMessageReplace, IOnNodeFinished, IOnNodeStarted, - IOnTTSChunk, - IOnTTSEnd, IOnTextChunk, IOnTextReplace, IOnThought, + IOnTTSChunk, + IOnTTSEnd, IOnWorkflowFinished, IOnWorkflowStarted, } from './base' -import { - del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, - delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, -} from './base' import type { FeedbackType } from '@/app/components/base/chat/chat/type' +import type { ChatConfig } from '@/app/components/base/chat/types' +import type { AccessMode } from '@/models/access-control' import type { AppConversationData, AppData, AppMeta, ConversationItem, } from '@/models/share' -import type { ChatConfig } from '@/app/components/base/chat/types' -import type { AccessMode } from '@/models/access-control' import { WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config' +import { + del as consoleDel, + get as consoleGet, + patch as consolePatch, + post as consolePost, + delPublic as del, + getPublic as get, + patchPublic as patch, + postPublic as post, + ssePost, +} from './base' import { getWebAppAccessToken } from './webapp-auth' function getAction(action: 'get' | 'post' | 'del' | 'patch', isInstalledApp: boolean) { @@ -255,7 +262,7 @@ export const fetchAppMeta = async (isInstalledApp: boolean, installedAppId = '') return (getAction('get', isInstalledApp))(getUrl('meta', isInstalledApp, installedAppId)) as Promise<AppMeta> } -export const updateFeedback = async ({ url, body }: { url: string; body: FeedbackType }, isInstalledApp: boolean, installedAppId = '') => { +export const updateFeedback = async ({ url, body }: { url: string, body: FeedbackType }, isInstalledApp: boolean, installedAppId = '') => { return (getAction('post', isInstalledApp))(getUrl(url, isInstalledApp, installedAppId), { body }) } @@ -291,7 +298,7 @@ export const textToAudio = (url: string, isPublicAPI: boolean, body: FormData) = return (getAction('post', !isPublicAPI))(url, { body }, { bodyStringify: false, deleteContentType: true }) as Promise<{ data: string }> } -export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { content_type: string }, body: { streaming: boolean; voice?: string; message_id?: string; text?: string | null | undefined }) => { +export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { content_type: string }, body: { streaming: boolean, voice?: string, message_id?: string, text?: string | null | undefined }) => { return (getAction('post', !isPublicAPI))(url, { body, header }, { needAllResponseContent: true }) } diff --git a/web/service/sso.ts b/web/service/sso.ts index eb22799ba5..ba331d4bc5 100644 --- a/web/service/sso.ts +++ b/web/service/sso.ts @@ -7,10 +7,10 @@ export const getUserSAMLSSOUrl = (invite_token?: string) => { export const getUserOIDCSSOUrl = (invite_token?: string) => { const url = invite_token ? `/enterprise/sso/oidc/login?invite_token=${invite_token}` : '/enterprise/sso/oidc/login' - return get<{ url: string; state: string }>(url) + return get<{ url: string, state: string }>(url) } export const getUserOAuth2SSOUrl = (invite_token?: string) => { const url = invite_token ? `/enterprise/sso/oauth2/login?invite_token=${invite_token}` : '/enterprise/sso/oauth2/login' - return get<{ url: string; state: string }>(url) + return get<{ url: string, state: string }>(url) } diff --git a/web/service/tag.ts b/web/service/tag.ts index 99cfe07342..cf9d56951e 100644 --- a/web/service/tag.ts +++ b/web/service/tag.ts @@ -1,5 +1,5 @@ -import { del, get, patch, post } from './base' import type { Tag } from '@/app/components/base/tag-management/constant' +import { del, get, patch, post } from './base' export const fetchTagList = (type: string) => { return get<Tag[]>('/tags', { params: { type } }) diff --git a/web/service/tools.ts b/web/service/tools.ts index 2897ccac12..99b84d3981 100644 --- a/web/service/tools.ts +++ b/web/service/tools.ts @@ -1,4 +1,3 @@ -import { get, post } from './base' import type { Collection, CustomCollectionBackend, @@ -9,6 +8,7 @@ import type { WorkflowToolProviderResponse, } from '@/app/components/tools/types' import { buildProviderQuery } from './_tools_util' +import { get, post } from './base' export const fetchCollectionList = () => { return get<Collection[]>('/workspaces/current/tool-providers') @@ -58,7 +58,7 @@ export const removeBuiltInToolCredential = (collectionName: string) => { } export const parseParamsSchema = (schema: string) => { - return post<{ parameters_schema: CustomParamSchema[]; schema_type: string }>('/workspaces/current/tool-provider/api/schema', { + return post<{ parameters_schema: CustomParamSchema[], schema_type: string }>('/workspaces/current/tool-provider/api/schema', { body: { schema, }, diff --git a/web/service/use-apps.ts b/web/service/use-apps.ts index cc408c5d1a..0f6c4a64ac 100644 --- a/web/service/use-apps.ts +++ b/web/service/use-apps.ts @@ -1,4 +1,4 @@ -import { get, post } from './base' +import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' import type { ApiKeysListResponse, AppDailyConversationsResponse, @@ -11,13 +11,13 @@ import type { WorkflowDailyConversationsResponse, } from '@/models/app' import type { App, AppModeEnum } from '@/types/app' -import { useInvalid } from './use-base' import { useInfiniteQuery, useQuery, useQueryClient, } from '@tanstack/react-query' -import type { GeneratorType } from '@/app/components/app/configuration/config/automatic/types' +import { get, post } from './base' +import { useInvalid } from './use-base' const NAME_SPACE = 'apps' diff --git a/web/service/use-base.ts b/web/service/use-base.ts index b6445f4baf..0a77501747 100644 --- a/web/service/use-base.ts +++ b/web/service/use-base.ts @@ -1,5 +1,6 @@ +import type { QueryKey } from '@tanstack/react-query' import { - type QueryKey, + useQueryClient, } from '@tanstack/react-query' diff --git a/web/service/use-billing.ts b/web/service/use-billing.ts index 2701861bc0..3dc2b8a994 100644 --- a/web/service/use-billing.ts +++ b/web/service/use-billing.ts @@ -6,7 +6,7 @@ const NAME_SPACE = 'billing' export const useBindPartnerStackInfo = () => { return useMutation({ mutationKey: [NAME_SPACE, 'bind-partner-stack'], - mutationFn: (data: { partnerKey: string; clickId: string }) => bindPartnerStackInfo(data.partnerKey, data.clickId), + mutationFn: (data: { partnerKey: string, clickId: string }) => bindPartnerStackInfo(data.partnerKey, data.clickId), }) } diff --git a/web/service/use-common.ts b/web/service/use-common.ts index 5c71553781..7db65caccb 100644 --- a/web/service/use-common.ts +++ b/web/service/use-common.ts @@ -1,27 +1,29 @@ -import { get, post } from './base' -import type { - AccountIntegrate, - CommonResponse, - DataSourceNotion, - FileUploadConfigResponse, - Member, - StructuredOutputRulesRequestBody, - StructuredOutputRulesResponse, -} from '@/models/common' -import { useMutation, useQuery } from '@tanstack/react-query' import type { FileTypesRes } from './datasets' -import type { ICurrentWorkspace, IWorkspace, UserProfileResponse } from '@/models/common' import type { Model, + ModelParameterRule, ModelProvider, ModelTypeEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' +import type { + AccountIntegrate, + ApiBasedExtension, + CodeBasedExtension, + CommonResponse, + DataSourceNotion, + FileUploadConfigResponse, + ICurrentWorkspace, + IWorkspace, + LangGeniusVersionResponse, + Member, + PluginProvider, + StructuredOutputRulesRequestBody, + StructuredOutputRulesResponse, + UserProfileResponse, +} from '@/models/common' import type { RETRIEVE_METHOD } from '@/types/app' -import type { LangGeniusVersionResponse } from '@/models/common' -import type { PluginProvider } from '@/models/common' -import type { ApiBasedExtension } from '@/models/common' -import type { ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { CodeBasedExtension } from '@/models/common' +import { useMutation, useQuery } from '@tanstack/react-query' +import { get, post } from './base' import { useInvalid } from './use-base' const NAME_SPACE = 'common' @@ -44,7 +46,7 @@ export const commonQueryKeys = { notionConnection: [NAME_SPACE, 'notion-connection'] as const, apiBasedExtensions: [NAME_SPACE, 'api-based-extensions'] as const, codeBasedExtensions: (module?: string) => [NAME_SPACE, 'code-based-extensions', module] as const, - invitationCheck: (params?: { workspace_id?: string; email?: string; token?: string }) => [ + invitationCheck: (params?: { workspace_id?: string, email?: string, token?: string }) => [ NAME_SPACE, 'invitation-check', params?.workspace_id ?? '', @@ -220,7 +222,7 @@ export const useIsLogin = () => { }) } catch (e: any) { - if(e.status === 401) + if (e.status === 401) return { logged_in: false } return { logged_in: true } } @@ -236,7 +238,7 @@ export const useLogout = () => { }) } -type ForgotPasswordValidity = CommonResponse & { is_valid: boolean; email: string } +type ForgotPasswordValidity = CommonResponse & { is_valid: boolean, email: string } export const useVerifyForgotPasswordToken = (token?: string | null) => { return useQuery<ForgotPasswordValidity>({ queryKey: commonQueryKeys.forgotPasswordValidity(token), @@ -345,12 +347,12 @@ export const useApiBasedExtensions = () => { }) } -export const useInvitationCheck = (params?: { workspace_id?: string; email?: string; token?: string }, enabled?: boolean) => { +export const useInvitationCheck = (params?: { workspace_id?: string, email?: string, token?: string }, enabled?: boolean) => { return useQuery({ queryKey: commonQueryKeys.invitationCheck(params), queryFn: () => get<{ is_valid: boolean - data: { workspace_name: string; email: string; workspace_id: string } + data: { workspace_name: string, email: string, workspace_id: string } result: string }>('/activate/check', { params }), enabled: enabled ?? !!params?.token, diff --git a/web/service/use-datasource.ts b/web/service/use-datasource.ts index ede9bbfe27..07fe6cd563 100644 --- a/web/service/use-datasource.ts +++ b/web/service/use-datasource.ts @@ -1,13 +1,13 @@ +import type { + DataSourceAuth, + DataSourceCredential, +} from '@/app/components/header/account-setting/data-source-page-new/types' import { useMutation, useQuery, } from '@tanstack/react-query' import { get } from './base' import { useInvalid } from './use-base' -import type { - DataSourceAuth, - DataSourceCredential, -} from '@/app/components/header/account-setting/data-source-page-new/types' const NAME_SPACE = 'data-source-auth' @@ -49,7 +49,8 @@ export const useGetDataSourceOAuthUrl = ( authorization_url: string state: string context_id: string - }>(`/oauth/plugin/${provider}/datasource/get-authorization-url?credential_id=${credentialId}`) + } + >(`/oauth/plugin/${provider}/datasource/get-authorization-url?credential_id=${credentialId}`) }, }) } diff --git a/web/service/use-education.ts b/web/service/use-education.ts index c71833c061..34b08e59c6 100644 --- a/web/service/use-education.ts +++ b/web/service/use-education.ts @@ -1,10 +1,10 @@ -import { get, post } from './base' +import type { EducationAddParams } from '@/app/education-apply/types' import { useMutation, useQuery, } from '@tanstack/react-query' +import { get, post } from './base' import { useInvalid } from './use-base' -import type { EducationAddParams } from '@/app/education-apply/types' const NAME_SPACE = 'education' @@ -46,7 +46,7 @@ export const useEducationAutocomplete = () => { page = 0, limit = 40, } = searchParams - return get<{ data: string[]; has_next: boolean; curr_page: number }>(`/account/education/autocomplete?keywords=${keywords}&page=${page}&limit=${limit}`) + return get<{ data: string[], has_next: boolean, curr_page: number }>(`/account/education/autocomplete?keywords=${keywords}&page=${page}&limit=${limit}`) }, }) } diff --git a/web/service/use-endpoints.ts b/web/service/use-endpoints.ts index 43a82480b9..79702068ab 100644 --- a/web/service/use-endpoints.ts +++ b/web/service/use-endpoints.ts @@ -1,4 +1,3 @@ -import { get, post } from './base' import type { EndpointsResponse, } from '@/app/components/plugins/types' @@ -7,6 +6,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' +import { get, post } from './base' const NAME_SPACE = 'endpoints' @@ -29,7 +29,8 @@ export const useInvalidateEndpointList = () => { queryClient.invalidateQueries( { queryKey: [NAME_SPACE, 'list', pluginID], - }) + }, + ) } } diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index b7d078edbc..6e57599b69 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -1,6 +1,6 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useGlobalPublicStore } from '@/context/global-public-context' import { AccessMode } from '@/models/access-control' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore' import { fetchAppMeta, fetchAppParams } from './share' @@ -30,7 +30,7 @@ export const useUpdateAppPinStatus = () => { const client = useQueryClient() return useMutation({ mutationKey: [NAME_SPACE, 'updateAppPinStatus'], - mutationFn: ({ appId, isPinned }: { appId: string; isPinned: boolean }) => updatePinStatus(appId, isPinned), + mutationFn: ({ appId, isPinned }: { appId: string, isPinned: boolean }) => updatePinStatus(appId, isPinned), onSuccess: () => { client.invalidateQueries({ queryKey: [NAME_SPACE, 'installedApps'] }) }, diff --git a/web/service/use-flow.ts b/web/service/use-flow.ts index 54820a8d1d..30bec6dd23 100644 --- a/web/service/use-flow.ts +++ b/web/service/use-flow.ts @@ -1,4 +1,5 @@ import type { FlowType } from '@/types/common' +import { curry } from 'lodash-es' import { useDeleteAllInspectorVars as useDeleteAllInspectorVarsInner, useDeleteInspectVar as useDeleteInspectVarInner, @@ -9,7 +10,6 @@ import { useResetConversationVar as useResetConversationVarInner, useResetToLastRunValue as useResetToLastRunValueInner, } from './use-workflow' -import { curry } from 'lodash-es' type Params = { flowType: FlowType diff --git a/web/service/use-models.ts b/web/service/use-models.ts index d6eb929646..d960bda33f 100644 --- a/web/service/use-models.ts +++ b/web/service/use-models.ts @@ -1,9 +1,3 @@ -import { - del, - get, - post, - put, -} from './base' import type { ModelCredential, ModelItem, @@ -16,6 +10,12 @@ import { useQuery, // useQueryClient, } from '@tanstack/react-query' +import { + del, + get, + post, + put, +} from './base' const NAME_SPACE = 'models' diff --git a/web/service/use-oauth.ts b/web/service/use-oauth.ts index d3860fe8d8..b55704e164 100644 --- a/web/service/use-oauth.ts +++ b/web/service/use-oauth.ts @@ -1,5 +1,5 @@ -import { post } from './base' import { useMutation, useQuery } from '@tanstack/react-query' +import { post } from './base' const NAME_SPACE = 'oauth-provider' diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts index 92a7542c56..c1abbb1984 100644 --- a/web/service/use-pipeline.ts +++ b/web/service/use-pipeline.ts @@ -1,7 +1,7 @@ import type { MutationOptions } from '@tanstack/react-query' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { del, get, patch, post } from './base' -import { DatasourceType } from '@/models/pipeline' +import type { ToolCredential } from '@/app/components/tools/types' +import type { DataSourceItem } from '@/app/components/workflow/block-selector/types' +import type { IconInfo } from '@/models/datasets' import type { ConversionResponse, DatasourceNodeSingleRunRequest, @@ -31,9 +31,9 @@ import type { UpdateTemplateInfoRequest, UpdateTemplateInfoResponse, } from '@/models/pipeline' -import type { DataSourceItem } from '@/app/components/workflow/block-selector/types' -import type { ToolCredential } from '@/app/components/tools/types' -import type { IconInfo } from '@/models/datasets' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { DatasourceType } from '@/models/pipeline' +import { del, get, patch, post } from './base' import { useInvalid } from './use-base' const NAME_SPACE = 'pipeline' @@ -248,7 +248,7 @@ export const useUpdateDataSourceCredentials = ( pluginId, credentials, name, - }: { provider: string; pluginId: string; credentials: Record<string, any>; name: string; }) => { + }: { provider: string, pluginId: string, credentials: Record<string, any>, name: string }) => { return post('/auth/plugin/datasource', { body: { provider, @@ -303,7 +303,7 @@ export const useExportPipelineDSL = () => { mutationFn: ({ pipelineId, include = false, - }: { pipelineId: string; include?: boolean }) => { + }: { pipelineId: string, include?: boolean }) => { return get<ExportTemplateDSLResponse>(`/rag/pipelines/${pipelineId}/exports?include_secret=${include}`) }, }) @@ -318,10 +318,10 @@ export const usePublishAsCustomizedPipeline = () => { icon_info, description, }: { - pipelineId: string, - name: string, - icon_info: IconInfo, - description?: string, + pipelineId: string + name: string + icon_info: IconInfo + description?: string }) => { return post(`/rag/pipelines/${pipelineId}/customized/publish`, { body: { diff --git a/web/service/use-plugins-auth.ts b/web/service/use-plugins-auth.ts index f2f3c5b532..3427fc7a6a 100644 --- a/web/service/use-plugins-auth.ts +++ b/web/service/use-plugins-auth.ts @@ -1,14 +1,14 @@ +import type { FormSchema } from '@/app/components/base/form/types' +import type { + Credential, + CredentialTypeEnum, +} from '@/app/components/plugins/plugin-auth/types' import { useMutation, useQuery, } from '@tanstack/react-query' import { del, get, post } from './base' import { useInvalid } from './use-base' -import type { - Credential, - CredentialTypeEnum, -} from '@/app/components/plugins/plugin-auth/types' -import type { FormSchema } from '@/app/components/base/form/types' const NAME_SPACE = 'plugins-auth' @@ -112,7 +112,8 @@ export const useGetPluginOAuthUrl = ( authorization_url: string state: string context_id: string - }>(url) + } + >(url) }, }) } diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 639d889fa0..58454125ed 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -1,50 +1,48 @@ -import { useCallback, useEffect, useState } from 'react' +import type { MutateOptions, QueryOptions } from '@tanstack/react-query' import type { FormOption, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { fetchModelProviderModelList } from '@/service/common' -import { fetchPluginInfoFromMarketPlace } from '@/service/plugins' +import type { + PluginsSearchParams, +} from '@/app/components/plugins/marketplace/types' import type { DebugInfo as DebugInfoTypes, Dependency, GitHubItemAndMarketPlaceDependency, - InstallPackageResponse, - InstallStatusResponse, InstalledLatestVersionResponse, InstalledPluginListWithTotalResponse, + InstallPackageResponse, + InstallStatusResponse, PackageDependency, Plugin, PluginDeclaration, PluginDetail, PluginInfoFromMarketPlace, - PluginTask, PluginsFromMarketplaceByInfoResponse, PluginsFromMarketplaceResponse, + PluginTask, ReferenceSetting, + uploadGitHubResponse, VersionInfo, VersionListResponse, - uploadGitHubResponse, } from '@/app/components/plugins/types' -import { TaskStatus } from '@/app/components/plugins/types' -import { PluginCategoryEnum } from '@/app/components/plugins/types' -import type { - PluginsSearchParams, -} from '@/app/components/plugins/marketplace/types' -import { get, getMarketplace, post, postMarketplace } from './base' -import type { MutateOptions, QueryOptions } from '@tanstack/react-query' import { useInfiniteQuery, useMutation, useQuery, useQueryClient, } from '@tanstack/react-query' -import { useInvalidateAllBuiltInTools } from './use-tools' -import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting' -import { uninstallPlugin } from '@/service/plugins' -import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import { cloneDeep } from 'lodash-es' +import { useCallback, useEffect, useState } from 'react' +import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils' +import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting' +import { PluginCategoryEnum, TaskStatus } from '@/app/components/plugins/types' +import { fetchModelProviderModelList } from '@/service/common' +import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins' +import { get, getMarketplace, post, postMarketplace } from './base' +import { useInvalidateAllBuiltInTools } from './use-tools' const NAME_SPACE = 'plugins' @@ -53,7 +51,7 @@ export const useCheckInstalled = ({ pluginIds, enabled, }: { - pluginIds: string[], + pluginIds: string[] enabled: boolean }) => { return useQuery<{ plugins: PluginDetail[] }>({ @@ -165,10 +163,12 @@ export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => { const total = data?.pages[0].total ?? 0 return { - data: disable ? undefined : { - plugins, - total, - }, + data: disable + ? undefined + : { + plugins, + total, + }, isLastPage: !hasNextPage, loadNextPage: () => { fetchNextPage() @@ -200,7 +200,8 @@ export const useInvalidateInstalledPluginList = () => { queryClient.invalidateQueries( { queryKey: useInstalledPluginListKey, - }) + }, + ) invalidateAllBuiltInTools() } } @@ -300,8 +301,8 @@ export const useInstallOrUpdate = ({ return useMutation({ mutationFn: (data: { - payload: Dependency[], - plugin: Plugin[], + payload: Dependency[] + plugin: Plugin[] installedInfo: Record<string, VersionInfo> }) => { const { payload, plugin, installedInfo } = data @@ -456,7 +457,8 @@ export const useInvalidateReferenceSettings = () => { queryClient.invalidateQueries( { queryKey: useReferenceSettingKey, - }) + }, + ) } } @@ -633,7 +635,7 @@ export const usePluginTaskList = (category?: PluginCategoryEnum | string) => { export const useMutationClearTaskPlugin = () => { return useMutation({ - mutationFn: ({ taskId, pluginId }: { taskId: string; pluginId: string }) => { + mutationFn: ({ taskId, pluginId }: { taskId: string, pluginId: string }) => { const encodedPluginId = encodeURIComponent(pluginId) return post<{ success: boolean }>(`/workspaces/current/plugin/tasks/${taskId}/delete/${encodedPluginId}`) }, @@ -657,7 +659,7 @@ export const usePluginManifestInfo = (pluginUID: string) => { }) } -export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => { +export const useDownloadPlugin = (info: { organization: string, pluginName: string, version: string }, needDownload: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'downloadPlugin', info], queryFn: () => getMarketplace<Blob>(`/plugins/${info.organization}/${info.pluginName}/${info.version}/download`), @@ -678,7 +680,8 @@ export const useModelInList = (currentProvider?: ModelProvider, modelId?: string return useQuery({ queryKey: ['modelInList', currentProvider?.provider, modelId], queryFn: async () => { - if (!modelId || !currentProvider) return false + if (!modelId || !currentProvider) + return false try { const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${currentProvider?.provider}/models`) return !!modelId && !!modelsData.data.find(item => item.model === modelId) @@ -695,7 +698,8 @@ export const usePluginInfo = (providerName?: string) => { return useQuery({ queryKey: ['pluginInfo', providerName], queryFn: async () => { - if (!providerName) return null + if (!providerName) + return null const parts = providerName.split('/') const org = parts[0] const name = parts[1] diff --git a/web/service/use-strategy.ts b/web/service/use-strategy.ts index af591ac019..b66a1e8b46 100644 --- a/web/service/use-strategy.ts +++ b/web/service/use-strategy.ts @@ -1,12 +1,12 @@ +import type { QueryOptions } from '@tanstack/react-query' import type { StrategyPluginDetail, } from '@/app/components/plugins/types' -import { useInvalid } from './use-base' -import type { QueryOptions } from '@tanstack/react-query' import { useQuery, } from '@tanstack/react-query' import { fetchStrategyDetail, fetchStrategyList } from './strategy' +import { useInvalid } from './use-base' const NAME_SPACE = 'agent_strategy' diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 6ac57e84d3..49a28eba3c 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -1,19 +1,19 @@ -import { del, get, post, put } from './base' +import type { QueryKey } from '@tanstack/react-query' import type { Collection, MCPServerDetail, Tool, } from '@/app/components/tools/types' -import { CollectionType } from '@/app/components/tools/types' import type { RAGRecommendedPlugins, ToolWithProvider } from '@/app/components/workflow/types' import type { AppIconType } from '@/types/app' -import { useInvalid } from './use-base' -import type { QueryKey } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient, } from '@tanstack/react-query' +import { CollectionType } from '@/app/components/tools/types' +import { del, get, post, put } from './base' +import { useInvalid } from './use-base' const NAME_SPACE = 'tools' @@ -160,8 +160,8 @@ export const useDeleteMCP = ({ export const useAuthorizeMCP = () => { return useMutation({ mutationKey: [NAME_SPACE, 'authorize-mcp'], - mutationFn: (payload: { provider_id: string; }) => { - return post<{ result?: string; authorization_url?: string }>('/workspaces/current/tool-provider/mcp/auth', { + mutationFn: (payload: { provider_id: string }) => { + return post<{ result?: string, authorization_url?: string }>('/workspaces/current/tool-provider/mcp/auth', { body: payload, }) }, @@ -171,7 +171,7 @@ export const useAuthorizeMCP = () => { export const useUpdateMCPAuthorizationToken = () => { return useMutation({ mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'], - mutationFn: (payload: { provider_id: string; authorization_code: string }) => { + mutationFn: (payload: { provider_id: string, authorization_code: string }) => { return get<MCPServerDetail>('/workspaces/current/tool-provider/mcp/token', { params: { ...payload, @@ -194,7 +194,8 @@ export const useInvalidateMCPTools = () => { queryClient.invalidateQueries( { queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID], - }) + }, + ) } } @@ -217,7 +218,8 @@ export const useInvalidateMCPServerDetail = () => { queryClient.invalidateQueries( { queryKey: [NAME_SPACE, 'MCPServerDetail', appID], - }) + }, + ) } } @@ -281,7 +283,8 @@ export const useInvalidateBuiltinProviderInfo = () => { queryClient.invalidateQueries( { queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], - }) + }, + ) } } diff --git a/web/service/use-triggers.ts b/web/service/use-triggers.ts index 67522d2e55..c21d1aa979 100644 --- a/web/service/use-triggers.ts +++ b/web/service/use-triggers.ts @@ -1,5 +1,3 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { del, get, post } from './base' import type { TriggerLogEntity, TriggerOAuthClientParams, @@ -9,7 +7,9 @@ import type { TriggerSubscriptionBuilder, TriggerWithProvider, } from '@/app/components/workflow/block-selector/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { CollectionType } from '@/app/components/tools/types' +import { del, get, post } from './base' import { useInvalid } from './use-base' const NAME_SPACE = 'triggers' @@ -274,7 +274,7 @@ export const useInitiateTriggerOAuth = () => { return useMutation({ mutationKey: [NAME_SPACE, 'initiate-oauth'], mutationFn: (provider: string) => { - return get<{ authorization_url: string; subscription_builder: TriggerSubscriptionBuilder }>( + return get<{ authorization_url: string, subscription_builder: TriggerSubscriptionBuilder }>( `/workspaces/current/trigger-provider/${provider}/subscriptions/oauth/authorize`, {}, { silent: true }, @@ -292,9 +292,9 @@ export const useTriggerPluginDynamicOptions = (payload: { credential_id: string extra?: Record<string, any> }, enabled = true) => { - return useQuery<{ options: Array<{ value: string; label: any }> }>({ + return useQuery<{ options: Array<{ value: string, label: any }> }>({ queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.extra], - queryFn: () => get<{ options: Array<{ value: string; label: any }> }>( + queryFn: () => get<{ options: Array<{ value: string, label: any }> }>( '/workspaces/current/plugin/parameters/dynamic-options', { params: { diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts index 5da83be360..f5c3021c92 100644 --- a/web/service/use-workflow.ts +++ b/web/service/use-workflow.ts @@ -1,5 +1,5 @@ -import { del, get, patch, post, put } from './base' -import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import type { CommonResponse } from '@/models/common' +import type { FlowType } from '@/types/common' import type { FetchWorkflowDraftPageParams, FetchWorkflowDraftPageResponse, @@ -10,9 +10,9 @@ import type { VarInInspect, WorkflowConfigResponse, } from '@/types/workflow' -import type { CommonResponse } from '@/models/common' +import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { del, get, patch, post, put } from './base' import { useInvalid, useReset } from './use-base' -import type { FlowType } from '@/types/common' import { getFlowPrefix } from './utils' const NAME_SPACE = 'workflow' @@ -31,7 +31,8 @@ export const useInvalidateAppWorkflow = () => { queryClient.invalidateQueries( { queryKey: [NAME_SPACE, 'publish', appID], - }) + }, + ) } } diff --git a/web/service/utils.spec.ts b/web/service/utils.spec.ts index fc5385c309..212e3dccc3 100644 --- a/web/service/utils.spec.ts +++ b/web/service/utils.spec.ts @@ -1,3 +1,4 @@ +import { FlowType } from '@/types/common' /** * Test suite for service utility functions * @@ -12,7 +13,6 @@ * with a fallback to 'apps' for undefined or unknown flow types. */ import { flowPrefixMap, getFlowPrefix } from './utils' -import { FlowType } from '@/types/common' describe('Service Utils', () => { describe('flowPrefixMap', () => { diff --git a/web/service/workflow-payload.ts b/web/service/workflow-payload.ts index b80c4a3731..b294141cb7 100644 --- a/web/service/workflow-payload.ts +++ b/web/service/workflow-payload.ts @@ -1,8 +1,8 @@ -import { produce } from 'immer' -import type { Edge, Node } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types' +import type { Edge, Node } from '@/app/components/workflow/types' import type { FetchWorkflowDraftResponse } from '@/types/workflow' +import { produce } from 'immer' +import { BlockEnum } from '@/app/components/workflow/types' export type TriggerPluginNodePayload = { title: string diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 654fe3d01a..96af869ba5 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,16 +1,16 @@ import type { Fetcher } from 'swr' -import { get, post } from './base' +import type { BlockEnum } from '@/app/components/workflow/types' import type { CommonResponse } from '@/models/common' +import type { FlowType } from '@/types/common' import type { ChatRunHistoryResponse, ConversationVariableResponse, FetchWorkflowDraftResponse, NodesDefaultConfigsResponse, + VarInInspect, WorkflowRunHistoryResponse, } from '@/types/workflow' -import type { BlockEnum } from '@/app/components/workflow/types' -import type { VarInInspect } from '@/types/workflow' -import type { FlowType } from '@/types/common' +import { get, post } from './base' import { getFlowPrefix } from './utils' export const fetchWorkflowDraft = (url: string) => { @@ -21,7 +21,7 @@ export const syncWorkflowDraft = ({ url, params }: { url: string params: Pick<FetchWorkflowDraftResponse, 'graph' | 'features' | 'environment_variables' | 'conversation_variables'> }) => { - return post<CommonResponse & { updated_at: number; hash: string }>(url, { body: params }, { silent: true }) + return post<CommonResponse & { updated_at: number, hash: string }>(url, { body: params }, { silent: true }) } export const fetchNodesDefaultConfigs: Fetcher<NodesDefaultConfigsResponse, string> = (url) => { diff --git a/web/tailwind.config.js b/web/tailwind.config.js index a9959e7b76..3cb71081da 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -1,5 +1,6 @@ // import type { Config } from 'tailwindcss' import commonConfig from './tailwind-common-config' + const config = { content: [ './app/**/*.{js,ts,jsx,tsx}', diff --git a/web/testing/analyze-component.js b/web/testing/analyze-component.js index f9414d9a74..3f70f3d1ec 100755 --- a/web/testing/analyze-component.js +++ b/web/testing/analyze-component.js @@ -3,9 +3,9 @@ import { spawnSync } from 'node:child_process' import fs from 'node:fs' import path from 'node:path' +import tsParser from '@typescript-eslint/parser' import { Linter } from 'eslint' import sonarPlugin from 'eslint-plugin-sonarjs' -import tsParser from '@typescript-eslint/parser' // ============================================================================ // Simple Analyzer @@ -47,7 +47,7 @@ class ComponentAnalyzer { hasImperativeHandle: code.includes('useImperativeHandle'), hasSWR: code.includes('useSWR'), hasReactQuery: code.includes('useQuery') || code.includes('useMutation'), - hasAhooks: code.includes("from 'ahooks'"), + hasAhooks: code.includes('from \'ahooks\''), complexity, maxComplexity, rawComplexity, @@ -60,17 +60,27 @@ class ComponentAnalyzer { detectType(filePath, code) { const normalizedPath = filePath.replace(/\\/g, '/') - if (normalizedPath.includes('/hooks/')) return 'hook' - if (normalizedPath.includes('/utils/')) return 'util' - if (/\/page\.(t|j)sx?$/.test(normalizedPath)) return 'page' - if (/\/layout\.(t|j)sx?$/.test(normalizedPath)) return 'layout' - if (/\/providers?\//.test(normalizedPath)) return 'provider' + if (normalizedPath.includes('/hooks/')) + return 'hook' + if (normalizedPath.includes('/utils/')) + return 'util' + if (/\/page\.(t|j)sx?$/.test(normalizedPath)) + return 'page' + if (/\/layout\.(t|j)sx?$/.test(normalizedPath)) + return 'layout' + if (/\/providers?\//.test(normalizedPath)) + return 'provider' // Dify-specific types - if (normalizedPath.includes('/components/base/')) return 'base-component' - if (normalizedPath.includes('/context/')) return 'context' - if (normalizedPath.includes('/store/')) return 'store' - if (normalizedPath.includes('/service/')) return 'service' - if (/use[A-Z]\w+/.test(code)) return 'component' + if (normalizedPath.includes('/components/base/')) + return 'base-component' + if (normalizedPath.includes('/context/')) + return 'context' + if (normalizedPath.includes('/store/')) + return 'store' + if (normalizedPath.includes('/service/')) + return 'service' + if (/use[A-Z]\w+/.test(code)) + return 'component' return 'component' } @@ -112,7 +122,7 @@ class ComponentAnalyzer { msg => msg.ruleId === 'sonarjs/cognitive-complexity' && msg.messageId === 'fileComplexity', ) - const total = totalMsg ? parseInt(totalMsg.message, 10) : 0 + const total = totalMsg ? Number.parseInt(totalMsg.message, 10) : 0 // Get max function complexity by analyzing each function const maxConfig = { @@ -127,7 +137,7 @@ class ComponentAnalyzer { if (msg.ruleId === 'sonarjs/cognitive-complexity') { const match = msg.message.match(complexityPattern) if (match && match[1]) - max = Math.max(max, parseInt(match[1], 10)) + max = Math.max(max, Number.parseInt(match[1], 10)) } }) @@ -182,10 +192,12 @@ class ComponentAnalyzer { searchName = path.basename(parentDir) } - if (!searchName) return 0 + if (!searchName) + return 0 const searchRoots = this.collectSearchRoots(resolvedComponentPath) - if (searchRoots.length === 0) return 0 + if (searchRoots.length === 0) + return 0 const escapedName = ComponentAnalyzer.escapeRegExp(searchName) const patterns = [ @@ -201,29 +213,34 @@ class ComponentAnalyzer { const stack = [...searchRoots] while (stack.length > 0) { const currentDir = stack.pop() - if (!currentDir || visited.has(currentDir)) continue + if (!currentDir || visited.has(currentDir)) + continue visited.add(currentDir) const entries = fs.readdirSync(currentDir, { withFileTypes: true }) - entries.forEach(entry => { + entries.forEach((entry) => { const entryPath = path.join(currentDir, entry.name) if (entry.isDirectory()) { - if (this.shouldSkipDir(entry.name)) return + if (this.shouldSkipDir(entry.name)) + return stack.push(entryPath) return } - if (!this.shouldInspectFile(entry.name)) return + if (!this.shouldInspectFile(entry.name)) + return const normalizedEntryPath = path.resolve(entryPath) - if (normalizedEntryPath === path.resolve(resolvedComponentPath)) return + if (normalizedEntryPath === path.resolve(resolvedComponentPath)) + return const source = fs.readFileSync(entryPath, 'utf-8') - if (!source.includes(searchName)) return + if (!source.includes(searchName)) + return - if (patterns.some(pattern => { + if (patterns.some((pattern) => { pattern.lastIndex = 0 return pattern.test(source) })) { @@ -252,7 +269,8 @@ class ComponentAnalyzer { break } - if (currentDir === workspaceRoot) break + if (currentDir === workspaceRoot) + break currentDir = path.dirname(currentDir) } @@ -262,8 +280,9 @@ class ComponentAnalyzer { path.join(workspaceRoot, 'src'), ] - fallbackRoots.forEach(root => { - if (fs.existsSync(root) && fs.statSync(root).isDirectory()) roots.add(root) + fallbackRoots.forEach((root) => { + if (fs.existsSync(root) && fs.statSync(root).isDirectory()) + roots.add(root) }) return Array.from(roots) @@ -286,10 +305,14 @@ class ComponentAnalyzer { shouldInspectFile(fileName) { const normalized = fileName.toLowerCase() - if (!(/\.(ts|tsx)$/i.test(fileName))) return false - if (normalized.endsWith('.d.ts')) return false - if (/\.(spec|test)\.(ts|tsx)$/.test(normalized)) return false - if (normalized.endsWith('.stories.tsx')) return false + if (!(/\.(ts|tsx)$/i.test(fileName))) + return false + if (normalized.endsWith('.d.ts')) + return false + if (/\.(spec|test)\.(ts|tsx)$/.test(normalized)) + return false + if (normalized.endsWith('.stories.tsx')) + return false return true } @@ -341,9 +364,12 @@ class ComponentAnalyzer { * Get priority level based on score (0-100 scale) */ getPriorityLevel(score) { - if (score > 75) return '🔴 CRITICAL' - if (score > 50) return '🟠 HIGH' - if (score > 25) return '🟡 MEDIUM' + if (score > 75) + return '🔴 CRITICAL' + if (score > 50) + return '🟠 HIGH' + if (score > 25) + return '🟡 MEDIUM' return '🟢 LOW' } } @@ -420,27 +446,42 @@ Create the test file at: ${testPath} getComplexityLevel(score) { // Normalized complexity thresholds (0-100 scale) - if (score <= 25) return '🟢 Simple' - if (score <= 50) return '🟡 Medium' - if (score <= 75) return '🟠 Complex' + if (score <= 25) + return '🟢 Simple' + if (score <= 50) + return '🟡 Medium' + if (score <= 75) + return '🟠 Complex' return '🔴 Very Complex' } buildFocusPoints(analysis) { const points = [] - if (analysis.hasState) points.push('- Testing state management and updates') - if (analysis.hasEffects) points.push('- Testing side effects and cleanup') - if (analysis.hasCallbacks) points.push('- Testing callback stability and memoization') - if (analysis.hasMemo) points.push('- Testing memoization logic and dependencies') - if (analysis.hasEvents) points.push('- Testing user interactions and event handlers') - if (analysis.hasRouter) points.push('- Mocking Next.js router hooks') - if (analysis.hasAPI) points.push('- Mocking API calls') - if (analysis.hasForwardRef) points.push('- Testing ref forwarding behavior') - if (analysis.hasComponentMemo) points.push('- Testing component memoization') - if (analysis.hasSuspense) points.push('- Testing Suspense boundaries and lazy loading') - if (analysis.hasPortal) points.push('- Testing Portal rendering') - if (analysis.hasImperativeHandle) points.push('- Testing imperative handle methods') + if (analysis.hasState) + points.push('- Testing state management and updates') + if (analysis.hasEffects) + points.push('- Testing side effects and cleanup') + if (analysis.hasCallbacks) + points.push('- Testing callback stability and memoization') + if (analysis.hasMemo) + points.push('- Testing memoization logic and dependencies') + if (analysis.hasEvents) + points.push('- Testing user interactions and event handlers') + if (analysis.hasRouter) + points.push('- Mocking Next.js router hooks') + if (analysis.hasAPI) + points.push('- Mocking API calls') + if (analysis.hasForwardRef) + points.push('- Testing ref forwarding behavior') + if (analysis.hasComponentMemo) + points.push('- Testing component memoization') + if (analysis.hasSuspense) + points.push('- Testing Suspense boundaries and lazy loading') + if (analysis.hasPortal) + points.push('- Testing Portal rendering') + if (analysis.hasImperativeHandle) + points.push('- Testing imperative handle methods') points.push('- Testing edge cases and error handling') points.push('- Testing all prop variations') @@ -524,9 +565,12 @@ Create the test file at: ${testPath} // ===== Performance Optimization ===== if (analysis.hasCallbacks || analysis.hasMemo || analysis.hasComponentMemo) { const features = [] - if (analysis.hasCallbacks) features.push('useCallback') - if (analysis.hasMemo) features.push('useMemo') - if (analysis.hasComponentMemo) features.push('React.memo') + if (analysis.hasCallbacks) + features.push('useCallback') + if (analysis.hasMemo) + features.push('useMemo') + if (analysis.hasComponentMemo) + features.push('React.memo') guidelines.push(`🚀 Performance optimization (${features.join(', ')}):`) guidelines.push(' - Verify callbacks maintain referential equality') @@ -689,12 +733,14 @@ Output format: function extractCopyContent(prompt) { const marker = '📋 PROMPT FOR AI ASSISTANT' const markerIndex = prompt.indexOf(marker) - if (markerIndex === -1) return '' + if (markerIndex === -1) + return '' const section = prompt.slice(markerIndex) const lines = section.split('\n') const firstDivider = lines.findIndex(line => line.includes('━━━━━━━━')) - if (firstDivider === -1) return '' + if (firstDivider === -1) + return '' const startIdx = firstDivider + 1 let endIdx = lines.length @@ -706,7 +752,8 @@ function extractCopyContent(prompt) { } } - if (startIdx >= endIdx) return '' + if (startIdx >= endIdx) + return '' return lines.slice(startIdx, endIdx).join('\n').trim() } @@ -722,8 +769,13 @@ function extractCopyContent(prompt) { function resolveDirectoryEntry(absolutePath, componentPath) { // Entry files in priority order: index files first, then common entry files const entryFiles = [ - 'index.tsx', 'index.ts', // Priority 1: index files - 'node.tsx', 'panel.tsx', 'component.tsx', 'main.tsx', 'container.tsx', // Priority 2: common entry files + 'index.tsx', + 'index.ts', // Priority 1: index files + 'node.tsx', + 'panel.tsx', + 'component.tsx', + 'main.tsx', + 'container.tsx', // Priority 2: common entry files ] for (const entryFile of entryFiles) { const entryPath = path.join(absolutePath, entryFile) @@ -752,9 +804,12 @@ function listAnalyzableFiles(dirPath) { const priority = ['index.tsx', 'index.ts', 'node.tsx', 'panel.tsx', 'component.tsx', 'main.tsx', 'container.tsx'] const aIdx = priority.indexOf(a) const bIdx = priority.indexOf(b) - if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx - if (aIdx !== -1) return -1 - if (bIdx !== -1) return 1 + if (aIdx !== -1 && bIdx !== -1) + return aIdx - bIdx + if (aIdx !== -1) + return -1 + if (bIdx !== -1) + return 1 return a.localeCompare(b) }) } @@ -797,7 +852,7 @@ function main() { let isJsonMode = false const args = [] - rawArgs.forEach(arg => { + rawArgs.forEach((arg) => { if (arg === '--review') { isReviewMode = true return @@ -949,9 +1004,11 @@ This component is too complex to test effectively. Please consider: try { const checkPbcopy = spawnSync('which', ['pbcopy'], { stdio: 'pipe' }) - if (checkPbcopy.status !== 0) return + if (checkPbcopy.status !== 0) + return const copyContent = extractCopyContent(prompt) - if (!copyContent) return + if (!copyContent) + return const result = spawnSync('pbcopy', [], { input: copyContent, @@ -973,7 +1030,8 @@ This component is too complex to test effectively. Please consider: function inferTestPath(componentPath) { const ext = path.extname(componentPath) - if (!ext) return `${componentPath}.spec.ts` + if (!ext) + return `${componentPath}.spec.ts` return componentPath.replace(ext, `.spec${ext}`) } diff --git a/web/testing/testing.md b/web/testing/testing.md index 78af5375d9..a2c8399d45 100644 --- a/web/testing/testing.md +++ b/web/testing/testing.md @@ -266,8 +266,8 @@ const mockGithubStar = (status: number, body: Record<string, unknown>, delayMs = ### Example Structure -```typescript -import { render, screen, fireEvent, waitFor } from '@testing-library/react' +```tsx +import { fireEvent, render, screen, waitFor } from '@testing-library/react' import Component from './index' // ✅ Import real project components (DO NOT mock these) @@ -286,18 +286,18 @@ let mockSharedState = false describe('ComponentName', () => { beforeEach(() => { - vi.clearAllMocks() // ✅ Reset mocks before each test - mockSharedState = false // ✅ Reset shared state if used in mocks + vi.clearAllMocks() // ✅ Reset mocks before each test + mockSharedState = false // ✅ Reset shared state if used in mocks }) describe('Rendering', () => { it('should render without crashing', () => { // Arrange const props = { title: 'Test' } - + // Act render(<Component {...props} />) - + // Assert expect(screen.getByText('Test')).toBeInTheDocument() }) @@ -307,9 +307,9 @@ describe('ComponentName', () => { it('should handle click events', () => { const handleClick = vi.fn() render(<Component onClick={handleClick} />) - + fireEvent.click(screen.getByRole('button')) - + expect(handleClick).toHaveBeenCalledTimes(1) }) }) @@ -348,26 +348,27 @@ describe('ComponentName', () => { 1. **Example - Correct mock with conditional rendering**: -```typescript +```tsx // ✅ CORRECT: Matches actual component behavior let mockPortalOpenState = false vi.mock('@/app/components/base/portal-to-follow-elem', () => ({ PortalToFollowElem: ({ children, open, ...props }: any) => { - mockPortalOpenState = open || false // Update shared state + mockPortalOpenState = open || false // Update shared state return <div data-open={open}>{children}</div> }, PortalToFollowElemContent: ({ children }: any) => { // ✅ Matches actual: returns null when open is false - if (!mockPortalOpenState) return null + if (!mockPortalOpenState) + return null return <div>{children}</div> }, })) describe('Component', () => { beforeEach(() => { - vi.clearAllMocks() // ✅ Reset mock call history - mockPortalOpenState = false // ✅ Reset shared state + vi.clearAllMocks() // ✅ Reset mock call history + mockPortalOpenState = false // ✅ Reset shared state }) }) ``` diff --git a/web/tsconfig.json b/web/tsconfig.json index d2ba8e7bc1..78c5930aa2 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,29 +1,15 @@ { "compilerOptions": { + "incremental": true, "target": "es2015", + "jsx": "preserve", "lib": [ "dom", "dom.iterable", "esnext" ], - "types": ["vitest/globals", "node"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], "paths": { "@/*": [ "./*" @@ -31,7 +17,21 @@ "~@/*": [ "./*" ] - } + }, + "resolveJsonModule": true, + "types": ["vitest/globals", "node"], + "allowJs": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "skipLibCheck": true, + "plugins": [ + { + "name": "next" + } + ] }, "include": [ "next-env.d.ts", diff --git a/web/types/app.ts b/web/types/app.ts index 73e11d396a..eb1b29bb60 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -1,14 +1,14 @@ -import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug' -import type { CollectionType } from '@/app/components/tools/types' -import type { LanguagesSupported } from '@/i18n-config/language' import type { Tag } from '@/app/components/base/tag-management/constant' +import type { CollectionType } from '@/app/components/tools/types' +import type { UploadFileSetting } from '@/app/components/workflow/types' +import type { LanguagesSupported } from '@/i18n-config/language' +import type { AccessMode } from '@/models/access-control' +import type { ExternalDataTool } from '@/models/common' import type { RerankingModeEnum, WeightedScoreEnum, } from '@/models/datasets' -import type { UploadFileSetting } from '@/app/components/workflow/types' -import type { AccessMode } from '@/models/access-control' -import type { ExternalDataTool } from '@/models/common' +import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, DatasetConfigs, PromptMode } from '@/models/debug' export enum Theme { light = 'light', @@ -274,9 +274,10 @@ export type SiteConfig = { title: string /** Application Description will be shown in the Client */ description: string - /** Define the color in hex for different elements of the chatbot, such as: + /** + * Define the color in hex for different elements of the chatbot, such as: * The header, the button , etc. - */ + */ chat_color_theme: string /** Invert the color of the theme set in chat_color_theme */ chat_color_theme_inverted: boolean @@ -328,12 +329,12 @@ export type App = { /** Description */ description: string /** Author Name */ - author_name: string; + author_name: string /** * Icon Type * @default 'emoji' - */ + */ icon_type: AppIconType | null /** Icon, stores file ID if icon_type is 'image' */ icon: string @@ -375,7 +376,7 @@ export type App = { updated_at: number updated_by?: string } - deleted_tools?: Array<{ id: string; tool_name: string }> + deleted_tools?: Array<{ id: string, tool_name: string }> /** access control */ access_mode: AccessMode max_active_requests?: number | null diff --git a/web/types/feature.ts b/web/types/feature.ts index 6c3bb29201..4f8d92a774 100644 --- a/web/types/feature.ts +++ b/web/types/feature.ts @@ -27,9 +27,9 @@ type License = { export type SystemFeatures = { plugin_installation_permission: { - plugin_installation_scope: InstallationScope, + plugin_installation_scope: InstallationScope restrict_to_marketplace_only: boolean - }, + } sso_enforced_for_signin: boolean sso_enforced_for_signin_protocol: SSOProtocol | '' sso_enforced_for_web: boolean diff --git a/web/types/i18n.d.ts b/web/types/i18n.d.ts index 826fcc1613..b5e5b39aa7 100644 --- a/web/types/i18n.d.ts +++ b/web/types/i18n.d.ts @@ -38,45 +38,45 @@ type WorkflowMessages = typeof import('../i18n/en-US/workflow').default // Complete type structure that matches i18next-config.ts camelCase conversion export type Messages = { - appAnnotation: AppAnnotationMessages; - appApi: AppApiMessages; - appDebug: AppDebugMessages; - appLog: AppLogMessages; - appOverview: AppOverviewMessages; - app: AppMessages; - billing: BillingMessages; - common: CommonMessages; - custom: CustomMessages; - datasetCreation: DatasetCreationMessages; - datasetDocuments: DatasetDocumentsMessages; - datasetHitTesting: DatasetHitTestingMessages; - datasetPipeline: DatasetPipelineMessages; - datasetSettings: DatasetSettingsMessages; - dataset: DatasetMessages; - education: EducationMessages; - explore: ExploreMessages; - layout: LayoutMessages; - login: LoginMessages; - oauth: OauthMessages; - pipeline: PipelineMessages; - pluginTags: PluginTagsMessages; - pluginTrigger: PluginTriggerMessages; - plugin: PluginMessages; - register: RegisterMessages; - runLog: RunLogMessages; - share: ShareMessages; - time: TimeMessages; - tools: ToolsMessages; - workflow: WorkflowMessages; + appAnnotation: AppAnnotationMessages + appApi: AppApiMessages + appDebug: AppDebugMessages + appLog: AppLogMessages + appOverview: AppOverviewMessages + app: AppMessages + billing: BillingMessages + common: CommonMessages + custom: CustomMessages + datasetCreation: DatasetCreationMessages + datasetDocuments: DatasetDocumentsMessages + datasetHitTesting: DatasetHitTestingMessages + datasetPipeline: DatasetPipelineMessages + datasetSettings: DatasetSettingsMessages + dataset: DatasetMessages + education: EducationMessages + explore: ExploreMessages + layout: LayoutMessages + login: LoginMessages + oauth: OauthMessages + pipeline: PipelineMessages + pluginTags: PluginTagsMessages + pluginTrigger: PluginTriggerMessages + plugin: PluginMessages + register: RegisterMessages + runLog: RunLogMessages + share: ShareMessages + time: TimeMessages + tools: ToolsMessages + workflow: WorkflowMessages } // Utility type to flatten nested object keys into dot notation type FlattenKeys<T> = T extends object ? { - [K in keyof T]: T[K] extends object - ? `${K & string}.${FlattenKeys<T[K]> & string}` - : `${K & string}` - }[keyof T] + [K in keyof T]: T[K] extends object + ? `${K & string}.${FlattenKeys<T[K]> & string}` + : `${K & string}` + }[keyof T] : never export type ValidTranslationKeys = FlattenKeys<Messages> @@ -84,19 +84,19 @@ export type ValidTranslationKeys = FlattenKeys<Messages> // Extend react-i18next with Dify's type structure declare module 'react-i18next' { type CustomTypeOptions = { - defaultNS: 'translation'; + defaultNS: 'translation' resources: { - translation: Messages; - }; + translation: Messages + } } } // Extend i18next for complete type safety declare module 'i18next' { type CustomTypeOptions = { - defaultNS: 'translation'; + defaultNS: 'translation' resources: { - translation: Messages; - }; + translation: Messages + } } } diff --git a/web/types/react-18-input-autosize.d.ts b/web/types/react-18-input-autosize.d.ts index 0864b33e6a..c8eae58efb 100644 --- a/web/types/react-18-input-autosize.d.ts +++ b/web/types/react-18-input-autosize.d.ts @@ -1,5 +1,5 @@ declare module 'react-18-input-autosize' { - import type { CSSProperties, ChangeEvent, FocusEvent, KeyboardEvent } from 'react' + import type { ChangeEvent, CSSProperties, FocusEvent, KeyboardEvent } from 'react' export type AutosizeInputProps = { value?: string | number diff --git a/web/types/workflow.ts b/web/types/workflow.ts index efeb53185a..5f74ef2c12 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -1,26 +1,26 @@ -import type { Viewport } from 'reactflow' -import type { BlockEnum, CommonNodeType, ConversationVariable, Edge, EnvironmentVariable, InputVar, Node, ValueSelector, VarType, Variable } from '@/app/components/workflow/types' -import type { TransferMethod } from '@/types/app' -import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' -import type { RAGPipelineVariables } from '@/models/pipeline' -import type { BeforeRunFormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form' -import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' import type { RefObject } from 'react' +import type { Viewport } from 'reactflow' +import type { BeforeRunFormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form' +import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' +import type { BlockEnum, CommonNodeType, ConversationVariable, Edge, EnvironmentVariable, InputVar, Node, ValueSelector, Variable, VarType } from '@/app/components/workflow/types' +import type { RAGPipelineVariables } from '@/models/pipeline' +import type { TransferMethod } from '@/types/app' export type AgentLogItem = { - node_execution_id: string, - message_id: string, - node_id: string, - parent_id?: string, - label: string, - data: object, // debug data - error?: string, - status: string, + node_execution_id: string + message_id: string + node_id: string + parent_id?: string + label: string + data: object // debug data + error?: string + status: string metadata?: { elapsed_time?: number provider?: string icon?: string - }, + } } export type AgentLogItemWithChildren = AgentLogItem & { @@ -126,7 +126,7 @@ export type FetchWorkflowDraftResponse = { id: string name: string email: string - }, + } tool_published: boolean environment_variables?: EnvironmentVariable[] conversation_variables?: ConversationVariable[] @@ -337,7 +337,7 @@ export type NodesDefaultConfigsResponse = { }[] export type ConversationVariableResponse = { - data: (ConversationVariable & { updated_at: number; created_at: number })[] + data: (ConversationVariable & { updated_at: number, created_at: number })[] has_more: boolean limit: number total: number diff --git a/web/utils/app-redirection.spec.ts b/web/utils/app-redirection.spec.ts index 00aada9e53..ab790b1acc 100644 --- a/web/utils/app-redirection.spec.ts +++ b/web/utils/app-redirection.spec.ts @@ -12,43 +12,43 @@ describe('app-redirection', () => { * - App mode (workflow, advanced-chat, chat, completion, agent-chat) */ describe('getRedirectionPath', () => { - test('returns overview path when user is not editor', () => { + it('returns overview path when user is not editor', () => { const app = { id: 'app-123', mode: AppModeEnum.CHAT } const result = getRedirectionPath(false, app) expect(result).toBe('/app/app-123/overview') }) - test('returns workflow path for workflow mode when user is editor', () => { + it('returns workflow path for workflow mode when user is editor', () => { const app = { id: 'app-123', mode: AppModeEnum.WORKFLOW } const result = getRedirectionPath(true, app) expect(result).toBe('/app/app-123/workflow') }) - test('returns workflow path for advanced-chat mode when user is editor', () => { + it('returns workflow path for advanced-chat mode when user is editor', () => { const app = { id: 'app-123', mode: AppModeEnum.ADVANCED_CHAT } const result = getRedirectionPath(true, app) expect(result).toBe('/app/app-123/workflow') }) - test('returns configuration path for chat mode when user is editor', () => { + it('returns configuration path for chat mode when user is editor', () => { const app = { id: 'app-123', mode: AppModeEnum.CHAT } const result = getRedirectionPath(true, app) expect(result).toBe('/app/app-123/configuration') }) - test('returns configuration path for completion mode when user is editor', () => { + it('returns configuration path for completion mode when user is editor', () => { const app = { id: 'app-123', mode: AppModeEnum.COMPLETION } const result = getRedirectionPath(true, app) expect(result).toBe('/app/app-123/configuration') }) - test('returns configuration path for agent-chat mode when user is editor', () => { + it('returns configuration path for agent-chat mode when user is editor', () => { const app = { id: 'app-456', mode: AppModeEnum.AGENT_CHAT } const result = getRedirectionPath(true, app) expect(result).toBe('/app/app-456/configuration') }) - test('handles different app IDs', () => { + it('handles different app IDs', () => { const app1 = { id: 'abc-123', mode: AppModeEnum.CHAT } const app2 = { id: 'xyz-789', mode: AppModeEnum.WORKFLOW } @@ -64,7 +64,7 @@ describe('app-redirection', () => { /** * Tests that the redirection function is called with the correct path */ - test('calls redirection function with correct path for non-editor', () => { + it('calls redirection function with correct path for non-editor', () => { const app = { id: 'app-123', mode: AppModeEnum.CHAT } const mockRedirect = vi.fn() @@ -74,7 +74,7 @@ describe('app-redirection', () => { expect(mockRedirect).toHaveBeenCalledTimes(1) }) - test('calls redirection function with workflow path for editor', () => { + it('calls redirection function with workflow path for editor', () => { const app = { id: 'app-123', mode: AppModeEnum.WORKFLOW } const mockRedirect = vi.fn() @@ -84,7 +84,7 @@ describe('app-redirection', () => { expect(mockRedirect).toHaveBeenCalledTimes(1) }) - test('calls redirection function with configuration path for chat mode editor', () => { + it('calls redirection function with configuration path for chat mode editor', () => { const app = { id: 'app-123', mode: AppModeEnum.CHAT } const mockRedirect = vi.fn() @@ -94,7 +94,7 @@ describe('app-redirection', () => { expect(mockRedirect).toHaveBeenCalledTimes(1) }) - test('works with different redirection functions', () => { + it('works with different redirection functions', () => { const app = { id: 'app-123', mode: AppModeEnum.WORKFLOW } const paths: string[] = [] const customRedirect = (path: string) => paths.push(path) diff --git a/web/utils/classnames.spec.ts b/web/utils/classnames.spec.ts index a1481e9e49..1b8f487856 100644 --- a/web/utils/classnames.spec.ts +++ b/web/utils/classnames.spec.ts @@ -13,7 +13,7 @@ describe('classnames', () => { * - Falsy value filtering * - Object-based conditional classes */ - test('classnames libs feature', () => { + it('classnames libs feature', () => { expect(cn('foo')).toBe('foo') expect(cn('foo', 'bar')).toBe('foo bar') expect(cn(['foo', 'bar'])).toBe('foo bar') @@ -37,7 +37,7 @@ describe('classnames', () => { * - Custom color classes * - Arbitrary values */ - test('tailwind-merge', () => { + it('tailwind-merge', () => { /* eslint-disable tailwindcss/classnames-order */ expect(cn('p-0')).toBe('p-0') expect(cn('text-right text-center text-left')).toBe('text-left') @@ -68,7 +68,7 @@ describe('classnames', () => { * Tests the integration of classnames and tailwind-merge: * - Object-based conditional classes with Tailwind conflict resolution */ - test('classnames combined with tailwind-merge', () => { + it('classnames combined with tailwind-merge', () => { expect(cn('text-right', { 'text-center': true, })).toBe('text-center') @@ -83,7 +83,7 @@ describe('classnames', () => { * - Strings, arrays, and objects in a single call * - Tailwind merge working across different argument types */ - test('multiple mixed argument types', () => { + it('multiple mixed argument types', () => { expect(cn('foo', ['bar', 'baz'], { qux: true, quux: false })).toBe('foo bar baz qux') expect(cn('p-4', ['p-2', 'm-4'], { 'text-left': true, 'text-right': true })).toBe('p-2 m-4 text-right') }) @@ -93,7 +93,7 @@ describe('classnames', () => { * - Deep array flattening * - Tailwind merge with nested structures */ - test('nested arrays', () => { + it('nested arrays', () => { expect(cn(['foo', ['bar', 'baz']])).toBe('foo bar baz') expect(cn(['p-4', ['p-2', 'text-center']])).toBe('p-2 text-center') }) @@ -103,7 +103,7 @@ describe('classnames', () => { * - Empty strings, arrays, and objects * - Mixed empty and non-empty values */ - test('empty inputs', () => { + it('empty inputs', () => { expect(cn('')).toBe('') expect(cn([])).toBe('') expect(cn({})).toBe('') @@ -116,7 +116,7 @@ describe('classnames', () => { * - Truthy numbers converted to strings * - Zero treated as falsy */ - test('numbers as inputs', () => { + it('numbers as inputs', () => { expect(cn(1)).toBe('1') expect(cn(0)).toBe('') expect(cn('foo', 1, 'bar')).toBe('foo 1 bar') @@ -127,7 +127,7 @@ describe('classnames', () => { * - Object merging * - Tailwind conflict resolution across objects */ - test('multiple objects', () => { + it('multiple objects', () => { expect(cn({ foo: true }, { bar: true })).toBe('foo bar') expect(cn({ foo: true, bar: false }, { bar: true, baz: true })).toBe('foo bar baz') expect(cn({ 'p-4': true }, { 'p-2': true })).toBe('p-2') @@ -139,7 +139,7 @@ describe('classnames', () => { * - Nested arrays with falsy values * - Multiple conflicting Tailwind classes */ - test('complex edge cases', () => { + it('complex edge cases', () => { expect(cn('foo', null, undefined, false, 'bar', 0, 1, '')).toBe('foo bar 1') expect(cn(['foo', null, ['bar', undefined, 'baz']])).toBe('foo bar baz') expect(cn('text-sm', { 'text-lg': false, 'text-xl': true }, 'text-2xl')).toBe('text-2xl') @@ -150,7 +150,7 @@ describe('classnames', () => { * - Important modifiers in objects * - Conflict resolution with important prefix */ - test('important modifier with objects', () => { + it('important modifier with objects', () => { expect(cn({ '!font-medium': true }, { '!font-bold': true })).toBe('!font-bold') expect(cn('font-normal', { '!font-bold': true })).toBe('font-normal !font-bold') }) diff --git a/web/utils/classnames.ts b/web/utils/classnames.ts index d32b0fe652..abba253f04 100644 --- a/web/utils/classnames.ts +++ b/web/utils/classnames.ts @@ -1,4 +1,5 @@ -import { type ClassValue, clsx } from 'clsx' +import type { ClassValue } from 'clsx' +import { clsx } from 'clsx' import { twMerge } from 'tailwind-merge' export function cn(...inputs: ClassValue[]) { diff --git a/web/utils/completion-params.spec.ts b/web/utils/completion-params.spec.ts index 56aa1c0586..0b691a0baa 100644 --- a/web/utils/completion-params.spec.ts +++ b/web/utils/completion-params.spec.ts @@ -1,9 +1,9 @@ -import { mergeValidCompletionParams } from './completion-params' import type { FormValue, ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { mergeValidCompletionParams } from './completion-params' describe('completion-params', () => { describe('mergeValidCompletionParams', () => { - test('returns empty params and removedDetails for undefined oldParams', () => { + it('returns empty params and removedDetails for undefined oldParams', () => { const rules: ModelParameterRule[] = [] const result = mergeValidCompletionParams(undefined, rules) @@ -11,7 +11,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('returns empty params and removedDetails for empty oldParams', () => { + it('returns empty params and removedDetails for empty oldParams', () => { const rules: ModelParameterRule[] = [] const result = mergeValidCompletionParams({}, rules) @@ -19,7 +19,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('validates int type parameter within range', () => { + it('validates int type parameter within range', () => { const rules: ModelParameterRule[] = [ { name: 'max_tokens', type: 'int', min: 1, max: 4096, label: { en_US: 'Max Tokens', zh_Hans: '最大标记' }, required: false }, ] @@ -30,7 +30,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes int parameter below minimum', () => { + it('removes int parameter below minimum', () => { const rules: ModelParameterRule[] = [ { name: 'max_tokens', type: 'int', min: 1, max: 4096, label: { en_US: 'Max Tokens', zh_Hans: '最大标记' }, required: false }, ] @@ -41,7 +41,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ max_tokens: 'out of range (1-4096)' }) }) - test('removes int parameter above maximum', () => { + it('removes int parameter above maximum', () => { const rules: ModelParameterRule[] = [ { name: 'max_tokens', type: 'int', min: 1, max: 4096, label: { en_US: 'Max Tokens', zh_Hans: '最大标记' }, required: false }, ] @@ -52,7 +52,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ max_tokens: 'out of range (1-4096)' }) }) - test('removes int parameter with invalid type', () => { + it('removes int parameter with invalid type', () => { const rules: ModelParameterRule[] = [ { name: 'max_tokens', type: 'int', min: 1, max: 4096, label: { en_US: 'Max Tokens', zh_Hans: '最大标记' }, required: false }, ] @@ -63,7 +63,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ max_tokens: 'invalid type' }) }) - test('validates float type parameter', () => { + it('validates float type parameter', () => { const rules: ModelParameterRule[] = [ { name: 'temperature', type: 'float', min: 0, max: 2, label: { en_US: 'Temperature', zh_Hans: '温度' }, required: false }, ] @@ -74,7 +74,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('validates float at boundary values', () => { + it('validates float at boundary values', () => { const rules: ModelParameterRule[] = [ { name: 'temperature', type: 'float', min: 0, max: 2, label: { en_US: 'Temperature', zh_Hans: '温度' }, required: false }, ] @@ -86,7 +86,7 @@ describe('completion-params', () => { expect(result2.params).toEqual({ temperature: 2 }) }) - test('validates boolean type parameter', () => { + it('validates boolean type parameter', () => { const rules: ModelParameterRule[] = [ { name: 'stream', type: 'boolean', label: { en_US: 'Stream', zh_Hans: '流' }, required: false }, ] @@ -97,7 +97,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes boolean parameter with invalid type', () => { + it('removes boolean parameter with invalid type', () => { const rules: ModelParameterRule[] = [ { name: 'stream', type: 'boolean', label: { en_US: 'Stream', zh_Hans: '流' }, required: false }, ] @@ -108,7 +108,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ stream: 'invalid type' }) }) - test('validates string type parameter', () => { + it('validates string type parameter', () => { const rules: ModelParameterRule[] = [ { name: 'model', type: 'string', label: { en_US: 'Model', zh_Hans: '模型' }, required: false }, ] @@ -119,7 +119,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('validates string parameter with options', () => { + it('validates string parameter with options', () => { const rules: ModelParameterRule[] = [ { name: 'model', type: 'string', options: ['gpt-3.5-turbo', 'gpt-4'], label: { en_US: 'Model', zh_Hans: '模型' }, required: false }, ] @@ -130,7 +130,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes string parameter with invalid option', () => { + it('removes string parameter with invalid option', () => { const rules: ModelParameterRule[] = [ { name: 'model', type: 'string', options: ['gpt-3.5-turbo', 'gpt-4'], label: { en_US: 'Model', zh_Hans: '模型' }, required: false }, ] @@ -141,7 +141,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ model: 'unsupported option' }) }) - test('validates text type parameter', () => { + it('validates text type parameter', () => { const rules: ModelParameterRule[] = [ { name: 'prompt', type: 'text', label: { en_US: 'Prompt', zh_Hans: '提示' }, required: false }, ] @@ -152,7 +152,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes unsupported parameters', () => { + it('removes unsupported parameters', () => { const rules: ModelParameterRule[] = [ { name: 'temperature', type: 'float', min: 0, max: 2, label: { en_US: 'Temperature', zh_Hans: '温度' }, required: false }, ] @@ -163,7 +163,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ unsupported_param: 'unsupported' }) }) - test('keeps stop parameter in advanced mode even without rule', () => { + it('keeps stop parameter in advanced mode even without rule', () => { const rules: ModelParameterRule[] = [] const oldParams: FormValue = { stop: ['END'] } const result = mergeValidCompletionParams(oldParams, rules, true) @@ -172,7 +172,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes stop parameter in normal mode without rule', () => { + it('removes stop parameter in normal mode without rule', () => { const rules: ModelParameterRule[] = [] const oldParams: FormValue = { stop: ['END'] } const result = mergeValidCompletionParams(oldParams, rules, false) @@ -181,7 +181,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({ stop: 'unsupported' }) }) - test('handles multiple parameters with mixed validity', () => { + it('handles multiple parameters with mixed validity', () => { const rules: ModelParameterRule[] = [ { name: 'temperature', type: 'float', min: 0, max: 2, label: { en_US: 'Temperature', zh_Hans: '温度' }, required: false }, { name: 'max_tokens', type: 'int', min: 1, max: 4096, label: { en_US: 'Max Tokens', zh_Hans: '最大标记' }, required: false }, @@ -205,7 +205,7 @@ describe('completion-params', () => { }) }) - test('handles parameters without min/max constraints', () => { + it('handles parameters without min/max constraints', () => { const rules: ModelParameterRule[] = [ { name: 'value', type: 'int', label: { en_US: 'Value', zh_Hans: '值' }, required: false }, ] @@ -216,7 +216,7 @@ describe('completion-params', () => { expect(result.removedDetails).toEqual({}) }) - test('removes parameter with unsupported rule type', () => { + it('removes parameter with unsupported rule type', () => { const rules: ModelParameterRule[] = [ { name: 'custom', type: 'unknown_type', label: { en_US: 'Custom', zh_Hans: '自定义' }, required: false } as any, ] diff --git a/web/utils/completion-params.ts b/web/utils/completion-params.ts index 62eb884825..27c34cc8ea 100644 --- a/web/utils/completion-params.ts +++ b/web/utils/completion-params.ts @@ -4,7 +4,7 @@ export const mergeValidCompletionParams = ( oldParams: FormValue | undefined, rules: ModelParameterRule[], isAdvancedMode: boolean = false, -): { params: FormValue; removedDetails: Record<string, string> } => { +): { params: FormValue, removedDetails: Record<string, string> } => { if (!oldParams || Object.keys(oldParams).length === 0) return { params: {}, removedDetails: {} } @@ -81,7 +81,7 @@ export const fetchAndMergeValidCompletionParams = async ( modelId: string, oldParams: FormValue | undefined, isAdvancedMode: boolean = false, -): Promise<{ params: FormValue; removedDetails: Record<string, string> }> => { +): Promise<{ params: FormValue, removedDetails: Record<string, string> }> => { const { fetchModelParameterRules } = await import('@/service/common') const url = `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` const { data: parameterRules } = await fetchModelParameterRules(url) diff --git a/web/utils/context.spec.ts b/web/utils/context.spec.ts index 48a086ac4d..b70a15639a 100644 --- a/web/utils/context.spec.ts +++ b/web/utils/context.spec.ts @@ -1,3 +1,4 @@ +import { renderHook } from '@testing-library/react' /** * Test suite for React context creation utilities * @@ -9,7 +10,6 @@ * - createSelectorCtx: Context with selector support using use-context-selector library */ import React from 'react' -import { renderHook } from '@testing-library/react' import { createCtx, createSelectorCtx } from './context' describe('Context Utilities', () => { @@ -106,8 +106,8 @@ describe('Context Utilities', () => { */ it('should handle complex context values', () => { type ComplexContext = { - user: { id: string; name: string } - settings: { theme: string; locale: string } + user: { id: string, name: string } + settings: { theme: string, locale: string } actions: Array<() => void> } diff --git a/web/utils/context.ts b/web/utils/context.ts index 8829a679ce..6afd6131c7 100644 --- a/web/utils/context.ts +++ b/web/utils/context.ts @@ -1,9 +1,11 @@ -import { type Context, type Provider, createContext, useContext } from 'react' +import type { Context, Provider } from 'react' +import { createContext, useContext } from 'react' import * as selector from 'use-context-selector' const createCreateCtxFunction = ( useContextImpl: typeof useContext, - createContextImpl: typeof createContext) => { + createContextImpl: typeof createContext, +) => { return function<T>({ name, defaultValue }: CreateCtxOptions<T> = {}): CreateCtxReturn<T> { const emptySymbol = Symbol(`empty ${name}`) // @ts-expect-error it's ok here diff --git a/web/utils/emoji.spec.ts b/web/utils/emoji.spec.ts index 7cd026f6b3..9ee12e9430 100644 --- a/web/utils/emoji.spec.ts +++ b/web/utils/emoji.spec.ts @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' -import { searchEmoji } from './emoji' import { SearchIndex } from 'emoji-mart' +import { searchEmoji } from './emoji' vi.mock('emoji-mart', () => ({ SearchIndex: { diff --git a/web/utils/emoji.ts b/web/utils/emoji.ts index 9123f780f2..7d58f12b45 100644 --- a/web/utils/emoji.ts +++ b/web/utils/emoji.ts @@ -1,5 +1,5 @@ -import { SearchIndex } from 'emoji-mart' import type { Emoji } from '@emoji-mart/data' +import { SearchIndex } from 'emoji-mart' export async function searchEmoji(value: string) { const emojis: Emoji[] = await SearchIndex.search(value) || [] diff --git a/web/utils/encryption.ts b/web/utils/encryption.ts index f96d8d02ac..2c9e985f12 100644 --- a/web/utils/encryption.ts +++ b/web/utils/encryption.ts @@ -14,15 +14,15 @@ */ export function encryptField(plaintext: string): string { try { - // Base64 encode the plaintext - // btoa works with ASCII, so we need to handle UTF-8 properly + // Base64 encode the plaintext + // btoa works with ASCII, so we need to handle UTF-8 properly const utf8Bytes = new TextEncoder().encode(plaintext) const base64 = btoa(String.fromCharCode(...utf8Bytes)) return base64 } catch (error) { console.error('Field encoding failed:', error) - // If encoding fails, throw error to prevent sending plaintext + // If encoding fails, throw error to prevent sending plaintext throw new Error('Encoding failed. Please check your input.') } } diff --git a/web/utils/format.spec.ts b/web/utils/format.spec.ts index e02d17d335..0fde0ccbe8 100644 --- a/web/utils/format.spec.ts +++ b/web/utils/format.spec.ts @@ -1,67 +1,67 @@ import { downloadFile, formatFileSize, formatNumber, formatNumberAbbreviated, formatTime } from './format' describe('formatNumber', () => { - test('should correctly format integers', () => { + it('should correctly format integers', () => { expect(formatNumber(1234567)).toBe('1,234,567') }) - test('should correctly format decimals', () => { + it('should correctly format decimals', () => { expect(formatNumber(1234567.89)).toBe('1,234,567.89') }) - test('should correctly handle string input', () => { + it('should correctly handle string input', () => { expect(formatNumber('1234567')).toBe('1,234,567') }) - test('should correctly handle zero', () => { + it('should correctly handle zero', () => { expect(formatNumber(0)).toBe(0) }) - test('should correctly handle negative numbers', () => { + it('should correctly handle negative numbers', () => { expect(formatNumber(-1234567)).toBe('-1,234,567') }) - test('should correctly handle empty input', () => { + it('should correctly handle empty input', () => { expect(formatNumber('')).toBe('') }) }) describe('formatFileSize', () => { - test('should return the input if it is falsy', () => { + it('should return the input if it is falsy', () => { expect(formatFileSize(0)).toBe(0) }) - test('should format bytes correctly', () => { + it('should format bytes correctly', () => { expect(formatFileSize(500)).toBe('500.00 bytes') }) - test('should format kilobytes correctly', () => { + it('should format kilobytes correctly', () => { expect(formatFileSize(1500)).toBe('1.46 KB') }) - test('should format megabytes correctly', () => { + it('should format megabytes correctly', () => { expect(formatFileSize(1500000)).toBe('1.43 MB') }) - test('should format gigabytes correctly', () => { + it('should format gigabytes correctly', () => { expect(formatFileSize(1500000000)).toBe('1.40 GB') }) - test('should format terabytes correctly', () => { + it('should format terabytes correctly', () => { expect(formatFileSize(1500000000000)).toBe('1.36 TB') }) - test('should format petabytes correctly', () => { + it('should format petabytes correctly', () => { expect(formatFileSize(1500000000000000)).toBe('1.33 PB') }) }) describe('formatTime', () => { - test('should return the input if it is falsy', () => { + it('should return the input if it is falsy', () => { expect(formatTime(0)).toBe(0) }) - test('should format seconds correctly', () => { + it('should format seconds correctly', () => { expect(formatTime(30)).toBe('30.00 sec') }) - test('should format minutes correctly', () => { + it('should format minutes correctly', () => { expect(formatTime(90)).toBe('1.50 min') }) - test('should format hours correctly', () => { + it('should format hours correctly', () => { expect(formatTime(3600)).toBe('1.00 h') }) - test('should handle large numbers', () => { + it('should handle large numbers', () => { expect(formatTime(7200)).toBe('2.00 h') }) }) describe('downloadFile', () => { - test('should create a link and trigger a download correctly', () => { + it('should create a link and trigger a download correctly', () => { // Mock data const blob = new Blob(['test content'], { type: 'text/plain' }) const fileName = 'test-file.txt' diff --git a/web/utils/format.ts b/web/utils/format.ts index fe5b1deb7d..a2c3ef9751 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -1,5 +1,5 @@ -import type { Locale } from '@/i18n-config' import type { Dayjs } from 'dayjs' +import type { Locale } from '@/i18n-config' import 'dayjs/locale/de' import 'dayjs/locale/es' import 'dayjs/locale/fa' @@ -95,7 +95,7 @@ export const formatTime = (seconds: number) => { return `${seconds.toFixed(2)} ${units[index]}` } -export const downloadFile = ({ data, fileName }: { data: Blob; fileName: string }) => { +export const downloadFile = ({ data, fileName }: { data: Blob, fileName: string }) => { const url = window.URL.createObjectURL(data) const a = document.createElement('a') a.href = url @@ -119,7 +119,8 @@ export const downloadFile = ({ data, fileName }: { data: Blob; fileName: string */ export const formatNumberAbbreviated = (num: number) => { // If less than 1000, return as-is - if (num < 1000) return num.toString() + if (num < 1000) + return num.toString() // Define thresholds and suffixes const units = [ diff --git a/web/utils/get-icon.spec.ts b/web/utils/get-icon.spec.ts index 98eb2288fd..84c2191dd8 100644 --- a/web/utils/get-icon.spec.ts +++ b/web/utils/get-icon.spec.ts @@ -1,16 +1,16 @@ +import { MARKETPLACE_API_PREFIX } from '@/config' /** * Test suite for icon utility functions * Tests the generation of marketplace plugin icon URLs */ import { getIconFromMarketPlace } from './get-icon' -import { MARKETPLACE_API_PREFIX } from '@/config' describe('get-icon', () => { describe('getIconFromMarketPlace', () => { /** * Tests basic URL generation for marketplace plugin icons */ - test('returns correct marketplace icon URL', () => { + it('returns correct marketplace icon URL', () => { const pluginId = 'test-plugin-123' const result = getIconFromMarketPlace(pluginId) expect(result).toBe(`${MARKETPLACE_API_PREFIX}/plugins/${pluginId}/icon`) @@ -20,7 +20,7 @@ describe('get-icon', () => { * Tests URL generation with plugin IDs containing special characters * like dashes and underscores */ - test('handles plugin ID with special characters', () => { + it('handles plugin ID with special characters', () => { const pluginId = 'plugin-with-dashes_and_underscores' const result = getIconFromMarketPlace(pluginId) expect(result).toBe(`${MARKETPLACE_API_PREFIX}/plugins/${pluginId}/icon`) @@ -30,7 +30,7 @@ describe('get-icon', () => { * Tests behavior with empty plugin ID * Note: This creates a malformed URL but doesn't throw an error */ - test('handles empty plugin ID', () => { + it('handles empty plugin ID', () => { const pluginId = '' const result = getIconFromMarketPlace(pluginId) expect(result).toBe(`${MARKETPLACE_API_PREFIX}/plugins//icon`) @@ -40,7 +40,7 @@ describe('get-icon', () => { * Tests URL generation with plugin IDs containing spaces * Spaces will be URL-encoded when actually used */ - test('handles plugin ID with spaces', () => { + it('handles plugin ID with spaces', () => { const pluginId = 'plugin with spaces' const result = getIconFromMarketPlace(pluginId) expect(result).toBe(`${MARKETPLACE_API_PREFIX}/plugins/${pluginId}/icon`) @@ -51,7 +51,7 @@ describe('get-icon', () => { * These tests document current behavior and potential security concerns * Note: Current implementation does not sanitize path traversal sequences */ - test('handles path traversal attempts', () => { + it('handles path traversal attempts', () => { const pluginId = '../../../etc/passwd' const result = getIconFromMarketPlace(pluginId) // Current implementation includes path traversal sequences in URL @@ -60,7 +60,7 @@ describe('get-icon', () => { expect(result).toContain(pluginId) }) - test('handles multiple path traversal attempts', () => { + it('handles multiple path traversal attempts', () => { const pluginId = '../../../../etc/passwd' const result = getIconFromMarketPlace(pluginId) // Current implementation includes path traversal sequences in URL @@ -68,7 +68,7 @@ describe('get-icon', () => { expect(result).toContain(pluginId) }) - test('passes through URL-encoded path traversal sequences', () => { + it('passes through URL-encoded path traversal sequences', () => { const pluginId = '..%2F..%2Fetc%2Fpasswd' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) @@ -79,14 +79,14 @@ describe('get-icon', () => { * These tests document current behavior with invalid input types * Note: Current implementation converts null/undefined to strings instead of throwing */ - test('handles null plugin ID', () => { + it('handles null plugin ID', () => { // Current implementation converts null to string "null" const result = getIconFromMarketPlace(null as any) expect(result).toContain('null') // This is a potential issue - should validate input type }) - test('handles undefined plugin ID', () => { + it('handles undefined plugin ID', () => { // Current implementation converts undefined to string "undefined" const result = getIconFromMarketPlace(undefined as any) expect(result).toContain('undefined') @@ -97,7 +97,7 @@ describe('get-icon', () => { * Security tests: URL-sensitive characters * These tests verify that URL-sensitive characters are handled appropriately */ - test('does not encode URL-sensitive characters', () => { + it('does not encode URL-sensitive characters', () => { const pluginId = 'plugin/with?special=chars#hash' const result = getIconFromMarketPlace(pluginId) // Note: Current implementation doesn't encode, but test documents the behavior @@ -107,7 +107,7 @@ describe('get-icon', () => { expect(result).toContain('=') }) - test('handles URL characters like & and %', () => { + it('handles URL characters like & and %', () => { const pluginId = 'plugin&with%encoding' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) @@ -117,20 +117,20 @@ describe('get-icon', () => { * Edge case tests: Extreme inputs * These tests verify behavior with unusual but valid inputs */ - test('handles very long plugin ID', () => { + it('handles very long plugin ID', () => { const pluginId = 'a'.repeat(10000) const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) expect(result.length).toBeGreaterThan(10000) }) - test('handles Unicode characters', () => { + it('handles Unicode characters', () => { const pluginId = '插件-🚀-测试-日本語' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) }) - test('handles control characters', () => { + it('handles control characters', () => { const pluginId = 'plugin\nwith\ttabs\r\nand\0null' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) @@ -140,20 +140,20 @@ describe('get-icon', () => { * Security tests: XSS attempts * These tests verify that XSS attempts are handled appropriately */ - test('handles XSS attempts with script tags', () => { + it('handles XSS attempts with script tags', () => { const pluginId = '<script>alert("xss")</script>' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) // Note: Current implementation doesn't sanitize, but test documents the behavior }) - test('handles XSS attempts with event handlers', () => { + it('handles XSS attempts with event handlers', () => { const pluginId = 'plugin"onerror="alert(1)"' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) }) - test('handles XSS attempts with encoded script tags', () => { + it('handles XSS attempts with encoded script tags', () => { const pluginId = '%3Cscript%3Ealert%28%22xss%22%29%3C%2Fscript%3E' const result = getIconFromMarketPlace(pluginId) expect(result).toContain(pluginId) diff --git a/web/utils/index.spec.ts b/web/utils/index.spec.ts index d547c75d67..7eb6c32eca 100644 --- a/web/utils/index.spec.ts +++ b/web/utils/index.spec.ts @@ -408,7 +408,7 @@ describe('randomString extended', () => { }) it('should only contain valid characters', () => { - const validChars = /^[0-9a-zA-Z_-]+$/ + const validChars = /^[\w-]+$/ const str = randomString(100) expect(validChars.test(str)).toBe(true) }) diff --git a/web/utils/mcp.spec.ts b/web/utils/mcp.spec.ts index d3c5ef1eab..39d5881e93 100644 --- a/web/utils/mcp.spec.ts +++ b/web/utils/mcp.spec.ts @@ -13,39 +13,39 @@ describe('mcp', () => { /** * The link emoji (🔗) is used as a special marker for MCP icons */ - test('returns true for emoji object with 🔗 content', () => { + it('returns true for emoji object with 🔗 content', () => { const src = { content: '🔗', background: '#fff' } expect(shouldUseMcpIcon(src)).toBe(true) }) - test('returns false for emoji object with different content', () => { + it('returns false for emoji object with different content', () => { const src = { content: '🎉', background: '#fff' } expect(shouldUseMcpIcon(src)).toBe(false) }) - test('returns false for string URL', () => { + it('returns false for string URL', () => { const src = 'https://example.com/icon.png' expect(shouldUseMcpIcon(src)).toBe(false) }) - test('returns false for null', () => { + it('returns false for null', () => { expect(shouldUseMcpIcon(null)).toBe(false) }) - test('returns false for undefined', () => { + it('returns false for undefined', () => { expect(shouldUseMcpIcon(undefined)).toBe(false) }) - test('returns false for empty object', () => { + it('returns false for empty object', () => { expect(shouldUseMcpIcon({})).toBe(false) }) - test('returns false for object without content property', () => { + it('returns false for object without content property', () => { const src = { background: '#fff' } expect(shouldUseMcpIcon(src)).toBe(false) }) - test('returns false for object with null content', () => { + it('returns false for object with null content', () => { const src = { content: null, background: '#fff' } expect(shouldUseMcpIcon(src)).toBe(false) }) @@ -61,27 +61,27 @@ describe('mcp', () => { * - Icon type is 'emoji' * - Icon content is the link emoji (🔗) */ - test('returns true when iconType is emoji and icon is 🔗', () => { + it('returns true when iconType is emoji and icon is 🔗', () => { expect(shouldUseMcpIconForAppIcon('emoji', '🔗')).toBe(true) }) - test('returns false when iconType is emoji but icon is different', () => { + it('returns false when iconType is emoji but icon is different', () => { expect(shouldUseMcpIconForAppIcon('emoji', '🎉')).toBe(false) }) - test('returns false when iconType is image', () => { + it('returns false when iconType is image', () => { expect(shouldUseMcpIconForAppIcon('image', '🔗')).toBe(false) }) - test('returns false when iconType is image and icon is different', () => { + it('returns false when iconType is image and icon is different', () => { expect(shouldUseMcpIconForAppIcon('image', 'file-id-123')).toBe(false) }) - test('returns false for empty strings', () => { + it('returns false for empty strings', () => { expect(shouldUseMcpIconForAppIcon('', '')).toBe(false) }) - test('returns false when iconType is empty but icon is 🔗', () => { + it('returns false when iconType is empty but icon is 🔗', () => { expect(shouldUseMcpIconForAppIcon('', '🔗')).toBe(false) }) }) diff --git a/web/utils/model-config.spec.ts b/web/utils/model-config.spec.ts index 2cccaabc61..9e39883e61 100644 --- a/web/utils/model-config.spec.ts +++ b/web/utils/model-config.spec.ts @@ -1,3 +1,5 @@ +import type { PromptVariable } from '@/models/debug' +import type { UserInputFormItem } from '@/types/app' /** * Test suite for model configuration transformation utilities * @@ -15,8 +17,6 @@ import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables, } from './model-config' -import type { UserInputFormItem } from '@/types/app' -import type { PromptVariable } from '@/models/debug' describe('Model Config Utilities', () => { describe('userInputsFormToPromptVariables', () => { diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index 707a3685b9..6ed408b37f 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -1,5 +1,5 @@ -import type { UserInputFormItem } from '@/types/app' import type { PromptVariable } from '@/models/debug' +import type { UserInputFormItem } from '@/types/app' export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null, dataset_query_variable?: string) => { if (!useInputs) @@ -196,7 +196,7 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ } export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record<string, string | number | object | boolean> | null) => { - if(!useInputs) + if (!useInputs) return inputs const res = { ...inputs } useInputs.forEach((item) => { diff --git a/web/utils/navigation.spec.ts b/web/utils/navigation.spec.ts index dadb34e714..c6327e6b63 100644 --- a/web/utils/navigation.spec.ts +++ b/web/utils/navigation.spec.ts @@ -33,28 +33,28 @@ describe('navigation', () => { * Tests createNavigationPath which builds URLs with optional query parameter preservation */ describe('createNavigationPath', () => { - test('preserves query parameters by default', () => { + it('preserves query parameters by default', () => { const result = createNavigationPath('/datasets/123/documents') expect(result).toBe('/datasets/123/documents?page=3&limit=10&keyword=test') }) - test('returns clean path when preserveParams is false', () => { + it('returns clean path when preserveParams is false', () => { const result = createNavigationPath('/datasets/123/documents', false) expect(result).toBe('/datasets/123/documents') }) - test('handles empty query string', () => { + it('handles empty query string', () => { globalThis.window.location.search = '' const result = createNavigationPath('/datasets/123/documents') expect(result).toBe('/datasets/123/documents') }) - test('handles path with trailing slash', () => { + it('handles path with trailing slash', () => { const result = createNavigationPath('/datasets/123/documents/') expect(result).toBe('/datasets/123/documents/?page=3&limit=10&keyword=test') }) - test('handles root path', () => { + it('handles root path', () => { const result = createNavigationPath('/') expect(result).toBe('/?page=3&limit=10&keyword=test') }) @@ -67,7 +67,7 @@ describe('navigation', () => { /** * Tests that the returned function properly navigates with preserved params */ - test('returns function that calls router.push with correct path', () => { + it('returns function that calls router.push with correct path', () => { const mockRouter = { push: vi.fn() } const backNav = createBackNavigation(mockRouter, '/datasets/123/documents') @@ -76,7 +76,7 @@ describe('navigation', () => { expect(mockRouter.push).toHaveBeenCalledWith('/datasets/123/documents?page=3&limit=10&keyword=test') }) - test('returns function that navigates without params when preserveParams is false', () => { + it('returns function that navigates without params when preserveParams is false', () => { const mockRouter = { push: vi.fn() } const backNav = createBackNavigation(mockRouter, '/datasets/123/documents', false) @@ -85,7 +85,7 @@ describe('navigation', () => { expect(mockRouter.push).toHaveBeenCalledWith('/datasets/123/documents') }) - test('can be called multiple times', () => { + it('can be called multiple times', () => { const mockRouter = { push: vi.fn() } const backNav = createBackNavigation(mockRouter, '/datasets/123/documents') @@ -103,32 +103,32 @@ describe('navigation', () => { /** * Tests selective parameter extraction */ - test('extracts specified parameters', () => { + it('extracts specified parameters', () => { const result = extractQueryParams(['page', 'limit']) expect(result).toEqual({ page: '3', limit: '10' }) }) - test('extracts all specified parameters including keyword', () => { + it('extracts all specified parameters including keyword', () => { const result = extractQueryParams(['page', 'limit', 'keyword']) expect(result).toEqual({ page: '3', limit: '10', keyword: 'test' }) }) - test('ignores non-existent parameters', () => { + it('ignores non-existent parameters', () => { const result = extractQueryParams(['page', 'nonexistent']) expect(result).toEqual({ page: '3' }) }) - test('returns empty object when no parameters match', () => { + it('returns empty object when no parameters match', () => { const result = extractQueryParams(['foo', 'bar']) expect(result).toEqual({}) }) - test('returns empty object for empty array', () => { + it('returns empty object for empty array', () => { const result = extractQueryParams([]) expect(result).toEqual({}) }) - test('handles empty query string', () => { + it('handles empty query string', () => { globalThis.window.location.search = '' const result = extractQueryParams(['page', 'limit']) expect(result).toEqual({}) @@ -142,7 +142,7 @@ describe('navigation', () => { /** * Tests URL construction with custom parameters */ - test('creates path with specified parameters', () => { + it('creates path with specified parameters', () => { const result = createNavigationPathWithParams('/datasets/123/documents', { page: '1', limit: '25', @@ -150,7 +150,7 @@ describe('navigation', () => { expect(result).toBe('/datasets/123/documents?page=1&limit=25') }) - test('handles string and number values', () => { + it('handles string and number values', () => { const result = createNavigationPathWithParams('/datasets/123/documents', { page: 1, limit: 25, @@ -159,7 +159,7 @@ describe('navigation', () => { expect(result).toBe('/datasets/123/documents?page=1&limit=25&keyword=search') }) - test('filters out empty string values', () => { + it('filters out empty string values', () => { const result = createNavigationPathWithParams('/datasets/123/documents', { page: '1', keyword: '', @@ -167,7 +167,7 @@ describe('navigation', () => { expect(result).toBe('/datasets/123/documents?page=1') }) - test('filters out null and undefined values', () => { + it('filters out null and undefined values', () => { const result = createNavigationPathWithParams('/datasets/123/documents', { page: '1', keyword: null as any, @@ -176,12 +176,12 @@ describe('navigation', () => { expect(result).toBe('/datasets/123/documents?page=1') }) - test('returns base path when params are empty', () => { + it('returns base path when params are empty', () => { const result = createNavigationPathWithParams('/datasets/123/documents', {}) expect(result).toBe('/datasets/123/documents') }) - test('encodes special characters in values', () => { + it('encodes special characters in values', () => { const result = createNavigationPathWithParams('/datasets/123/documents', { keyword: 'search term', }) @@ -196,51 +196,51 @@ describe('navigation', () => { /** * Tests parameter merging and overriding */ - test('merges new params with existing ones', () => { + it('merges new params with existing ones', () => { const result = mergeQueryParams({ keyword: 'new', page: '1' }) expect(result.get('page')).toBe('1') expect(result.get('limit')).toBe('10') expect(result.get('keyword')).toBe('new') }) - test('overrides existing parameters', () => { + it('overrides existing parameters', () => { const result = mergeQueryParams({ page: '5' }) expect(result.get('page')).toBe('5') expect(result.get('limit')).toBe('10') }) - test('adds new parameters', () => { + it('adds new parameters', () => { const result = mergeQueryParams({ filter: 'active' }) expect(result.get('filter')).toBe('active') expect(result.get('page')).toBe('3') }) - test('removes parameters with null value', () => { + it('removes parameters with null value', () => { const result = mergeQueryParams({ page: null }) expect(result.get('page')).toBeNull() expect(result.get('limit')).toBe('10') }) - test('removes parameters with undefined value', () => { + it('removes parameters with undefined value', () => { const result = mergeQueryParams({ page: undefined }) expect(result.get('page')).toBeNull() expect(result.get('limit')).toBe('10') }) - test('does not preserve existing when preserveExisting is false', () => { + it('does not preserve existing when preserveExisting is false', () => { const result = mergeQueryParams({ filter: 'active' }, false) expect(result.get('filter')).toBe('active') expect(result.get('page')).toBeNull() expect(result.get('limit')).toBeNull() }) - test('handles number values', () => { + it('handles number values', () => { const result = mergeQueryParams({ page: 5, limit: 20 }) expect(result.get('page')).toBe('5') expect(result.get('limit')).toBe('20') }) - test('does not add empty string values', () => { + it('does not add empty string values', () => { const result = mergeQueryParams({ newParam: '' }) expect(result.get('newParam')).toBeNull() // Existing params are preserved @@ -256,7 +256,7 @@ describe('navigation', () => { * Tests navigation back to dataset documents list */ describe('backToDocuments', () => { - test('creates navigation function with preserved params', () => { + it('creates navigation function with preserved params', () => { const mockRouter = { push: vi.fn() } const backNav = datasetNavigation.backToDocuments(mockRouter, 'dataset-123') @@ -270,7 +270,7 @@ describe('navigation', () => { * Tests navigation to document detail page */ describe('toDocumentDetail', () => { - test('creates navigation function to document detail', () => { + it('creates navigation function to document detail', () => { const mockRouter = { push: vi.fn() } const navFunc = datasetNavigation.toDocumentDetail(mockRouter, 'dataset-123', 'doc-456') @@ -284,7 +284,7 @@ describe('navigation', () => { * Tests navigation to document settings page */ describe('toDocumentSettings', () => { - test('creates navigation function to document settings', () => { + it('creates navigation function to document settings', () => { const mockRouter = { push: vi.fn() } const navFunc = datasetNavigation.toDocumentSettings(mockRouter, 'dataset-123', 'doc-456') diff --git a/web/utils/permission.spec.ts b/web/utils/permission.spec.ts index 758c38037e..42c6e9636e 100644 --- a/web/utils/permission.spec.ts +++ b/web/utils/permission.spec.ts @@ -1,9 +1,9 @@ +import { DatasetPermission } from '@/models/datasets' /** * Test suite for permission utility functions * Tests dataset edit permission logic based on user roles and dataset settings */ import { hasEditPermissionForDataset } from './permission' -import { DatasetPermission } from '@/models/datasets' describe('permission', () => { /** @@ -18,7 +18,7 @@ describe('permission', () => { const creatorId = 'creator-456' const otherUserId = 'user-789' - test('returns true when permission is onlyMe and user is creator', () => { + it('returns true when permission is onlyMe and user is creator', () => { const config = { createdBy: userId, partialMemberList: [], @@ -27,7 +27,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(userId, config)).toBe(true) }) - test('returns false when permission is onlyMe and user is not creator', () => { + it('returns false when permission is onlyMe and user is not creator', () => { const config = { createdBy: creatorId, partialMemberList: [], @@ -36,7 +36,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(userId, config)).toBe(false) }) - test('returns true when permission is allTeamMembers for any user', () => { + it('returns true when permission is allTeamMembers for any user', () => { const config = { createdBy: creatorId, partialMemberList: [], @@ -47,7 +47,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(creatorId, config)).toBe(true) }) - test('returns true when permission is partialMembers and user is in list', () => { + it('returns true when permission is partialMembers and user is in list', () => { const config = { createdBy: creatorId, partialMemberList: [userId, otherUserId], @@ -56,7 +56,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(userId, config)).toBe(true) }) - test('returns false when permission is partialMembers and user is not in list', () => { + it('returns false when permission is partialMembers and user is not in list', () => { const config = { createdBy: creatorId, partialMemberList: [otherUserId], @@ -65,7 +65,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(userId, config)).toBe(false) }) - test('returns false when permission is partialMembers with empty list', () => { + it('returns false when permission is partialMembers with empty list', () => { const config = { createdBy: creatorId, partialMemberList: [], @@ -74,7 +74,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(userId, config)).toBe(false) }) - test('creator is not automatically granted access with partialMembers permission', () => { + it('creator is not automatically granted access with partialMembers permission', () => { const config = { createdBy: creatorId, partialMemberList: [userId], @@ -83,7 +83,7 @@ describe('permission', () => { expect(hasEditPermissionForDataset(creatorId, config)).toBe(false) }) - test('creator has access when included in partialMemberList', () => { + it('creator has access when included in partialMemberList', () => { const config = { createdBy: creatorId, partialMemberList: [creatorId, userId], diff --git a/web/utils/time.spec.ts b/web/utils/time.spec.ts index fc57390fad..9f194950e7 100644 --- a/web/utils/time.spec.ts +++ b/web/utils/time.spec.ts @@ -10,36 +10,36 @@ describe('time', () => { * Returns true if the first date is after the second */ describe('isAfter', () => { - test('returns true when first date is after second date', () => { + it('returns true when first date is after second date', () => { const date1 = '2024-01-02' const date2 = '2024-01-01' expect(isAfter(date1, date2)).toBe(true) }) - test('returns false when first date is before second date', () => { + it('returns false when first date is before second date', () => { const date1 = '2024-01-01' const date2 = '2024-01-02' expect(isAfter(date1, date2)).toBe(false) }) - test('returns false when dates are equal', () => { + it('returns false when dates are equal', () => { const date = '2024-01-01' expect(isAfter(date, date)).toBe(false) }) - test('works with Date objects', () => { + it('works with Date objects', () => { const date1 = new Date('2024-01-02') const date2 = new Date('2024-01-01') expect(isAfter(date1, date2)).toBe(true) }) - test('works with timestamps', () => { + it('works with timestamps', () => { const date1 = 1704240000000 // 2024-01-03 const date2 = 1704153600000 // 2024-01-02 expect(isAfter(date1, date2)).toBe(true) }) - test('handles time differences within same day', () => { + it('handles time differences within same day', () => { const date1 = '2024-01-01 12:00:00' const date2 = '2024-01-01 11:00:00' expect(isAfter(date1, date2)).toBe(true) @@ -54,44 +54,44 @@ describe('time', () => { /** * Tests basic date formatting with standard format */ - test('formats date with YYYY-MM-DD format', () => { + it('formats date with YYYY-MM-DD format', () => { const date = '2024-01-15' const result = formatTime({ date, dateFormat: 'YYYY-MM-DD' }) expect(result).toBe('2024-01-15') }) - test('formats date with custom format', () => { + it('formats date with custom format', () => { const date = '2024-01-15 14:30:00' const result = formatTime({ date, dateFormat: 'MMM DD, YYYY HH:mm' }) expect(result).toBe('Jan 15, 2024 14:30') }) - test('formats date with full month name', () => { + it('formats date with full month name', () => { const date = '2024-01-15' const result = formatTime({ date, dateFormat: 'MMMM DD, YYYY' }) expect(result).toBe('January 15, 2024') }) - test('formats date with time only', () => { + it('formats date with time only', () => { const date = '2024-01-15 14:30:45' const result = formatTime({ date, dateFormat: 'HH:mm:ss' }) expect(result).toBe('14:30:45') }) - test('works with Date objects', () => { + it('works with Date objects', () => { const date = new Date(2024, 0, 15) // Month is 0-indexed const result = formatTime({ date, dateFormat: 'YYYY-MM-DD' }) expect(result).toBe('2024-01-15') }) - test('works with timestamps', () => { + it('works with timestamps', () => { const date = 1705276800000 // 2024-01-15 00:00:00 UTC const result = formatTime({ date, dateFormat: 'YYYY-MM-DD' }) // Account for timezone differences: UTC-5 to UTC+8 can result in 2024-01-14 or 2024-01-15 expect(result).toMatch(/^2024-01-(14|15)$/) }) - test('handles ISO 8601 format', () => { + it('handles ISO 8601 format', () => { const date = '2024-01-15T14:30:00Z' const result = formatTime({ date, dateFormat: 'YYYY-MM-DD HH:mm' }) expect(result).toContain('2024-01-15') diff --git a/web/utils/time.ts b/web/utils/time.ts index daa54a5bf3..0a26a24652 100644 --- a/web/utils/time.ts +++ b/web/utils/time.ts @@ -1,4 +1,5 @@ -import dayjs, { type ConfigType } from 'dayjs' +import type { ConfigType } from 'dayjs' +import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' dayjs.extend(utc) @@ -7,7 +8,7 @@ export const isAfter = (date: ConfigType, compare: ConfigType) => { return dayjs(date).isAfter(dayjs(compare)) } -export const formatTime = ({ date, dateFormat }: { date: ConfigType; dateFormat: string }) => { +export const formatTime = ({ date, dateFormat }: { date: ConfigType, dateFormat: string }) => { return dayjs(date).format(dateFormat) } diff --git a/web/utils/timezone.json b/web/utils/timezone.json index 80e07ac416..99104a7801 100644 --- a/web/utils/timezone.json +++ b/web/utils/timezone.json @@ -1,1274 +1,1274 @@ [ - { - "name": "-11:00 Niue Time - Alofi", - "value": "Pacific/Niue" - }, - { - "name": "-11:00 Samoa Time - Midway", - "value": "Pacific/Midway" - }, - { - "name": "-11:00 Samoa Time - Pago Pago", - "value": "Pacific/Pago_Pago" - }, - { - "name": "-10:00 Cook Islands Time - Avarua", - "value": "Pacific/Rarotonga" - }, - { - "name": "-10:00 Hawaii-Aleutian Time - Adak", - "value": "America/Adak" - }, - { - "name": "-10:00 Hawaii-Aleutian Time - Honolulu, East Honolulu, Pearl City, Hilo", - "value": "Pacific/Honolulu" - }, - { - "name": "-10:00 Tahiti Time - Faaa, Papeete, Punaauia", - "value": "Pacific/Tahiti" - }, - { - "name": "-09:30 Marquesas Time - Marquesas", - "value": "Pacific/Marquesas" - }, - { - "name": "-09:00 Alaska Time - Anchorage, Juneau, Fairbanks, Eagle River", - "value": "America/Anchorage" - }, - { - "name": "-09:00 Gambier Time - Gambier", - "value": "Pacific/Gambier" - }, - { - "name": "-08:00 Pacific Time - Los Angeles, San Diego, San Jose, San Francisco", - "value": "America/Los_Angeles" - }, - { - "name": "-08:00 Pacific Time - Tijuana, Mexicali, Ensenada, Rosarito", - "value": "America/Tijuana" - }, - { - "name": "-08:00 Pacific Time - Vancouver, Surrey, Okanagan, Victoria", - "value": "America/Vancouver" - }, - { - "name": "-08:00 Pitcairn Time - Adamstown", - "value": "Pacific/Pitcairn" - }, - { - "name": "-07:00 Mexican Pacific Time - Hermosillo, Culiacán, Ciudad Obregón, Mazatlán", - "value": "America/Hermosillo" - }, - { - "name": "-07:00 Mountain Time - Calgary, Edmonton, Red Deer, Sherwood Park", - "value": "America/Edmonton" - }, - { - "name": "-07:00 Mountain Time - Ciudad Juárez", - "value": "America/Ciudad_Juarez" - }, - { - "name": "-07:00 Mountain Time - Denver, El Paso, Albuquerque, Colorado Springs", - "value": "America/Denver" - }, - { - "name": "-07:00 Mountain Time - Phoenix, Tucson, Mesa, Chandler", - "value": "America/Phoenix" - }, - { - "name": "-07:00 Yukon Time - Whitehorse, Fort St. John, Creston, Dawson", - "value": "America/Whitehorse" - }, - { - "name": "-06:00 Central Time - Belize City, San Ignacio, San Pedro, Orange Walk", - "value": "America/Belize" - }, - { - "name": "-06:00 Central Time - Chicago, Houston, San Antonio, Dallas", - "value": "America/Chicago" - }, - { - "name": "-06:00 Central Time - Guatemala City, Villa Nueva, Mixco, Cobán", - "value": "America/Guatemala" - }, - { - "name": "-06:00 Central Time - Managua, León, Masaya, Chinandega", - "value": "America/Managua" - }, - { - "name": "-06:00 Central Time - Mexico City, Iztapalapa, León de los Aldama, Puebla", - "value": "America/Mexico_City" - }, - { - "name": "-06:00 Central Time - Reynosa, Heroica Matamoros, Nuevo Laredo, Piedras Negras", - "value": "America/Matamoros" - }, - { - "name": "-06:00 Central Time - San José, Limón, San Francisco, Alajuela", - "value": "America/Costa_Rica" - }, - { - "name": "-06:00 Central Time - San Salvador, Soyapango, San Miguel, Santa Ana", - "value": "America/El_Salvador" - }, - { - "name": "-06:00 Central Time - Saskatoon, Regina, Prince Albert, Moose Jaw", - "value": "America/Regina" - }, - { - "name": "-06:00 Central Time - Tegucigalpa, San Pedro Sula, La Ceiba, Choloma", - "value": "America/Tegucigalpa" - }, - { - "name": "-06:00 Central Time - Winnipeg, Brandon, Steinbach, Kenora", - "value": "America/Winnipeg" - }, - { - "name": "-06:00 Easter Island Time - Easter", - "value": "Pacific/Easter" - }, - { - "name": "-06:00 Galapagos Time - Galapagos", - "value": "Pacific/Galapagos" - }, - { - "name": "-05:00 Acre Time - Rio Branco, Cruzeiro do Sul, Senador Guiomard, Sena Madureira", - "value": "America/Rio_Branco" - }, - { - "name": "-05:00 Colombia Time - Bogotá, Cali, Medellín, Barranquilla", - "value": "America/Bogota" - }, - { - "name": "-05:00 Cuba Time - Havana, Santiago de Cuba, Camagüey, Holguín", - "value": "America/Havana" - }, - { - "name": "-05:00 Eastern Time - Atikokan", - "value": "America/Atikokan" - }, - { - "name": "-05:00 Eastern Time - Cancún, Chetumal, Playa del Carmen, Cozumel", - "value": "America/Cancun" - }, - { - "name": "-05:00 Eastern Time - Cockburn Town", - "value": "America/Grand_Turk" - }, - { - "name": "-05:00 Eastern Time - George Town, West Bay", - "value": "America/Cayman" - }, - { - "name": "-05:00 Eastern Time - Kingston, New Kingston, Spanish Town, Portmore", - "value": "America/Jamaica" - }, - { - "name": "-05:00 Eastern Time - Nassau, Lucaya, Freeport", - "value": "America/Nassau" - }, - { - "name": "-05:00 Eastern Time - New York City, Brooklyn, Queens, Philadelphia", - "value": "America/New_York" - }, - { - "name": "-05:00 Eastern Time - Panamá, San Miguelito, Juan Díaz, David", - "value": "America/Panama" - }, - { - "name": "-05:00 Eastern Time - Port-au-Prince, Carrefour, Delmas 73, Port-de-Paix", - "value": "America/Port-au-Prince" - }, - { - "name": "-05:00 Eastern Time - Toronto, Montréal, Ottawa, Mississauga", - "value": "America/Toronto" - }, - { - "name": "-05:00 Ecuador Time - Quito, Guayaquil, Cuenca, Santo Domingo de los Colorados", - "value": "America/Guayaquil" - }, - { - "name": "-05:00 Peru Time - Lima, Callao, Arequipa, Trujillo", - "value": "America/Lima" - }, - { - "name": "-04:00 Amazon Time - Manaus, Campo Grande, Cuiabá, Porto Velho", - "value": "America/Manaus" - }, - { - "name": "-04:00 Atlantic Time - Basseterre", - "value": "America/St_Kitts" - }, - { - "name": "-04:00 Atlantic Time - Blanc-Sablon", - "value": "America/Blanc-Sablon" - }, - { - "name": "-04:00 Atlantic Time - Brades, Plymouth", - "value": "America/Montserrat" - }, - { - "name": "-04:00 Atlantic Time - Bridgetown", - "value": "America/Barbados" - }, - { - "name": "-04:00 Atlantic Time - Castries", - "value": "America/St_Lucia" - }, - { - "name": "-04:00 Atlantic Time - Chaguanas, Mon Repos, San Fernando, Port of Spain", - "value": "America/Port_of_Spain" - }, - { - "name": "-04:00 Atlantic Time - Fort-de-France, Le Lamentin, Le Robert, Sainte-Marie", - "value": "America/Martinique" - }, - { - "name": "-04:00 Atlantic Time - Gustavia", - "value": "America/St_Barthelemy" - }, - { - "name": "-04:00 Atlantic Time - Halifax, Moncton, Sydney, Dartmouth", - "value": "America/Halifax" - }, - { - "name": "-04:00 Atlantic Time - Hamilton", - "value": "Atlantic/Bermuda" - }, - { - "name": "-04:00 Atlantic Time - Kingstown, Kingstown Park", - "value": "America/St_Vincent" - }, - { - "name": "-04:00 Atlantic Time - Kralendijk", - "value": "America/Kralendijk" - }, - { - "name": "-04:00 Atlantic Time - Les Abymes, Baie-Mahault, Le Gosier, Petit-Bourg", - "value": "America/Guadeloupe" - }, - { - "name": "-04:00 Atlantic Time - Marigot", - "value": "America/Marigot" - }, - { - "name": "-04:00 Atlantic Time - Oranjestad, Tanki Leendert, San Nicolas", - "value": "America/Aruba" - }, - { - "name": "-04:00 Atlantic Time - Philipsburg", - "value": "America/Lower_Princes" - }, - { - "name": "-04:00 Atlantic Time - Road Town", - "value": "America/Tortola" - }, - { - "name": "-04:00 Atlantic Time - Roseau", - "value": "America/Dominica" - }, - { - "name": "-04:00 Atlantic Time - Saint Croix, Charlotte Amalie", - "value": "America/St_Thomas" - }, - { - "name": "-04:00 Atlantic Time - Saint George's", - "value": "America/Grenada" - }, - { - "name": "-04:00 Atlantic Time - Saint John’s", - "value": "America/Antigua" - }, - { - "name": "-04:00 Atlantic Time - San Juan, Bayamón, Carolina, Ponce", - "value": "America/Puerto_Rico" - }, - { - "name": "-04:00 Atlantic Time - Santo Domingo, Santiago de los Caballeros, Santo Domingo Oeste, Santo Domingo Este", - "value": "America/Santo_Domingo" - }, - { - "name": "-04:00 Atlantic Time - The Valley", - "value": "America/Anguilla" - }, - { - "name": "-04:00 Atlantic Time - Thule", - "value": "America/Thule" - }, - { - "name": "-04:00 Atlantic Time - Willemstad", - "value": "America/Curacao" - }, - { - "name": "-04:00 Bolivia Time - La Paz, Santa Cruz de la Sierra, Cochabamba, Sucre", - "value": "America/La_Paz" - }, - { - "name": "-04:00 Chile Time - Santiago, Puente Alto, Antofagasta, Viña del Mar", - "value": "America/Santiago" - }, - { - "name": "-04:00 Guyana Time - Georgetown, Linden, New Amsterdam", - "value": "America/Guyana" - }, - { - "name": "-04:00 Paraguay Time - Asunción, Ciudad del Este, San Lorenzo, Capiatá", - "value": "America/Asuncion" - }, - { - "name": "-04:00 Venezuela Time - Caracas, Maracaibo, Maracay, Valencia", - "value": "America/Caracas" - }, - { - "name": "-03:30 Newfoundland Time - St. John's, Mount Pearl, Corner Brook, Conception Bay South", - "value": "America/St_Johns" - }, - { - "name": "-03:00 Argentina Time - Buenos Aires, Córdoba, Rosario, Mar del Plata", - "value": "America/Argentina/Buenos_Aires" - }, - { - "name": "-03:00 Brasilia Time - São Paulo, Rio de Janeiro, Belo Horizonte, Salvador", - "value": "America/Sao_Paulo" - }, - { - "name": "-03:00 Chile Time - Palmer, Rothera", - "value": "Antarctica/Palmer" - }, - { - "name": "-03:00 Chile Time - Punta Arenas, Puerto Natales", - "value": "America/Punta_Arenas" - }, - { - "name": "-03:00 Falkland Islands Time - Stanley", - "value": "Atlantic/Stanley" - }, - { - "name": "-03:00 French Guiana Time - Cayenne, Matoury, Saint-Laurent-du-Maroni, Kourou", - "value": "America/Cayenne" - }, - { - "name": "-03:00 St. Pierre & Miquelon Time - Saint-Pierre", - "value": "America/Miquelon" - }, - { - "name": "-03:00 Suriname Time - Paramaribo, Lelydorp", - "value": "America/Paramaribo" - }, - { - "name": "-03:00 Uruguay Time - Montevideo, Salto, Paysandú, Las Piedras", - "value": "America/Montevideo" - }, - { - "name": "-03:00 West Greenland Time - Nuuk", - "value": "America/Nuuk" - }, - { - "name": "-02:00 Fernando de Noronha Time - Noronha", - "value": "America/Noronha" - }, - { - "name": "-02:00 South Georgia Time - Grytviken", - "value": "Atlantic/South_Georgia" - }, - { - "name": "-01:00 Azores Time - Ponta Delgada", - "value": "Atlantic/Azores" - }, - { - "name": "-01:00 Cape Verde Time - Praia, Mindelo, Santa Maria, Cova Figueira", - "value": "Atlantic/Cape_Verde" - }, - { - "name": "-01:00 East Greenland Time - Scoresbysund", - "value": "America/Scoresbysund" - }, - { - "name": "+00:00 Greenwich Mean Time - Abidjan, Abobo, Bouaké, Korhogo", - "value": "Africa/Abidjan" - }, - { - "name": "+00:00 Greenwich Mean Time - Accra, Kumasi, Tamale, Takoradi", - "value": "Africa/Accra" - }, - { - "name": "+00:00 Greenwich Mean Time - Bamako, Ségou, Sikasso, Mopti", - "value": "Africa/Bamako" - }, - { - "name": "+00:00 Greenwich Mean Time - Bissau, Bafatá", - "value": "Africa/Bissau" - }, - { - "name": "+00:00 Greenwich Mean Time - Camayenne, Conakry, Nzérékoré, Kindia", - "value": "Africa/Conakry" - }, - { - "name": "+00:00 Greenwich Mean Time - Dakar, Pikine, Touba, Thiès", - "value": "Africa/Dakar" - }, - { - "name": "+00:00 Greenwich Mean Time - Danmarkshavn", - "value": "America/Danmarkshavn" - }, - { - "name": "+00:00 Greenwich Mean Time - Douglas", - "value": "Europe/Isle_of_Man" - }, - { - "name": "+00:00 Greenwich Mean Time - Dublin, South Dublin, Cork, Limerick", - "value": "Europe/Dublin" - }, - { - "name": "+00:00 Greenwich Mean Time - Freetown, Bo, Kenema, Koidu", - "value": "Africa/Freetown" - }, - { - "name": "+00:00 Greenwich Mean Time - Jamestown", - "value": "Atlantic/St_Helena" - }, - { - "name": "+00:00 Greenwich Mean Time - Lomé, Sokodé, Kara, Atakpamé", - "value": "Africa/Lome" - }, - { - "name": "+00:00 Greenwich Mean Time - London, Birmingham, Liverpool, Glasgow", - "value": "Europe/London" - }, - { - "name": "+00:00 Greenwich Mean Time - Monrovia, Gbarnga, Kakata, Bensonville", - "value": "Africa/Monrovia" - }, - { - "name": "+00:00 Greenwich Mean Time - Nouakchott, Nouadhibou, Dar Naim, Néma", - "value": "Africa/Nouakchott" - }, - { - "name": "+00:00 Greenwich Mean Time - Ouagadougou, Bobo-Dioulasso, Koudougou, Ouahigouya", - "value": "Africa/Ouagadougou" - }, - { - "name": "+00:00 Greenwich Mean Time - Reykjavík, Kópavogur, Hafnarfjörður, Reykjanesbær", - "value": "Atlantic/Reykjavik" - }, - { - "name": "+00:00 Greenwich Mean Time - Saint Helier", - "value": "Europe/Jersey" - }, - { - "name": "+00:00 Greenwich Mean Time - Saint Peter Port", - "value": "Europe/Guernsey" - }, - { - "name": "+00:00 Greenwich Mean Time - Serekunda, Brikama, Bakau, Banjul", - "value": "Africa/Banjul" - }, - { - "name": "+00:00 Greenwich Mean Time - São Tomé", - "value": "Africa/Sao_Tome" - }, - { - "name": "+00:00 Greenwich Mean Time - Troll", - "value": "Antarctica/Troll" - }, - { - "name": "+00:00 Western European Time - Casablanca, Rabat, Fès, Sale", - "value": "Africa/Casablanca" - }, - { - "name": "+00:00 Western European Time - Laayoune, Dakhla, Boujdour", - "value": "Africa/El_Aaiun" - }, - { - "name": "+00:00 Western European Time - Las Palmas de Gran Canaria, Santa Cruz de Tenerife, La Laguna, Telde", - "value": "Atlantic/Canary" - }, - { - "name": "+00:00 Western European Time - Lisbon, Porto, Amadora, Braga", - "value": "Europe/Lisbon" - }, - { - "name": "+00:00 Western European Time - Tórshavn", - "value": "Atlantic/Faroe" - }, - { - "name": "+01:00 Central Africa Time - Windhoek, Rundu, Walvis Bay, Oshakati", - "value": "Africa/Windhoek" - }, - { - "name": "+01:00 Central European Time - Algiers, Boumerdas, Oran, Tébessa", - "value": "Africa/Algiers" - }, - { - "name": "+01:00 Central European Time - Amsterdam, Rotterdam, The Hague, Utrecht", - "value": "Europe/Amsterdam" - }, - { - "name": "+01:00 Central European Time - Andorra la Vella, les Escaldes", - "value": "Europe/Andorra" - }, - { - "name": "+01:00 Central European Time - Belgrade, Niš, Novi Sad, Zemun", - "value": "Europe/Belgrade" - }, - { - "name": "+01:00 Central European Time - Berlin, Hamburg, Munich, Köln", - "value": "Europe/Berlin" - }, - { - "name": "+01:00 Central European Time - Bratislava, Košice, Nitra, Prešov", - "value": "Europe/Bratislava" - }, - { - "name": "+01:00 Central European Time - Brussels, Antwerpen, Gent, Charleroi", - "value": "Europe/Brussels" - }, - { - "name": "+01:00 Central European Time - Budapest, Debrecen, Szeged, Miskolc", - "value": "Europe/Budapest" - }, - { - "name": "+01:00 Central European Time - Copenhagen, Århus, Odense, Aalborg", - "value": "Europe/Copenhagen" - }, - { - "name": "+01:00 Central European Time - Gibraltar", - "value": "Europe/Gibraltar" - }, - { - "name": "+01:00 Central European Time - Ljubljana, Maribor, Kranj, Celje", - "value": "Europe/Ljubljana" - }, - { - "name": "+01:00 Central European Time - Longyearbyen", - "value": "Arctic/Longyearbyen" - }, - { - "name": "+01:00 Central European Time - Luxembourg, Esch-sur-Alzette, Dudelange", - "value": "Europe/Luxembourg" - }, - { - "name": "+01:00 Central European Time - Madrid, Barcelona, Valencia, Sevilla", - "value": "Europe/Madrid" - }, - { - "name": "+01:00 Central European Time - Monaco, Monte-Carlo", - "value": "Europe/Monaco" - }, - { - "name": "+01:00 Central European Time - Oslo, Bergen, Trondheim, Stavanger", - "value": "Europe/Oslo" - }, - { - "name": "+01:00 Central European Time - Paris, Marseille, Lyon, Toulouse", - "value": "Europe/Paris" - }, - { - "name": "+01:00 Central European Time - Podgorica, Nikšić, Herceg Novi, Pljevlja", - "value": "Europe/Podgorica" - }, - { - "name": "+01:00 Central European Time - Prague, Brno, Ostrava, Pilsen", - "value": "Europe/Prague" - }, - { - "name": "+01:00 Central European Time - Rome, Milan, Naples, Turin", - "value": "Europe/Rome" - }, - { - "name": "+01:00 Central European Time - San Marino", - "value": "Europe/San_Marino" - }, - { - "name": "+01:00 Central European Time - San Pawl il-Baħar, Birkirkara, Mosta, Sliema", - "value": "Europe/Malta" - }, - { - "name": "+01:00 Central European Time - Sarajevo, Banja Luka, Zenica, Tuzla", - "value": "Europe/Sarajevo" - }, - { - "name": "+01:00 Central European Time - Skopje, Kumanovo, Prilep, Bitola", - "value": "Europe/Skopje" - }, - { - "name": "+01:00 Central European Time - Stockholm, Göteborg, Malmö, Uppsala", - "value": "Europe/Stockholm" - }, - { - "name": "+01:00 Central European Time - Tirana, Durrës, Elbasan, Vlorë", - "value": "Europe/Tirane" - }, - { - "name": "+01:00 Central European Time - Tunis, Sfax, Sousse, Kairouan", - "value": "Africa/Tunis" - }, - { - "name": "+01:00 Central European Time - Vaduz", - "value": "Europe/Vaduz" - }, - { - "name": "+01:00 Central European Time - Vatican City", - "value": "Europe/Vatican" - }, - { - "name": "+01:00 Central European Time - Vienna, Graz, Linz, Favoriten", - "value": "Europe/Vienna" - }, - { - "name": "+01:00 Central European Time - Warsaw, Łódź, Kraków, Wrocław", - "value": "Europe/Warsaw" - }, - { - "name": "+01:00 Central European Time - Zagreb, Split, Rijeka, Osijek", - "value": "Europe/Zagreb" - }, - { - "name": "+01:00 Central European Time - Zürich, Genève, Basel, Lausanne", - "value": "Europe/Zurich" - }, - { - "name": "+01:00 West Africa Time - Bangui, Bimbo, Mbaïki, Berbérati", - "value": "Africa/Bangui" - }, - { - "name": "+01:00 West Africa Time - Bata, Malabo, Ebebiyin", - "value": "Africa/Malabo" - }, - { - "name": "+01:00 West Africa Time - Brazzaville, Pointe-Noire, Dolisie, Kayes", - "value": "Africa/Brazzaville" - }, - { - "name": "+01:00 West Africa Time - Cotonou, Abomey-Calavi, Djougou, Porto-Novo", - "value": "Africa/Porto-Novo" - }, - { - "name": "+01:00 West Africa Time - Douala, Yaoundé, Garoua, Kousséri", - "value": "Africa/Douala" - }, - { - "name": "+01:00 West Africa Time - Kinshasa, Masina, Kikwit, Mbandaka", - "value": "Africa/Kinshasa" - }, - { - "name": "+01:00 West Africa Time - Lagos, Kano, Ibadan, Port Harcourt", - "value": "Africa/Lagos" - }, - { - "name": "+01:00 West Africa Time - Libreville, Port-Gentil, Franceville, Oyem", - "value": "Africa/Libreville" - }, - { - "name": "+01:00 West Africa Time - Luanda, N’dalatando, Huambo, Lobito", - "value": "Africa/Luanda" - }, - { - "name": "+01:00 West Africa Time - N'Djamena, Moundou, Sarh, Abéché", - "value": "Africa/Ndjamena" - }, - { - "name": "+01:00 West Africa Time - Niamey, Zinder, Maradi, Agadez", - "value": "Africa/Niamey" - }, - { - "name": "+02:00 Central Africa Time - Bujumbura, Muyinga, Gitega, Ruyigi", - "value": "Africa/Bujumbura" - }, - { - "name": "+02:00 Central Africa Time - Gaborone, Francistown, Molepolole, Maun", - "value": "Africa/Gaborone" - }, - { - "name": "+02:00 Central Africa Time - Harare, Bulawayo, Chitungwiza, Mutare", - "value": "Africa/Harare" - }, - { - "name": "+02:00 Central Africa Time - Juba, Winejok, Yei, Malakal", - "value": "Africa/Juba" - }, - { - "name": "+02:00 Central Africa Time - Khartoum, Omdurman, Nyala, Port Sudan", - "value": "Africa/Khartoum" - }, - { - "name": "+02:00 Central Africa Time - Kigali, Gisenyi, Butare, Gitarama", - "value": "Africa/Kigali" - }, - { - "name": "+02:00 Central Africa Time - Lilongwe, Blantyre, Mzuzu, Zomba", - "value": "Africa/Blantyre" - }, - { - "name": "+02:00 Central Africa Time - Lubumbashi, Mbuji-Mayi, Kisangani, Kananga", - "value": "Africa/Lubumbashi" - }, - { - "name": "+02:00 Central Africa Time - Lusaka, Ndola, Kitwe, Chipata", - "value": "Africa/Lusaka" - }, - { - "name": "+02:00 Central Africa Time - Maputo, Matola, Nampula, Beira", - "value": "Africa/Maputo" - }, - { - "name": "+02:00 Eastern European Time - Athens, Thessaloníki, Pátra, Piraeus", - "value": "Europe/Athens" - }, - { - "name": "+02:00 Eastern European Time - Beirut, Ra’s Bayrūt, Tripoli, Sidon", - "value": "Asia/Beirut" - }, - { - "name": "+02:00 Eastern European Time - Bucharest, Sector 3, Iaşi, Sector 6", - "value": "Europe/Bucharest" - }, - { - "name": "+02:00 Eastern European Time - Cairo, Alexandria, Giza, Shubrā al Khaymah", - "value": "Africa/Cairo" - }, - { - "name": "+02:00 Eastern European Time - Chisinau, Tiraspol, Bălţi, Bender", - "value": "Europe/Chisinau" - }, - { - "name": "+02:00 Eastern European Time - East Jerusalem, Gaza, Khān Yūnis, Jabālyā", - "value": "Asia/Hebron" - }, - { - "name": "+02:00 Eastern European Time - Helsinki, Espoo, Tampere, Oulu", - "value": "Europe/Helsinki" - }, - { - "name": "+02:00 Eastern European Time - Kaliningrad, Chernyakhovsk, Sovetsk, Baltiysk", - "value": "Europe/Kaliningrad" - }, - { - "name": "+02:00 Eastern European Time - Kyiv, Kharkiv, Odesa, Dnipro", - "value": "Europe/Kyiv" - }, - { - "name": "+02:00 Eastern European Time - Mariehamn", - "value": "Europe/Mariehamn" - }, - { - "name": "+02:00 Eastern European Time - Nicosia, Limassol, Larnaca, Stróvolos", - "value": "Asia/Nicosia" - }, - { - "name": "+02:00 Eastern European Time - Riga, Daugavpils, Liepāja, Jelgava", - "value": "Europe/Riga" - }, - { - "name": "+02:00 Eastern European Time - Sofia, Plovdiv, Varna, Burgas", - "value": "Europe/Sofia" - }, - { - "name": "+02:00 Eastern European Time - Tallinn, Tartu, Narva, Pärnu", - "value": "Europe/Tallinn" - }, - { - "name": "+02:00 Eastern European Time - Tripoli, Benghazi, Ajdabiya, Mişrātah", - "value": "Africa/Tripoli" - }, - { - "name": "+02:00 Eastern European Time - Vilnius, Kaunas, Klaipėda, Šiauliai", - "value": "Europe/Vilnius" - }, - { - "name": "+02:00 Israel Time - Jerusalem, Tel Aviv, West Jerusalem, Haifa", - "value": "Asia/Jerusalem" - }, - { - "name": "+02:00 South Africa Time - Johannesburg, Cape Town, Durban, Soweto", - "value": "Africa/Johannesburg" - }, - { - "name": "+02:00 South Africa Time - Manzini, Mbabane, Lobamba", - "value": "Africa/Mbabane" - }, - { - "name": "+02:00 South Africa Time - Maseru, Mohale’s Hoek, Mafeteng, Leribe", - "value": "Africa/Maseru" - }, - { - "name": "+03:00 Arabian Time - Al Aḩmadī, Ḩawallī, As Sālimīyah, Şabāḩ as Sālim", - "value": "Asia/Kuwait" - }, - { - "name": "+03:00 Arabian Time - Ar Rifā‘, Manama, Al Muharraq, Dār Kulayb", - "value": "Asia/Bahrain" - }, - { - "name": "+03:00 Arabian Time - Baghdad, Al Mawşil al Jadīdah, Al Başrah al Qadīmah, Mosul", - "value": "Asia/Baghdad" - }, - { - "name": "+03:00 Arabian Time - Doha, Ar Rayyān, Umm Şalāl Muḩammad, Al Wakrah", - "value": "Asia/Qatar" - }, - { - "name": "+03:00 Arabian Time - Jeddah, Riyadh, Mecca, Medina", - "value": "Asia/Riyadh" - }, - { - "name": "+03:00 Arabian Time - Sanaa, Aden, Al Ḩudaydah, Taiz", - "value": "Asia/Aden" - }, - { - "name": "+03:00 Asia/Amman - Amman, Zarqa, Irbid, Russeifa", - "value": "Asia/Amman" - }, - { - "name": "+03:00 Asia/Damascus - Aleppo, Damascus, Homs, Latakia", - "value": "Asia/Damascus" - }, - { - "name": "+03:00 East Africa Time - Addis Ababa, Jijiga, Gondar, Mek'ele", - "value": "Africa/Addis_Ababa" - }, - { - "name": "+03:00 East Africa Time - Antananarivo, Toamasina, Antsirabe, Mahajanga", - "value": "Indian/Antananarivo" - }, - { - "name": "+03:00 East Africa Time - Asmara, Keren, Massawa, Assab", - "value": "Africa/Asmara" - }, - { - "name": "+03:00 East Africa Time - Dar es Salaam, Mwanza, Zanzibar, Arusha", - "value": "Africa/Dar_es_Salaam" - }, - { - "name": "+03:00 East Africa Time - Djibouti, 'Ali Sabieh, Tadjourah, Obock", - "value": "Africa/Djibouti" - }, - { - "name": "+03:00 East Africa Time - Kampala, Gulu, Lira, Mbarara", - "value": "Africa/Kampala" - }, - { - "name": "+03:00 East Africa Time - Mamoudzou, Koungou, Dzaoudzi", - "value": "Indian/Mayotte" - }, - { - "name": "+03:00 East Africa Time - Mogadishu, Hargeysa, Berbera, Kismayo", - "value": "Africa/Mogadishu" - }, - { - "name": "+03:00 East Africa Time - Moroni, Moutsamoudou", - "value": "Indian/Comoro" - }, - { - "name": "+03:00 East Africa Time - Nairobi, Kakamega, Mombasa, Ruiru", - "value": "Africa/Nairobi" - }, - { - "name": "+03:00 Moscow Time - Minsk, Homyel', Hrodna, Mahilyow", - "value": "Europe/Minsk" - }, - { - "name": "+03:00 Moscow Time - Moscow, Saint Petersburg, Nizhniy Novgorod, Kazan", - "value": "Europe/Moscow" - }, - { - "name": "+03:00 Moscow Time - Sevastopol, Simferopol, Kerch, Yevpatoriya", - "value": "Europe/Simferopol" - }, - { - "name": "+03:00 Syowa Time - Syowa", - "value": "Antarctica/Syowa" - }, - { - "name": "+03:00 Turkey Time - Istanbul, Ankara, Bursa, İzmir", - "value": "Europe/Istanbul" - }, - { - "name": "+03:30 Iran Time - Tehran, Mashhad, Isfahan, Karaj", - "value": "Asia/Tehran" - }, - { - "name": "+04:00 Armenia Time - Yerevan, Gyumri, Vanadzor, Vagharshapat", - "value": "Asia/Yerevan" - }, - { - "name": "+04:00 Azerbaijan Time - Baku, Sumqayıt, Ganja, Lankaran", - "value": "Asia/Baku" - }, - { - "name": "+04:00 Georgia Time - Tbilisi, Batumi, Kutaisi, Rustavi", - "value": "Asia/Tbilisi" - }, - { - "name": "+04:00 Gulf Time - Dubai, Abu Dhabi, Sharjah, Al Ain City", - "value": "Asia/Dubai" - }, - { - "name": "+04:00 Gulf Time - Muscat, Seeb, Bawshar, ‘Ibrī", - "value": "Asia/Muscat" - }, - { - "name": "+04:00 Mauritius Time - Port Louis, Vacoas, Beau Bassin-Rose Hill, Curepipe", - "value": "Indian/Mauritius" - }, - { - "name": "+04:00 Réunion Time - Saint-Denis, Saint-Paul, Le Tampon, Saint-Pierre", - "value": "Indian/Reunion" - }, - { - "name": "+04:00 Samara Time - Samara, Saratov, Tolyatti, Izhevsk", - "value": "Europe/Samara" - }, - { - "name": "+04:00 Seychelles Time - Victoria", - "value": "Indian/Mahe" - }, - { - "name": "+04:30 Afghanistan Time - Kabul, Herāt, Mazār-e Sharīf, Kandahār", - "value": "Asia/Kabul" - }, - { - "name": "+05:00 French Southern & Antarctic Time - Port-aux-Français", - "value": "Indian/Kerguelen" - }, - { - "name": "+05:00 Maldives Time - Male", - "value": "Indian/Maldives" - }, - { - "name": "+05:00 Mawson Time - Mawson", - "value": "Antarctica/Mawson" - }, - { - "name": "+05:00 Pakistan Time - Karachi, Lahore, Faisalabad, Rawalpindi", - "value": "Asia/Karachi" - }, - { - "name": "+05:00 Tajikistan Time - Dushanbe, Isfara, Istaravshan, Kŭlob", - "value": "Asia/Dushanbe" - }, - { - "name": "+05:00 Turkmenistan Time - Ashgabat, Türkmenabat, Daşoguz, Mary", - "value": "Asia/Ashgabat" - }, - { - "name": "+05:00 Uzbekistan Time - Tashkent, Namangan, Samarkand, Andijon", - "value": "Asia/Tashkent" - }, - { - "name": "+05:00 West Kazakhstan Time - Aktobe, Kyzylorda, Oral, Atyrau", - "value": "Asia/Aqtobe" - }, - { - "name": "+05:00 Yekaterinburg Time - Yekaterinburg, Chelyabinsk, Ufa, Perm", - "value": "Asia/Yekaterinburg" - }, - { - "name": "+05:30 India Time - Colombo, Dehiwala-Mount Lavinia, Maharagama, Jaffna", - "value": "Asia/Colombo" - }, - { - "name": "+05:30 India Time - Mumbai, Delhi, Bengaluru, Hyderābād", - "value": "Asia/Kolkata" - }, - { - "name": "+05:45 Nepal Time - Kathmandu, Bharatpur, Pātan, Birgañj", - "value": "Asia/Kathmandu" - }, - { - "name": "+06:00 Bangladesh Time - Dhaka, Chattogram, Khulna, Rangpur", - "value": "Asia/Dhaka" - }, - { - "name": "+06:00 Bhutan Time - Thimphu, Phuntsholing, Tsirang, Punākha", - "value": "Asia/Thimphu" - }, - { - "name": "+06:00 China Time - Ürümqi, Shihezi, Korla, Aksu", - "value": "Asia/Urumqi" - }, - { - "name": "+06:00 East Kazakhstan Time - Almaty, Shymkent, Karagandy, Taraz", - "value": "Asia/Almaty" - }, - { - "name": "+06:00 Indian Ocean Time - Chagos", - "value": "Indian/Chagos" - }, - { - "name": "+06:00 Kyrgyzstan Time - Bishkek, Osh, Jalal-Abad, Karakol", - "value": "Asia/Bishkek" - }, - { - "name": "+06:00 Omsk Time - Omsk, Tara, Kalachinsk", - "value": "Asia/Omsk" - }, - { - "name": "+06:00 Vostok Time - Vostok", - "value": "Antarctica/Vostok" - }, - { - "name": "+06:30 Cocos Islands Time - West Island", - "value": "Indian/Cocos" - }, - { - "name": "+06:30 Myanmar Time - Yangon, Mandalay, Nay Pyi Taw, Mawlamyine", - "value": "Asia/Yangon" - }, - { - "name": "+07:00 Christmas Island Time - Flying Fish Cove", - "value": "Indian/Christmas" - }, - { - "name": "+07:00 Davis Time - Davis", - "value": "Antarctica/Davis" - }, - { - "name": "+07:00 Hovd Time - Ulaangom, Khovd, Ölgii, Altai", - "value": "Asia/Hovd" - }, - { - "name": "+07:00 Indochina Time - Bangkok, Samut Prakan, Mueang Nonthaburi, Chon Buri", - "value": "Asia/Bangkok" - }, - { - "name": "+07:00 Indochina Time - Ho Chi Minh City, Da Nang, Biên Hòa, Cần Thơ", - "value": "Asia/Ho_Chi_Minh" - }, - { - "name": "+07:00 Indochina Time - Phnom Penh, Takeo, Siem Reap, Battambang", - "value": "Asia/Phnom_Penh" - }, - { - "name": "+07:00 Indochina Time - Vientiane, Savannakhet, Pakse, Thakhèk", - "value": "Asia/Vientiane" - }, - { - "name": "+07:00 Novosibirsk Time - Novosibirsk, Krasnoyarsk, Barnaul, Tomsk", - "value": "Asia/Novosibirsk" - }, - { - "name": "+07:00 Western Indonesia Time - Jakarta, Surabaya, Bekasi, Bandung", - "value": "Asia/Jakarta" - }, - { - "name": "+08:00 Australian Western Time - Perth, Mandurah, Bunbury, Baldivis", - "value": "Australia/Perth" - }, - { - "name": "+08:00 Brunei Darussalam Time - Bandar Seri Begawan, Kuala Belait, Seria, Tutong", - "value": "Asia/Brunei" - }, - { - "name": "+08:00 Central Indonesia Time - Makassar, Samarinda, Denpasar, Balikpapan", - "value": "Asia/Makassar" - }, - { - "name": "+08:00 China Time - Macau", - "value": "Asia/Macau" - }, - { - "name": "+08:00 China Time - Shanghai, Beijing, Shenzhen, Guangzhou", - "value": "Asia/Shanghai" - }, - { - "name": "+08:00 Hong Kong Time - Hong Kong, Kowloon, Victoria, Tuen Mun", - "value": "Asia/Hong_Kong" - }, - { - "name": "+08:00 Irkutsk Time - Irkutsk, Ulan-Ude, Bratsk, Angarsk", - "value": "Asia/Irkutsk" - }, - { - "name": "+08:00 Malaysia Time - Kuala Lumpur, Petaling Jaya, Klang, Johor Bahru", - "value": "Asia/Kuala_Lumpur" - }, - { - "name": "+08:00 Philippine Time - Quezon City, Davao, Manila, Caloocan City", - "value": "Asia/Manila" - }, - { - "name": "+08:00 Singapore Time - Singapore, Woodlands, Geylang, Queenstown Estate", - "value": "Asia/Singapore" - }, - { - "name": "+08:00 Taipei Time - Taipei, Kaohsiung, Taichung, Tainan", - "value": "Asia/Taipei" - }, - { - "name": "+08:00 Ulaanbaatar Time - Ulan Bator, Erdenet, Darhan, Mörön", - "value": "Asia/Ulaanbaatar" - }, - { - "name": "+08:45 Australian Central Western Time - Eucla", - "value": "Australia/Eucla" - }, - { - "name": "+09:00 East Timor Time - Dili, Maliana, Suai, Likisá", - "value": "Asia/Dili" - }, - { - "name": "+09:00 Eastern Indonesia Time - Jayapura, Ambon, Sorong, Ternate", - "value": "Asia/Jayapura" - }, - { - "name": "+09:00 Japan Time - Tokyo, Yokohama, Osaka, Nagoya", - "value": "Asia/Tokyo" - }, - { - "name": "+09:00 Korean Time - Pyongyang, Hamhŭng, Namp’o, Sunch’ŏn", - "value": "Asia/Pyongyang" - }, - { - "name": "+09:00 Korean Time - Seoul, Busan, Incheon, Daegu", - "value": "Asia/Seoul" - }, - { - "name": "+09:00 Palau Time - Ngerulmud", - "value": "Pacific/Palau" - }, - { - "name": "+09:00 Yakutsk Time - Chita, Yakutsk, Blagoveshchensk, Belogorsk", - "value": "Asia/Chita" - }, - { - "name": "+09:30 Australian Central Time - Adelaide, Adelaide Hills, Mount Gambier, Morphett Vale", - "value": "Australia/Adelaide" - }, - { - "name": "+09:30 Australian Central Time - Darwin, Alice Springs, Palmerston", - "value": "Australia/Darwin" - }, - { - "name": "+10:00 Australian Eastern Time - Brisbane, Gold Coast, Logan City, Townsville", - "value": "Australia/Brisbane" - }, - { - "name": "+10:00 Australian Eastern Time - Sydney, Melbourne, Canberra, Newcastle", - "value": "Australia/Sydney" - }, - { - "name": "+10:00 Chamorro Time - Dededo Village, Yigo Village, Tamuning-Tumon-Harmon Village, Tamuning", - "value": "Pacific/Guam" - }, - { - "name": "+10:00 Chamorro Time - Saipan", - "value": "Pacific/Saipan" - }, - { - "name": "+10:00 Chuuk Time - Chuuk", - "value": "Pacific/Chuuk" - }, - { - "name": "+10:00 Dumont-d’Urville Time - DumontDUrville", - "value": "Antarctica/DumontDUrville" - }, - { - "name": "+10:00 Papua New Guinea Time - Port Moresby, Lae, Mount Hagen, Popondetta", - "value": "Pacific/Port_Moresby" - }, - { - "name": "+10:00 Vladivostok Time - Khabarovsk, Vladivostok, Khabarovsk Vtoroy, Komsomolsk-on-Amur", - "value": "Asia/Vladivostok" - }, - { - "name": "+10:30 Lord Howe Time - Lord Howe", - "value": "Australia/Lord_Howe" - }, - { - "name": "+11:00 Bougainville Time - Arawa", - "value": "Pacific/Bougainville" - }, - { - "name": "+11:00 Casey Time - Casey", - "value": "Antarctica/Casey" - }, - { - "name": "+11:00 Kosrae Time - Kosrae, Palikir - National Government Center", - "value": "Pacific/Kosrae" - }, - { - "name": "+11:00 New Caledonia Time - Nouméa, Mont-Dore, Dumbéa", - "value": "Pacific/Noumea" - }, - { - "name": "+11:00 Norfolk Island Time - Kingston", - "value": "Pacific/Norfolk" - }, - { - "name": "+11:00 Sakhalin Time - Yuzhno-Sakhalinsk, Magadan, Korsakov, Kholmsk", - "value": "Asia/Sakhalin" - }, - { - "name": "+11:00 Solomon Islands Time - Honiara", - "value": "Pacific/Guadalcanal" - }, - { - "name": "+11:00 Vanuatu Time - Port-Vila", - "value": "Pacific/Efate" - }, - { - "name": "+12:00 Fiji Time - Nasinu, Suva, Lautoka, Nadi", - "value": "Pacific/Fiji" - }, - { - "name": "+12:00 Gilbert Islands Time - Tarawa", - "value": "Pacific/Tarawa" - }, - { - "name": "+12:00 Marshall Islands Time - Majuro, Kwajalein, RMI Capitol", - "value": "Pacific/Majuro" - }, - { - "name": "+12:00 Nauru Time - Yaren", - "value": "Pacific/Nauru" - }, - { - "name": "+12:00 New Zealand Time - Auckland, Wellington, Christchurch, Manukau City", - "value": "Pacific/Auckland" - }, - { - "name": "+12:00 New Zealand Time - McMurdo", - "value": "Antarctica/McMurdo" - }, - { - "name": "+12:00 Petropavlovsk-Kamchatski Time - Petropavlovsk-Kamchatsky, Yelizovo, Vilyuchinsk, Anadyr", - "value": "Asia/Kamchatka" - }, - { - "name": "+12:00 Tuvalu Time - Funafuti", - "value": "Pacific/Funafuti" - }, - { - "name": "+12:00 Wake Island Time - Wake", - "value": "Pacific/Wake" - }, - { - "name": "+12:00 Wallis & Futuna Time - Mata-Utu", - "value": "Pacific/Wallis" - }, - { - "name": "+12:45 Chatham Time - Chatham", - "value": "Pacific/Chatham" - }, - { - "name": "+13:00 Apia Time - Apia", - "value": "Pacific/Apia" - }, - { - "name": "+13:00 Phoenix Islands Time - Kanton", - "value": "Pacific/Kanton" - }, - { - "name": "+13:00 Tokelau Time - Fakaofo", - "value": "Pacific/Fakaofo" - }, - { - "name": "+13:00 Tonga Time - Nuku‘alofa", - "value": "Pacific/Tongatapu" - }, - { - "name": "+14:00 Line Islands Time - Kiritimati", - "value": "Pacific/Kiritimati" - } + { + "name": "-11:00 Niue Time - Alofi", + "value": "Pacific/Niue" + }, + { + "name": "-11:00 Samoa Time - Midway", + "value": "Pacific/Midway" + }, + { + "name": "-11:00 Samoa Time - Pago Pago", + "value": "Pacific/Pago_Pago" + }, + { + "name": "-10:00 Cook Islands Time - Avarua", + "value": "Pacific/Rarotonga" + }, + { + "name": "-10:00 Hawaii-Aleutian Time - Adak", + "value": "America/Adak" + }, + { + "name": "-10:00 Hawaii-Aleutian Time - Honolulu, East Honolulu, Pearl City, Hilo", + "value": "Pacific/Honolulu" + }, + { + "name": "-10:00 Tahiti Time - Faaa, Papeete, Punaauia", + "value": "Pacific/Tahiti" + }, + { + "name": "-09:30 Marquesas Time - Marquesas", + "value": "Pacific/Marquesas" + }, + { + "name": "-09:00 Alaska Time - Anchorage, Juneau, Fairbanks, Eagle River", + "value": "America/Anchorage" + }, + { + "name": "-09:00 Gambier Time - Gambier", + "value": "Pacific/Gambier" + }, + { + "name": "-08:00 Pacific Time - Los Angeles, San Diego, San Jose, San Francisco", + "value": "America/Los_Angeles" + }, + { + "name": "-08:00 Pacific Time - Tijuana, Mexicali, Ensenada, Rosarito", + "value": "America/Tijuana" + }, + { + "name": "-08:00 Pacific Time - Vancouver, Surrey, Okanagan, Victoria", + "value": "America/Vancouver" + }, + { + "name": "-08:00 Pitcairn Time - Adamstown", + "value": "Pacific/Pitcairn" + }, + { + "name": "-07:00 Mexican Pacific Time - Hermosillo, Culiacán, Ciudad Obregón, Mazatlán", + "value": "America/Hermosillo" + }, + { + "name": "-07:00 Mountain Time - Calgary, Edmonton, Red Deer, Sherwood Park", + "value": "America/Edmonton" + }, + { + "name": "-07:00 Mountain Time - Ciudad Juárez", + "value": "America/Ciudad_Juarez" + }, + { + "name": "-07:00 Mountain Time - Denver, El Paso, Albuquerque, Colorado Springs", + "value": "America/Denver" + }, + { + "name": "-07:00 Mountain Time - Phoenix, Tucson, Mesa, Chandler", + "value": "America/Phoenix" + }, + { + "name": "-07:00 Yukon Time - Whitehorse, Fort St. John, Creston, Dawson", + "value": "America/Whitehorse" + }, + { + "name": "-06:00 Central Time - Belize City, San Ignacio, San Pedro, Orange Walk", + "value": "America/Belize" + }, + { + "name": "-06:00 Central Time - Chicago, Houston, San Antonio, Dallas", + "value": "America/Chicago" + }, + { + "name": "-06:00 Central Time - Guatemala City, Villa Nueva, Mixco, Cobán", + "value": "America/Guatemala" + }, + { + "name": "-06:00 Central Time - Managua, León, Masaya, Chinandega", + "value": "America/Managua" + }, + { + "name": "-06:00 Central Time - Mexico City, Iztapalapa, León de los Aldama, Puebla", + "value": "America/Mexico_City" + }, + { + "name": "-06:00 Central Time - Reynosa, Heroica Matamoros, Nuevo Laredo, Piedras Negras", + "value": "America/Matamoros" + }, + { + "name": "-06:00 Central Time - San José, Limón, San Francisco, Alajuela", + "value": "America/Costa_Rica" + }, + { + "name": "-06:00 Central Time - San Salvador, Soyapango, San Miguel, Santa Ana", + "value": "America/El_Salvador" + }, + { + "name": "-06:00 Central Time - Saskatoon, Regina, Prince Albert, Moose Jaw", + "value": "America/Regina" + }, + { + "name": "-06:00 Central Time - Tegucigalpa, San Pedro Sula, La Ceiba, Choloma", + "value": "America/Tegucigalpa" + }, + { + "name": "-06:00 Central Time - Winnipeg, Brandon, Steinbach, Kenora", + "value": "America/Winnipeg" + }, + { + "name": "-06:00 Easter Island Time - Easter", + "value": "Pacific/Easter" + }, + { + "name": "-06:00 Galapagos Time - Galapagos", + "value": "Pacific/Galapagos" + }, + { + "name": "-05:00 Acre Time - Rio Branco, Cruzeiro do Sul, Senador Guiomard, Sena Madureira", + "value": "America/Rio_Branco" + }, + { + "name": "-05:00 Colombia Time - Bogotá, Cali, Medellín, Barranquilla", + "value": "America/Bogota" + }, + { + "name": "-05:00 Cuba Time - Havana, Santiago de Cuba, Camagüey, Holguín", + "value": "America/Havana" + }, + { + "name": "-05:00 Eastern Time - Atikokan", + "value": "America/Atikokan" + }, + { + "name": "-05:00 Eastern Time - Cancún, Chetumal, Playa del Carmen, Cozumel", + "value": "America/Cancun" + }, + { + "name": "-05:00 Eastern Time - Cockburn Town", + "value": "America/Grand_Turk" + }, + { + "name": "-05:00 Eastern Time - George Town, West Bay", + "value": "America/Cayman" + }, + { + "name": "-05:00 Eastern Time - Kingston, New Kingston, Spanish Town, Portmore", + "value": "America/Jamaica" + }, + { + "name": "-05:00 Eastern Time - Nassau, Lucaya, Freeport", + "value": "America/Nassau" + }, + { + "name": "-05:00 Eastern Time - New York City, Brooklyn, Queens, Philadelphia", + "value": "America/New_York" + }, + { + "name": "-05:00 Eastern Time - Panamá, San Miguelito, Juan Díaz, David", + "value": "America/Panama" + }, + { + "name": "-05:00 Eastern Time - Port-au-Prince, Carrefour, Delmas 73, Port-de-Paix", + "value": "America/Port-au-Prince" + }, + { + "name": "-05:00 Eastern Time - Toronto, Montréal, Ottawa, Mississauga", + "value": "America/Toronto" + }, + { + "name": "-05:00 Ecuador Time - Quito, Guayaquil, Cuenca, Santo Domingo de los Colorados", + "value": "America/Guayaquil" + }, + { + "name": "-05:00 Peru Time - Lima, Callao, Arequipa, Trujillo", + "value": "America/Lima" + }, + { + "name": "-04:00 Amazon Time - Manaus, Campo Grande, Cuiabá, Porto Velho", + "value": "America/Manaus" + }, + { + "name": "-04:00 Atlantic Time - Basseterre", + "value": "America/St_Kitts" + }, + { + "name": "-04:00 Atlantic Time - Blanc-Sablon", + "value": "America/Blanc-Sablon" + }, + { + "name": "-04:00 Atlantic Time - Brades, Plymouth", + "value": "America/Montserrat" + }, + { + "name": "-04:00 Atlantic Time - Bridgetown", + "value": "America/Barbados" + }, + { + "name": "-04:00 Atlantic Time - Castries", + "value": "America/St_Lucia" + }, + { + "name": "-04:00 Atlantic Time - Chaguanas, Mon Repos, San Fernando, Port of Spain", + "value": "America/Port_of_Spain" + }, + { + "name": "-04:00 Atlantic Time - Fort-de-France, Le Lamentin, Le Robert, Sainte-Marie", + "value": "America/Martinique" + }, + { + "name": "-04:00 Atlantic Time - Gustavia", + "value": "America/St_Barthelemy" + }, + { + "name": "-04:00 Atlantic Time - Halifax, Moncton, Sydney, Dartmouth", + "value": "America/Halifax" + }, + { + "name": "-04:00 Atlantic Time - Hamilton", + "value": "Atlantic/Bermuda" + }, + { + "name": "-04:00 Atlantic Time - Kingstown, Kingstown Park", + "value": "America/St_Vincent" + }, + { + "name": "-04:00 Atlantic Time - Kralendijk", + "value": "America/Kralendijk" + }, + { + "name": "-04:00 Atlantic Time - Les Abymes, Baie-Mahault, Le Gosier, Petit-Bourg", + "value": "America/Guadeloupe" + }, + { + "name": "-04:00 Atlantic Time - Marigot", + "value": "America/Marigot" + }, + { + "name": "-04:00 Atlantic Time - Oranjestad, Tanki Leendert, San Nicolas", + "value": "America/Aruba" + }, + { + "name": "-04:00 Atlantic Time - Philipsburg", + "value": "America/Lower_Princes" + }, + { + "name": "-04:00 Atlantic Time - Road Town", + "value": "America/Tortola" + }, + { + "name": "-04:00 Atlantic Time - Roseau", + "value": "America/Dominica" + }, + { + "name": "-04:00 Atlantic Time - Saint Croix, Charlotte Amalie", + "value": "America/St_Thomas" + }, + { + "name": "-04:00 Atlantic Time - Saint George's", + "value": "America/Grenada" + }, + { + "name": "-04:00 Atlantic Time - Saint John’s", + "value": "America/Antigua" + }, + { + "name": "-04:00 Atlantic Time - San Juan, Bayamón, Carolina, Ponce", + "value": "America/Puerto_Rico" + }, + { + "name": "-04:00 Atlantic Time - Santo Domingo, Santiago de los Caballeros, Santo Domingo Oeste, Santo Domingo Este", + "value": "America/Santo_Domingo" + }, + { + "name": "-04:00 Atlantic Time - The Valley", + "value": "America/Anguilla" + }, + { + "name": "-04:00 Atlantic Time - Thule", + "value": "America/Thule" + }, + { + "name": "-04:00 Atlantic Time - Willemstad", + "value": "America/Curacao" + }, + { + "name": "-04:00 Bolivia Time - La Paz, Santa Cruz de la Sierra, Cochabamba, Sucre", + "value": "America/La_Paz" + }, + { + "name": "-04:00 Chile Time - Santiago, Puente Alto, Antofagasta, Viña del Mar", + "value": "America/Santiago" + }, + { + "name": "-04:00 Guyana Time - Georgetown, Linden, New Amsterdam", + "value": "America/Guyana" + }, + { + "name": "-04:00 Paraguay Time - Asunción, Ciudad del Este, San Lorenzo, Capiatá", + "value": "America/Asuncion" + }, + { + "name": "-04:00 Venezuela Time - Caracas, Maracaibo, Maracay, Valencia", + "value": "America/Caracas" + }, + { + "name": "-03:30 Newfoundland Time - St. John's, Mount Pearl, Corner Brook, Conception Bay South", + "value": "America/St_Johns" + }, + { + "name": "-03:00 Argentina Time - Buenos Aires, Córdoba, Rosario, Mar del Plata", + "value": "America/Argentina/Buenos_Aires" + }, + { + "name": "-03:00 Brasilia Time - São Paulo, Rio de Janeiro, Belo Horizonte, Salvador", + "value": "America/Sao_Paulo" + }, + { + "name": "-03:00 Chile Time - Palmer, Rothera", + "value": "Antarctica/Palmer" + }, + { + "name": "-03:00 Chile Time - Punta Arenas, Puerto Natales", + "value": "America/Punta_Arenas" + }, + { + "name": "-03:00 Falkland Islands Time - Stanley", + "value": "Atlantic/Stanley" + }, + { + "name": "-03:00 French Guiana Time - Cayenne, Matoury, Saint-Laurent-du-Maroni, Kourou", + "value": "America/Cayenne" + }, + { + "name": "-03:00 St. Pierre & Miquelon Time - Saint-Pierre", + "value": "America/Miquelon" + }, + { + "name": "-03:00 Suriname Time - Paramaribo, Lelydorp", + "value": "America/Paramaribo" + }, + { + "name": "-03:00 Uruguay Time - Montevideo, Salto, Paysandú, Las Piedras", + "value": "America/Montevideo" + }, + { + "name": "-03:00 West Greenland Time - Nuuk", + "value": "America/Nuuk" + }, + { + "name": "-02:00 Fernando de Noronha Time - Noronha", + "value": "America/Noronha" + }, + { + "name": "-02:00 South Georgia Time - Grytviken", + "value": "Atlantic/South_Georgia" + }, + { + "name": "-01:00 Azores Time - Ponta Delgada", + "value": "Atlantic/Azores" + }, + { + "name": "-01:00 Cape Verde Time - Praia, Mindelo, Santa Maria, Cova Figueira", + "value": "Atlantic/Cape_Verde" + }, + { + "name": "-01:00 East Greenland Time - Scoresbysund", + "value": "America/Scoresbysund" + }, + { + "name": "+00:00 Greenwich Mean Time - Abidjan, Abobo, Bouaké, Korhogo", + "value": "Africa/Abidjan" + }, + { + "name": "+00:00 Greenwich Mean Time - Accra, Kumasi, Tamale, Takoradi", + "value": "Africa/Accra" + }, + { + "name": "+00:00 Greenwich Mean Time - Bamako, Ségou, Sikasso, Mopti", + "value": "Africa/Bamako" + }, + { + "name": "+00:00 Greenwich Mean Time - Bissau, Bafatá", + "value": "Africa/Bissau" + }, + { + "name": "+00:00 Greenwich Mean Time - Camayenne, Conakry, Nzérékoré, Kindia", + "value": "Africa/Conakry" + }, + { + "name": "+00:00 Greenwich Mean Time - Dakar, Pikine, Touba, Thiès", + "value": "Africa/Dakar" + }, + { + "name": "+00:00 Greenwich Mean Time - Danmarkshavn", + "value": "America/Danmarkshavn" + }, + { + "name": "+00:00 Greenwich Mean Time - Douglas", + "value": "Europe/Isle_of_Man" + }, + { + "name": "+00:00 Greenwich Mean Time - Dublin, South Dublin, Cork, Limerick", + "value": "Europe/Dublin" + }, + { + "name": "+00:00 Greenwich Mean Time - Freetown, Bo, Kenema, Koidu", + "value": "Africa/Freetown" + }, + { + "name": "+00:00 Greenwich Mean Time - Jamestown", + "value": "Atlantic/St_Helena" + }, + { + "name": "+00:00 Greenwich Mean Time - Lomé, Sokodé, Kara, Atakpamé", + "value": "Africa/Lome" + }, + { + "name": "+00:00 Greenwich Mean Time - London, Birmingham, Liverpool, Glasgow", + "value": "Europe/London" + }, + { + "name": "+00:00 Greenwich Mean Time - Monrovia, Gbarnga, Kakata, Bensonville", + "value": "Africa/Monrovia" + }, + { + "name": "+00:00 Greenwich Mean Time - Nouakchott, Nouadhibou, Dar Naim, Néma", + "value": "Africa/Nouakchott" + }, + { + "name": "+00:00 Greenwich Mean Time - Ouagadougou, Bobo-Dioulasso, Koudougou, Ouahigouya", + "value": "Africa/Ouagadougou" + }, + { + "name": "+00:00 Greenwich Mean Time - Reykjavík, Kópavogur, Hafnarfjörður, Reykjanesbær", + "value": "Atlantic/Reykjavik" + }, + { + "name": "+00:00 Greenwich Mean Time - Saint Helier", + "value": "Europe/Jersey" + }, + { + "name": "+00:00 Greenwich Mean Time - Saint Peter Port", + "value": "Europe/Guernsey" + }, + { + "name": "+00:00 Greenwich Mean Time - Serekunda, Brikama, Bakau, Banjul", + "value": "Africa/Banjul" + }, + { + "name": "+00:00 Greenwich Mean Time - São Tomé", + "value": "Africa/Sao_Tome" + }, + { + "name": "+00:00 Greenwich Mean Time - Troll", + "value": "Antarctica/Troll" + }, + { + "name": "+00:00 Western European Time - Casablanca, Rabat, Fès, Sale", + "value": "Africa/Casablanca" + }, + { + "name": "+00:00 Western European Time - Laayoune, Dakhla, Boujdour", + "value": "Africa/El_Aaiun" + }, + { + "name": "+00:00 Western European Time - Las Palmas de Gran Canaria, Santa Cruz de Tenerife, La Laguna, Telde", + "value": "Atlantic/Canary" + }, + { + "name": "+00:00 Western European Time - Lisbon, Porto, Amadora, Braga", + "value": "Europe/Lisbon" + }, + { + "name": "+00:00 Western European Time - Tórshavn", + "value": "Atlantic/Faroe" + }, + { + "name": "+01:00 Central Africa Time - Windhoek, Rundu, Walvis Bay, Oshakati", + "value": "Africa/Windhoek" + }, + { + "name": "+01:00 Central European Time - Algiers, Boumerdas, Oran, Tébessa", + "value": "Africa/Algiers" + }, + { + "name": "+01:00 Central European Time - Amsterdam, Rotterdam, The Hague, Utrecht", + "value": "Europe/Amsterdam" + }, + { + "name": "+01:00 Central European Time - Andorra la Vella, les Escaldes", + "value": "Europe/Andorra" + }, + { + "name": "+01:00 Central European Time - Belgrade, Niš, Novi Sad, Zemun", + "value": "Europe/Belgrade" + }, + { + "name": "+01:00 Central European Time - Berlin, Hamburg, Munich, Köln", + "value": "Europe/Berlin" + }, + { + "name": "+01:00 Central European Time - Bratislava, Košice, Nitra, Prešov", + "value": "Europe/Bratislava" + }, + { + "name": "+01:00 Central European Time - Brussels, Antwerpen, Gent, Charleroi", + "value": "Europe/Brussels" + }, + { + "name": "+01:00 Central European Time - Budapest, Debrecen, Szeged, Miskolc", + "value": "Europe/Budapest" + }, + { + "name": "+01:00 Central European Time - Copenhagen, Århus, Odense, Aalborg", + "value": "Europe/Copenhagen" + }, + { + "name": "+01:00 Central European Time - Gibraltar", + "value": "Europe/Gibraltar" + }, + { + "name": "+01:00 Central European Time - Ljubljana, Maribor, Kranj, Celje", + "value": "Europe/Ljubljana" + }, + { + "name": "+01:00 Central European Time - Longyearbyen", + "value": "Arctic/Longyearbyen" + }, + { + "name": "+01:00 Central European Time - Luxembourg, Esch-sur-Alzette, Dudelange", + "value": "Europe/Luxembourg" + }, + { + "name": "+01:00 Central European Time - Madrid, Barcelona, Valencia, Sevilla", + "value": "Europe/Madrid" + }, + { + "name": "+01:00 Central European Time - Monaco, Monte-Carlo", + "value": "Europe/Monaco" + }, + { + "name": "+01:00 Central European Time - Oslo, Bergen, Trondheim, Stavanger", + "value": "Europe/Oslo" + }, + { + "name": "+01:00 Central European Time - Paris, Marseille, Lyon, Toulouse", + "value": "Europe/Paris" + }, + { + "name": "+01:00 Central European Time - Podgorica, Nikšić, Herceg Novi, Pljevlja", + "value": "Europe/Podgorica" + }, + { + "name": "+01:00 Central European Time - Prague, Brno, Ostrava, Pilsen", + "value": "Europe/Prague" + }, + { + "name": "+01:00 Central European Time - Rome, Milan, Naples, Turin", + "value": "Europe/Rome" + }, + { + "name": "+01:00 Central European Time - San Marino", + "value": "Europe/San_Marino" + }, + { + "name": "+01:00 Central European Time - San Pawl il-Baħar, Birkirkara, Mosta, Sliema", + "value": "Europe/Malta" + }, + { + "name": "+01:00 Central European Time - Sarajevo, Banja Luka, Zenica, Tuzla", + "value": "Europe/Sarajevo" + }, + { + "name": "+01:00 Central European Time - Skopje, Kumanovo, Prilep, Bitola", + "value": "Europe/Skopje" + }, + { + "name": "+01:00 Central European Time - Stockholm, Göteborg, Malmö, Uppsala", + "value": "Europe/Stockholm" + }, + { + "name": "+01:00 Central European Time - Tirana, Durrës, Elbasan, Vlorë", + "value": "Europe/Tirane" + }, + { + "name": "+01:00 Central European Time - Tunis, Sfax, Sousse, Kairouan", + "value": "Africa/Tunis" + }, + { + "name": "+01:00 Central European Time - Vaduz", + "value": "Europe/Vaduz" + }, + { + "name": "+01:00 Central European Time - Vatican City", + "value": "Europe/Vatican" + }, + { + "name": "+01:00 Central European Time - Vienna, Graz, Linz, Favoriten", + "value": "Europe/Vienna" + }, + { + "name": "+01:00 Central European Time - Warsaw, Łódź, Kraków, Wrocław", + "value": "Europe/Warsaw" + }, + { + "name": "+01:00 Central European Time - Zagreb, Split, Rijeka, Osijek", + "value": "Europe/Zagreb" + }, + { + "name": "+01:00 Central European Time - Zürich, Genève, Basel, Lausanne", + "value": "Europe/Zurich" + }, + { + "name": "+01:00 West Africa Time - Bangui, Bimbo, Mbaïki, Berbérati", + "value": "Africa/Bangui" + }, + { + "name": "+01:00 West Africa Time - Bata, Malabo, Ebebiyin", + "value": "Africa/Malabo" + }, + { + "name": "+01:00 West Africa Time - Brazzaville, Pointe-Noire, Dolisie, Kayes", + "value": "Africa/Brazzaville" + }, + { + "name": "+01:00 West Africa Time - Cotonou, Abomey-Calavi, Djougou, Porto-Novo", + "value": "Africa/Porto-Novo" + }, + { + "name": "+01:00 West Africa Time - Douala, Yaoundé, Garoua, Kousséri", + "value": "Africa/Douala" + }, + { + "name": "+01:00 West Africa Time - Kinshasa, Masina, Kikwit, Mbandaka", + "value": "Africa/Kinshasa" + }, + { + "name": "+01:00 West Africa Time - Lagos, Kano, Ibadan, Port Harcourt", + "value": "Africa/Lagos" + }, + { + "name": "+01:00 West Africa Time - Libreville, Port-Gentil, Franceville, Oyem", + "value": "Africa/Libreville" + }, + { + "name": "+01:00 West Africa Time - Luanda, N’dalatando, Huambo, Lobito", + "value": "Africa/Luanda" + }, + { + "name": "+01:00 West Africa Time - N'Djamena, Moundou, Sarh, Abéché", + "value": "Africa/Ndjamena" + }, + { + "name": "+01:00 West Africa Time - Niamey, Zinder, Maradi, Agadez", + "value": "Africa/Niamey" + }, + { + "name": "+02:00 Central Africa Time - Bujumbura, Muyinga, Gitega, Ruyigi", + "value": "Africa/Bujumbura" + }, + { + "name": "+02:00 Central Africa Time - Gaborone, Francistown, Molepolole, Maun", + "value": "Africa/Gaborone" + }, + { + "name": "+02:00 Central Africa Time - Harare, Bulawayo, Chitungwiza, Mutare", + "value": "Africa/Harare" + }, + { + "name": "+02:00 Central Africa Time - Juba, Winejok, Yei, Malakal", + "value": "Africa/Juba" + }, + { + "name": "+02:00 Central Africa Time - Khartoum, Omdurman, Nyala, Port Sudan", + "value": "Africa/Khartoum" + }, + { + "name": "+02:00 Central Africa Time - Kigali, Gisenyi, Butare, Gitarama", + "value": "Africa/Kigali" + }, + { + "name": "+02:00 Central Africa Time - Lilongwe, Blantyre, Mzuzu, Zomba", + "value": "Africa/Blantyre" + }, + { + "name": "+02:00 Central Africa Time - Lubumbashi, Mbuji-Mayi, Kisangani, Kananga", + "value": "Africa/Lubumbashi" + }, + { + "name": "+02:00 Central Africa Time - Lusaka, Ndola, Kitwe, Chipata", + "value": "Africa/Lusaka" + }, + { + "name": "+02:00 Central Africa Time - Maputo, Matola, Nampula, Beira", + "value": "Africa/Maputo" + }, + { + "name": "+02:00 Eastern European Time - Athens, Thessaloníki, Pátra, Piraeus", + "value": "Europe/Athens" + }, + { + "name": "+02:00 Eastern European Time - Beirut, Ra’s Bayrūt, Tripoli, Sidon", + "value": "Asia/Beirut" + }, + { + "name": "+02:00 Eastern European Time - Bucharest, Sector 3, Iaşi, Sector 6", + "value": "Europe/Bucharest" + }, + { + "name": "+02:00 Eastern European Time - Cairo, Alexandria, Giza, Shubrā al Khaymah", + "value": "Africa/Cairo" + }, + { + "name": "+02:00 Eastern European Time - Chisinau, Tiraspol, Bălţi, Bender", + "value": "Europe/Chisinau" + }, + { + "name": "+02:00 Eastern European Time - East Jerusalem, Gaza, Khān Yūnis, Jabālyā", + "value": "Asia/Hebron" + }, + { + "name": "+02:00 Eastern European Time - Helsinki, Espoo, Tampere, Oulu", + "value": "Europe/Helsinki" + }, + { + "name": "+02:00 Eastern European Time - Kaliningrad, Chernyakhovsk, Sovetsk, Baltiysk", + "value": "Europe/Kaliningrad" + }, + { + "name": "+02:00 Eastern European Time - Kyiv, Kharkiv, Odesa, Dnipro", + "value": "Europe/Kyiv" + }, + { + "name": "+02:00 Eastern European Time - Mariehamn", + "value": "Europe/Mariehamn" + }, + { + "name": "+02:00 Eastern European Time - Nicosia, Limassol, Larnaca, Stróvolos", + "value": "Asia/Nicosia" + }, + { + "name": "+02:00 Eastern European Time - Riga, Daugavpils, Liepāja, Jelgava", + "value": "Europe/Riga" + }, + { + "name": "+02:00 Eastern European Time - Sofia, Plovdiv, Varna, Burgas", + "value": "Europe/Sofia" + }, + { + "name": "+02:00 Eastern European Time - Tallinn, Tartu, Narva, Pärnu", + "value": "Europe/Tallinn" + }, + { + "name": "+02:00 Eastern European Time - Tripoli, Benghazi, Ajdabiya, Mişrātah", + "value": "Africa/Tripoli" + }, + { + "name": "+02:00 Eastern European Time - Vilnius, Kaunas, Klaipėda, Šiauliai", + "value": "Europe/Vilnius" + }, + { + "name": "+02:00 Israel Time - Jerusalem, Tel Aviv, West Jerusalem, Haifa", + "value": "Asia/Jerusalem" + }, + { + "name": "+02:00 South Africa Time - Johannesburg, Cape Town, Durban, Soweto", + "value": "Africa/Johannesburg" + }, + { + "name": "+02:00 South Africa Time - Manzini, Mbabane, Lobamba", + "value": "Africa/Mbabane" + }, + { + "name": "+02:00 South Africa Time - Maseru, Mohale’s Hoek, Mafeteng, Leribe", + "value": "Africa/Maseru" + }, + { + "name": "+03:00 Arabian Time - Al Aḩmadī, Ḩawallī, As Sālimīyah, Şabāḩ as Sālim", + "value": "Asia/Kuwait" + }, + { + "name": "+03:00 Arabian Time - Ar Rifā‘, Manama, Al Muharraq, Dār Kulayb", + "value": "Asia/Bahrain" + }, + { + "name": "+03:00 Arabian Time - Baghdad, Al Mawşil al Jadīdah, Al Başrah al Qadīmah, Mosul", + "value": "Asia/Baghdad" + }, + { + "name": "+03:00 Arabian Time - Doha, Ar Rayyān, Umm Şalāl Muḩammad, Al Wakrah", + "value": "Asia/Qatar" + }, + { + "name": "+03:00 Arabian Time - Jeddah, Riyadh, Mecca, Medina", + "value": "Asia/Riyadh" + }, + { + "name": "+03:00 Arabian Time - Sanaa, Aden, Al Ḩudaydah, Taiz", + "value": "Asia/Aden" + }, + { + "name": "+03:00 Asia/Amman - Amman, Zarqa, Irbid, Russeifa", + "value": "Asia/Amman" + }, + { + "name": "+03:00 Asia/Damascus - Aleppo, Damascus, Homs, Latakia", + "value": "Asia/Damascus" + }, + { + "name": "+03:00 East Africa Time - Addis Ababa, Jijiga, Gondar, Mek'ele", + "value": "Africa/Addis_Ababa" + }, + { + "name": "+03:00 East Africa Time - Antananarivo, Toamasina, Antsirabe, Mahajanga", + "value": "Indian/Antananarivo" + }, + { + "name": "+03:00 East Africa Time - Asmara, Keren, Massawa, Assab", + "value": "Africa/Asmara" + }, + { + "name": "+03:00 East Africa Time - Dar es Salaam, Mwanza, Zanzibar, Arusha", + "value": "Africa/Dar_es_Salaam" + }, + { + "name": "+03:00 East Africa Time - Djibouti, 'Ali Sabieh, Tadjourah, Obock", + "value": "Africa/Djibouti" + }, + { + "name": "+03:00 East Africa Time - Kampala, Gulu, Lira, Mbarara", + "value": "Africa/Kampala" + }, + { + "name": "+03:00 East Africa Time - Mamoudzou, Koungou, Dzaoudzi", + "value": "Indian/Mayotte" + }, + { + "name": "+03:00 East Africa Time - Mogadishu, Hargeysa, Berbera, Kismayo", + "value": "Africa/Mogadishu" + }, + { + "name": "+03:00 East Africa Time - Moroni, Moutsamoudou", + "value": "Indian/Comoro" + }, + { + "name": "+03:00 East Africa Time - Nairobi, Kakamega, Mombasa, Ruiru", + "value": "Africa/Nairobi" + }, + { + "name": "+03:00 Moscow Time - Minsk, Homyel', Hrodna, Mahilyow", + "value": "Europe/Minsk" + }, + { + "name": "+03:00 Moscow Time - Moscow, Saint Petersburg, Nizhniy Novgorod, Kazan", + "value": "Europe/Moscow" + }, + { + "name": "+03:00 Moscow Time - Sevastopol, Simferopol, Kerch, Yevpatoriya", + "value": "Europe/Simferopol" + }, + { + "name": "+03:00 Syowa Time - Syowa", + "value": "Antarctica/Syowa" + }, + { + "name": "+03:00 Turkey Time - Istanbul, Ankara, Bursa, İzmir", + "value": "Europe/Istanbul" + }, + { + "name": "+03:30 Iran Time - Tehran, Mashhad, Isfahan, Karaj", + "value": "Asia/Tehran" + }, + { + "name": "+04:00 Armenia Time - Yerevan, Gyumri, Vanadzor, Vagharshapat", + "value": "Asia/Yerevan" + }, + { + "name": "+04:00 Azerbaijan Time - Baku, Sumqayıt, Ganja, Lankaran", + "value": "Asia/Baku" + }, + { + "name": "+04:00 Georgia Time - Tbilisi, Batumi, Kutaisi, Rustavi", + "value": "Asia/Tbilisi" + }, + { + "name": "+04:00 Gulf Time - Dubai, Abu Dhabi, Sharjah, Al Ain City", + "value": "Asia/Dubai" + }, + { + "name": "+04:00 Gulf Time - Muscat, Seeb, Bawshar, ‘Ibrī", + "value": "Asia/Muscat" + }, + { + "name": "+04:00 Mauritius Time - Port Louis, Vacoas, Beau Bassin-Rose Hill, Curepipe", + "value": "Indian/Mauritius" + }, + { + "name": "+04:00 Réunion Time - Saint-Denis, Saint-Paul, Le Tampon, Saint-Pierre", + "value": "Indian/Reunion" + }, + { + "name": "+04:00 Samara Time - Samara, Saratov, Tolyatti, Izhevsk", + "value": "Europe/Samara" + }, + { + "name": "+04:00 Seychelles Time - Victoria", + "value": "Indian/Mahe" + }, + { + "name": "+04:30 Afghanistan Time - Kabul, Herāt, Mazār-e Sharīf, Kandahār", + "value": "Asia/Kabul" + }, + { + "name": "+05:00 French Southern & Antarctic Time - Port-aux-Français", + "value": "Indian/Kerguelen" + }, + { + "name": "+05:00 Maldives Time - Male", + "value": "Indian/Maldives" + }, + { + "name": "+05:00 Mawson Time - Mawson", + "value": "Antarctica/Mawson" + }, + { + "name": "+05:00 Pakistan Time - Karachi, Lahore, Faisalabad, Rawalpindi", + "value": "Asia/Karachi" + }, + { + "name": "+05:00 Tajikistan Time - Dushanbe, Isfara, Istaravshan, Kŭlob", + "value": "Asia/Dushanbe" + }, + { + "name": "+05:00 Turkmenistan Time - Ashgabat, Türkmenabat, Daşoguz, Mary", + "value": "Asia/Ashgabat" + }, + { + "name": "+05:00 Uzbekistan Time - Tashkent, Namangan, Samarkand, Andijon", + "value": "Asia/Tashkent" + }, + { + "name": "+05:00 West Kazakhstan Time - Aktobe, Kyzylorda, Oral, Atyrau", + "value": "Asia/Aqtobe" + }, + { + "name": "+05:00 Yekaterinburg Time - Yekaterinburg, Chelyabinsk, Ufa, Perm", + "value": "Asia/Yekaterinburg" + }, + { + "name": "+05:30 India Time - Colombo, Dehiwala-Mount Lavinia, Maharagama, Jaffna", + "value": "Asia/Colombo" + }, + { + "name": "+05:30 India Time - Mumbai, Delhi, Bengaluru, Hyderābād", + "value": "Asia/Kolkata" + }, + { + "name": "+05:45 Nepal Time - Kathmandu, Bharatpur, Pātan, Birgañj", + "value": "Asia/Kathmandu" + }, + { + "name": "+06:00 Bangladesh Time - Dhaka, Chattogram, Khulna, Rangpur", + "value": "Asia/Dhaka" + }, + { + "name": "+06:00 Bhutan Time - Thimphu, Phuntsholing, Tsirang, Punākha", + "value": "Asia/Thimphu" + }, + { + "name": "+06:00 China Time - Ürümqi, Shihezi, Korla, Aksu", + "value": "Asia/Urumqi" + }, + { + "name": "+06:00 East Kazakhstan Time - Almaty, Shymkent, Karagandy, Taraz", + "value": "Asia/Almaty" + }, + { + "name": "+06:00 Indian Ocean Time - Chagos", + "value": "Indian/Chagos" + }, + { + "name": "+06:00 Kyrgyzstan Time - Bishkek, Osh, Jalal-Abad, Karakol", + "value": "Asia/Bishkek" + }, + { + "name": "+06:00 Omsk Time - Omsk, Tara, Kalachinsk", + "value": "Asia/Omsk" + }, + { + "name": "+06:00 Vostok Time - Vostok", + "value": "Antarctica/Vostok" + }, + { + "name": "+06:30 Cocos Islands Time - West Island", + "value": "Indian/Cocos" + }, + { + "name": "+06:30 Myanmar Time - Yangon, Mandalay, Nay Pyi Taw, Mawlamyine", + "value": "Asia/Yangon" + }, + { + "name": "+07:00 Christmas Island Time - Flying Fish Cove", + "value": "Indian/Christmas" + }, + { + "name": "+07:00 Davis Time - Davis", + "value": "Antarctica/Davis" + }, + { + "name": "+07:00 Hovd Time - Ulaangom, Khovd, Ölgii, Altai", + "value": "Asia/Hovd" + }, + { + "name": "+07:00 Indochina Time - Bangkok, Samut Prakan, Mueang Nonthaburi, Chon Buri", + "value": "Asia/Bangkok" + }, + { + "name": "+07:00 Indochina Time - Ho Chi Minh City, Da Nang, Biên Hòa, Cần Thơ", + "value": "Asia/Ho_Chi_Minh" + }, + { + "name": "+07:00 Indochina Time - Phnom Penh, Takeo, Siem Reap, Battambang", + "value": "Asia/Phnom_Penh" + }, + { + "name": "+07:00 Indochina Time - Vientiane, Savannakhet, Pakse, Thakhèk", + "value": "Asia/Vientiane" + }, + { + "name": "+07:00 Novosibirsk Time - Novosibirsk, Krasnoyarsk, Barnaul, Tomsk", + "value": "Asia/Novosibirsk" + }, + { + "name": "+07:00 Western Indonesia Time - Jakarta, Surabaya, Bekasi, Bandung", + "value": "Asia/Jakarta" + }, + { + "name": "+08:00 Australian Western Time - Perth, Mandurah, Bunbury, Baldivis", + "value": "Australia/Perth" + }, + { + "name": "+08:00 Brunei Darussalam Time - Bandar Seri Begawan, Kuala Belait, Seria, Tutong", + "value": "Asia/Brunei" + }, + { + "name": "+08:00 Central Indonesia Time - Makassar, Samarinda, Denpasar, Balikpapan", + "value": "Asia/Makassar" + }, + { + "name": "+08:00 China Time - Macau", + "value": "Asia/Macau" + }, + { + "name": "+08:00 China Time - Shanghai, Beijing, Shenzhen, Guangzhou", + "value": "Asia/Shanghai" + }, + { + "name": "+08:00 Hong Kong Time - Hong Kong, Kowloon, Victoria, Tuen Mun", + "value": "Asia/Hong_Kong" + }, + { + "name": "+08:00 Irkutsk Time - Irkutsk, Ulan-Ude, Bratsk, Angarsk", + "value": "Asia/Irkutsk" + }, + { + "name": "+08:00 Malaysia Time - Kuala Lumpur, Petaling Jaya, Klang, Johor Bahru", + "value": "Asia/Kuala_Lumpur" + }, + { + "name": "+08:00 Philippine Time - Quezon City, Davao, Manila, Caloocan City", + "value": "Asia/Manila" + }, + { + "name": "+08:00 Singapore Time - Singapore, Woodlands, Geylang, Queenstown Estate", + "value": "Asia/Singapore" + }, + { + "name": "+08:00 Taipei Time - Taipei, Kaohsiung, Taichung, Tainan", + "value": "Asia/Taipei" + }, + { + "name": "+08:00 Ulaanbaatar Time - Ulan Bator, Erdenet, Darhan, Mörön", + "value": "Asia/Ulaanbaatar" + }, + { + "name": "+08:45 Australian Central Western Time - Eucla", + "value": "Australia/Eucla" + }, + { + "name": "+09:00 East Timor Time - Dili, Maliana, Suai, Likisá", + "value": "Asia/Dili" + }, + { + "name": "+09:00 Eastern Indonesia Time - Jayapura, Ambon, Sorong, Ternate", + "value": "Asia/Jayapura" + }, + { + "name": "+09:00 Japan Time - Tokyo, Yokohama, Osaka, Nagoya", + "value": "Asia/Tokyo" + }, + { + "name": "+09:00 Korean Time - Pyongyang, Hamhŭng, Namp’o, Sunch’ŏn", + "value": "Asia/Pyongyang" + }, + { + "name": "+09:00 Korean Time - Seoul, Busan, Incheon, Daegu", + "value": "Asia/Seoul" + }, + { + "name": "+09:00 Palau Time - Ngerulmud", + "value": "Pacific/Palau" + }, + { + "name": "+09:00 Yakutsk Time - Chita, Yakutsk, Blagoveshchensk, Belogorsk", + "value": "Asia/Chita" + }, + { + "name": "+09:30 Australian Central Time - Adelaide, Adelaide Hills, Mount Gambier, Morphett Vale", + "value": "Australia/Adelaide" + }, + { + "name": "+09:30 Australian Central Time - Darwin, Alice Springs, Palmerston", + "value": "Australia/Darwin" + }, + { + "name": "+10:00 Australian Eastern Time - Brisbane, Gold Coast, Logan City, Townsville", + "value": "Australia/Brisbane" + }, + { + "name": "+10:00 Australian Eastern Time - Sydney, Melbourne, Canberra, Newcastle", + "value": "Australia/Sydney" + }, + { + "name": "+10:00 Chamorro Time - Dededo Village, Yigo Village, Tamuning-Tumon-Harmon Village, Tamuning", + "value": "Pacific/Guam" + }, + { + "name": "+10:00 Chamorro Time - Saipan", + "value": "Pacific/Saipan" + }, + { + "name": "+10:00 Chuuk Time - Chuuk", + "value": "Pacific/Chuuk" + }, + { + "name": "+10:00 Dumont-d’Urville Time - DumontDUrville", + "value": "Antarctica/DumontDUrville" + }, + { + "name": "+10:00 Papua New Guinea Time - Port Moresby, Lae, Mount Hagen, Popondetta", + "value": "Pacific/Port_Moresby" + }, + { + "name": "+10:00 Vladivostok Time - Khabarovsk, Vladivostok, Khabarovsk Vtoroy, Komsomolsk-on-Amur", + "value": "Asia/Vladivostok" + }, + { + "name": "+10:30 Lord Howe Time - Lord Howe", + "value": "Australia/Lord_Howe" + }, + { + "name": "+11:00 Bougainville Time - Arawa", + "value": "Pacific/Bougainville" + }, + { + "name": "+11:00 Casey Time - Casey", + "value": "Antarctica/Casey" + }, + { + "name": "+11:00 Kosrae Time - Kosrae, Palikir - National Government Center", + "value": "Pacific/Kosrae" + }, + { + "name": "+11:00 New Caledonia Time - Nouméa, Mont-Dore, Dumbéa", + "value": "Pacific/Noumea" + }, + { + "name": "+11:00 Norfolk Island Time - Kingston", + "value": "Pacific/Norfolk" + }, + { + "name": "+11:00 Sakhalin Time - Yuzhno-Sakhalinsk, Magadan, Korsakov, Kholmsk", + "value": "Asia/Sakhalin" + }, + { + "name": "+11:00 Solomon Islands Time - Honiara", + "value": "Pacific/Guadalcanal" + }, + { + "name": "+11:00 Vanuatu Time - Port-Vila", + "value": "Pacific/Efate" + }, + { + "name": "+12:00 Fiji Time - Nasinu, Suva, Lautoka, Nadi", + "value": "Pacific/Fiji" + }, + { + "name": "+12:00 Gilbert Islands Time - Tarawa", + "value": "Pacific/Tarawa" + }, + { + "name": "+12:00 Marshall Islands Time - Majuro, Kwajalein, RMI Capitol", + "value": "Pacific/Majuro" + }, + { + "name": "+12:00 Nauru Time - Yaren", + "value": "Pacific/Nauru" + }, + { + "name": "+12:00 New Zealand Time - Auckland, Wellington, Christchurch, Manukau City", + "value": "Pacific/Auckland" + }, + { + "name": "+12:00 New Zealand Time - McMurdo", + "value": "Antarctica/McMurdo" + }, + { + "name": "+12:00 Petropavlovsk-Kamchatski Time - Petropavlovsk-Kamchatsky, Yelizovo, Vilyuchinsk, Anadyr", + "value": "Asia/Kamchatka" + }, + { + "name": "+12:00 Tuvalu Time - Funafuti", + "value": "Pacific/Funafuti" + }, + { + "name": "+12:00 Wake Island Time - Wake", + "value": "Pacific/Wake" + }, + { + "name": "+12:00 Wallis & Futuna Time - Mata-Utu", + "value": "Pacific/Wallis" + }, + { + "name": "+12:45 Chatham Time - Chatham", + "value": "Pacific/Chatham" + }, + { + "name": "+13:00 Apia Time - Apia", + "value": "Pacific/Apia" + }, + { + "name": "+13:00 Phoenix Islands Time - Kanton", + "value": "Pacific/Kanton" + }, + { + "name": "+13:00 Tokelau Time - Fakaofo", + "value": "Pacific/Fakaofo" + }, + { + "name": "+13:00 Tonga Time - Nuku‘alofa", + "value": "Pacific/Tongatapu" + }, + { + "name": "+14:00 Line Islands Time - Kiritimati", + "value": "Pacific/Kiritimati" + } ] diff --git a/web/utils/tool-call.spec.ts b/web/utils/tool-call.spec.ts index ccfb06f0cc..faffc0b9e4 100644 --- a/web/utils/tool-call.spec.ts +++ b/web/utils/tool-call.spec.ts @@ -1,9 +1,9 @@ +import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' /** * Test suite for tool call utility functions * Tests detection of function/tool call support in AI models */ import { supportFunctionCall } from './tool-call' -import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' describe('tool-call', () => { /** @@ -14,7 +14,7 @@ describe('tool-call', () => { /** * Tests detection of basic tool call support */ - test('returns true when features include toolCall', () => { + it('returns true when features include toolCall', () => { const features = [ModelFeatureEnum.toolCall] expect(supportFunctionCall(features)).toBe(true) }) @@ -22,7 +22,7 @@ describe('tool-call', () => { /** * Tests detection of multi-tool call support (calling multiple tools in one request) */ - test('returns true when features include multiToolCall', () => { + it('returns true when features include multiToolCall', () => { const features = [ModelFeatureEnum.multiToolCall] expect(supportFunctionCall(features)).toBe(true) }) @@ -30,12 +30,12 @@ describe('tool-call', () => { /** * Tests detection of streaming tool call support */ - test('returns true when features include streamToolCall', () => { + it('returns true when features include streamToolCall', () => { const features = [ModelFeatureEnum.streamToolCall] expect(supportFunctionCall(features)).toBe(true) }) - test('returns true when features include multiple tool call types', () => { + it('returns true when features include multiple tool call types', () => { const features = [ ModelFeatureEnum.toolCall, ModelFeatureEnum.multiToolCall, @@ -47,7 +47,7 @@ describe('tool-call', () => { /** * Tests that tool call support is detected even when mixed with other features */ - test('returns true when features include tool call among other features', () => { + it('returns true when features include tool call among other features', () => { const features = [ ModelFeatureEnum.agentThought, ModelFeatureEnum.toolCall, @@ -59,20 +59,20 @@ describe('tool-call', () => { /** * Tests that false is returned when no tool call features are present */ - test('returns false when features do not include any tool call type', () => { + it('returns false when features do not include any tool call type', () => { const features = [ModelFeatureEnum.agentThought, ModelFeatureEnum.vision] expect(supportFunctionCall(features)).toBe(false) }) - test('returns false for empty array', () => { + it('returns false for empty array', () => { expect(supportFunctionCall([])).toBe(false) }) - test('returns false for undefined', () => { + it('returns false for undefined', () => { expect(supportFunctionCall(undefined)).toBe(false) }) - test('returns false for null', () => { + it('returns false for null', () => { expect(supportFunctionCall(null as any)).toBe(false) }) }) diff --git a/web/utils/tool-call.ts b/web/utils/tool-call.ts index 8735cc5d81..8d34cbdcb6 100644 --- a/web/utils/tool-call.ts +++ b/web/utils/tool-call.ts @@ -1,6 +1,7 @@ import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' export const supportFunctionCall = (features: ModelFeatureEnum[] = []): boolean => { - if (!features || !features.length) return false + if (!features || !features.length) + return false return features.some(feature => [ModelFeatureEnum.toolCall, ModelFeatureEnum.multiToolCall, ModelFeatureEnum.streamToolCall].includes(feature)) } diff --git a/web/utils/urlValidation.ts b/web/utils/urlValidation.ts index db6de5275a..fcc5c4b5d8 100644 --- a/web/utils/urlValidation.ts +++ b/web/utils/urlValidation.ts @@ -15,8 +15,9 @@ export function validateRedirectUrl(url: string): void { if ( error instanceof Error && error.message === 'Authorization URL must be HTTP or HTTPS' - ) + ) { throw error + } // If URL parsing fails, it's also invalid throw new Error(`Invalid URL: ${url}`) } diff --git a/web/utils/var.spec.ts b/web/utils/var.spec.ts index 6f55df0d34..9120b7a784 100644 --- a/web/utils/var.spec.ts +++ b/web/utils/var.spec.ts @@ -1,3 +1,4 @@ +import { InputVarType } from '@/app/components/workflow/types' import { checkKey, checkKeys, @@ -8,7 +9,6 @@ import { hasDuplicateStr, replaceSpaceWithUnderscoreInVarNameInput, } from './var' -import { InputVarType } from '@/app/components/workflow/types' describe('Variable Utilities', () => { describe('checkKey', () => { diff --git a/web/utils/var.ts b/web/utils/var.ts index 3181d2bbd7..c72310178d 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -1,12 +1,12 @@ -import { MARKETPLACE_URL_PREFIX, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config' +import type { InputVar } from '@/app/components/workflow/types' import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, } from '@/app/components/base/prompt-editor/constants' -import type { InputVar } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types' +import { getMaxVarNameLength, MARKETPLACE_URL_PREFIX, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW } from '@/config' const otherAllowedRegex = /^\w+$/ @@ -97,7 +97,7 @@ export const hasDuplicateStr = (strArr: string[]) => { return !!Object.keys(strObj).find(key => strObj[key] > 1) } -const varRegex = /\{\{([a-zA-Z_]\w*)\}\}/g +const varRegex = /\{\{([a-z_]\w*)\}\}/gi export const getVars = (value: string) => { if (!value) return [] diff --git a/web/utils/zod.spec.ts b/web/utils/zod.spec.ts index f5d079e23d..e3676aa054 100644 --- a/web/utils/zod.spec.ts +++ b/web/utils/zod.spec.ts @@ -1,4 +1,4 @@ -import { ZodError, z } from 'zod' +import { z, ZodError } from 'zod' describe('Zod Features', () => { it('should support string', () => { diff --git a/web/vitest.config.ts b/web/vitest.config.ts index 77e63b49bc..2befbecba6 100644 --- a/web/vitest.config.ts +++ b/web/vitest.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import tsconfigPaths from 'vite-tsconfig-paths' +import { defineConfig } from 'vitest/config' export default defineConfig({ plugins: [tsconfigPaths(), react() as any], diff --git a/web/vitest.setup.ts b/web/vitest.setup.ts index 5d997ac329..e4ea9ed31c 100644 --- a/web/vitest.setup.ts +++ b/web/vitest.setup.ts @@ -1,6 +1,6 @@ -import '@testing-library/jest-dom/vitest' import { cleanup } from '@testing-library/react' import { mockAnimationsApi, mockResizeObserver } from 'jsdom-testing-mocks' +import '@testing-library/jest-dom/vitest' mockResizeObserver() From 72ca3607a34631beb44855ec316a37f697b2e75b Mon Sep 17 00:00:00 2001 From: Coding On Star <447357187@qq.com> Date: Tue, 23 Dec 2025 17:48:20 +0800 Subject: [PATCH 42/71] feat: Add polyfill for Array.prototype.toSpliced method (#30031) Co-authored-by: CodingOnStar <hanxujiang@dify.ai> --- .../components/browser-initializer.spec.ts | 78 +++++++++++++++++++ web/app/components/browser-initializer.tsx | 14 ++++ 2 files changed, 92 insertions(+) create mode 100644 web/app/components/browser-initializer.spec.ts diff --git a/web/app/components/browser-initializer.spec.ts b/web/app/components/browser-initializer.spec.ts new file mode 100644 index 0000000000..f767b80826 --- /dev/null +++ b/web/app/components/browser-initializer.spec.ts @@ -0,0 +1,78 @@ +/** + * Tests for Array.prototype.toSpliced polyfill + */ + +describe('toSpliced polyfill', () => { + let originalToSpliced: typeof Array.prototype.toSpliced + + beforeEach(() => { + // Save original method + originalToSpliced = Array.prototype.toSpliced + }) + + afterEach(() => { + // Restore original method + // eslint-disable-next-line no-extend-native + Array.prototype.toSpliced = originalToSpliced + }) + + const applyPolyfill = () => { + // @ts-expect-error - intentionally deleting for test + delete Array.prototype.toSpliced + + if (!Array.prototype.toSpliced) { + // eslint-disable-next-line no-extend-native + Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] { + const copy = this.slice() + copy.splice(start, deleteCount ?? copy.length - start, ...items) + return copy + } + } + } + + it('should add toSpliced method when not available', () => { + applyPolyfill() + expect(typeof Array.prototype.toSpliced).toBe('function') + }) + + it('should return a new array without modifying the original', () => { + applyPolyfill() + const arr = [1, 2, 3, 4, 5] + const result = arr.toSpliced(1, 2) + + expect(result).toEqual([1, 4, 5]) + expect(arr).toEqual([1, 2, 3, 4, 5]) // original unchanged + }) + + it('should insert items at the specified position', () => { + applyPolyfill() + const arr: (number | string)[] = [1, 2, 3] + const result = arr.toSpliced(1, 0, 'a', 'b') + + expect(result).toEqual([1, 'a', 'b', 2, 3]) + }) + + it('should replace items at the specified position', () => { + applyPolyfill() + const arr: (number | string)[] = [1, 2, 3, 4, 5] + const result = arr.toSpliced(1, 2, 'a', 'b') + + expect(result).toEqual([1, 'a', 'b', 4, 5]) + }) + + it('should handle negative start index', () => { + applyPolyfill() + const arr = [1, 2, 3, 4, 5] + const result = arr.toSpliced(-2, 1) + + expect(result).toEqual([1, 2, 3, 5]) + }) + + it('should delete to end when deleteCount is omitted', () => { + applyPolyfill() + const arr = [1, 2, 3, 4, 5] + const result = arr.toSpliced(2) + + expect(result).toEqual([1, 2]) + }) +}) diff --git a/web/app/components/browser-initializer.tsx b/web/app/components/browser-initializer.tsx index fcae22c448..c2194ca8d4 100644 --- a/web/app/components/browser-initializer.tsx +++ b/web/app/components/browser-initializer.tsx @@ -1,5 +1,19 @@ 'use client' +// Polyfill for Array.prototype.toSpliced (ES2023, Chrome 110+) +if (!Array.prototype.toSpliced) { + // eslint-disable-next-line no-extend-native + Array.prototype.toSpliced = function <T>(this: T[], start: number, deleteCount?: number, ...items: T[]): T[] { + const copy = this.slice() + // When deleteCount is undefined (omitted), delete to end; otherwise let splice handle coercion + if (deleteCount === undefined) + copy.splice(start, copy.length - start, ...items) + else + copy.splice(start, deleteCount, ...items) + return copy + } +} + class StorageMock { data: Record<string, string> From 403adefc0725e2a28543603c73d59747d31457cb Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:02:10 +0800 Subject: [PATCH 43/71] chore: lint require and how to import react (#30041) --- web/__tests__/check-i18n.test.ts | 6 +- web/__tests__/embedded-user-id-auth.test.tsx | 2 +- web/__tests__/embedded-user-id-store.test.tsx | 2 +- .../goto-anything/command-selector.test.tsx | 2 +- .../goto-anything/scope-command-tags.test.tsx | 2 +- .../workflow-parallel-limit.test.tsx | 2 +- web/__tests__/xss-prevention.test.tsx | 2 +- .../[appId]/annotations/page.tsx | 2 +- .../[appId]/configuration/page.tsx | 2 +- .../[appId]/develop/page.tsx | 2 +- .../(appDetailLayout)/[appId]/layout-main.tsx | 3 +- .../(appDetailLayout)/[appId]/logs/page.tsx | 2 +- .../[appId]/overview/card-view.tsx | 3 +- .../[appId]/overview/chart-view.tsx | 3 +- .../overview/long-time-range-picker.tsx | 2 +- .../[appId]/overview/page.tsx | 2 +- .../time-range-picker/date-picker.tsx | 3 +- .../overview/time-range-picker/index.tsx | 3 +- .../time-range-picker/range-selector.tsx | 3 +- .../svg-attribute-error-reproduction.spec.tsx | 2 +- .../overview/tracing/config-button.tsx | 3 +- .../[appId]/overview/tracing/config-popup.tsx | 3 +- .../[appId]/overview/tracing/field.tsx | 2 +- .../[appId]/overview/tracing/panel.tsx | 3 +- .../tracing/provider-config-modal.tsx | 3 +- .../overview/tracing/provider-panel.tsx | 3 +- .../[appId]/overview/tracing/tracing-icon.tsx | 2 +- .../app/(appDetailLayout)/layout.tsx | 3 +- .../[datasetId]/api/page.tsx | 2 +- .../documents/[documentId]/page.tsx | 2 +- .../documents/[documentId]/settings/page.tsx | 2 +- .../documents/create-from-pipeline/page.tsx | 2 +- .../[datasetId]/documents/create/page.tsx | 2 +- .../[datasetId]/documents/page.tsx | 2 +- .../[datasetId]/hitTesting/page.tsx | 2 +- .../[datasetId]/layout-main.tsx | 3 +- .../[datasetId]/settings/page.tsx | 2 +- .../datasets/(datasetDetailLayout)/layout.tsx | 2 +- .../(commonLayout)/datasets/connect/page.tsx | 2 +- .../datasets/create-from-pipeline/page.tsx | 2 +- .../(commonLayout)/datasets/create/page.tsx | 2 +- web/app/(commonLayout)/explore/apps/page.tsx | 2 +- .../explore/installed/[appId]/page.tsx | 2 +- web/app/(commonLayout)/explore/layout.tsx | 2 +- web/app/(commonLayout)/layout.tsx | 2 +- web/app/(commonLayout)/tools/page.tsx | 3 +- web/app/(shareLayout)/chat/[token]/page.tsx | 2 +- .../(shareLayout)/chatbot/[token]/page.tsx | 2 +- .../(shareLayout)/completion/[token]/page.tsx | 2 +- .../components/authenticated-layout.tsx | 3 +- .../components/external-member-sso-auth.tsx | 3 +- .../webapp-signin/normalForm.tsx | 3 +- web/app/(shareLayout)/webapp-signin/page.tsx | 3 +- .../(shareLayout)/workflow/[token]/page.tsx | 2 +- .../account-page/AvatarWithEdit.tsx | 3 +- .../account-page/email-change-modal.tsx | 3 +- web/app/account/(commonLayout)/layout.tsx | 2 +- web/app/account/oauth/authorize/page.tsx | 3 +- web/app/activate/page.tsx | 2 +- web/app/components/app-sidebar/app-info.tsx | 3 +- .../app-sidebar/app-sidebar-dropdown.tsx | 3 +- web/app/components/app-sidebar/basic.tsx | 2 +- .../app-sidebar/dataset-info/dropdown.tsx | 3 +- .../app-sidebar/dataset-info/index.spec.tsx | 2 +- .../app-sidebar/dataset-info/index.tsx | 3 +- .../app-sidebar/dataset-info/menu-item.tsx | 2 +- .../app-sidebar/dataset-info/menu.tsx | 2 +- .../app-sidebar/dataset-sidebar-dropdown.tsx | 3 +- web/app/components/app-sidebar/index.tsx | 3 +- .../components/app-sidebar/navLink.spec.tsx | 2 +- web/app/components/app-sidebar/navLink.tsx | 2 +- .../sidebar-animation-issues.spec.tsx | 2 +- .../text-squeeze-fix-verification.spec.tsx | 2 +- .../components/app-sidebar/toggle-button.tsx | 2 +- .../edit-item/index.spec.tsx | 2 +- .../add-annotation-modal/edit-item/index.tsx | 2 +- .../add-annotation-modal/index.spec.tsx | 2 +- .../annotation/add-annotation-modal/index.tsx | 3 +- .../app/annotation/batch-action.spec.tsx | 2 +- .../app/annotation/batch-action.tsx | 2 +- .../csv-downloader.spec.tsx | 2 +- .../csv-downloader.tsx | 2 +- .../csv-uploader.spec.tsx | 2 +- .../csv-uploader.tsx | 3 +- .../batch-add-annotation-modal/index.spec.tsx | 2 +- .../batch-add-annotation-modal/index.tsx | 3 +- .../index.spec.tsx | 2 +- .../index.tsx | 2 +- .../edit-annotation-modal/edit-item/index.tsx | 3 +- .../edit-annotation-modal/index.tsx | 3 +- .../app/annotation/empty-element.spec.tsx | 2 +- .../app/annotation/empty-element.tsx | 2 +- .../components/app/annotation/filter.spec.tsx | 2 +- web/app/components/app/annotation/filter.tsx | 2 +- .../app/annotation/header-opts/index.tsx | 3 +- .../components/app/annotation/index.spec.tsx | 2 +- web/app/components/app/annotation/index.tsx | 3 +- .../components/app/annotation/list.spec.tsx | 2 +- web/app/components/app/annotation/list.tsx | 3 +- .../index.spec.tsx | 2 +- .../remove-annotation-confirm-modal/index.tsx | 2 +- .../hit-history-no-data.tsx | 2 +- .../view-annotation-modal/index.spec.tsx | 2 +- .../view-annotation-modal/index.tsx | 3 +- .../app/app-publisher/features-wrapper.tsx | 3 +- .../app/app-publisher/version-info-modal.tsx | 3 +- .../base/feature-panel/index.tsx | 2 +- .../configuration/base/group-name/index.tsx | 2 +- .../base/operation-btn/index.tsx | 2 +- .../base/var-highlight/index.tsx | 2 +- .../cannot-query-dataset.spec.tsx | 2 +- .../warning-mask/cannot-query-dataset.tsx | 2 +- .../warning-mask/formatting-changed.spec.tsx | 2 +- .../base/warning-mask/formatting-changed.tsx | 2 +- .../warning-mask/has-not-set-api.spec.tsx | 2 +- .../base/warning-mask/has-not-set-api.tsx | 2 +- .../base/warning-mask/index.spec.tsx | 2 +- .../configuration/base/warning-mask/index.tsx | 2 +- .../config-prompt/advanced-prompt-input.tsx | 2 +- .../confirm-add-var/index.spec.tsx | 2 +- .../config-prompt/confirm-add-var/index.tsx | 3 +- .../conversation-history/edit-modal.spec.tsx | 2 +- .../conversation-history/edit-modal.tsx | 3 +- .../history-panel.spec.tsx | 2 +- .../conversation-history/history-panel.tsx | 2 +- .../config-prompt/index.spec.tsx | 2 +- .../app/configuration/config-prompt/index.tsx | 2 +- .../message-type-selector.spec.tsx | 2 +- .../config-prompt/message-type-selector.tsx | 2 +- .../prompt-editor-height-resize-wrap.spec.tsx | 2 +- .../prompt-editor-height-resize-wrap.tsx | 3 +- .../config-prompt/simple-prompt-input.tsx | 3 +- .../config-var/config-modal/field.tsx | 2 +- .../config-var/config-modal/index.tsx | 3 +- .../config-var/config-modal/type-select.tsx | 3 +- .../config-var/config-select/index.tsx | 3 +- .../config-var/config-string/index.tsx | 3 +- .../app/configuration/config-var/index.tsx | 3 +- .../config-var/input-type-icon.tsx | 2 +- .../configuration/config-var/modal-foot.tsx | 2 +- .../select-type-item/index.spec.tsx | 2 +- .../config-var/select-type-item/index.tsx | 2 +- .../config-var/select-var-type.tsx | 3 +- .../app/configuration/config-var/var-item.tsx | 3 +- .../config-vision/index.spec.tsx | 2 +- .../app/configuration/config-vision/index.tsx | 3 +- .../config-vision/param-config-content.tsx | 3 +- .../config/agent-setting-button.spec.tsx | 2 +- .../config/agent-setting-button.tsx | 3 +- .../config/agent/agent-setting/index.spec.tsx | 2 +- .../config/agent/agent-setting/index.tsx | 3 +- .../agent/agent-setting/item-panel.spec.tsx | 2 +- .../config/agent/agent-setting/item-panel.tsx | 2 +- .../config/agent/agent-tools/index.spec.tsx | 3 +- .../config/agent/agent-tools/index.tsx | 3 +- .../setting-built-in-tool.spec.tsx | 2 +- .../agent-tools/setting-built-in-tool.tsx | 3 +- .../config/agent/prompt-editor.tsx | 2 +- .../assistant-type-picker/index.spec.tsx | 2 +- .../config/assistant-type-picker/index.tsx | 3 +- .../config/automatic/automatic-btn.tsx | 2 +- .../config/automatic/get-automatic-res.tsx | 3 +- .../config/automatic/idea-output.tsx | 2 +- .../instruction-editor-in-workflow.tsx | 3 +- .../config/automatic/instruction-editor.tsx | 2 +- .../automatic/prompt-res-in-workflow.tsx | 2 +- .../config/automatic/prompt-res.tsx | 3 +- .../config/automatic/prompt-toast.tsx | 2 +- .../config/automatic/res-placeholder.tsx | 2 +- .../configuration/config/automatic/result.tsx | 2 +- .../config/automatic/version-selector.tsx | 3 +- .../code-generator/get-code-generator-res.tsx | 3 +- .../config/config-audio.spec.tsx | 2 +- .../app/configuration/config/config-audio.tsx | 3 +- .../config/config-document.spec.tsx | 2 +- .../configuration/config/config-document.tsx | 3 +- .../app/configuration/config/index.spec.tsx | 2 +- .../app/configuration/config/index.tsx | 2 +- .../configuration/ctrl-btn-group/index.tsx | 2 +- .../dataset-config/card-item/index.spec.tsx | 2 +- .../dataset-config/card-item/index.tsx | 3 +- .../dataset-config/context-var/index.tsx | 2 +- .../dataset-config/context-var/var-picker.tsx | 3 +- .../configuration/dataset-config/index.tsx | 3 +- .../dataset-config/select-dataset/index.tsx | 3 +- .../configuration/debug/chat-user-input.tsx | 3 +- .../app/configuration/debug/index.tsx | 3 +- .../components/app/configuration/index.tsx | 3 +- .../prompt-value-panel/index.tsx | 3 +- .../app/create-app-dialog/app-list/index.tsx | 3 +- .../app/create-from-dsl-modal/uploader.tsx | 3 +- .../app/duplicate-modal/index.spec.tsx | 2 +- .../components/app/duplicate-modal/index.tsx | 3 +- .../components/app/log-annotation/index.tsx | 3 +- web/app/components/app/log/empty-element.tsx | 2 +- web/app/components/app/log/filter.tsx | 2 +- web/app/components/app/log/index.tsx | 3 +- web/app/components/app/log/list.tsx | 3 +- web/app/components/app/log/model-info.tsx | 2 +- web/app/components/app/log/var-panel.tsx | 3 +- .../app/overview/apikey-info-panel/index.tsx | 3 +- web/app/components/app/overview/app-card.tsx | 3 +- web/app/components/app/overview/app-chart.tsx | 2 +- .../app/overview/customize/index.tsx | 2 +- .../app/overview/embedded/index.tsx | 3 +- .../app/overview/settings/index.tsx | 3 +- .../components/app/overview/trigger-card.tsx | 2 +- .../app/switch-app-modal/index.spec.tsx | 2 +- .../app/text-generate/item/index.tsx | 3 +- .../app/text-generate/saved-items/index.tsx | 2 +- .../saved-items/no-data/index.tsx | 2 +- .../app/type-selector/index.spec.tsx | 2 +- .../components/app/type-selector/index.tsx | 3 +- .../components/app/workflow-log/filter.tsx | 2 +- web/app/components/app/workflow-log/index.tsx | 3 +- web/app/components/app/workflow-log/list.tsx | 3 +- .../app/workflow-log/trigger-by-display.tsx | 2 +- web/app/components/apps/app-card.spec.tsx | 141 +++++++++--------- web/app/components/apps/app-card.tsx | 3 +- web/app/components/apps/empty.spec.tsx | 2 +- web/app/components/apps/empty.tsx | 2 +- web/app/components/apps/footer.spec.tsx | 2 +- web/app/components/apps/footer.tsx | 2 +- web/app/components/apps/index.spec.tsx | 3 +- web/app/components/apps/list.spec.tsx | 55 +++---- web/app/components/apps/new-app-card.spec.tsx | 53 ++++--- web/app/components/apps/new-app-card.tsx | 3 +- .../components/base/action-button/index.tsx | 2 +- .../base/agent-log-modal/detail.tsx | 3 +- .../base/amplitude/AmplitudeProvider.tsx | 3 +- web/app/components/base/app-icon/index.tsx | 3 +- web/app/components/base/app-unavailable.tsx | 2 +- .../base/audio-gallery/AudioPlayer.tsx | 3 +- .../components/base/audio-gallery/index.tsx | 2 +- web/app/components/base/badge/index.tsx | 2 +- web/app/components/base/block-input/index.tsx | 3 +- web/app/components/base/button/add-button.tsx | 2 +- web/app/components/base/button/index.spec.tsx | 2 +- web/app/components/base/button/index.tsx | 2 +- .../components/base/button/sync-button.tsx | 2 +- .../chat-with-history/header/operation.tsx | 3 +- .../chat-with-history/inputs-form/content.tsx | 3 +- .../chat-with-history/inputs-form/index.tsx | 2 +- .../chat-with-history/sidebar/operation.tsx | 3 +- .../sidebar/rename-modal.tsx | 3 +- .../base/chat/chat/citation/tooltip.tsx | 3 +- .../base/chat/chat/loading-anim/index.tsx | 2 +- .../base/chat/chat/thought/index.tsx | 2 +- .../chat/embedded-chatbot/header/index.tsx | 3 +- .../embedded-chatbot/inputs-form/content.tsx | 3 +- .../embedded-chatbot/inputs-form/index.tsx | 2 +- web/app/components/base/confirm/index.tsx | 3 +- .../components/base/copy-feedback/index.tsx | 3 +- web/app/components/base/copy-icon/index.tsx | 3 +- .../calendar/days-of-week.tsx | 2 +- .../date-and-time-picker/calendar/item.tsx | 2 +- .../common/option-list-item.tsx | 3 +- .../date-picker/footer.tsx | 2 +- .../date-picker/header.tsx | 2 +- .../date-picker/index.tsx | 3 +- .../time-picker/footer.tsx | 2 +- .../time-picker/header.tsx | 2 +- .../time-picker/index.spec.tsx | 2 +- .../time-picker/index.tsx | 3 +- .../time-picker/options.tsx | 2 +- .../year-and-month-picker/footer.tsx | 2 +- .../year-and-month-picker/header.tsx | 2 +- .../year-and-month-picker/options.tsx | 2 +- web/app/components/base/divider/index.tsx | 2 +- web/app/components/base/drawer-plus/index.tsx | 3 +- web/app/components/base/drawer/index.spec.tsx | 2 +- web/app/components/base/effect/index.tsx | 2 +- .../components/base/emoji-picker/Inner.tsx | 3 +- .../components/base/emoji-picker/index.tsx | 3 +- .../components/base/error-boundary/index.tsx | 3 +- .../annotation-ctrl-button.tsx | 2 +- .../annotation-reply/config-param-modal.tsx | 3 +- .../annotation-reply/config-param.tsx | 2 +- .../annotation-reply/index.tsx | 3 +- .../annotation-reply/score-slider/index.tsx | 2 +- .../annotation-reply/use-annotation-config.ts | 3 +- .../features/new-feature-panel/citation.tsx | 3 +- .../conversation-opener/index.tsx | 3 +- .../conversation-opener/modal.tsx | 3 +- .../new-feature-panel/feature-bar.tsx | 3 +- .../new-feature-panel/feature-card.tsx | 2 +- .../new-feature-panel/file-upload/index.tsx | 3 +- .../file-upload/setting-content.tsx | 3 +- .../features/new-feature-panel/follow-up.tsx | 3 +- .../new-feature-panel/image-upload/index.tsx | 3 +- .../base/features/new-feature-panel/index.tsx | 2 +- .../new-feature-panel/moderation/index.tsx | 3 +- .../new-feature-panel/more-like-this.tsx | 3 +- .../new-feature-panel/speech-to-text.tsx | 3 +- .../text-to-speech/index.tsx | 3 +- .../text-to-speech/param-config-content.tsx | 3 +- .../base/file-thumb/image-render.tsx | 2 +- web/app/components/base/file-thumb/index.tsx | 3 +- .../base/file-uploader/audio-preview.tsx | 2 +- .../base/file-uploader/file-list-in-log.tsx | 3 +- .../base/file-uploader/pdf-preview.tsx | 3 +- .../base/file-uploader/video-preview.tsx | 2 +- .../form/components/field/file-uploader.tsx | 2 +- .../field/input-type-select/option.tsx | 2 +- .../field/input-type-select/trigger.tsx | 2 +- .../form/components/field/number-input.tsx | 2 +- .../base/form/components/field/text-area.tsx | 2 +- .../base/form/components/field/text.tsx | 2 +- .../base/form/form-scenarios/base/field.tsx | 2 +- .../base/form/form-scenarios/base/index.tsx | 3 +- .../form/form-scenarios/input-field/field.tsx | 2 +- .../form/form-scenarios/node-panel/field.tsx | 2 +- web/app/components/base/ga/index.tsx | 2 +- .../components/base/icons/IconBase.spec.tsx | 2 +- .../base/icons/icon-gallery.stories.tsx | 2 +- web/app/components/base/icons/utils.ts | 2 +- .../components/base/image-gallery/index.tsx | 3 +- .../base/image-uploader/image-preview.tsx | 3 +- .../base/inline-delete-confirm/index.spec.tsx | 2 +- .../base/input-with-copy/index.spec.tsx | 2 +- .../components/base/input-with-copy/index.tsx | 3 +- web/app/components/base/input/index.spec.tsx | 2 +- web/app/components/base/input/index.tsx | 2 +- .../base/linked-apps-panel/index.tsx | 2 +- web/app/components/base/list-empty/index.tsx | 2 +- .../components/base/loading/index.spec.tsx | 2 +- web/app/components/base/loading/index.tsx | 2 +- .../base/markdown-blocks/audio-block.tsx | 3 +- .../components/base/markdown-blocks/form.tsx | 3 +- .../components/base/markdown-blocks/img.tsx | 2 +- .../components/base/markdown-blocks/link.tsx | 2 +- .../base/markdown-blocks/paragraph.tsx | 2 +- .../base/markdown-blocks/plugin-img.tsx | 3 +- .../base/markdown-blocks/plugin-paragraph.tsx | 3 +- .../base/markdown-blocks/pre-code.tsx | 3 +- .../base/markdown-blocks/think-block.tsx | 3 +- .../base/markdown-blocks/video-block.tsx | 3 +- .../base/markdown/error-boundary.tsx | 3 +- web/app/components/base/mermaid/index.tsx | 3 +- .../components/base/modal-like-wrap/index.tsx | 2 +- web/app/components/base/node-status/index.tsx | 2 +- .../base/notion-connector/index.tsx | 2 +- .../credential-selector/index.tsx | 3 +- web/app/components/base/pagination/hook.ts | 3 +- web/app/components/base/pagination/index.tsx | 2 +- .../components/base/pagination/pagination.tsx | 2 +- .../base/param-item/score-threshold-item.tsx | 2 +- .../components/base/param-item/top-k-item.tsx | 2 +- .../base/portal-to-follow-elem/index.spec.tsx | 2 +- .../base/portal-to-follow-elem/index.tsx | 3 +- .../components/base/premium-badge/index.tsx | 2 +- .../components/base/prompt-editor/index.tsx | 3 +- web/app/components/base/qrcode/index.tsx | 3 +- web/app/components/base/radio-card/index.tsx | 2 +- .../base/radio-card/simple/index.tsx | 2 +- web/app/components/base/radio/index.tsx | 2 +- web/app/components/base/radio/ui.tsx | 2 +- .../base/segmented-control/index.tsx | 2 +- web/app/components/base/select/index.tsx | 3 +- .../components/base/spinner/index.spec.tsx | 2 +- web/app/components/base/spinner/index.tsx | 2 +- web/app/components/base/svg/index.tsx | 2 +- web/app/components/base/switch/index.tsx | 3 +- web/app/components/base/tab-header/index.tsx | 2 +- .../base/tab-slider-plain/index.tsx | 2 +- .../components/base/tag-management/panel.tsx | 3 +- .../base/tag-management/trigger.tsx | 2 +- web/app/components/base/tag/index.tsx | 2 +- web/app/components/base/textarea/index.tsx | 2 +- .../timezone-label/__tests__/index.test.tsx | 2 +- .../components/base/timezone-label/index.tsx | 3 +- web/app/components/base/toast/index.spec.tsx | 2 +- web/app/components/base/toast/index.tsx | 3 +- .../components/base/tooltip/index.spec.tsx | 2 +- web/app/components/base/tooltip/index.tsx | 3 +- .../base/video-gallery/VideoPlayer.tsx | 3 +- .../components/base/video-gallery/index.tsx | 2 +- .../base/with-input-validation/index.tsx | 2 +- .../billing/annotation-full/index.tsx | 2 +- .../billing/annotation-full/modal.tsx | 2 +- .../billing/annotation-full/usage.tsx | 2 +- .../billing/apps-full-in-dialog/index.tsx | 2 +- .../components/billing/billing-page/index.tsx | 2 +- .../billing/header-billing-btn/index.tsx | 2 +- .../billing/partner-stack/index.tsx | 3 +- .../billing/plan-upgrade-modal/index.spec.tsx | 2 +- .../billing/plan-upgrade-modal/index.tsx | 3 +- .../billing/plan/assets/sandbox.spec.tsx | 2 +- .../billing/plan/assets/sandbox.tsx | 2 +- web/app/components/billing/plan/index.tsx | 3 +- web/app/components/billing/pricing/footer.tsx | 2 +- web/app/components/billing/pricing/header.tsx | 2 +- web/app/components/billing/pricing/index.tsx | 3 +- .../billing/pricing/plan-switcher/index.tsx | 2 +- .../plan-switcher/plan-range-switcher.tsx | 2 +- .../billing/pricing/plan-switcher/tab.tsx | 3 +- .../plans/cloud-plan-item/button.spec.tsx | 2 +- .../pricing/plans/cloud-plan-item/button.tsx | 2 +- .../plans/cloud-plan-item/index.spec.tsx | 2 +- .../pricing/plans/cloud-plan-item/index.tsx | 3 +- .../plans/cloud-plan-item/list/index.spec.tsx | 2 +- .../plans/cloud-plan-item/list/index.tsx | 2 +- .../plans/cloud-plan-item/list/item/index.tsx | 2 +- .../cloud-plan-item/list/item/tooltip.tsx | 2 +- .../billing/pricing/plans/index.spec.tsx | 2 +- .../self-hosted-plan-item/button.spec.tsx | 2 +- .../plans/self-hosted-plan-item/button.tsx | 3 +- .../self-hosted-plan-item/index.spec.tsx | 2 +- .../plans/self-hosted-plan-item/index.tsx | 3 +- .../self-hosted-plan-item/list/index.spec.tsx | 2 +- .../self-hosted-plan-item/list/index.tsx | 2 +- .../self-hosted-plan-item/list/item.spec.tsx | 2 +- .../plans/self-hosted-plan-item/list/item.tsx | 2 +- .../trigger-events-limit-modal/index.tsx | 2 +- .../components/billing/upgrade-btn/index.tsx | 2 +- .../billing/usage-info/apps-info.tsx | 2 +- .../components/billing/usage-info/index.tsx | 2 +- .../billing/usage-info/vector-space-info.tsx | 2 +- .../billing/vector-space-full/index.tsx | 2 +- .../custom/custom-page/index.spec.tsx | 2 +- web/app/components/datasets/api/index.tsx | 2 +- .../datasets/common/chunking-mode-label.tsx | 2 +- .../datasets/common/credential-icon.tsx | 3 +- .../datasets/common/document-file-icon.tsx | 2 +- .../common/document-picker/document-list.tsx | 3 +- .../common/document-picker/index.spec.tsx | 2 +- .../datasets/common/document-picker/index.tsx | 3 +- .../preview-document-picker.spec.tsx | 2 +- .../preview-document-picker.tsx | 3 +- .../auto-disabled-document.tsx | 3 +- .../index-failed.tsx | 3 +- .../status-with-action.tsx | 2 +- .../index.tsx | 2 +- .../datasets/common/image-list/more.tsx | 3 +- .../image-uploader-in-chunk/image-input.tsx | 2 +- .../image-input.tsx | 2 +- .../retrieval-method-config/index.spec.tsx | 2 +- .../common/retrieval-method-config/index.tsx | 3 +- .../common/retrieval-method-info/index.tsx | 2 +- .../common/retrieval-param-config/index.tsx | 3 +- .../create-from-dsl-modal/header.tsx | 2 +- .../create-from-dsl-modal/tab/index.tsx | 2 +- .../create-from-dsl-modal/tab/item.tsx | 2 +- .../create-from-dsl-modal/uploader.tsx | 3 +- .../datasets/create-from-pipeline/footer.tsx | 3 +- .../datasets/create-from-pipeline/header.tsx | 2 +- .../create-from-pipeline/list/create-card.tsx | 3 +- .../list/template-card/actions.tsx | 2 +- .../list/template-card/content.tsx | 2 +- .../details/chunk-structure-card.tsx | 2 +- .../list/template-card/details/index.tsx | 3 +- .../list/template-card/edit-pipeline-info.tsx | 3 +- .../list/template-card/index.tsx | 3 +- .../list/template-card/operations.tsx | 2 +- .../create/embedding-process/index.tsx | 3 +- .../index.spec.tsx | 2 +- .../empty-dataset-creation-modal/index.tsx | 3 +- .../datasets/create/file-preview/index.tsx | 3 +- .../datasets/create/file-uploader/index.tsx | 3 +- .../components/datasets/create/index.spec.tsx | 2 +- web/app/components/datasets/create/index.tsx | 3 +- .../create/notion-page-preview/index.tsx | 3 +- .../datasets/create/step-one/index.tsx | 3 +- .../datasets/create/step-one/upgrade-card.tsx | 3 +- .../datasets/create/step-three/index.tsx | 2 +- .../datasets/create/step-two/index.tsx | 3 +- .../step-two/language-select/index.spec.tsx | 2 +- .../create/step-two/language-select/index.tsx | 2 +- .../step-two/preview-item/index.spec.tsx | 2 +- .../create/step-two/preview-item/index.tsx | 2 +- .../create/stop-embedding-modal/index.tsx | 2 +- .../website/base/checkbox-with-label.tsx | 2 +- .../website/base/crawled-result-item.tsx | 3 +- .../create/website/base/crawled-result.tsx | 3 +- .../datasets/create/website/base/crawling.tsx | 2 +- .../create/website/base/error-message.tsx | 2 +- .../datasets/create/website/base/field.tsx | 2 +- .../datasets/create/website/base/header.tsx | 2 +- .../datasets/create/website/base/input.tsx | 3 +- .../create/website/base/options-wrap.tsx | 3 +- .../create/website/base/url-input.tsx | 3 +- .../create/website/firecrawl/index.tsx | 3 +- .../create/website/firecrawl/options.tsx | 3 +- .../datasets/create/website/index.tsx | 3 +- .../website/jina-reader/base/url-input.tsx | 3 +- .../create/website/jina-reader/index.tsx | 3 +- .../create/website/jina-reader/options.tsx | 3 +- .../datasets/create/website/no-data.tsx | 2 +- .../datasets/create/website/preview.tsx | 2 +- .../create/website/watercrawl/index.tsx | 3 +- .../create/website/watercrawl/options.tsx | 3 +- .../actions/index.spec.tsx | 2 +- .../create-from-pipeline/actions/index.tsx | 3 +- .../data-source-options/index.spec.tsx | 2 +- .../data-source-options/option-card.tsx | 2 +- .../base/credential-selector/index.spec.tsx | 2 +- .../base/credential-selector/index.tsx | 3 +- .../base/credential-selector/item.tsx | 3 +- .../base/credential-selector/list.tsx | 2 +- .../base/credential-selector/trigger.tsx | 2 +- .../data-source/base/header.spec.tsx | 2 +- .../data-source/base/header.tsx | 2 +- .../data-source/local-file/index.tsx | 3 +- .../online-documents/index.spec.tsx | 2 +- .../page-selector/index.spec.tsx | 2 +- .../online-documents/page-selector/item.tsx | 2 +- .../data-source/online-documents/title.tsx | 2 +- .../file-list/header/breadcrumbs/bucket.tsx | 3 +- .../file-list/header/breadcrumbs/drive.tsx | 2 +- .../breadcrumbs/dropdown/index.spec.tsx | 2 +- .../header/breadcrumbs/dropdown/index.tsx | 3 +- .../header/breadcrumbs/dropdown/item.tsx | 3 +- .../header/breadcrumbs/dropdown/menu.tsx | 2 +- .../header/breadcrumbs/index.spec.tsx | 2 +- .../file-list/header/breadcrumbs/index.tsx | 3 +- .../file-list/header/breadcrumbs/item.tsx | 3 +- .../file-list/header/index.spec.tsx | 2 +- .../online-drive/file-list/header/index.tsx | 2 +- .../online-drive/file-list/index.spec.tsx | 2 +- .../file-list/list/empty-folder.tsx | 2 +- .../file-list/list/empty-search-result.tsx | 2 +- .../online-drive/file-list/list/file-icon.tsx | 3 +- .../file-list/list/index.spec.tsx | 2 +- .../online-drive/file-list/list/index.tsx | 3 +- .../online-drive/file-list/list/item.tsx | 3 +- .../data-source/online-drive/header.tsx | 2 +- .../data-source/online-drive/index.spec.tsx | 2 +- .../base/checkbox-with-label.tsx | 2 +- .../base/crawled-result-item.tsx | 3 +- .../website-crawl/base/crawled-result.tsx | 3 +- .../website-crawl/base/crawling.tsx | 2 +- .../website-crawl/base/error-message.tsx | 2 +- .../website-crawl/base/index.spec.tsx | 2 +- .../website-crawl/base/options/index.spec.tsx | 2 +- .../data-source/website-crawl/index.spec.tsx | 2 +- .../data-source/website-crawl/index.tsx | 3 +- .../create-from-pipeline/left-header.tsx | 2 +- .../preview/chunk-preview.spec.tsx | 2 +- .../preview/chunk-preview.tsx | 3 +- .../preview/file-preview.spec.tsx | 2 +- .../preview/file-preview.tsx | 3 +- .../create-from-pipeline/preview/loading.tsx | 2 +- .../preview/online-document-preview.spec.tsx | 2 +- .../preview/online-document-preview.tsx | 3 +- .../preview/web-preview.spec.tsx | 2 +- .../preview/web-preview.tsx | 2 +- .../process-documents/actions.tsx | 2 +- .../process-documents/components.spec.tsx | 2 +- .../process-documents/header.tsx | 2 +- .../process-documents/index.spec.tsx | 2 +- .../process-documents/index.tsx | 2 +- .../embedding-process/index.spec.tsx | 2 +- .../processing/embedding-process/index.tsx | 3 +- .../embedding-process/rule-detail.spec.tsx | 2 +- .../embedding-process/rule-detail.tsx | 3 +- .../processing/index.spec.tsx | 2 +- .../create-from-pipeline/processing/index.tsx | 2 +- .../create-from-pipeline/step-indicator.tsx | 2 +- .../detail/batch-modal/csv-downloader.tsx | 2 +- .../detail/batch-modal/csv-uploader.tsx | 3 +- .../documents/detail/batch-modal/index.tsx | 3 +- .../detail/completed/child-segment-detail.tsx | 3 +- .../completed/common/action-buttons.tsx | 3 +- .../detail/completed/common/add-another.tsx | 2 +- .../detail/completed/common/batch-action.tsx | 2 +- .../detail/completed/common/chunk-content.tsx | 3 +- .../documents/detail/completed/common/dot.tsx | 2 +- .../detail/completed/common/drawer.tsx | 3 +- .../detail/completed/common/empty.tsx | 2 +- .../completed/common/full-screen-drawer.tsx | 2 +- .../detail/completed/common/keywords.tsx | 2 +- .../completed/common/regeneration-modal.tsx | 3 +- .../completed/common/segment-index-tag.tsx | 3 +- .../documents/detail/completed/common/tag.tsx | 2 +- .../detail/completed/display-toggle.tsx | 2 +- .../documents/detail/completed/index.tsx | 3 +- .../completed/segment-card/chunk-content.tsx | 2 +- .../completed/segment-card/index.spec.tsx | 2 +- .../detail/completed/segment-card/index.tsx | 3 +- .../detail/completed/segment-detail.tsx | 3 +- .../detail/completed/segment-list.tsx | 3 +- .../skeleton/full-doc-list-skeleton.tsx | 2 +- .../skeleton/general-list-skeleton.tsx | 2 +- .../skeleton/paragraph-list-skeleton.tsx | 2 +- .../skeleton/parent-chunk-card-skeleton.tsx | 2 +- .../detail/completed/status-item.tsx | 2 +- .../documents/detail/embedding/index.tsx | 3 +- .../detail/embedding/skeleton/index.tsx | 2 +- .../datasets/documents/detail/index.tsx | 3 +- .../documents/detail/metadata/index.tsx | 3 +- .../documents/detail/segment-add/index.tsx | 3 +- .../detail/settings/document-settings.tsx | 3 +- .../documents/detail/settings/index.tsx | 2 +- .../pipeline-settings/left-header.tsx | 3 +- .../process-documents/actions.tsx | 2 +- .../components/datasets/documents/index.tsx | 3 +- .../components/datasets/documents/list.tsx | 3 +- .../datasets/documents/operations.tsx | 3 +- .../datasets/documents/rename-modal.tsx | 3 +- .../datasets/documents/status-item/index.tsx | 3 +- .../external-api/external-api-modal/Form.tsx | 2 +- .../external-api/external-api-panel/index.tsx | 2 +- .../external-knowledge-api-card/index.tsx | 3 +- .../connector/index.tsx | 3 +- .../create/ExternalApiSelect.tsx | 3 +- .../create/ExternalApiSelection.tsx | 3 +- .../create/KnowledgeBaseInfo.tsx | 2 +- .../create/RetrievalSettings.tsx | 2 +- .../create/index.spec.tsx | 2 +- .../components/datasets/extra-info/index.tsx | 2 +- .../datasets/extra-info/service-api/card.tsx | 3 +- .../datasets/extra-info/service-api/index.tsx | 3 +- .../datasets/extra-info/statistics.tsx | 2 +- .../components/child-chunks-item.tsx | 2 +- .../components/chunk-detail-modal.tsx | 3 +- .../hit-testing/components/empty-records.tsx | 2 +- .../datasets/hit-testing/components/mask.tsx | 2 +- .../components/query-input/index.tsx | 3 +- .../components/query-input/textarea.tsx | 2 +- .../hit-testing/components/records.tsx | 3 +- .../components/result-item-external.tsx | 2 +- .../components/result-item-footer.tsx | 2 +- .../components/result-item-meta.tsx | 2 +- .../hit-testing/components/result-item.tsx | 3 +- .../datasets/hit-testing/components/score.tsx | 2 +- .../components/datasets/hit-testing/index.tsx | 3 +- .../hit-testing/modify-retrieval-modal.tsx | 3 +- .../datasets/list/dataset-card/index.tsx | 3 +- .../list/dataset-card/operation-item.tsx | 2 +- .../datasets/list/dataset-card/operations.tsx | 2 +- .../datasets/list/dataset-footer/index.tsx | 2 +- .../datasets/list/new-dataset-card/index.tsx | 2 +- .../datasets/list/new-dataset-card/option.tsx | 2 +- .../datasets/metadata/add-metadata-button.tsx | 2 +- .../metadata/edit-metadata-batch/add-row.tsx | 2 +- .../metadata/edit-metadata-batch/edit-row.tsx | 2 +- .../edit-metadata-batch/edited-beacon.tsx | 3 +- .../edit-metadata-batch/input-combined.tsx | 2 +- .../input-has-set-multiple-value.tsx | 2 +- .../metadata/edit-metadata-batch/label.tsx | 2 +- .../metadata/edit-metadata-batch/modal.tsx | 3 +- .../metadata-dataset/create-content.tsx | 3 +- .../create-metadata-modal.tsx | 2 +- .../dataset-metadata-drawer.tsx | 3 +- .../metadata/metadata-dataset/field.tsx | 2 +- .../select-metadata-modal.tsx | 3 +- .../metadata-dataset/select-metadata.tsx | 3 +- .../metadata/metadata-document/field.tsx | 2 +- .../metadata/metadata-document/index.tsx | 2 +- .../metadata/metadata-document/info-group.tsx | 2 +- .../metadata/metadata-document/no-data.tsx | 2 +- .../datasets/no-linked-apps-panel.tsx | 2 +- .../settings/chunk-structure/index.tsx | 2 +- .../settings/index-method/keyword-number.tsx | 3 +- .../datasets/settings/option-card.tsx | 2 +- .../settings/permission-selector/index.tsx | 3 +- .../permission-selector/member-item.tsx | 2 +- .../permission-selector/permission-item.tsx | 2 +- .../develop/secret-key/input-copy.tsx | 3 +- .../explore/app-card/index.spec.tsx | 2 +- web/app/components/explore/app-list/index.tsx | 3 +- web/app/components/explore/category.tsx | 2 +- .../explore/create-app-modal/index.spec.tsx | 2 +- .../explore/create-app-modal/index.tsx | 3 +- web/app/components/explore/index.tsx | 3 +- .../explore/installed-app/index.tsx | 3 +- .../explore/item-operation/index.tsx | 3 +- .../explore/sidebar/app-nav-item/index.tsx | 3 +- web/app/components/explore/sidebar/index.tsx | 3 +- .../actions/commands/account.tsx | 2 +- .../actions/commands/community.tsx | 2 +- .../goto-anything/actions/commands/docs.tsx | 2 +- .../goto-anything/actions/commands/forum.tsx | 2 +- .../goto-anything/actions/commands/theme.tsx | 2 +- .../goto-anything/actions/commands/zen.tsx | 2 +- .../goto-anything/command-selector.spec.tsx | 2 +- .../components/goto-anything/context.spec.tsx | 2 +- web/app/components/goto-anything/context.tsx | 3 +- .../components/goto-anything/index.spec.tsx | 2 +- .../data-source-notion/index.tsx | 3 +- .../config-firecrawl-modal.tsx | 3 +- .../config-jina-reader-modal.tsx | 3 +- .../config-watercrawl-modal.tsx | 3 +- .../data-source-website/index.tsx | 3 +- .../data-source-page/panel/config-item.tsx | 2 +- .../data-source-page/panel/index.tsx | 2 +- .../invite-modal/role-selector.tsx | 3 +- .../invited-modal/invitation-link.tsx | 3 +- .../transfer-ownership-modal/index.tsx | 3 +- .../member-selector.tsx | 3 +- web/app/components/header/app-back/index.tsx | 3 +- .../header/github-star/index.spec.tsx | 2 +- web/app/components/header/header-wrapper.tsx | 3 +- web/app/components/header/nav/index.tsx | 3 +- web/app/components/i18n-server.tsx | 2 +- web/app/components/i18n.tsx | 3 +- .../plugins/base/badges/icon-with-tooltip.tsx | 2 +- .../plugins/base/deprecation-notice.tsx | 3 +- .../plugins/base/key-value-item.tsx | 3 +- .../plugins/card/base/description.tsx | 3 +- .../plugins/card/base/download-count.tsx | 2 +- .../plugins/card/card-more-info.tsx | 2 +- web/app/components/plugins/card/index.tsx | 2 +- .../plugins/install-plugin/base/installed.tsx | 2 +- .../install-plugin/base/loading-error.tsx | 2 +- .../plugins/install-plugin/base/loading.tsx | 2 +- .../plugins/install-plugin/base/version.tsx | 2 +- .../install-plugin/install-bundle/index.tsx | 3 +- .../install-bundle/item/github-item.tsx | 3 +- .../install-bundle/item/loaded-item.tsx | 2 +- .../install-bundle/item/marketplace-item.tsx | 2 +- .../install-bundle/item/package-item.tsx | 2 +- .../install-bundle/ready-to-install.tsx | 3 +- .../install-bundle/steps/install-multi.tsx | 3 +- .../install-bundle/steps/install.tsx | 3 +- .../install-bundle/steps/installed.tsx | 2 +- .../install-from-github/index.tsx | 3 +- .../install-from-github/steps/loaded.tsx | 3 +- .../steps/selectPackage.tsx | 2 +- .../install-from-github/steps/setURL.tsx | 2 +- .../install-from-local-package/index.tsx | 3 +- .../ready-to-install.tsx | 3 +- .../steps/install.tsx | 3 +- .../steps/uploading.tsx | 2 +- .../install-from-marketplace/index.tsx | 3 +- .../steps/install.tsx | 3 +- .../plugins/marketplace/list/card-wrapper.tsx | 3 +- .../search-box/trigger/marketplace.tsx | 2 +- .../search-box/trigger/tool-selector.tsx | 2 +- .../plugin-detail-panel/action-list.tsx | 3 +- .../agent-strategy-list.tsx | 3 +- .../app-selector/app-inputs-panel.tsx | 3 +- .../app-selector/app-picker.tsx | 3 +- .../app-selector/app-trigger.tsx | 2 +- .../app-selector/index.tsx | 3 +- .../datasource-action-list.tsx | 3 +- .../plugin-detail-panel/detail-header.tsx | 3 +- .../plugin-detail-panel/endpoint-card.tsx | 3 +- .../plugin-detail-panel/endpoint-list.tsx | 3 +- .../plugin-detail-panel/endpoint-modal.tsx | 2 +- .../plugin-detail-panel/model-list.tsx | 2 +- .../model-selector/llm-params-panel.tsx | 3 +- .../model-selector/tts-params-panel.tsx | 3 +- .../multiple-tool-selector/index.tsx | 2 +- .../operation-dropdown.tsx | 3 +- .../plugin-detail-panel/strategy-detail.tsx | 3 +- .../plugin-detail-panel/strategy-item.tsx | 3 +- .../subscription-list/create/common-modal.tsx | 3 +- .../subscription-list/create/oauth-client.tsx | 3 +- .../subscription-list/list-view.tsx | 2 +- .../subscription-list/log-viewer.tsx | 3 +- .../subscription-list/selector-view.tsx | 3 +- .../tool-selector/index.tsx | 3 +- .../tool-selector/schema-modal.tsx | 2 +- .../tool-selector/tool-credentials-form.tsx | 3 +- .../tool-selector/tool-item.tsx | 3 +- .../tool-selector/tool-trigger.tsx | 2 +- .../components/plugins/plugin-item/action.tsx | 3 +- .../components/plugins/plugin-item/index.tsx | 3 +- .../plugins/plugin-mutation-model/index.tsx | 3 +- .../plugins/plugin-page/debug-info.tsx | 2 +- .../plugins/plugin-page/empty/index.tsx | 3 +- .../plugin-page/filter-management/index.tsx | 3 +- .../plugins/plugin-page/plugin-info.tsx | 2 +- web/app/components/plugins/provider-card.tsx | 3 +- .../plugins/readme-panel/entrance.tsx | 2 +- .../auto-update-setting/index.tsx | 3 +- .../no-data-placeholder.tsx | 2 +- .../no-plugin-selected.tsx | 2 +- .../auto-update-setting/plugins-picker.tsx | 2 +- .../auto-update-setting/plugins-selected.tsx | 2 +- .../auto-update-setting/tool-item.tsx | 2 +- .../auto-update-setting/tool-picker.tsx | 3 +- .../plugins/reference-setting-modal/label.tsx | 2 +- .../plugins/reference-setting-modal/modal.tsx | 3 +- .../plugins/update-plugin/from-github.tsx | 2 +- .../update-plugin/from-market-place.tsx | 3 +- .../plugins/update-plugin/index.tsx | 2 +- .../update-plugin/plugin-version-picker.tsx | 3 +- .../components/chunk-card-list/chunk-card.tsx | 3 +- .../components/chunk-card-list/q-a-item.tsx | 2 +- .../rag-pipeline/components/conversion.tsx | 3 +- .../input-field/editor/form/hidden-fields.tsx | 2 +- .../editor/form/initial-fields.tsx | 3 +- .../editor/form/show-all-settings.tsx | 2 +- .../input-field/field-list/field-item.tsx | 3 +- .../panel/input-field/field-list/index.tsx | 3 +- .../panel/input-field/footer-tip.tsx | 2 +- .../label-right-content/datasource.tsx | 2 +- .../label-right-content/global-inputs.tsx | 2 +- .../panel/input-field/preview/data-source.tsx | 2 +- .../input-field/preview/process-documents.tsx | 2 +- .../components/panel/test-run/header.tsx | 3 +- .../test-run/preparation/actions/index.tsx | 2 +- .../data-source-options/option-card.tsx | 3 +- .../document-processing/actions.tsx | 2 +- .../preparation/document-processing/index.tsx | 3 +- .../test-run/preparation/footer-tips.tsx | 2 +- .../panel/test-run/preparation/index.tsx | 3 +- .../test-run/preparation/step-indicator.tsx | 2 +- .../test-run/result/result-preview/index.tsx | 3 +- .../panel/test-run/result/tabs/index.tsx | 2 +- .../panel/test-run/result/tabs/tab.tsx | 3 +- .../rag-pipeline-header/run-mode.tsx | 3 +- .../rag-pipeline/components/screenshot.tsx | 2 +- .../share/text-generation/index.tsx | 3 +- .../share/text-generation/info-modal.tsx | 2 +- .../share/text-generation/menu-dropdown.tsx | 3 +- .../text-generation/no-data/index.spec.tsx | 2 +- .../share/text-generation/no-data/index.tsx | 2 +- .../share/text-generation/result/content.tsx | 2 +- .../share/text-generation/result/header.tsx | 2 +- .../share/text-generation/result/index.tsx | 3 +- .../run-batch/csv-download/index.spec.tsx | 2 +- .../run-batch/csv-download/index.tsx | 2 +- .../run-batch/csv-reader/index.spec.tsx | 2 +- .../run-batch/csv-reader/index.tsx | 3 +- .../text-generation/run-batch/index.spec.tsx | 2 +- .../share/text-generation/run-batch/index.tsx | 2 +- .../run-batch/res-download/index.spec.tsx | 2 +- .../run-batch/res-download/index.tsx | 2 +- .../text-generation/run-once/index.spec.tsx | 3 +- .../share/text-generation/run-once/index.tsx | 3 +- web/app/components/splash.tsx | 2 +- .../config-credentials.tsx | 2 +- .../get-schema.tsx | 3 +- .../edit-custom-collection-modal/index.tsx | 3 +- .../edit-custom-collection-modal/test-api.tsx | 3 +- .../tools/marketplace/index.spec.tsx | 2 +- .../components/tools/mcp/detail/content.tsx | 3 +- .../tools/mcp/detail/list-loading.tsx | 2 +- .../tools/mcp/detail/operation-dropdown.tsx | 3 +- .../tools/mcp/detail/provider-detail.tsx | 2 +- .../components/tools/mcp/detail/tool-item.tsx | 2 +- .../components/tools/mcp/headers-input.tsx | 2 +- .../components/tools/mcp/mcp-server-modal.tsx | 2 +- .../tools/mcp/mcp-server-param-item.tsx | 2 +- .../components/tools/mcp/mcp-service-card.tsx | 3 +- web/app/components/tools/mcp/modal.tsx | 3 +- web/app/components/tools/provider/detail.tsx | 3 +- .../components/tools/provider/tool-item.tsx | 3 +- .../setting/build-in/config-credentials.tsx | 3 +- .../tools/workflow-tool/configure-button.tsx | 3 +- .../confirm-modal/index.spec.tsx | 2 +- .../components/tools/workflow-tool/index.tsx | 3 +- .../workflow-onboarding-modal/index.spec.tsx | 2 +- .../start-node-option.spec.tsx | 2 +- .../start-node-selection-panel.spec.tsx | 2 +- .../__tests__/trigger-status-sync.test.tsx | 3 +- .../market-place-plugin/action.tsx | 3 +- .../market-place-plugin/item.tsx | 2 +- .../rag-tool-recommendations/index.tsx | 3 +- .../uninstalled-item.tsx | 2 +- .../workflow/block-selector/tool-picker.tsx | 3 +- .../block-selector/tool/action-item.tsx | 3 +- .../tool/tool-list-flat-view/list.tsx | 3 +- .../tool/tool-list-tree-view/item.tsx | 2 +- .../tool/tool-list-tree-view/list.tsx | 3 +- .../workflow/block-selector/tool/tool.tsx | 3 +- .../trigger-plugin/action-item.tsx | 2 +- .../block-selector/trigger-plugin/item.tsx | 3 +- .../block-selector/use-sticky-scroll.ts | 2 +- .../block-selector/view-type-select.tsx | 3 +- .../workflow/dsl-export-confirm-modal.tsx | 3 +- .../components/workflow/header/run-mode.tsx | 3 +- .../header/version-history-button.tsx | 3 +- .../nodes/_base/components/add-button.tsx | 2 +- .../components/before-run-form/bool-input.tsx | 3 +- .../components/before-run-form/form-item.tsx | 3 +- .../_base/components/before-run-form/form.tsx | 3 +- .../components/before-run-form/index.tsx | 3 +- .../components/before-run-form/panel-wrap.tsx | 2 +- .../components/code-generator-button.tsx | 3 +- .../nodes/_base/components/config-vision.tsx | 3 +- .../nodes/_base/components/editor/base.tsx | 3 +- .../code-editor/editor-support-vars.tsx | 3 +- .../components/editor/code-editor/index.tsx | 3 +- .../_base/components/editor/text-editor.tsx | 3 +- .../nodes/_base/components/editor/wrap.tsx | 2 +- .../workflow/nodes/_base/components/field.tsx | 2 +- .../nodes/_base/components/file-type-item.tsx | 3 +- .../_base/components/file-upload-setting.tsx | 3 +- .../nodes/_base/components/info-panel.tsx | 2 +- .../components/input-number-with-slider.tsx | 3 +- .../components/input-support-select-var.tsx | 3 +- .../_base/components/input-var-type-icon.tsx | 2 +- .../components/list-no-data-placeholder.tsx | 2 +- .../mcp-tool-not-support-tooltip.tsx | 2 +- .../nodes/_base/components/memory-config.tsx | 3 +- .../nodes/_base/components/option-card.tsx | 3 +- .../nodes/_base/components/output-vars.tsx | 2 +- .../nodes/_base/components/prompt/editor.tsx | 3 +- .../readonly-input-with-select-var.tsx | 2 +- .../nodes/_base/components/remove-button.tsx | 2 +- .../components/remove-effect-var-confirm.tsx | 2 +- .../nodes/_base/components/selector.tsx | 2 +- .../workflow/nodes/_base/components/split.tsx | 2 +- .../components/support-var-input/index.tsx | 2 +- .../_base/components/toggle-expand-btn.tsx | 3 +- .../variable/assigned-var-reference-popup.tsx | 2 +- .../components/variable/constant-field.tsx | 3 +- .../object-child-tree-panel/picker/field.tsx | 2 +- .../object-child-tree-panel/picker/index.tsx | 3 +- .../object-child-tree-panel/show/field.tsx | 2 +- .../object-child-tree-panel/show/index.tsx | 2 +- .../tree-indent-line.tsx | 2 +- .../components/variable/output-var-list.tsx | 3 +- .../variable/var-full-path-panel.tsx | 2 +- .../_base/components/variable/var-list.tsx | 3 +- .../variable/var-reference-picker.tsx | 3 +- .../variable/var-reference-popup.tsx | 3 +- .../variable/var-reference-vars.tsx | 3 +- .../components/variable/var-type-picker.tsx | 3 +- .../_base/components/workflow-panel/index.tsx | 3 +- .../workflow-panel/last-run/index.tsx | 3 +- .../workflow-panel/last-run/no-data.tsx | 2 +- .../_base/components/workflow-panel/tab.tsx | 2 +- .../components/workflow/nodes/answer/node.tsx | 2 +- .../workflow/nodes/answer/panel.tsx | 2 +- .../assigner/components/var-list/index.tsx | 3 +- .../workflow/nodes/assigner/node.tsx | 2 +- .../workflow/nodes/assigner/panel.tsx | 2 +- .../workflow/nodes/code/dependency-picker.tsx | 3 +- .../components/workflow/nodes/code/node.tsx | 2 +- .../components/workflow/nodes/code/panel.tsx | 2 +- .../nodes/data-source/before-run-form.tsx | 3 +- .../nodes/document-extractor/node.tsx | 2 +- .../nodes/document-extractor/panel.tsx | 2 +- .../components/workflow/nodes/end/node.tsx | 2 +- .../components/workflow/nodes/end/panel.tsx | 2 +- .../nodes/http/components/api-input.tsx | 3 +- .../http/components/authorization/index.tsx | 3 +- .../components/authorization/radio-group.tsx | 3 +- .../nodes/http/components/curl-panel.tsx | 3 +- .../nodes/http/components/edit-body/index.tsx | 3 +- .../components/key-value/bulk-edit/index.tsx | 3 +- .../nodes/http/components/key-value/index.tsx | 2 +- .../key-value/key-value-edit/index.tsx | 3 +- .../key-value/key-value-edit/input-item.tsx | 3 +- .../key-value/key-value-edit/item.tsx | 3 +- .../nodes/http/components/timeout/index.tsx | 2 +- .../components/workflow/nodes/http/node.tsx | 2 +- .../if-else/components/condition-wrap.tsx | 3 +- .../workflow/nodes/if-else/node.tsx | 3 +- .../workflow/nodes/iteration/panel.tsx | 2 +- .../chunk-structure/instruction/index.tsx | 2 +- .../chunk-structure/instruction/line.tsx | 2 +- .../components/add-dataset.tsx | 3 +- .../components/dataset-item.tsx | 3 +- .../components/dataset-list.tsx | 3 +- .../components/retrieval-config.tsx | 3 +- .../nodes/knowledge-retrieval/node.tsx | 3 +- .../components/extract-input.tsx | 3 +- .../components/filter-condition.tsx | 3 +- .../list-operator/components/limit-config.tsx | 3 +- .../components/sub-variable-picker.tsx | 3 +- .../workflow/nodes/list-operator/node.tsx | 2 +- .../workflow/nodes/list-operator/panel.tsx | 2 +- .../llm/components/config-prompt-item.tsx | 3 +- .../nodes/llm/components/config-prompt.tsx | 3 +- .../json-schema-config-modal/code-editor.tsx | 3 +- .../error-message.tsx | 2 +- .../json-schema-config-modal/index.tsx | 2 +- .../json-importer.tsx | 3 +- .../json-schema-config.tsx | 3 +- .../generated-result.tsx | 3 +- .../json-schema-generator/index.tsx | 3 +- .../json-schema-generator/prompt-editor.tsx | 3 +- .../schema-editor.tsx | 2 +- .../visual-editor/add-field.tsx | 3 +- .../visual-editor/card.tsx | 2 +- .../visual-editor/edit-card/actions.tsx | 2 +- .../edit-card/advanced-actions.tsx | 2 +- .../edit-card/advanced-options.tsx | 3 +- .../edit-card/auto-width-input.tsx | 3 +- .../visual-editor/edit-card/index.tsx | 3 +- .../edit-card/required-switch.tsx | 2 +- .../visual-editor/schema-node.tsx | 3 +- .../llm/components/prompt-generator-btn.tsx | 3 +- .../components/reasoning-format-config.tsx | 2 +- .../llm/components/resolution-picker.tsx | 3 +- .../nodes/llm/components/structure-output.tsx | 3 +- .../components/workflow/nodes/llm/node.tsx | 2 +- .../components/workflow/nodes/llm/panel.tsx | 3 +- .../nodes/loop/components/condition-wrap.tsx | 3 +- .../components/workflow/nodes/loop/panel.tsx | 2 +- .../components/extract-parameter/item.tsx | 2 +- .../components/extract-parameter/list.tsx | 3 +- .../components/extract-parameter/update.tsx | 3 +- .../components/reasoning-mode-picker.tsx | 3 +- .../nodes/parameter-extractor/node.tsx | 2 +- .../nodes/parameter-extractor/panel.tsx | 2 +- .../components/advanced-setting.tsx | 2 +- .../components/class-item.tsx | 3 +- .../components/class-list.tsx | 3 +- .../nodes/question-classifier/node.tsx | 2 +- .../nodes/question-classifier/panel.tsx | 2 +- .../nodes/start/components/var-item.tsx | 3 +- .../nodes/start/components/var-list.tsx | 3 +- .../components/workflow/nodes/start/node.tsx | 2 +- .../components/workflow/nodes/start/panel.tsx | 2 +- .../nodes/template-transform/node.tsx | 2 +- .../nodes/template-transform/panel.tsx | 2 +- .../nodes/tool/components/copy-id.tsx | 3 +- .../nodes/tool/components/input-var-list.tsx | 3 +- .../components/workflow/nodes/tool/node.tsx | 3 +- .../components/workflow/nodes/tool/panel.tsx | 2 +- .../workflow/nodes/trigger-plugin/node.tsx | 3 +- .../workflow/nodes/trigger-plugin/panel.tsx | 2 +- .../components/frequency-selector.tsx | 3 +- .../components/mode-switcher.tsx | 2 +- .../components/mode-toggle.tsx | 2 +- .../components/monthly-days-selector.tsx | 2 +- .../components/next-execution-times.tsx | 2 +- .../components/on-minute-selector.tsx | 2 +- .../components/weekday-selector.tsx | 2 +- .../workflow/nodes/trigger-schedule/node.tsx | 2 +- .../workflow/nodes/trigger-schedule/panel.tsx | 2 +- .../components/generic-table.tsx | 3 +- .../components/header-table.tsx | 2 +- .../components/paragraph-input.tsx | 3 +- .../components/parameter-table.tsx | 3 +- .../workflow/nodes/trigger-webhook/node.tsx | 2 +- .../workflow/nodes/trigger-webhook/panel.tsx | 3 +- .../utils/render-output-vars.tsx | 2 +- .../components/var-group-item.tsx | 3 +- .../components/var-list/index.tsx | 3 +- .../nodes/variable-assigner/panel.tsx | 2 +- .../components/array-bool-list.tsx | 3 +- .../components/array-value-list.tsx | 3 +- .../components/bool-value.tsx | 3 +- .../components/object-value-item.tsx | 3 +- .../components/object-value-list.tsx | 2 +- .../components/variable-modal-trigger.tsx | 2 +- .../components/variable-modal.tsx | 3 +- .../components/variable-type-select.tsx | 3 +- .../conversation-variable-modal.tsx | 3 +- .../panel/env-panel/variable-modal.tsx | 3 +- .../panel/env-panel/variable-trigger.tsx | 2 +- .../context-menu/index.tsx | 3 +- .../context-menu/menu-item.tsx | 2 +- .../delete-confirm-modal.tsx | 2 +- .../panel/version-history-panel/empty.tsx | 2 +- .../filter/filter-item.tsx | 2 +- .../filter/filter-switch.tsx | 2 +- .../version-history-panel/filter/index.tsx | 3 +- .../panel/version-history-panel/index.tsx | 3 +- .../version-history-panel/loading/item.tsx | 2 +- .../restore-confirm-modal.tsx | 2 +- .../version-history-item.tsx | 3 +- web/app/components/workflow/run/index.tsx | 3 +- .../iteration-log/iteration-result-panel.tsx | 3 +- .../run/loop-log/loop-result-panel.tsx | 3 +- .../workflow/run/loop-result-panel.tsx | 3 +- .../components/workflow/run/tracing-panel.tsx | 5 +- .../variable-inspect/display-content.tsx | 3 +- .../variable-inspect/large-data-alert.tsx | 2 +- .../variable-inspect/value-content.tsx | 3 +- .../components/nodes/if-else/node.tsx | 3 +- .../nodes/question-classifier/node.tsx | 2 +- .../education-apply/expire-notice-modal.tsx | 2 +- .../education-apply/verify-state-modal.tsx | 3 +- .../forgot-password/ForgotPasswordForm.tsx | 3 +- web/app/forgot-password/page.tsx | 2 +- web/app/init/page.tsx | 2 +- web/app/install/installForm.tsx | 3 +- web/app/install/page.tsx | 2 +- web/app/signin/_header.tsx | 2 +- web/app/signin/normal-form.tsx | 3 +- web/app/signin/one-more-step.tsx | 3 +- web/app/signin/split.tsx | 2 +- web/context/external-api-panel-context.tsx | 3 +- web/context/modal-context.test.tsx | 2 +- web/context/provider-context-mock.tsx | 2 +- web/eslint.config.mjs | 2 +- web/hooks/use-breakpoints.ts | 2 +- web/service/demo/index.tsx | 2 +- web/utils/context.spec.ts | 2 +- 1078 files changed, 1680 insertions(+), 1216 deletions(-) diff --git a/web/__tests__/check-i18n.test.ts b/web/__tests__/check-i18n.test.ts index bd315a3fa9..a6f86d8107 100644 --- a/web/__tests__/check-i18n.test.ts +++ b/web/__tests__/check-i18n.test.ts @@ -1,9 +1,7 @@ import fs from 'node:fs' import path from 'node:path' - -// Mock functions to simulate the check-i18n functionality -const vm = require('node:vm') -const transpile = require('typescript').transpile +import vm from 'node:vm' +import { transpile } from 'typescript' describe('check-i18n script functionality', () => { const testDir = path.join(__dirname, '../i18n-test') diff --git a/web/__tests__/embedded-user-id-auth.test.tsx b/web/__tests__/embedded-user-id-auth.test.tsx index 8817aa2e1e..9231ac6199 100644 --- a/web/__tests__/embedded-user-id-auth.test.tsx +++ b/web/__tests__/embedded-user-id-auth.test.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CheckCode from '@/app/(shareLayout)/webapp-signin/check-code/page' import MailAndPasswordAuth from '@/app/(shareLayout)/webapp-signin/components/mail-and-password-auth' diff --git a/web/__tests__/embedded-user-id-store.test.tsx b/web/__tests__/embedded-user-id-store.test.tsx index d1f99b8833..276b22bcd7 100644 --- a/web/__tests__/embedded-user-id-store.test.tsx +++ b/web/__tests__/embedded-user-id-store.test.tsx @@ -1,5 +1,5 @@ import { render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context' import { AccessMode } from '@/models/access-control' diff --git a/web/__tests__/goto-anything/command-selector.test.tsx b/web/__tests__/goto-anything/command-selector.test.tsx index 1a9ef33b00..f0168ab3be 100644 --- a/web/__tests__/goto-anything/command-selector.test.tsx +++ b/web/__tests__/goto-anything/command-selector.test.tsx @@ -1,6 +1,6 @@ import type { ActionItem } from '../../app/components/goto-anything/actions/types' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CommandSelector from '../../app/components/goto-anything/command-selector' vi.mock('cmdk', () => ({ diff --git a/web/__tests__/goto-anything/scope-command-tags.test.tsx b/web/__tests__/goto-anything/scope-command-tags.test.tsx index de34875d02..c25f4fc74e 100644 --- a/web/__tests__/goto-anything/scope-command-tags.test.tsx +++ b/web/__tests__/goto-anything/scope-command-tags.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Type alias for search mode type SearchMode = 'scopes' | 'commands' | null diff --git a/web/__tests__/workflow-parallel-limit.test.tsx b/web/__tests__/workflow-parallel-limit.test.tsx index d2afb30882..18657f4bd2 100644 --- a/web/__tests__/workflow-parallel-limit.test.tsx +++ b/web/__tests__/workflow-parallel-limit.test.tsx @@ -6,7 +6,7 @@ */ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Mock environment variables before importing constants const originalEnv = process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT diff --git a/web/__tests__/xss-prevention.test.tsx b/web/__tests__/xss-prevention.test.tsx index b236f9ed3e..10a8b21a30 100644 --- a/web/__tests__/xss-prevention.test.tsx +++ b/web/__tests__/xss-prevention.test.tsx @@ -6,7 +6,7 @@ */ import { cleanup, render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import BlockInput from '../app/components/base/block-input' import SupportVarInput from '../app/components/workflow/nodes/_base/components/support-var-input' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx index 5fb3ff4b4d..a17a4a3d03 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/app/log-annotation' import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/configuration/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/configuration/page.tsx index 41143b979a..850bd47aad 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/configuration/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/configuration/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Configuration from '@/app/components/app/configuration' const IConfiguration = async () => { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx index ba04eae64a..14864aba8b 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx @@ -1,5 +1,5 @@ import type { Locale } from '@/i18n-config' -import React from 'react' +import * as React from 'react' import DevelopMain from '@/app/components/develop' export type IDevelopProps = { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index 41bf4c3acf..c1feb3ea5d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -15,7 +15,8 @@ import { import { useUnmount } from 'ahooks' import dynamic from 'next/dynamic' import { usePathname, useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import AppSideBar from '@/app/components/app-sidebar' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/logs/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/logs/page.tsx index 244a357616..eb8ff8f795 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/logs/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/logs/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/app/log-annotation' import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx index f9110c9c1b..e9877f1715 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx @@ -4,7 +4,8 @@ import type { IAppCardProps } from '@/app/components/app/overview/app-card' import type { BlockEnum } from '@/app/components/workflow/types' import type { UpdateAppSiteCodeResponse } from '@/models/app' import type { App } from '@/types/app' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AppCard from '@/app/components/app/overview/app-card' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx index 9304b45284..e1fca90c5d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chart-view.tsx @@ -2,7 +2,8 @@ import type { PeriodParams } from '@/app/components/app/overview/app-chart' import dayjs from 'dayjs' import quarterOfYear from 'dayjs/plugin/quarterOfYear' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { TIME_PERIOD_MAPPING as LONG_TIME_PERIOD_MAPPING } from '@/app/components/app/log/filter' import { AvgResponseTime, AvgSessionInteractions, AvgUserInteractions, ConversationsChart, CostChart, EndUsersChart, MessagesChart, TokenPerSecond, UserSatisfactionRate, WorkflowCostChart, WorkflowDailyTerminalsChart, WorkflowMessagesChart } from '@/app/components/app/overview/app-chart' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx index 4d59f4a67f..557b723259 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { PeriodParams } from '@/app/components/app/overview/app-chart' import type { Item } from '@/app/components/base/select' import dayjs from 'dayjs' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { SimpleSelect } from '@/app/components/base/select' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx index e7ee4eb203..39c42d067d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel' import ChartView from './chart-view' import TracingPanel from './tracing/panel' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx index bc8bf58354..ab39846a36 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx @@ -5,7 +5,8 @@ import type { TriggerProps } from '@/app/components/base/date-and-time-picker/ty import { RiCalendarLine } from '@remixicon/react' import dayjs from 'dayjs' import { noop } from 'lodash-es' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import Picker from '@/app/components/base/date-and-time-picker/date-picker' import { useI18N } from '@/context/i18n' import { cn } from '@/utils/classnames' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx index 2a6dca33d7..469bc97737 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx @@ -3,7 +3,8 @@ import type { Dayjs } from 'dayjs' import type { FC } from 'react' import type { PeriodParams, PeriodParamsWithTimeRange } from '@/app/components/app/overview/app-chart' import dayjs from 'dayjs' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { HourglassShape } from '@/app/components/base/icons/src/vender/other' import { useI18N } from '@/context/i18n' import { formatToLocalTime } from '@/utils/format' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx index e820ae46e1..be7181c759 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx @@ -4,7 +4,8 @@ import type { PeriodParamsWithTimeRange, TimeRange } from '@/app/components/app/ import type { Item } from '@/app/components/base/select' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' import dayjs from 'dayjs' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { SimpleSelect } from '@/app/components/base/select' import { cn } from '@/utils/classnames' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/svg-attribute-error-reproduction.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/svg-attribute-error-reproduction.spec.tsx index a08a1476d7..fc27f84c60 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/svg-attribute-error-reproduction.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/svg-attribute-error-reproduction.spec.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { OpikIconBig } from '@/app/components/base/icons/src/public/tracing' import iconData from '@/app/components/base/icons/src/public/tracing/OpikIconBig.json' import { normalizeAttrs } from '@/app/components/base/icons/utils' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx index 8dbb410f8c..8429f8a3a9 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { PopupProps } from './config-popup' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 3cbfcb4315..35ab8a61ec 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -2,7 +2,8 @@ import type { FC, JSX } from 'react' import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type' import { useBoolean } from 'ahooks' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Switch from '@/app/components/base/switch' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx index a827a2b80c..7c47249830 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Input from '@/app/components/base/input' import { cn } from '@/utils/classnames' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 1ea89012e3..438dbddfb8 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -8,7 +8,8 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import { usePathname } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { AliyunIcon, ArizeIcon, DatabricksIcon, LangfuseIcon, LangsmithIcon, MlflowIcon, OpikIcon, PhoenixIcon, TencentIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index 254ca4e01a..0ef97e6970 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type' import { useBoolean } from 'ahooks' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx index aebdf70b55..6c66b19ad3 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import { RiEqualizer2Line, } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { AliyunIconBig, ArizeIconBig, DatabricksIconBig, LangfuseIconBig, LangsmithIconBig, MlflowIconBig, OpikIconBig, PhoenixIconBig, TencentIconBig, WeaveIconBig } from '@/app/components/base/icons/src/public/tracing' import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx index aa8567548d..137fff05df 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { TracingIcon as Icon } from '@/app/components/base/icons/src/public/tracing' import { cn } from '@/utils/classnames' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx index f41babeb4e..4135482dd9 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useRouter } from 'next/navigation' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import useDocumentTitle from '@/hooks/use-document-title' diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/api/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/api/page.tsx index 167520ca7b..200fc994ea 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/api/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/api/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' const page = () => { return ( diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx index ed6365c890..dd51c84bcf 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import MainDetail from '@/app/components/datasets/documents/detail' export type IDocumentDetailProps = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx index e8576d4a4b..cd9a37b426 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Settings from '@/app/components/datasets/documents/detail/settings' export type IProps = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx index 9ce86bbef4..046f69dab2 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create-from-pipeline/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import CreateFromPipeline from '@/app/components/datasets/documents/create-from-pipeline' const CreateFromPipelinePage = async () => { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx index 8fd2caa246..987bd1ea70 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import DatasetUpdateForm from '@/app/components/datasets/create' export type IProps = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx index 2ff4631dea..7a049e0b1b 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/datasets/documents' export type IProps = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx index 9a701c68f5..9a339030b9 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/datasets/hit-testing' type Props = { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 6caecc4826..10a12d75e1 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -10,7 +10,8 @@ import { RiFocus2Line, } from '@remixicon/react' import { usePathname } from 'next/navigation' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppSideBar from '@/app/components/app-sidebar' import { useStore } from '@/app/components/app/store' diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx index 59540a1854..9dfeaef528 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Form from '@/app/components/datasets/settings/form' import { getLocaleOnServer, useTranslation as translate } from '@/i18n-config/server' diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/layout.tsx index ccbc58f5e5..09555ae0f0 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/layout.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' export type IDatasetDetail = { children: React.ReactNode diff --git a/web/app/(commonLayout)/datasets/connect/page.tsx b/web/app/(commonLayout)/datasets/connect/page.tsx index 724c506a7f..1ac4dc2f7c 100644 --- a/web/app/(commonLayout)/datasets/connect/page.tsx +++ b/web/app/(commonLayout)/datasets/connect/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import ExternalKnowledgeBaseConnector from '@/app/components/datasets/external-knowledge-base/connector' const ExternalKnowledgeBaseCreation = () => { diff --git a/web/app/(commonLayout)/datasets/create-from-pipeline/page.tsx b/web/app/(commonLayout)/datasets/create-from-pipeline/page.tsx index 72f5ecdfd9..63f09655b3 100644 --- a/web/app/(commonLayout)/datasets/create-from-pipeline/page.tsx +++ b/web/app/(commonLayout)/datasets/create-from-pipeline/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import CreateFromPipeline from '@/app/components/datasets/create-from-pipeline' const DatasetCreation = async () => { diff --git a/web/app/(commonLayout)/datasets/create/page.tsx b/web/app/(commonLayout)/datasets/create/page.tsx index 50fd1f5a19..fe5765437f 100644 --- a/web/app/(commonLayout)/datasets/create/page.tsx +++ b/web/app/(commonLayout)/datasets/create/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import DatasetUpdateForm from '@/app/components/datasets/create' const DatasetCreation = async () => { diff --git a/web/app/(commonLayout)/explore/apps/page.tsx b/web/app/(commonLayout)/explore/apps/page.tsx index b2430605e7..e0da5d64d2 100644 --- a/web/app/(commonLayout)/explore/apps/page.tsx +++ b/web/app/(commonLayout)/explore/apps/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import AppList from '@/app/components/explore/app-list' const Apps = () => { diff --git a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx index 983fdb9d23..0b71d1a26c 100644 --- a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx +++ b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/explore/installed-app' export type IInstalledAppProps = { diff --git a/web/app/(commonLayout)/explore/layout.tsx b/web/app/(commonLayout)/explore/layout.tsx index 46bd41cb0c..5928308cdc 100644 --- a/web/app/(commonLayout)/explore/layout.tsx +++ b/web/app/(commonLayout)/explore/layout.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, PropsWithChildren } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ExploreClient from '@/app/components/explore' import useDocumentTitle from '@/hooks/use-document-title' diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index a759506bf0..91bdb3f99a 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import AmplitudeProvider from '@/app/components/base/amplitude' import GA, { GaType } from '@/app/components/base/ga' import Zendesk from '@/app/components/base/zendesk' diff --git a/web/app/(commonLayout)/tools/page.tsx b/web/app/(commonLayout)/tools/page.tsx index 3ea50e70ef..2d5c1a8e44 100644 --- a/web/app/(commonLayout)/tools/page.tsx +++ b/web/app/(commonLayout)/tools/page.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useRouter } from 'next/navigation' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import ToolProviderList from '@/app/components/tools/provider-list' import { useAppContext } from '@/context/app-context' diff --git a/web/app/(shareLayout)/chat/[token]/page.tsx b/web/app/(shareLayout)/chat/[token]/page.tsx index 8ce67585f0..b4248aedbc 100644 --- a/web/app/(shareLayout)/chat/[token]/page.tsx +++ b/web/app/(shareLayout)/chat/[token]/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import ChatWithHistoryWrap from '@/app/components/base/chat/chat-with-history' import AuthenticatedLayout from '../../components/authenticated-layout' diff --git a/web/app/(shareLayout)/chatbot/[token]/page.tsx b/web/app/(shareLayout)/chatbot/[token]/page.tsx index 5323d0dacc..187d736c54 100644 --- a/web/app/(shareLayout)/chatbot/[token]/page.tsx +++ b/web/app/(shareLayout)/chatbot/[token]/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import EmbeddedChatbot from '@/app/components/base/chat/embedded-chatbot' import AuthenticatedLayout from '../../components/authenticated-layout' diff --git a/web/app/(shareLayout)/completion/[token]/page.tsx b/web/app/(shareLayout)/completion/[token]/page.tsx index ae91338b9a..ba96ac05cb 100644 --- a/web/app/(shareLayout)/completion/[token]/page.tsx +++ b/web/app/(shareLayout)/completion/[token]/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/share/text-generation' import AuthenticatedLayout from '../../components/authenticated-layout' diff --git a/web/app/(shareLayout)/components/authenticated-layout.tsx b/web/app/(shareLayout)/components/authenticated-layout.tsx index 5f436429d3..00288b7a61 100644 --- a/web/app/(shareLayout)/components/authenticated-layout.tsx +++ b/web/app/(shareLayout)/components/authenticated-layout.tsx @@ -1,7 +1,8 @@ 'use client' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect } from 'react' +import * as React from 'react' +import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx index 091493812f..0776df036d 100644 --- a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -1,6 +1,7 @@ 'use client' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect } from 'react' +import * as React from 'react' +import { useCallback, useEffect } from 'react' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx index 2aaa267962..40d34dcaf5 100644 --- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -1,7 +1,8 @@ 'use client' import { RiContractLine, RiDoorLockLine, RiErrorWarningFill } from '@remixicon/react' import Link from 'next/link' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { IS_CE_EDITION } from '@/config' diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index ca380c9398..cfa0295b28 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' import { useGlobalPublicStore } from '@/context/global-public-context' diff --git a/web/app/(shareLayout)/workflow/[token]/page.tsx b/web/app/(shareLayout)/workflow/[token]/page.tsx index 4f5923e91f..b2828ee5db 100644 --- a/web/app/(shareLayout)/workflow/[token]/page.tsx +++ b/web/app/(shareLayout)/workflow/[token]/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Main from '@/app/components/share/text-generation' import AuthenticatedLayout from '../../components/authenticated-layout' diff --git a/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx b/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx index dd254435bb..9b65db4eaa 100644 --- a/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx +++ b/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx @@ -5,7 +5,8 @@ import type { OnImageInput } from '@/app/components/base/app-icon-picker/ImageIn import type { AvatarProps } from '@/app/components/base/avatar' import type { ImageFile } from '@/types/app' import { RiDeleteBin5Line, RiPencilLine } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ImageInput from '@/app/components/base/app-icon-picker/ImageInput' diff --git a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx index 83e667a1f3..99b4f5c686 100644 --- a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx +++ b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx @@ -2,7 +2,8 @@ import type { ResponseError } from '@/service/fetch' import { RiCloseLine } from '@remixicon/react' import { noop } from 'lodash-es' import { useRouter } from 'next/navigation' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/account/(commonLayout)/layout.tsx b/web/app/account/(commonLayout)/layout.tsx index af1ec0afd6..f264441b86 100644 --- a/web/app/account/(commonLayout)/layout.tsx +++ b/web/app/account/(commonLayout)/layout.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import AmplitudeProvider from '@/app/components/base/amplitude' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/header-wrapper' diff --git a/web/app/account/oauth/authorize/page.tsx b/web/app/account/oauth/authorize/page.tsx index 9993c27e4b..bbb96abf4e 100644 --- a/web/app/account/oauth/authorize/page.tsx +++ b/web/app/account/oauth/authorize/page.tsx @@ -9,7 +9,8 @@ import { } from '@remixicon/react' import dayjs from 'dayjs' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useEffect, useRef } from 'react' +import * as React from 'react' +import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import Avatar from '@/app/components/base/avatar' import Button from '@/app/components/base/button' diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index 01e4e1a35f..5852ef54e4 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Header from '../signin/_header' diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index e175de5c6e..497124c702 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -13,7 +13,8 @@ import { } from '@remixicon/react' import dynamic from 'next/dynamic' import { useRouter } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view' diff --git a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx index 30ec906c93..4d80af8bcb 100644 --- a/web/app/components/app-sidebar/app-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/app-sidebar-dropdown.tsx @@ -3,7 +3,8 @@ import { RiEqualizer2Line, RiMenuLine, } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { diff --git a/web/app/components/app-sidebar/basic.tsx b/web/app/components/app-sidebar/basic.tsx index 40e95206c8..1503afcdad 100644 --- a/web/app/components/app-sidebar/basic.tsx +++ b/web/app/components/app-sidebar/basic.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { ApiAggregate, diff --git a/web/app/components/app-sidebar/dataset-info/dropdown.tsx b/web/app/components/app-sidebar/dataset-info/dropdown.tsx index f20b35de80..c072bd7547 100644 --- a/web/app/components/app-sidebar/dataset-info/dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-info/dropdown.tsx @@ -1,7 +1,8 @@ import type { DataSet } from '@/models/datasets' import { RiMoreFill } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector as useAppContextWithSelector } from '@/context/app-context' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' diff --git a/web/app/components/app-sidebar/dataset-info/index.spec.tsx b/web/app/components/app-sidebar/dataset-info/index.spec.tsx index a03b5da488..da7eb6d7ff 100644 --- a/web/app/components/app-sidebar/dataset-info/index.spec.tsx +++ b/web/app/components/app-sidebar/dataset-info/index.spec.tsx @@ -2,7 +2,7 @@ import type { DataSet } from '@/models/datasets' import { RiEditLine } from '@remixicon/react' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { ChunkingMode, DatasetPermission, diff --git a/web/app/components/app-sidebar/dataset-info/index.tsx b/web/app/components/app-sidebar/dataset-info/index.tsx index 43fded35a9..ce409ff13a 100644 --- a/web/app/components/app-sidebar/dataset-info/index.tsx +++ b/web/app/components/app-sidebar/dataset-info/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { DataSet } from '@/models/datasets' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useKnowledge } from '@/hooks/use-knowledge' diff --git a/web/app/components/app-sidebar/dataset-info/menu-item.tsx b/web/app/components/app-sidebar/dataset-info/menu-item.tsx index 5cc082a19e..441482283b 100644 --- a/web/app/components/app-sidebar/dataset-info/menu-item.tsx +++ b/web/app/components/app-sidebar/dataset-info/menu-item.tsx @@ -1,5 +1,5 @@ import type { RemixiconComponentType } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type MenuItemProps = { name: string diff --git a/web/app/components/app-sidebar/dataset-info/menu.tsx b/web/app/components/app-sidebar/dataset-info/menu.tsx index f4a49b3eea..a17e0ed96d 100644 --- a/web/app/components/app-sidebar/dataset-info/menu.tsx +++ b/web/app/components/app-sidebar/dataset-info/menu.tsx @@ -1,5 +1,5 @@ import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import Divider from '../../base/divider' diff --git a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx index e8fa050a6d..d8e26826ca 100644 --- a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx @@ -3,7 +3,8 @@ import type { DataSet } from '@/models/datasets' import { RiMenuLine, } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 790d5340bc..afc6bd0f13 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -1,7 +1,8 @@ import type { NavIcon } from './navLink' import { useHover, useKeyPress } from 'ahooks' import { usePathname } from 'next/navigation' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' import { useEventEmitterContextContext } from '@/context/event-emitter' diff --git a/web/app/components/app-sidebar/navLink.spec.tsx b/web/app/components/app-sidebar/navLink.spec.tsx index 410dae6b2a..62ef553386 100644 --- a/web/app/components/app-sidebar/navLink.spec.tsx +++ b/web/app/components/app-sidebar/navLink.spec.tsx @@ -1,6 +1,6 @@ import type { NavLinkProps } from './navLink' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import NavLink from './navLink' // Mock Next.js navigation diff --git a/web/app/components/app-sidebar/navLink.tsx b/web/app/components/app-sidebar/navLink.tsx index 999c892199..9d5e319046 100644 --- a/web/app/components/app-sidebar/navLink.tsx +++ b/web/app/components/app-sidebar/navLink.tsx @@ -2,7 +2,7 @@ import type { RemixiconComponentType } from '@remixicon/react' import Link from 'next/link' import { useSelectedLayoutSegment } from 'next/navigation' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' export type NavIcon = React.ComponentType< diff --git a/web/app/components/app-sidebar/sidebar-animation-issues.spec.tsx b/web/app/components/app-sidebar/sidebar-animation-issues.spec.tsx index 61f278b577..5d85b99d9a 100644 --- a/web/app/components/app-sidebar/sidebar-animation-issues.spec.tsx +++ b/web/app/components/app-sidebar/sidebar-animation-issues.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Simple Mock Components that reproduce the exact UI issues const MockNavLink = ({ name, mode }: { name: string, mode: string }) => { diff --git a/web/app/components/app-sidebar/text-squeeze-fix-verification.spec.tsx b/web/app/components/app-sidebar/text-squeeze-fix-verification.spec.tsx index 7752e29f46..7c0c8b3aca 100644 --- a/web/app/components/app-sidebar/text-squeeze-fix-verification.spec.tsx +++ b/web/app/components/app-sidebar/text-squeeze-fix-verification.spec.tsx @@ -4,7 +4,7 @@ */ import { render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Mock Next.js navigation vi.mock('next/navigation', () => ({ diff --git a/web/app/components/app-sidebar/toggle-button.tsx b/web/app/components/app-sidebar/toggle-button.tsx index e144d29e92..b4dc2e9199 100644 --- a/web/app/components/app-sidebar/toggle-button.tsx +++ b/web/app/components/app-sidebar/toggle-button.tsx @@ -1,5 +1,5 @@ import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Button from '../base/button' diff --git a/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx index 34fbe93be6..ce660f7880 100644 --- a/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import EditItem, { EditItemType } from './index' describe('AddAnnotationModal/EditItem', () => { diff --git a/web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx index 62785d0032..ec53243077 100644 --- a/web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Robot, User } from '@/app/components/base/icons/src/public/avatar' import Textarea from '@/app/components/base/textarea' diff --git a/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx index a0ddcd13d3..6837516b3c 100644 --- a/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/index.spec.tsx @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { useProviderContext } from '@/context/provider-context' import AddAnnotationModal from './index' diff --git a/web/app/components/app/annotation/add-annotation-modal/index.tsx b/web/app/components/app/annotation/add-annotation-modal/index.tsx index 24ef88681a..b31fb20822 100644 --- a/web/app/components/app/annotation/add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { AnnotationItemBasic } from '../type' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/app/annotation/batch-action.spec.tsx b/web/app/components/app/annotation/batch-action.spec.tsx index 8598088702..8d56dde14a 100644 --- a/web/app/components/app/annotation/batch-action.spec.tsx +++ b/web/app/components/app/annotation/batch-action.spec.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import BatchAction from './batch-action' describe('BatchAction', () => { diff --git a/web/app/components/app/annotation/batch-action.tsx b/web/app/components/app/annotation/batch-action.tsx index 5d170d7054..491c68a656 100644 --- a/web/app/components/app/annotation/batch-action.tsx +++ b/web/app/components/app/annotation/batch-action.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiDeleteBinLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx index a4ca710b88..a3ab73b339 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx @@ -1,6 +1,6 @@ import type { Locale } from '@/i18n-config' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import I18nContext from '@/context/i18n' import { LanguagesSupported } from '@/i18n-config/language' import CSVDownload from './csv-downloader' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx index d966a3e28a..4735afb5cb 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader, diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx index 342d2baeca..6a67ba3207 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx @@ -1,6 +1,6 @@ import type { Props } from './csv-uploader' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ToastContext } from '@/app/components/base/toast' import CSVUploader from './csv-uploader' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index ac10e636cf..79e2faf283 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiDeleteBinLine } from '@remixicon/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.spec.tsx index feaa5e27e7..d7458d6b90 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/index.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import type { IBatchModalProps } from './index' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Toast from '@/app/components/base/toast' import { useProviderContext } from '@/context/provider-context' import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx index fcba222656..7fbb745c48 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.spec.tsx b/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.spec.tsx index 5c2bbcf62f..5dbfd664f8 100644 --- a/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.spec.tsx +++ b/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import ClearAllAnnotationsConfirmModal from './index' vi.mock('react-i18next', () => ({ diff --git a/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx b/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx index ab8c15eab6..df85f4956b 100644 --- a/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx +++ b/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx index 9ba7d9d5fb..cd03406a67 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiDeleteBinLine, RiEditFill, RiEditLine } from '@remixicon/react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { Robot, User } from '@/app/components/base/icons/src/public/avatar' diff --git a/web/app/components/app/annotation/edit-annotation-modal/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/index.tsx index 288ca009bc..4c4380bc20 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' import Drawer from '@/app/components/base/drawer-plus' diff --git a/web/app/components/app/annotation/empty-element.spec.tsx b/web/app/components/app/annotation/empty-element.spec.tsx index 3f96e917fd..89ba7e9ff8 100644 --- a/web/app/components/app/annotation/empty-element.spec.tsx +++ b/web/app/components/app/annotation/empty-element.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import EmptyElement from './empty-element' describe('EmptyElement', () => { diff --git a/web/app/components/app/annotation/empty-element.tsx b/web/app/components/app/annotation/empty-element.tsx index 523f83a7cb..4f41a59f4f 100644 --- a/web/app/components/app/annotation/empty-element.tsx +++ b/web/app/components/app/annotation/empty-element.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, SVGProps } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const ThreeDotsIcon = ({ className }: SVGProps<SVGElement>) => { diff --git a/web/app/components/app/annotation/filter.spec.tsx b/web/app/components/app/annotation/filter.spec.tsx index 3dfc60d73f..9b733a8c10 100644 --- a/web/app/components/app/annotation/filter.spec.tsx +++ b/web/app/components/app/annotation/filter.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import type { QueryParam } from './filter' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import useSWR from 'swr' import Filter from './filter' diff --git a/web/app/components/app/annotation/filter.tsx b/web/app/components/app/annotation/filter.tsx index 02c2a859bf..76f33d2f1b 100644 --- a/web/app/components/app/annotation/filter.tsx +++ b/web/app/components/app/annotation/filter.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import Input from '@/app/components/base/input' diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index a6c9d71391..6d2d365808 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -7,7 +7,8 @@ import { RiDeleteBinLine, RiMoreFill, } from '@remixicon/react' -import React, { Fragment, useEffect, useState } from 'react' +import * as React from 'react' +import { Fragment, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader, diff --git a/web/app/components/app/annotation/index.spec.tsx b/web/app/components/app/annotation/index.spec.tsx index 202b631f65..2d989a9a59 100644 --- a/web/app/components/app/annotation/index.spec.tsx +++ b/web/app/components/app/annotation/index.spec.tsx @@ -2,7 +2,7 @@ import type { Mock } from 'vitest' import type { AnnotationItem } from './type' import type { App } from '@/types/app' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Toast from '@/app/components/base/toast' import { useProviderContext } from '@/context/provider-context' import { diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index c673db2c28..18175193db 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -6,7 +6,8 @@ import type { AnnotationReplyConfig } from '@/models/debug' import type { App } from '@/types/app' import { RiEqualizer2Line } from '@remixicon/react' import { useDebounce } from 'ahooks' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import ConfigParamModal from '@/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal' diff --git a/web/app/components/app/annotation/list.spec.tsx b/web/app/components/app/annotation/list.spec.tsx index f0a8aa9d93..37e4832740 100644 --- a/web/app/components/app/annotation/list.spec.tsx +++ b/web/app/components/app/annotation/list.spec.tsx @@ -1,6 +1,6 @@ import type { AnnotationItem } from './type' import { fireEvent, render, screen, within } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import List from './list' const mockFormatTime = vi.fn(() => 'formatted-time') diff --git a/web/app/components/app/annotation/list.tsx b/web/app/components/app/annotation/list.tsx index 45e33b1a1a..4d821aa994 100644 --- a/web/app/components/app/annotation/list.tsx +++ b/web/app/components/app/annotation/list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { AnnotationItem } from './type' import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/app/annotation/remove-annotation-confirm-modal/index.spec.tsx b/web/app/components/app/annotation/remove-annotation-confirm-modal/index.spec.tsx index e26ea691c6..db3bb63c43 100644 --- a/web/app/components/app/annotation/remove-annotation-confirm-modal/index.spec.tsx +++ b/web/app/components/app/annotation/remove-annotation-confirm-modal/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import RemoveAnnotationConfirmModal from './index' vi.mock('react-i18next', () => ({ diff --git a/web/app/components/app/annotation/remove-annotation-confirm-modal/index.tsx b/web/app/components/app/annotation/remove-annotation-confirm-modal/index.tsx index a6ade49a79..bf21c95d8a 100644 --- a/web/app/components/app/annotation/remove-annotation-confirm-modal/index.tsx +++ b/web/app/components/app/annotation/remove-annotation-confirm-modal/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/annotation/view-annotation-modal/hit-history-no-data.tsx b/web/app/components/app/annotation/view-annotation-modal/hit-history-no-data.tsx index cebb5630eb..52f360fecc 100644 --- a/web/app/components/app/annotation/view-annotation-modal/hit-history-no-data.tsx +++ b/web/app/components/app/annotation/view-annotation-modal/hit-history-no-data.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { ClockFastForward } from '@/app/components/base/icons/src/vender/line/time' diff --git a/web/app/components/app/annotation/view-annotation-modal/index.spec.tsx b/web/app/components/app/annotation/view-annotation-modal/index.spec.tsx index 9fe8e585f4..3eb278b874 100644 --- a/web/app/components/app/annotation/view-annotation-modal/index.spec.tsx +++ b/web/app/components/app/annotation/view-annotation-modal/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import type { AnnotationItem, HitHistoryItem } from '../type' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { fetchHitHistoryList } from '@/service/annotation' import ViewAnnotationModal from './index' diff --git a/web/app/components/app/annotation/view-annotation-modal/index.tsx b/web/app/components/app/annotation/view-annotation-modal/index.tsx index f2db6de7c0..8f24a830bb 100644 --- a/web/app/components/app/annotation/view-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/view-annotation-modal/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { AnnotationItem, HitHistoryItem } from '../type' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/app-publisher/features-wrapper.tsx b/web/app/components/app/app-publisher/features-wrapper.tsx index 35d9728728..8257b69fca 100644 --- a/web/app/components/app/app-publisher/features-wrapper.tsx +++ b/web/app/components/app/app-publisher/features-wrapper.tsx @@ -2,7 +2,8 @@ import type { AppPublisherProps } from '@/app/components/app/app-publisher' import type { ModelAndParameter } from '@/app/components/app/configuration/debug/types' import type { FileUpload } from '@/app/components/base/features/types' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppPublisher from '@/app/components/app/app-publisher' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/app-publisher/version-info-modal.tsx b/web/app/components/app/app-publisher/version-info-modal.tsx index 647dc57f36..ba8b7a3074 100644 --- a/web/app/components/app/app-publisher/version-info-modal.tsx +++ b/web/app/components/app/app-publisher/version-info-modal.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { VersionHistory } from '@/types/workflow' import { RiCloseLine } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/app/configuration/base/feature-panel/index.tsx b/web/app/components/app/configuration/base/feature-panel/index.tsx index b69de31ac4..20c4a8dc17 100644 --- a/web/app/components/app/configuration/base/feature-panel/index.tsx +++ b/web/app/components/app/configuration/base/feature-panel/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' export type IFeaturePanelProps = { diff --git a/web/app/components/app/configuration/base/group-name/index.tsx b/web/app/components/app/configuration/base/group-name/index.tsx index 1ae3107876..b21b0c5825 100644 --- a/web/app/components/app/configuration/base/group-name/index.tsx +++ b/web/app/components/app/configuration/base/group-name/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' export type IGroupNameProps = { name: string diff --git a/web/app/components/app/configuration/base/operation-btn/index.tsx b/web/app/components/app/configuration/base/operation-btn/index.tsx index 2deaba3743..b9f55de26b 100644 --- a/web/app/components/app/configuration/base/operation-btn/index.tsx +++ b/web/app/components/app/configuration/base/operation-btn/index.tsx @@ -5,7 +5,7 @@ import { RiEditLine, } from '@remixicon/react' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/base/var-highlight/index.tsx b/web/app/components/app/configuration/base/var-highlight/index.tsx index b2360751c2..697007d0b0 100644 --- a/web/app/components/app/configuration/base/var-highlight/index.tsx +++ b/web/app/components/app/configuration/base/var-highlight/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import s from './style.module.css' diff --git a/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.spec.tsx b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.spec.tsx index 4ca20f7117..730b251e67 100644 --- a/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.spec.tsx +++ b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CannotQueryDataset from './cannot-query-dataset' describe('CannotQueryDataset WarningMask', () => { diff --git a/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx index 2c916eaf3b..baa7782b12 100644 --- a/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx +++ b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import WarningMask from '.' diff --git a/web/app/components/app/configuration/base/warning-mask/formatting-changed.spec.tsx b/web/app/components/app/configuration/base/warning-mask/formatting-changed.spec.tsx index 3171e2c183..9b5a5d93e1 100644 --- a/web/app/components/app/configuration/base/warning-mask/formatting-changed.spec.tsx +++ b/web/app/components/app/configuration/base/warning-mask/formatting-changed.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import FormattingChanged from './formatting-changed' describe('FormattingChanged WarningMask', () => { diff --git a/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx b/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx index 24bb245b25..df7d8569f8 100644 --- a/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx +++ b/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import WarningMask from '.' diff --git a/web/app/components/app/configuration/base/warning-mask/has-not-set-api.spec.tsx b/web/app/components/app/configuration/base/warning-mask/has-not-set-api.spec.tsx index 24ba0aeb3b..be4377bfd9 100644 --- a/web/app/components/app/configuration/base/warning-mask/has-not-set-api.spec.tsx +++ b/web/app/components/app/configuration/base/warning-mask/has-not-set-api.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import HasNotSetAPI from './has-not-set-api' describe('HasNotSetAPI WarningMask', () => { diff --git a/web/app/components/app/configuration/base/warning-mask/has-not-set-api.tsx b/web/app/components/app/configuration/base/warning-mask/has-not-set-api.tsx index 001a7ff79e..7be3f2001d 100644 --- a/web/app/components/app/configuration/base/warning-mask/has-not-set-api.tsx +++ b/web/app/components/app/configuration/base/warning-mask/has-not-set-api.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import WarningMask from '.' diff --git a/web/app/components/app/configuration/base/warning-mask/index.spec.tsx b/web/app/components/app/configuration/base/warning-mask/index.spec.tsx index 9546a3e8d9..cb8ef0b678 100644 --- a/web/app/components/app/configuration/base/warning-mask/index.spec.tsx +++ b/web/app/components/app/configuration/base/warning-mask/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import WarningMask from './index' describe('WarningMask', () => { diff --git a/web/app/components/app/configuration/base/warning-mask/index.tsx b/web/app/components/app/configuration/base/warning-mask/index.tsx index 3d6fef72a4..6d6aeceb97 100644 --- a/web/app/components/app/configuration/base/warning-mask/index.tsx +++ b/web/app/components/app/configuration/base/warning-mask/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import s from './style.module.css' diff --git a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx index a5d97c47f1..15ba089d77 100644 --- a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx @@ -9,7 +9,7 @@ import { import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' import { produce } from 'immer' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var' diff --git a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.spec.tsx b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.spec.tsx index f732da3f95..360676f829 100644 --- a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import ConfirmAddVar from './index' vi.mock('../../base/var-highlight', () => ({ diff --git a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx index e8106a790a..6c149688f4 100644 --- a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx +++ b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import VarHighlight from '../../base/var-highlight' diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.spec.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.spec.tsx index a6033fcb60..e6532d26fc 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.spec.tsx @@ -1,6 +1,6 @@ import type { ConversationHistoriesRole } from '@/models/debug' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import EditModal from './edit-modal' vi.mock('@/app/components/base/modal', () => ({ diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.tsx index c21ddeb30e..741461d53a 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/edit-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { ConversationHistoriesRole } from '@/models/debug' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.spec.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.spec.tsx index 4d3da2afa4..c6f5b3ed19 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import HistoryPanel from './history-panel' const mockDocLink = vi.fn(() => 'doc-link') diff --git a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx index b4a133db94..d6df316201 100644 --- a/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx +++ b/web/app/components/app/configuration/config-prompt/conversation-history/history-panel.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Panel from '@/app/components/app/configuration/base/feature-panel' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' diff --git a/web/app/components/app/configuration/config-prompt/index.spec.tsx b/web/app/components/app/configuration/config-prompt/index.spec.tsx index f3992d2a05..ceb9cf3f42 100644 --- a/web/app/components/app/configuration/config-prompt/index.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/index.spec.tsx @@ -1,7 +1,7 @@ import type { IPromptProps } from './index' import type { PromptItem, PromptVariable } from '@/models/debug' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { MAX_PROMPT_MESSAGE_LENGTH } from '@/config' import ConfigContext from '@/context/debug-configuration' import { PromptRole } from '@/models/debug' diff --git a/web/app/components/app/configuration/config-prompt/index.tsx b/web/app/components/app/configuration/config-prompt/index.tsx index ab13c6fc70..0d30e0d863 100644 --- a/web/app/components/app/configuration/config-prompt/index.tsx +++ b/web/app/components/app/configuration/config-prompt/index.tsx @@ -6,7 +6,7 @@ import { RiAddLine, } from '@remixicon/react' import { produce } from 'immer' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AdvancedMessageInput from '@/app/components/app/configuration/config-prompt/advanced-prompt-input' diff --git a/web/app/components/app/configuration/config-prompt/message-type-selector.spec.tsx b/web/app/components/app/configuration/config-prompt/message-type-selector.spec.tsx index 5354beda8a..5eb1896dc6 100644 --- a/web/app/components/app/configuration/config-prompt/message-type-selector.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/message-type-selector.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { PromptRole } from '@/models/debug' import MessageTypeSelector from './message-type-selector' diff --git a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx index 5bc29da9e4..0b404eea90 100644 --- a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx +++ b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { useBoolean, useClickAway } from 'ahooks' -import React from 'react' +import * as React from 'react' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' import { PromptRole } from '@/models/debug' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.spec.tsx b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.spec.tsx index cfdff40559..abd95e7660 100644 --- a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.spec.tsx +++ b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' describe('PromptEditorHeightResizeWrap', () => { diff --git a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx index 985aa527b0..24c77c1dae 100644 --- a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx +++ b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useDebounceFn } from 'ahooks' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index 5d9d4abe1e..9b558b58c1 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -6,7 +6,8 @@ import type { GenRes } from '@/service/debug' import { useBoolean } from 'ahooks' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var' diff --git a/web/app/components/app/configuration/config-var/config-modal/field.tsx b/web/app/components/app/configuration/config-var/config-modal/field.tsx index 8fe612a82b..c7a9bbfa03 100644 --- a/web/app/components/app/configuration/config-var/config-modal/field.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/field.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index 155266469a..41f37b5895 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -4,7 +4,8 @@ import type { Item as SelectItem } from './type-select' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { InputVar, MoreInfo, UploadFileSetting } from '@/app/components/workflow/types' import { produce } from 'immer' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useStore as useAppStore } from '@/app/components/app/store' diff --git a/web/app/components/app/configuration/config-var/config-modal/type-select.tsx b/web/app/components/app/configuration/config-var/config-modal/type-select.tsx index f476887409..66ec5a2a69 100644 --- a/web/app/components/app/configuration/config-var/config-modal/type-select.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/type-select.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { InputVarType } from '@/app/components/workflow/types' import { ChevronDownIcon } from '@heroicons/react/20/solid' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import Badge from '@/app/components/base/badge' import { PortalToFollowElem, diff --git a/web/app/components/app/configuration/config-var/config-select/index.tsx b/web/app/components/app/configuration/config-var/config-select/index.tsx index 565cf77207..61bc8b7023 100644 --- a/web/app/components/app/configuration/config-var/config-select/index.tsx +++ b/web/app/components/app/configuration/config-var/config-select/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiAddLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-var/config-string/index.tsx b/web/app/components/app/configuration/config-var/config-string/index.tsx index 78f185bd85..fce687ac3e 100644 --- a/web/app/components/app/configuration/config-var/config-string/index.tsx +++ b/web/app/components/app/configuration/config-var/config-string/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import Input from '@/app/components/base/input' export type IConfigStringProps = { diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 092a7aee6c..7a2a86393a 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -5,7 +5,8 @@ import type { ExternalDataTool } from '@/models/common' import type { PromptVariable } from '@/models/debug' import { useBoolean } from 'ahooks' import { produce } from 'immer' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/configuration/config-var/input-type-icon.tsx b/web/app/components/app/configuration/config-var/input-type-icon.tsx index 3d6db27616..d79964ab70 100644 --- a/web/app/components/app/configuration/config-var/input-type-icon.tsx +++ b/web/app/components/app/configuration/config-var/input-type-icon.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development' import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon' import { InputVarType } from '@/app/components/workflow/types' diff --git a/web/app/components/app/configuration/config-var/modal-foot.tsx b/web/app/components/app/configuration/config-var/modal-foot.tsx index d1eed20a03..cad5bcb3e9 100644 --- a/web/app/components/app/configuration/config-var/modal-foot.tsx +++ b/web/app/components/app/configuration/config-var/modal-foot.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.spec.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.spec.tsx index b21d69bc8e..f34dd52f49 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.spec.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { InputVarType } from '@/app/components/workflow/types' import SelectTypeItem from './index' diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.tsx index 4f0c3ace9e..ccb958977c 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { InputVarType } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-var/select-var-type.tsx b/web/app/components/app/configuration/config-var/select-var-type.tsx index a74aeff45c..0c19aeb137 100644 --- a/web/app/components/app/configuration/config-var/select-var-type.tsx +++ b/web/app/components/app/configuration/config-var/select-var-type.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' import { ApiConnection } from '@/app/components/base/icons/src/vender/solid/development' diff --git a/web/app/components/app/configuration/config-var/var-item.tsx b/web/app/components/app/configuration/config-var/var-item.tsx index 633af6dc28..a4888db628 100644 --- a/web/app/components/app/configuration/config-var/var-item.tsx +++ b/web/app/components/app/configuration/config-var/var-item.tsx @@ -6,7 +6,8 @@ import { RiDraggable, RiEditLine, } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import Badge from '@/app/components/base/badge' import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-vision/index.spec.tsx b/web/app/components/app/configuration/config-vision/index.spec.tsx index 7fd1d448e3..5fc7648bea 100644 --- a/web/app/components/app/configuration/config-vision/index.spec.tsx +++ b/web/app/components/app/configuration/config-vision/index.spec.tsx @@ -3,7 +3,7 @@ import type { FeatureStoreState } from '@/app/components/base/features/store' import type { FileUpload } from '@/app/components/base/features/types' import { fireEvent, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { Resolution, TransferMethod } from '@/types/app' import ConfigVision from './index' diff --git a/web/app/components/app/configuration/config-vision/index.tsx b/web/app/components/app/configuration/config-vision/index.tsx index 6a73b9e545..e53cdd4dfd 100644 --- a/web/app/components/app/configuration/config-vision/index.tsx +++ b/web/app/components/app/configuration/config-vision/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' // import { Resolution } from '@/types/app' diff --git a/web/app/components/app/configuration/config-vision/param-config-content.tsx b/web/app/components/app/configuration/config-vision/param-config-content.tsx index ebb8befbb3..2de14b9b6d 100644 --- a/web/app/components/app/configuration/config-vision/param-config-content.tsx +++ b/web/app/components/app/configuration/config-vision/param-config-content.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { FileUpload } from '@/app/components/base/features/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import ParamItem from '@/app/components/base/param-item' diff --git a/web/app/components/app/configuration/config/agent-setting-button.spec.tsx b/web/app/components/app/configuration/config/agent-setting-button.spec.tsx index 1858a67a3b..963a671a23 100644 --- a/web/app/components/app/configuration/config/agent-setting-button.spec.tsx +++ b/web/app/components/app/configuration/config/agent-setting-button.spec.tsx @@ -1,7 +1,7 @@ import type { AgentConfig } from '@/models/debug' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { AgentStrategy } from '@/types/app' import AgentSettingButton from './agent-setting-button' diff --git a/web/app/components/app/configuration/config/agent-setting-button.tsx b/web/app/components/app/configuration/config/agent-setting-button.tsx index 332b25eb1e..c7c6ea417a 100644 --- a/web/app/components/app/configuration/config/agent-setting-button.tsx +++ b/web/app/components/app/configuration/config/agent-setting-button.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { AgentConfig } from '@/models/debug' import { RiSettings2Line } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import AgentSetting from './agent/agent-setting' diff --git a/web/app/components/app/configuration/config/agent/agent-setting/index.spec.tsx b/web/app/components/app/configuration/config/agent/agent-setting/index.spec.tsx index 5bb955842b..b3a9bd7abc 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/index.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/index.spec.tsx @@ -1,6 +1,6 @@ import type { AgentConfig } from '@/models/debug' import { act, fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { MAX_ITERATIONS_NUM } from '@/config' import AgentSetting from './index' diff --git a/web/app/components/app/configuration/config/agent/agent-setting/index.tsx b/web/app/components/app/configuration/config/agent/agent-setting/index.tsx index 1adab75ed1..dae2461876 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { AgentConfig } from '@/models/debug' import { RiCloseLine } from '@remixicon/react' import { useClickAway } from 'ahooks' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' diff --git a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.spec.tsx b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.spec.tsx index dad576c983..a4dcb7a6f3 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import ItemPanel from './item-panel' describe('AgentSetting/ItemPanel', () => { diff --git a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx index 2a6002632f..92d3239608 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.spec.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.spec.tsx index cf9dd79031..1625db97b8 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.spec.tsx @@ -12,7 +12,8 @@ import type { AgentTool } from '@/types/app' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import copy from 'copy-to-clipboard' -import React, { +import * as React from 'react' +import { useEffect, useMemo, useState, diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index f1e70e304f..dfbab1f6f2 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -11,7 +11,8 @@ import { } from '@remixicon/react' import copy from 'copy-to-clipboard' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Panel from '@/app/components/app/configuration/base/feature-panel' diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.spec.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.spec.tsx index 654cd627a2..e056baaa2f 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.spec.tsx @@ -1,7 +1,7 @@ import type { Tool, ToolParameter } from '@/app/components/tools/types' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { CollectionType } from '@/app/components/tools/types' import I18n from '@/context/i18n' import SettingBuiltInTool from './setting-built-in-tool' diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index 31cccbf39c..c59d7a3b6e 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -6,7 +6,8 @@ import { RiArrowLeftLine, RiCloseLine, } from '@remixicon/react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/app/configuration/config/agent/prompt-editor.tsx b/web/app/components/app/configuration/config/agent/prompt-editor.tsx index 6319cc1bc5..0a09609cca 100644 --- a/web/app/components/app/configuration/config/agent/prompt-editor.tsx +++ b/web/app/components/app/configuration/config/agent/prompt-editor.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { ExternalDataTool } from '@/models/common' import copy from 'copy-to-clipboard' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import s from '@/app/components/app/configuration/config-prompt/style.module.css' diff --git a/web/app/components/app/configuration/config/assistant-type-picker/index.spec.tsx b/web/app/components/app/configuration/config/assistant-type-picker/index.spec.tsx index 8436a132d6..86201e996d 100644 --- a/web/app/components/app/configuration/config/assistant-type-picker/index.spec.tsx +++ b/web/app/components/app/configuration/config/assistant-type-picker/index.spec.tsx @@ -1,7 +1,7 @@ import type { AgentConfig } from '@/models/debug' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { AgentStrategy } from '@/types/app' import AssistantTypePicker from './index' diff --git a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx index 0a283835fc..8c08e7c921 100644 --- a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx +++ b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { AgentConfig } from '@/models/debug' import { RiArrowDownSLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/app/configuration/config/automatic/automatic-btn.tsx b/web/app/components/app/configuration/config/automatic/automatic-btn.tsx index 86a16ba995..636c2577b1 100644 --- a/web/app/components/app/configuration/config/automatic/automatic-btn.tsx +++ b/web/app/components/app/configuration/config/automatic/automatic-btn.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiSparklingFill, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index bc5d0fc7de..46fcaee52b 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -16,7 +16,8 @@ import { RiUser2Line, } from '@remixicon/react' import { useBoolean, useSessionStorageState } from 'ahooks' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/configuration/config/automatic/idea-output.tsx b/web/app/components/app/configuration/config/automatic/idea-output.tsx index 560d9be15b..2d91683ac8 100644 --- a/web/app/components/app/configuration/config/automatic/idea-output.tsx +++ b/web/app/components/app/configuration/config/automatic/idea-output.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' import Textarea from '@/app/components/base/textarea' diff --git a/web/app/components/app/configuration/config/automatic/instruction-editor-in-workflow.tsx b/web/app/components/app/configuration/config/automatic/instruction-editor-in-workflow.tsx index 2c6e09302c..31a96c5818 100644 --- a/web/app/components/app/configuration/config/automatic/instruction-editor-in-workflow.tsx +++ b/web/app/components/app/configuration/config/automatic/instruction-editor-in-workflow.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { GeneratorType } from './types' import type { ValueSelector, Var } from '@/app/components/workflow/types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useWorkflowVariableType } from '@/app/components/workflow/hooks' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import { useWorkflowStore } from '@/app/components/workflow/store' diff --git a/web/app/components/app/configuration/config/automatic/instruction-editor.tsx b/web/app/components/app/configuration/config/automatic/instruction-editor.tsx index e42d027061..77d6e9b56c 100644 --- a/web/app/components/app/configuration/config/automatic/instruction-editor.tsx +++ b/web/app/components/app/configuration/config/automatic/instruction-editor.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { GeneratorType } from './types' import type { Node, NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import PromptEditor from '@/app/components/base/prompt-editor' import { PROMPT_EDITOR_INSERT_QUICKLY } from '@/app/components/base/prompt-editor/plugins/update-block' diff --git a/web/app/components/app/configuration/config/automatic/prompt-res-in-workflow.tsx b/web/app/components/app/configuration/config/automatic/prompt-res-in-workflow.tsx index 80cd357eb9..71d0c95d1d 100644 --- a/web/app/components/app/configuration/config/automatic/prompt-res-in-workflow.tsx +++ b/web/app/components/app/configuration/config/automatic/prompt-res-in-workflow.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import { Type } from '@/app/components/workflow/nodes/llm/types' diff --git a/web/app/components/app/configuration/config/automatic/prompt-res.tsx b/web/app/components/app/configuration/config/automatic/prompt-res.tsx index 8a0e85aab7..ced438a18a 100644 --- a/web/app/components/app/configuration/config/automatic/prompt-res.tsx +++ b/web/app/components/app/configuration/config/automatic/prompt-res.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { WorkflowVariableBlockType } from '@/app/components/base/prompt-editor/types' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import PromptEditor from '@/app/components/base/prompt-editor' type Props = { diff --git a/web/app/components/app/configuration/config/automatic/prompt-toast.tsx b/web/app/components/app/configuration/config/automatic/prompt-toast.tsx index 2e53eb563c..65a78d1e67 100644 --- a/web/app/components/app/configuration/config/automatic/prompt-toast.tsx +++ b/web/app/components/app/configuration/config/automatic/prompt-toast.tsx @@ -1,6 +1,6 @@ import { RiArrowDownSLine, RiSparklingFill } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Markdown } from '@/app/components/base/markdown' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config/automatic/res-placeholder.tsx b/web/app/components/app/configuration/config/automatic/res-placeholder.tsx index 01d9043c82..80ab8e1f3f 100644 --- a/web/app/components/app/configuration/config/automatic/res-placeholder.tsx +++ b/web/app/components/app/configuration/config/automatic/res-placeholder.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Generator } from '@/app/components/base/icons/src/vender/other' diff --git a/web/app/components/app/configuration/config/automatic/result.tsx b/web/app/components/app/configuration/config/automatic/result.tsx index c2a4f55f80..b97975a6be 100644 --- a/web/app/components/app/configuration/config/automatic/result.tsx +++ b/web/app/components/app/configuration/config/automatic/result.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { GenRes } from '@/service/debug' import { RiClipboardLine } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/app/configuration/config/automatic/version-selector.tsx b/web/app/components/app/configuration/config/automatic/version-selector.tsx index fe8c26f2d5..5449f518a5 100644 --- a/web/app/components/app/configuration/config/automatic/version-selector.tsx +++ b/web/app/components/app/configuration/config/automatic/version-selector.tsx @@ -1,6 +1,7 @@ import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx b/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx index 9eb1acf77d..23ab46973f 100644 --- a/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx +++ b/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx @@ -5,7 +5,8 @@ import type { GenRes } from '@/service/debug' import type { AppModeEnum, CompletionParams, Model, ModelModeType } from '@/types/app' import { useSessionStorageState } from 'ahooks' import useBoolean from 'ahooks/lib/useBoolean' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/configuration/config/config-audio.spec.tsx b/web/app/components/app/configuration/config/config-audio.spec.tsx index 2ca34f9742..c29e2ac2b4 100644 --- a/web/app/components/app/configuration/config/config-audio.spec.tsx +++ b/web/app/components/app/configuration/config/config-audio.spec.tsx @@ -2,7 +2,7 @@ import type { Mock } from 'vitest' import type { FeatureStoreState } from '@/app/components/base/features/store' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import ConfigAudio from './config-audio' diff --git a/web/app/components/app/configuration/config/config-audio.tsx b/web/app/components/app/configuration/config/config-audio.tsx index 66a094804e..93cc0d5bae 100644 --- a/web/app/components/app/configuration/config/config-audio.tsx +++ b/web/app/components/app/configuration/config/config-audio.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/configuration/config/config-document.spec.tsx b/web/app/components/app/configuration/config/config-document.spec.tsx index 4d874dc37e..2aa87717fc 100644 --- a/web/app/components/app/configuration/config/config-document.spec.tsx +++ b/web/app/components/app/configuration/config/config-document.spec.tsx @@ -2,7 +2,7 @@ import type { Mock } from 'vitest' import type { FeatureStoreState } from '@/app/components/base/features/store' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import ConfigDocument from './config-document' diff --git a/web/app/components/app/configuration/config/config-document.tsx b/web/app/components/app/configuration/config/config-document.tsx index 5616c201c6..b2caf73397 100644 --- a/web/app/components/app/configuration/config/config-document.tsx +++ b/web/app/components/app/configuration/config/config-document.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/configuration/config/index.spec.tsx b/web/app/components/app/configuration/config/index.spec.tsx index 94361ba28c..25a112ec09 100644 --- a/web/app/components/app/configuration/config/index.spec.tsx +++ b/web/app/components/app/configuration/config/index.spec.tsx @@ -2,7 +2,7 @@ import type { Mock } from 'vitest' import type { ModelConfig, PromptVariable } from '@/models/debug' import type { ToolItem } from '@/types/app' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import * as useContextSelector from 'use-context-selector' import { AgentStrategy, AppModeEnum, ModelModeType } from '@/types/app' import Config from './index' diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx index 2b9d2ce44e..f208b99e59 100644 --- a/web/app/components/app/configuration/config/index.tsx +++ b/web/app/components/app/configuration/config/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { ModelConfig, PromptVariable } from '@/models/debug' import { produce } from 'immer' -import React from 'react' +import * as React from 'react' import { useContext } from 'use-context-selector' import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import ConfigVar from '@/app/components/app/configuration/config-var' diff --git a/web/app/components/app/configuration/ctrl-btn-group/index.tsx b/web/app/components/app/configuration/ctrl-btn-group/index.tsx index c955c497fb..efd3809201 100644 --- a/web/app/components/app/configuration/ctrl-btn-group/index.tsx +++ b/web/app/components/app/configuration/ctrl-btn-group/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import s from './style.module.css' diff --git a/web/app/components/app/configuration/dataset-config/card-item/index.spec.tsx b/web/app/components/app/configuration/dataset-config/card-item/index.spec.tsx index a69b0882d3..2e3cb47c98 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/index.spec.tsx @@ -1,4 +1,4 @@ -import type React from 'react' +import type * as React from 'react' import type { MockedFunction } from 'vitest' import type { IndexingType } from '@/app/components/datasets/create/step-two' import type { DataSet } from '@/models/datasets' diff --git a/web/app/components/app/configuration/dataset-config/card-item/index.tsx b/web/app/components/app/configuration/dataset-config/card-item/index.tsx index 223c594870..e0b50d1be3 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/index.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/index.tsx @@ -5,7 +5,8 @@ import { RiDeleteBinLine, RiEditLine, } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/app/configuration/dataset-config/context-var/index.tsx b/web/app/components/app/configuration/dataset-config/context-var/index.tsx index 4baa731f28..b2983c313d 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/index.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Props } from './var-picker' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { BracketsX } from '@/app/components/base/icons/src/vender/line/development' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx index 485e7aeb89..6f467afa84 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { IInputTypeIconProps } from '@/app/components/app/configuration/config-var/input-type-icon' import { ChevronDownIcon } from '@heroicons/react/24/outline' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import IconTypeIcon from '@/app/components/app/configuration/config-var/input-type-icon' import { diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index b954944a6e..9ac1729590 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -10,7 +10,8 @@ import type { import type { DataSet } from '@/models/datasets' import { produce } from 'immer' import { intersectionBy } from 'lodash-es' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { v4 as uuid4 } from 'uuid' diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 4e6bbe4a69..c1df954bde 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { DataSet } from '@/models/datasets' import { useInfiniteScroll } from 'ahooks' import Link from 'next/link' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Badge from '@/app/components/base/badge' diff --git a/web/app/components/app/configuration/debug/chat-user-input.tsx b/web/app/components/app/configuration/debug/chat-user-input.tsx index 2847f55307..3c65394301 100644 --- a/web/app/components/app/configuration/debug/chat-user-input.tsx +++ b/web/app/components/app/configuration/debug/chat-user-input.tsx @@ -1,5 +1,6 @@ import type { Inputs } from '@/models/debug' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Input from '@/app/components/base/input' diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 875e3bc35f..fe1c6550f5 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -14,7 +14,8 @@ import { useBoolean } from 'ahooks' import { produce, setAutoFreeze } from 'immer' import { noop } from 'lodash-es' import cloneDeep from 'lodash-es/cloneDeep' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 9f9baea042..eb7a9f5a32 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -23,7 +23,8 @@ import { useBoolean, useGetState } from 'ahooks' import { produce } from 'immer' import { clone, isEqual } from 'lodash-es' import { usePathname } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' diff --git a/web/app/components/app/configuration/prompt-value-panel/index.tsx b/web/app/components/app/configuration/prompt-value-panel/index.tsx index 8d006e8b2b..0b9388c664 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -7,7 +7,8 @@ import { RiArrowRightSLine, RiPlayLargeFill, } from '@remixicon/react' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useStore as useAppStore } from '@/app/components/app/store' diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 0af1b347af..df54de2ff1 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -5,7 +5,8 @@ import type { App } from '@/models/explore' import { RiRobot2Line } from '@remixicon/react' import { useDebounceFn } from 'ahooks' import { useRouter } from 'next/navigation' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx index 73043643c7..cef288acfc 100644 --- a/web/app/components/app/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx @@ -4,7 +4,8 @@ import { RiDeleteBinLine, RiUploadCloud2Line, } from '@remixicon/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/app/duplicate-modal/index.spec.tsx b/web/app/components/app/duplicate-modal/index.spec.tsx index 7a8c33d0d2..f214f8e343 100644 --- a/web/app/components/app/duplicate-modal/index.spec.tsx +++ b/web/app/components/app/duplicate-modal/index.spec.tsx @@ -1,7 +1,7 @@ import type { ProviderContextState } from '@/context/provider-context' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import Toast from '@/app/components/base/toast' import { Plan } from '@/app/components/billing/type' import { baseProviderContextValue } from '@/context/provider-context' diff --git a/web/app/components/app/duplicate-modal/index.tsx b/web/app/components/app/duplicate-modal/index.tsx index 39b7d87304..420a6b159a 100644 --- a/web/app/components/app/duplicate-modal/index.tsx +++ b/web/app/components/app/duplicate-modal/index.tsx @@ -2,7 +2,8 @@ import type { AppIconType } from '@/types/app' import { RiCloseLine } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/log-annotation/index.tsx b/web/app/components/app/log-annotation/index.tsx index fd2a730ca1..27a70c29e7 100644 --- a/web/app/components/app/log-annotation/index.tsx +++ b/web/app/components/app/log-annotation/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useRouter } from 'next/navigation' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Annotation from '@/app/components/app/annotation' import Log from '@/app/components/app/log' diff --git a/web/app/components/app/log/empty-element.tsx b/web/app/components/app/log/empty-element.tsx index e19bc6e90e..792684587c 100644 --- a/web/app/components/app/log/empty-element.tsx +++ b/web/app/components/app/log/empty-element.tsx @@ -2,7 +2,7 @@ import type { FC, SVGProps } from 'react' import type { App } from '@/types/app' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { AppModeEnum } from '@/types/app' import { getRedirectionPath } from '@/utils/app-redirection' diff --git a/web/app/components/app/log/filter.tsx b/web/app/components/app/log/filter.tsx index 34c39d822a..8984ff3494 100644 --- a/web/app/components/app/log/filter.tsx +++ b/web/app/components/app/log/filter.tsx @@ -4,7 +4,7 @@ import type { QueryParam } from './index' import { RiCalendarLine } from '@remixicon/react' import dayjs from 'dayjs' import quarterOfYear from 'dayjs/plugin/quarterOfYear' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import Chip from '@/app/components/base/chip' diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx index 4ac9a577a9..183826464f 100644 --- a/web/app/components/app/log/index.tsx +++ b/web/app/components/app/log/index.tsx @@ -5,7 +5,8 @@ import { useDebounce } from 'ahooks' import dayjs from 'dayjs' import { omit } from 'lodash-es' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index c29a47123c..06cd20b323 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -14,7 +14,8 @@ import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' import { get, noop } from 'lodash-es' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { createContext, useContext } from 'use-context-selector' diff --git a/web/app/components/app/log/model-info.tsx b/web/app/components/app/log/model-info.tsx index 8094b74c28..c89ea61e18 100644 --- a/web/app/components/app/log/model-info.tsx +++ b/web/app/components/app/log/model-info.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiInformation2Line, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/app/log/var-panel.tsx b/web/app/components/app/log/var-panel.tsx index f95f2de571..f41737dec3 100644 --- a/web/app/components/app/log/var-panel.tsx +++ b/web/app/components/app/log/var-panel.tsx @@ -5,7 +5,8 @@ import { RiArrowRightSLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import ImagePreview from '@/app/components/base/image-uploader/image-preview' diff --git a/web/app/components/app/overview/apikey-info-panel/index.tsx b/web/app/components/app/overview/apikey-info-panel/index.tsx index e1a4ea0891..77e0eb99c2 100644 --- a/web/app/components/app/overview/apikey-info-panel/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index 7b33a05b5e..ac43828c36 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -15,7 +15,8 @@ import { RiWindowLine, } from '@remixicon/react' import { usePathname, useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppBasic from '@/app/components/app-sidebar/basic' import { useStore as useAppStore } from '@/app/components/app/store' diff --git a/web/app/components/app/overview/app-chart.tsx b/web/app/components/app/overview/app-chart.tsx index 3c2a3734d0..d876dbda27 100644 --- a/web/app/components/app/overview/app-chart.tsx +++ b/web/app/components/app/overview/app-chart.tsx @@ -7,7 +7,7 @@ import dayjs from 'dayjs' import Decimal from 'decimal.js' import ReactECharts from 'echarts-for-react' import { get } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Basic from '@/app/components/app-sidebar/basic' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/app/overview/customize/index.tsx b/web/app/components/app/overview/customize/index.tsx index 9dd3e3816a..453c91f51b 100644 --- a/web/app/components/app/overview/customize/index.tsx +++ b/web/app/components/app/overview/customize/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index 92ea8bc49a..5cad0b79fa 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -4,7 +4,8 @@ import { RiClipboardLine, } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context' diff --git a/web/app/components/app/overview/settings/index.tsx b/web/app/components/app/overview/settings/index.tsx index 6464f40309..c75c6fe53e 100644 --- a/web/app/components/app/overview/settings/index.tsx +++ b/web/app/components/app/overview/settings/index.tsx @@ -5,7 +5,8 @@ import type { AppDetailResponse } from '@/models/app' import type { AppIconType, AppSSO, Language } from '@/types/app' import { RiArrowRightSLine, RiCloseLine } from '@remixicon/react' import Link from 'next/link' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/app/overview/trigger-card.tsx b/web/app/components/app/overview/trigger-card.tsx index 74b8e4100c..bcff6cb844 100644 --- a/web/app/components/app/overview/trigger-card.tsx +++ b/web/app/components/app/overview/trigger-card.tsx @@ -3,7 +3,7 @@ import type { AppDetailResponse } from '@/models/app' import type { AppTrigger } from '@/service/use-tools' import type { AppSSO } from '@/types/app' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/app/switch-app-modal/index.spec.tsx b/web/app/components/app/switch-app-modal/index.spec.tsx index 1f3c787dfa..abb8dcca2a 100644 --- a/web/app/components/app/switch-app-modal/index.spec.tsx +++ b/web/app/components/app/switch-app-modal/index.spec.tsx @@ -1,7 +1,7 @@ import type { App } from '@/types/app' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { ToastContext } from '@/app/components/base/toast' import { Plan } from '@/app/components/billing/type' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index 320bb10301..faa8c73999 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -17,7 +17,8 @@ import { import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' import { useParams } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' diff --git a/web/app/components/app/text-generate/saved-items/index.tsx b/web/app/components/app/text-generate/saved-items/index.tsx index 8e065d095e..59a4db8fcc 100644 --- a/web/app/components/app/text-generate/saved-items/index.tsx +++ b/web/app/components/app/text-generate/saved-items/index.tsx @@ -6,7 +6,7 @@ import { RiDeleteBinLine, } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { Markdown } from '@/app/components/base/markdown' diff --git a/web/app/components/app/text-generate/saved-items/no-data/index.tsx b/web/app/components/app/text-generate/saved-items/no-data/index.tsx index ed60372bab..e93d2f5275 100644 --- a/web/app/components/app/text-generate/saved-items/no-data/index.tsx +++ b/web/app/components/app/text-generate/saved-items/no-data/index.tsx @@ -4,7 +4,7 @@ import { RiAddLine, RiBookmark3Line, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/type-selector/index.spec.tsx b/web/app/components/app/type-selector/index.spec.tsx index 0fb51e40a9..e24d963305 100644 --- a/web/app/components/app/type-selector/index.spec.tsx +++ b/web/app/components/app/type-selector/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen, within } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { AppModeEnum } from '@/types/app' import AppTypeSelector, { AppTypeIcon, AppTypeLabel } from './index' diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx index 07e4b3f343..2e5a8286ab 100644 --- a/web/app/components/app/type-selector/index.tsx +++ b/web/app/components/app/type-selector/index.tsx @@ -1,5 +1,6 @@ import { RiArrowDownSLine, RiCloseCircleFill, RiExchange2Fill, RiFilter3Line } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { BubbleTextMod, ChatBot, ListSparkle, Logic } from '@/app/components/base/icons/src/vender/solid/communication' import { diff --git a/web/app/components/app/workflow-log/filter.tsx b/web/app/components/app/workflow-log/filter.tsx index 21e956ed50..9e3b213deb 100644 --- a/web/app/components/app/workflow-log/filter.tsx +++ b/web/app/components/app/workflow-log/filter.tsx @@ -4,7 +4,7 @@ import type { QueryParam } from './index' import { RiCalendarLine } from '@remixicon/react' import dayjs from 'dayjs' import quarterOfYear from 'dayjs/plugin/quarterOfYear' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude/utils' import Chip from '@/app/components/base/chip' diff --git a/web/app/components/app/workflow-log/index.tsx b/web/app/components/app/workflow-log/index.tsx index 14751ac809..1390f2d435 100644 --- a/web/app/components/app/workflow-log/index.tsx +++ b/web/app/components/app/workflow-log/index.tsx @@ -6,7 +6,8 @@ import dayjs from 'dayjs' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' import { omit } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import EmptyElement from '@/app/components/app/log/empty-element' diff --git a/web/app/components/app/workflow-log/list.tsx b/web/app/components/app/workflow-log/list.tsx index bafede0890..d3896e5227 100644 --- a/web/app/components/app/workflow-log/list.tsx +++ b/web/app/components/app/workflow-log/list.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunTriggeredFrom } from '@/models/log' import type { App } from '@/types/app' import { ArrowDownIcon } from '@heroicons/react/24/outline' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Drawer from '@/app/components/base/drawer' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/app/workflow-log/trigger-by-display.tsx b/web/app/components/app/workflow-log/trigger-by-display.tsx index 736a75841e..f243bcd2f1 100644 --- a/web/app/components/app/workflow-log/trigger-by-display.tsx +++ b/web/app/components/app/workflow-log/trigger-by-display.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { TriggerMetadata } from '@/models/log' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Code, diff --git a/web/app/components/apps/app-card.spec.tsx b/web/app/components/apps/app-card.spec.tsx index 60ee222837..b2afbabcb0 100644 --- a/web/app/components/apps/app-card.spec.tsx +++ b/web/app/components/apps/app-card.spec.tsx @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { AccessMode } from '@/models/access-control' // Mock API services - import for direct manipulation import * as appsService from '@/service/apps' @@ -23,18 +23,15 @@ vi.mock('next/navigation', () => ({ // Mock use-context-selector with stable mockNotify reference for tracking calls // Include createContext for components that use it (like Toast) const mockNotify = vi.fn() -vi.mock('use-context-selector', () => { - const React = require('react') - return { - createContext: (defaultValue: any) => React.createContext(defaultValue), - useContext: () => ({ - notify: mockNotify, - }), - useContextSelector: (_context: any, selector: any) => selector({ - notify: mockNotify, - }), - } -}) +vi.mock('use-context-selector', () => ({ + createContext: (defaultValue: any) => React.createContext(defaultValue), + useContext: () => ({ + notify: mockNotify, + }), + useContextSelector: (_context: any, selector: any) => selector({ + notify: mockNotify, + }), +})) // Mock app context vi.mock('@/context/app-context', () => ({ @@ -108,73 +105,70 @@ vi.mock('@/utils/time', () => ({ })) // Mock dynamic imports -vi.mock('next/dynamic', () => { - const React = require('react') - return { - default: (importFn: () => Promise<any>) => { - const fnString = importFn.toString() +vi.mock('next/dynamic', () => ({ + default: (importFn: () => Promise<any>) => { + const fnString = importFn.toString() - if (fnString.includes('create-app-modal') || fnString.includes('explore/create-app-modal')) { - return function MockEditAppModal({ show, onHide, onConfirm }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'edit-app-modal' }, React.createElement('button', { 'onClick': onHide, 'data-testid': 'close-edit-modal' }, 'Close'), React.createElement('button', { - 'onClick': () => onConfirm?.({ - name: 'Updated App', - icon_type: 'emoji', - icon: '🎯', - icon_background: '#FFEAD5', - description: 'Updated description', - use_icon_as_answer_icon: false, - max_active_requests: null, - }), - 'data-testid': 'confirm-edit-modal', - }, 'Confirm')) - } + if (fnString.includes('create-app-modal') || fnString.includes('explore/create-app-modal')) { + return function MockEditAppModal({ show, onHide, onConfirm }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'edit-app-modal' }, React.createElement('button', { 'onClick': onHide, 'data-testid': 'close-edit-modal' }, 'Close'), React.createElement('button', { + 'onClick': () => onConfirm?.({ + name: 'Updated App', + icon_type: 'emoji', + icon: '🎯', + icon_background: '#FFEAD5', + description: 'Updated description', + use_icon_as_answer_icon: false, + max_active_requests: null, + }), + 'data-testid': 'confirm-edit-modal', + }, 'Confirm')) } - if (fnString.includes('duplicate-modal')) { - return function MockDuplicateAppModal({ show, onHide, onConfirm }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'duplicate-modal' }, React.createElement('button', { 'onClick': onHide, 'data-testid': 'close-duplicate-modal' }, 'Close'), React.createElement('button', { - 'onClick': () => onConfirm?.({ - name: 'Copied App', - icon_type: 'emoji', - icon: '📋', - icon_background: '#E4FBCC', - }), - 'data-testid': 'confirm-duplicate-modal', - }, 'Confirm')) - } + } + if (fnString.includes('duplicate-modal')) { + return function MockDuplicateAppModal({ show, onHide, onConfirm }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'duplicate-modal' }, React.createElement('button', { 'onClick': onHide, 'data-testid': 'close-duplicate-modal' }, 'Close'), React.createElement('button', { + 'onClick': () => onConfirm?.({ + name: 'Copied App', + icon_type: 'emoji', + icon: '📋', + icon_background: '#E4FBCC', + }), + 'data-testid': 'confirm-duplicate-modal', + }, 'Confirm')) } - if (fnString.includes('switch-app-modal')) { - return function MockSwitchAppModal({ show, onClose, onSuccess }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'switch-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-switch-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'confirm-switch-modal' }, 'Switch')) - } + } + if (fnString.includes('switch-app-modal')) { + return function MockSwitchAppModal({ show, onClose, onSuccess }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'switch-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-switch-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'confirm-switch-modal' }, 'Switch')) } - if (fnString.includes('base/confirm')) { - return function MockConfirm({ isShow, onCancel, onConfirm }: any) { - if (!isShow) - return null - return React.createElement('div', { 'data-testid': 'confirm-dialog' }, React.createElement('button', { 'onClick': onCancel, 'data-testid': 'cancel-confirm' }, 'Cancel'), React.createElement('button', { 'onClick': onConfirm, 'data-testid': 'confirm-confirm' }, 'Confirm')) - } + } + if (fnString.includes('base/confirm')) { + return function MockConfirm({ isShow, onCancel, onConfirm }: any) { + if (!isShow) + return null + return React.createElement('div', { 'data-testid': 'confirm-dialog' }, React.createElement('button', { 'onClick': onCancel, 'data-testid': 'cancel-confirm' }, 'Cancel'), React.createElement('button', { 'onClick': onConfirm, 'data-testid': 'confirm-confirm' }, 'Confirm')) } - if (fnString.includes('dsl-export-confirm-modal')) { - return function MockDSLExportModal({ onClose, onConfirm }: any) { - return React.createElement('div', { 'data-testid': 'dsl-export-modal' }, React.createElement('button', { 'onClick': () => onClose?.(), 'data-testid': 'close-dsl-export' }, 'Close'), React.createElement('button', { 'onClick': () => onConfirm?.(true), 'data-testid': 'confirm-dsl-export' }, 'Export with secrets'), React.createElement('button', { 'onClick': () => onConfirm?.(false), 'data-testid': 'confirm-dsl-export-no-secrets' }, 'Export without secrets')) - } + } + if (fnString.includes('dsl-export-confirm-modal')) { + return function MockDSLExportModal({ onClose, onConfirm }: any) { + return React.createElement('div', { 'data-testid': 'dsl-export-modal' }, React.createElement('button', { 'onClick': () => onClose?.(), 'data-testid': 'close-dsl-export' }, 'Close'), React.createElement('button', { 'onClick': () => onConfirm?.(true), 'data-testid': 'confirm-dsl-export' }, 'Export with secrets'), React.createElement('button', { 'onClick': () => onConfirm?.(false), 'data-testid': 'confirm-dsl-export-no-secrets' }, 'Export without secrets')) } - if (fnString.includes('app-access-control')) { - return function MockAccessControl({ onClose, onConfirm }: any) { - return React.createElement('div', { 'data-testid': 'access-control-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-access-control' }, 'Close'), React.createElement('button', { 'onClick': onConfirm, 'data-testid': 'confirm-access-control' }, 'Confirm')) - } + } + if (fnString.includes('app-access-control')) { + return function MockAccessControl({ onClose, onConfirm }: any) { + return React.createElement('div', { 'data-testid': 'access-control-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-access-control' }, 'Close'), React.createElement('button', { 'onClick': onConfirm, 'data-testid': 'confirm-access-control' }, 'Confirm')) } - return () => null - }, - } -}) + } + return () => null + }, +})) // Popover uses @headlessui/react portals - mock for controlled interaction testing vi.mock('@/app/components/base/popover', () => { @@ -202,7 +196,6 @@ vi.mock('@/app/components/base/tooltip', () => ({ vi.mock('@/app/components/base/tag-management/selector', () => ({ __esModule: true, default: ({ tags }: any) => { - const React = require('react') return React.createElement('div', { 'aria-label': 'tag-selector' }, tags?.map((tag: any) => React.createElement('span', { key: tag.id }, tag.name))) }, })) diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index 0490c771b0..3a3b9d6153 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -9,7 +9,8 @@ import type { App } from '@/types/app' import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react' import dynamic from 'next/dynamic' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { AppTypeIcon } from '@/app/components/app/type-selector' diff --git a/web/app/components/apps/empty.spec.tsx b/web/app/components/apps/empty.spec.tsx index b7bd7f2d65..58a96f313a 100644 --- a/web/app/components/apps/empty.spec.tsx +++ b/web/app/components/apps/empty.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Empty from './empty' describe('Empty', () => { diff --git a/web/app/components/apps/empty.tsx b/web/app/components/apps/empty.tsx index 86e03a5fb4..b59efa9b35 100644 --- a/web/app/components/apps/empty.tsx +++ b/web/app/components/apps/empty.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const DefaultCards = React.memo(() => { diff --git a/web/app/components/apps/footer.spec.tsx b/web/app/components/apps/footer.spec.tsx index 89fe6fa471..d93869b480 100644 --- a/web/app/components/apps/footer.spec.tsx +++ b/web/app/components/apps/footer.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Footer from './footer' describe('Footer', () => { diff --git a/web/app/components/apps/footer.tsx b/web/app/components/apps/footer.tsx index 49ac7a0eb4..09ec7ee7c4 100644 --- a/web/app/components/apps/footer.tsx +++ b/web/app/components/apps/footer.tsx @@ -1,6 +1,6 @@ import { RiDiscordFill, RiDiscussLine, RiGithubFill } from '@remixicon/react' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type CustomLinkProps = { diff --git a/web/app/components/apps/index.spec.tsx b/web/app/components/apps/index.spec.tsx index 6bb53386a4..f518c5e039 100644 --- a/web/app/components/apps/index.spec.tsx +++ b/web/app/components/apps/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Import after mocks import Apps from './index' @@ -27,7 +27,6 @@ vi.mock('@/app/education-apply/hooks', () => ({ vi.mock('./list', () => ({ __esModule: true, default: () => { - const React = require('react') return React.createElement('div', { 'data-testid': 'apps-list' }, 'Apps List') }, })) diff --git a/web/app/components/apps/list.spec.tsx b/web/app/components/apps/list.spec.tsx index d39f2621c5..244a8d997d 100644 --- a/web/app/components/apps/list.spec.tsx +++ b/web/app/components/apps/list.spec.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { AppModeEnum } from '@/types/app' // Import after mocks @@ -141,7 +141,6 @@ let mockTagFilterOnChange: ((value: string[]) => void) | null = null vi.mock('@/app/components/base/tag-management/filter', () => ({ __esModule: true, default: ({ onChange }: { onChange: (value: string[]) => void }) => { - const React = require('react') mockTagFilterOnChange = onChange return React.createElement('div', { 'data-testid': 'tag-filter' }, 'common.tag.placeholder') }, @@ -161,7 +160,6 @@ vi.mock('@/hooks/use-pay', () => ({ vi.mock('ahooks', () => ({ useDebounceFn: (fn: () => void) => ({ run: fn }), useMount: (fn: () => void) => { - const React = require('react') const fnRef = React.useRef(fn) fnRef.current = fn React.useEffect(() => { @@ -171,28 +169,25 @@ vi.mock('ahooks', () => ({ })) // Mock dynamic imports -vi.mock('next/dynamic', () => { - const React = require('react') - return { - default: (importFn: () => Promise<any>) => { - const fnString = importFn.toString() +vi.mock('next/dynamic', () => ({ + default: (importFn: () => Promise<any>) => { + const fnString = importFn.toString() - if (fnString.includes('tag-management')) { - return function MockTagManagement() { - return React.createElement('div', { 'data-testid': 'tag-management-modal' }) - } + if (fnString.includes('tag-management')) { + return function MockTagManagement() { + return React.createElement('div', { 'data-testid': 'tag-management-modal' }) } - if (fnString.includes('create-from-dsl-modal')) { - return function MockCreateFromDSLModal({ show, onClose, onSuccess }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'create-dsl-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-dsl-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-dsl-modal' }, 'Success')) - } + } + if (fnString.includes('create-from-dsl-modal')) { + return function MockCreateFromDSLModal({ show, onClose, onSuccess }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'create-dsl-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-dsl-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-dsl-modal' }, 'Success')) } - return () => null - }, - } -}) + } + return () => null + }, +})) /** * Mock child components for focused List component testing. @@ -202,24 +197,19 @@ vi.mock('next/dynamic', () => { vi.mock('./app-card', () => ({ __esModule: true, default: ({ app }: any) => { - const React = require('react') return React.createElement('div', { 'data-testid': `app-card-${app.id}`, 'role': 'article' }, app.name) }, })) -vi.mock('./new-app-card', () => { - const React = require('react') - return { - default: React.forwardRef((_props: any, _ref: any) => { - return React.createElement('div', { 'data-testid': 'new-app-card', 'role': 'button' }, 'New App Card') - }), - } -}) +vi.mock('./new-app-card', () => ({ + default: React.forwardRef((_props: any, _ref: any) => { + return React.createElement('div', { 'data-testid': 'new-app-card', 'role': 'button' }, 'New App Card') + }), +})) vi.mock('./empty', () => ({ __esModule: true, default: () => { - const React = require('react') return React.createElement('div', { 'data-testid': 'empty-state', 'role': 'status' }, 'No apps found') }, })) @@ -227,7 +217,6 @@ vi.mock('./empty', () => ({ vi.mock('./footer', () => ({ __esModule: true, default: () => { - const React = require('react') return React.createElement('footer', { 'data-testid': 'footer', 'role': 'contentinfo' }, 'Footer') }, })) diff --git a/web/app/components/apps/new-app-card.spec.tsx b/web/app/components/apps/new-app-card.spec.tsx index bc8b10a2b6..92e769adc7 100644 --- a/web/app/components/apps/new-app-card.spec.tsx +++ b/web/app/components/apps/new-app-card.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' // Import after mocks import CreateAppCard from './new-app-card' @@ -22,37 +22,34 @@ vi.mock('@/context/provider-context', () => ({ })) // Mock next/dynamic to immediately resolve components -vi.mock('next/dynamic', () => { - const React = require('react') - return { - default: (importFn: () => Promise<any>) => { - const fnString = importFn.toString() +vi.mock('next/dynamic', () => ({ + default: (importFn: () => Promise<any>) => { + const fnString = importFn.toString() - if (fnString.includes('create-app-modal') && !fnString.includes('create-from-dsl-modal')) { - return function MockCreateAppModal({ show, onClose, onSuccess, onCreateFromTemplate }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'create-app-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-create-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-create-modal' }, 'Success'), React.createElement('button', { 'onClick': onCreateFromTemplate, 'data-testid': 'to-template-modal' }, 'To Template')) - } + if (fnString.includes('create-app-modal') && !fnString.includes('create-from-dsl-modal')) { + return function MockCreateAppModal({ show, onClose, onSuccess, onCreateFromTemplate }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'create-app-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-create-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-create-modal' }, 'Success'), React.createElement('button', { 'onClick': onCreateFromTemplate, 'data-testid': 'to-template-modal' }, 'To Template')) } - if (fnString.includes('create-app-dialog')) { - return function MockCreateAppTemplateDialog({ show, onClose, onSuccess, onCreateFromBlank }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'create-template-dialog' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-template-dialog' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-template-dialog' }, 'Success'), React.createElement('button', { 'onClick': onCreateFromBlank, 'data-testid': 'to-blank-modal' }, 'To Blank')) - } + } + if (fnString.includes('create-app-dialog')) { + return function MockCreateAppTemplateDialog({ show, onClose, onSuccess, onCreateFromBlank }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'create-template-dialog' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-template-dialog' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-template-dialog' }, 'Success'), React.createElement('button', { 'onClick': onCreateFromBlank, 'data-testid': 'to-blank-modal' }, 'To Blank')) } - if (fnString.includes('create-from-dsl-modal')) { - return function MockCreateFromDSLModal({ show, onClose, onSuccess }: any) { - if (!show) - return null - return React.createElement('div', { 'data-testid': 'create-dsl-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-dsl-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-dsl-modal' }, 'Success')) - } + } + if (fnString.includes('create-from-dsl-modal')) { + return function MockCreateFromDSLModal({ show, onClose, onSuccess }: any) { + if (!show) + return null + return React.createElement('div', { 'data-testid': 'create-dsl-modal' }, React.createElement('button', { 'onClick': onClose, 'data-testid': 'close-dsl-modal' }, 'Close'), React.createElement('button', { 'onClick': onSuccess, 'data-testid': 'success-dsl-modal' }, 'Success')) } - return () => null - }, - } -}) + } + return () => null + }, +})) // Mock CreateFromDSLModalTab enum vi.mock('@/app/components/app/create-from-dsl-modal', () => ({ diff --git a/web/app/components/apps/new-app-card.tsx b/web/app/components/apps/new-app-card.tsx index dd0918c746..2117b3d2d0 100644 --- a/web/app/components/apps/new-app-card.tsx +++ b/web/app/components/apps/new-app-card.tsx @@ -5,7 +5,8 @@ import { useRouter, useSearchParams, } from 'next/navigation' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal' import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' diff --git a/web/app/components/base/action-button/index.tsx b/web/app/components/base/action-button/index.tsx index 503847ba6b..0c86adb6db 100644 --- a/web/app/components/base/action-button/index.tsx +++ b/web/app/components/base/action-button/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' enum ActionButtonState { diff --git a/web/app/components/base/agent-log-modal/detail.tsx b/web/app/components/base/agent-log-modal/detail.tsx index c436e9e23a..5451126c9e 100644 --- a/web/app/components/base/agent-log-modal/detail.tsx +++ b/web/app/components/base/agent-log-modal/detail.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { IChatItem } from '@/app/components/base/chat/chat/type' import type { AgentIteration, AgentLogDetailResponse } from '@/models/log' import { flatten, uniq } from 'lodash-es' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useStore as useAppStore } from '@/app/components/app/store' diff --git a/web/app/components/base/amplitude/AmplitudeProvider.tsx b/web/app/components/base/amplitude/AmplitudeProvider.tsx index ec4d586d48..91c3713a07 100644 --- a/web/app/components/base/amplitude/AmplitudeProvider.tsx +++ b/web/app/components/base/amplitude/AmplitudeProvider.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import * as amplitude from '@amplitude/analytics-browser' import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { AMPLITUDE_API_KEY, IS_CLOUD_EDITION } from '@/config' export type IAmplitudeProps = { diff --git a/web/app/components/base/app-icon/index.tsx b/web/app/components/base/app-icon/index.tsx index 01d4530b4d..78624bab07 100644 --- a/web/app/components/base/app-icon/index.tsx +++ b/web/app/components/base/app-icon/index.tsx @@ -6,7 +6,8 @@ import { RiEditLine } from '@remixicon/react' import { useHover } from 'ahooks' import { cva } from 'class-variance-authority' import { init } from 'emoji-mart' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import { cn } from '@/utils/classnames' init({ data }) diff --git a/web/app/components/base/app-unavailable.tsx b/web/app/components/base/app-unavailable.tsx index 898f9dbd00..e25b8c9ca6 100644 --- a/web/app/components/base/app-unavailable.tsx +++ b/web/app/components/base/app-unavailable.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/audio-gallery/AudioPlayer.tsx b/web/app/components/base/audio-gallery/AudioPlayer.tsx index d1a245d193..859e27b08e 100644 --- a/web/app/components/base/audio-gallery/AudioPlayer.tsx +++ b/web/app/components/base/audio-gallery/AudioPlayer.tsx @@ -3,7 +3,8 @@ import { RiPlayLargeFill, } from '@remixicon/react' import { t } from 'i18next' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import Toast from '@/app/components/base/toast' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' diff --git a/web/app/components/base/audio-gallery/index.tsx b/web/app/components/base/audio-gallery/index.tsx index 2fdacab582..e5f0b7f46b 100644 --- a/web/app/components/base/audio-gallery/index.tsx +++ b/web/app/components/base/audio-gallery/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import AudioPlayer from './AudioPlayer' type Props = { diff --git a/web/app/components/base/badge/index.tsx b/web/app/components/base/badge/index.tsx index 663e40d2e5..9120b28ea6 100644 --- a/web/app/components/base/badge/index.tsx +++ b/web/app/components/base/badge/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties, ReactNode } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import './index.css' diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index 51f5097e7d..1b80b21059 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { ChangeEvent, FC } from 'react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { checkKeys } from '@/utils/var' diff --git a/web/app/components/base/button/add-button.tsx b/web/app/components/base/button/add-button.tsx index 762766239a..332b52daca 100644 --- a/web/app/components/base/button/add-button.tsx +++ b/web/app/components/base/button/add-button.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/base/button/index.spec.tsx b/web/app/components/base/button/index.spec.tsx index aa83070964..0377fa334f 100644 --- a/web/app/components/base/button/index.spec.tsx +++ b/web/app/components/base/button/index.spec.tsx @@ -1,5 +1,5 @@ import { cleanup, fireEvent, render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Button from './index' afterEach(cleanup) diff --git a/web/app/components/base/button/index.tsx b/web/app/components/base/button/index.tsx index 8d87174a3e..0de57617af 100644 --- a/web/app/components/base/button/index.tsx +++ b/web/app/components/base/button/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import Spinner from '../spinner' diff --git a/web/app/components/base/button/sync-button.tsx b/web/app/components/base/button/sync-button.tsx index 1e0a4a42f7..12c34026cb 100644 --- a/web/app/components/base/button/sync-button.tsx +++ b/web/app/components/base/button/sync-button.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiRefreshLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import TooltipPlus from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/chat/chat-with-history/header/operation.tsx b/web/app/components/base/chat/chat-with-history/header/operation.tsx index c279c7b785..fa239e198e 100644 --- a/web/app/components/base/chat/chat-with-history/header/operation.tsx +++ b/web/app/components/base/chat/chat-with-history/header/operation.tsx @@ -4,7 +4,8 @@ import type { FC } from 'react' import { RiArrowDownSLine, } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx index 105bbe52ea..0fd9a4cd9e 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx @@ -1,4 +1,5 @@ -import React, { memo, useCallback } from 'react' +import * as React from 'react' +import { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import Input from '@/app/components/base/input' diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx index d6e0a4003d..d3e52eb549 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content' diff --git a/web/app/components/base/chat/chat-with-history/sidebar/operation.tsx b/web/app/components/base/chat/chat-with-history/sidebar/operation.tsx index c3084c5536..feff8d6a13 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/operation.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/operation.tsx @@ -8,7 +8,8 @@ import { RiUnpinLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' diff --git a/web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx b/web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx index 2d6168e21f..a4f5d48316 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/rename-modal.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/base/chat/chat/citation/tooltip.tsx b/web/app/components/base/chat/chat/citation/tooltip.tsx index 24490a96c0..a4ac64c82f 100644 --- a/web/app/components/base/chat/chat/citation/tooltip.tsx +++ b/web/app/components/base/chat/chat/citation/tooltip.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/base/chat/chat/loading-anim/index.tsx b/web/app/components/base/chat/chat/loading-anim/index.tsx index 496937332d..74cc3444de 100644 --- a/web/app/components/base/chat/chat/loading-anim/index.tsx +++ b/web/app/components/base/chat/chat/loading-anim/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import s from './style.module.css' diff --git a/web/app/components/base/chat/chat/thought/index.tsx b/web/app/components/base/chat/chat/thought/index.tsx index f77208541b..00b8676cd8 100644 --- a/web/app/components/base/chat/chat/thought/index.tsx +++ b/web/app/components/base/chat/chat/thought/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { ThoughtItem, ToolInfoInThought } from '../type' -import React from 'react' +import * as React from 'react' import ToolDetail from '@/app/components/base/chat/chat/answer/tool-detail' export type IThoughtProps = { diff --git a/web/app/components/base/chat/embedded-chatbot/header/index.tsx b/web/app/components/base/chat/embedded-chatbot/header/index.tsx index 8d0b1dfd07..84f8691aff 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/index.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { Theme } from '../theme/theme-context' import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown' diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx index 9c17013196..0136453149 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx @@ -1,4 +1,5 @@ -import React, { memo, useCallback } from 'react' +import * as React from 'react' +import { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import Input from '@/app/components/base/input' diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx index 20585067c1..b54f261f3d 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import InputsFormContent from '@/app/components/base/chat/embedded-chatbot/inputs-form/content' diff --git a/web/app/components/base/confirm/index.tsx b/web/app/components/base/confirm/index.tsx index f9f4d3b1d7..590d28682b 100644 --- a/web/app/components/base/confirm/index.tsx +++ b/web/app/components/base/confirm/index.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import Button from '../button' diff --git a/web/app/components/base/copy-feedback/index.tsx b/web/app/components/base/copy-feedback/index.tsx index 788dcee89c..bf809a2d18 100644 --- a/web/app/components/base/copy-feedback/index.tsx +++ b/web/app/components/base/copy-feedback/index.tsx @@ -5,7 +5,8 @@ import { } from '@remixicon/react' import copy from 'copy-to-clipboard' import { debounce } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/base/copy-icon/index.tsx b/web/app/components/base/copy-icon/index.tsx index a7cb12c12b..935444a3c1 100644 --- a/web/app/components/base/copy-icon/index.tsx +++ b/web/app/components/base/copy-icon/index.tsx @@ -1,7 +1,8 @@ 'use client' import copy from 'copy-to-clipboard' import { debounce } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Copy, diff --git a/web/app/components/base/date-and-time-picker/calendar/days-of-week.tsx b/web/app/components/base/date-and-time-picker/calendar/days-of-week.tsx index 30aab7a30f..ac14d49ead 100644 --- a/web/app/components/base/date-and-time-picker/calendar/days-of-week.tsx +++ b/web/app/components/base/date-and-time-picker/calendar/days-of-week.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useDaysOfWeek } from '../hooks' export const DaysOfWeek = () => { diff --git a/web/app/components/base/date-and-time-picker/calendar/item.tsx b/web/app/components/base/date-and-time-picker/calendar/item.tsx index 8b6b33f96e..f79fed0879 100644 --- a/web/app/components/base/date-and-time-picker/calendar/item.tsx +++ b/web/app/components/base/date-and-time-picker/calendar/item.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { CalendarItemProps } from '../types' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import dayjs from '../utils/dayjs' diff --git a/web/app/components/base/date-and-time-picker/common/option-list-item.tsx b/web/app/components/base/date-and-time-picker/common/option-list-item.tsx index 916e4e3d88..040f30f686 100644 --- a/web/app/components/base/date-and-time-picker/common/option-list-item.tsx +++ b/web/app/components/base/date-and-time-picker/common/option-list-item.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useEffect, useRef } from 'react' +import * as React from 'react' +import { useEffect, useRef } from 'react' import { cn } from '@/utils/classnames' type OptionListItemProps = { diff --git a/web/app/components/base/date-and-time-picker/date-picker/footer.tsx b/web/app/components/base/date-and-time-picker/date-picker/footer.tsx index 44288b5109..d229564596 100644 --- a/web/app/components/base/date-and-time-picker/date-picker/footer.tsx +++ b/web/app/components/base/date-and-time-picker/date-picker/footer.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { DatePickerFooterProps } from '../types' import { RiTimeLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Button from '../../button' diff --git a/web/app/components/base/date-and-time-picker/date-picker/header.tsx b/web/app/components/base/date-and-time-picker/date-picker/header.tsx index 811a8387e2..4177edfe7a 100644 --- a/web/app/components/base/date-and-time-picker/date-picker/header.tsx +++ b/web/app/components/base/date-and-time-picker/date-picker/header.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { DatePickerHeaderProps } from '../types' import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useMonths } from '../hooks' const Header: FC<DatePickerHeaderProps> = ({ diff --git a/web/app/components/base/date-and-time-picker/date-picker/index.tsx b/web/app/components/base/date-and-time-picker/date-picker/index.tsx index 877b221dfc..75a3ec6144 100644 --- a/web/app/components/base/date-and-time-picker/date-picker/index.tsx +++ b/web/app/components/base/date-and-time-picker/date-picker/index.tsx @@ -1,7 +1,8 @@ import type { Dayjs } from 'dayjs' import type { DatePickerProps, Period } from '../types' import { RiCalendarLine, RiCloseCircleFill } from '@remixicon/react' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/base/date-and-time-picker/time-picker/footer.tsx b/web/app/components/base/date-and-time-picker/time-picker/footer.tsx index 41ab93e25e..74103fbf0b 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/footer.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/footer.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { TimePickerFooterProps } from '../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '../../button' diff --git a/web/app/components/base/date-and-time-picker/time-picker/header.tsx b/web/app/components/base/date-and-time-picker/time-picker/header.tsx index 3f74b8e20d..b5c7fb0bcb 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/header.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/header.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type Props = { diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx index 5489002210..596b1a69b6 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/index.spec.tsx @@ -1,6 +1,6 @@ import type { TimePickerProps } from '../types' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import dayjs, { isDayjsObject } from '../utils/dayjs' import TimePicker from './index' diff --git a/web/app/components/base/date-and-time-picker/time-picker/index.tsx b/web/app/components/base/date-and-time-picker/time-picker/index.tsx index 874146b457..3e0ff2d353 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/index.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/index.tsx @@ -1,7 +1,8 @@ import type { Dayjs } from 'dayjs' import type { TimePickerProps } from '../types' import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/base/date-and-time-picker/time-picker/options.tsx b/web/app/components/base/date-and-time-picker/time-picker/options.tsx index abd7e33b56..a4117f03fb 100644 --- a/web/app/components/base/date-and-time-picker/time-picker/options.tsx +++ b/web/app/components/base/date-and-time-picker/time-picker/options.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { TimeOptionsProps } from '../types' -import React from 'react' +import * as React from 'react' import OptionListItem from '../common/option-list-item' import { useTimeOptions } from '../hooks' diff --git a/web/app/components/base/date-and-time-picker/year-and-month-picker/footer.tsx b/web/app/components/base/date-and-time-picker/year-and-month-picker/footer.tsx index 883540a36b..b77ab0f8a7 100644 --- a/web/app/components/base/date-and-time-picker/year-and-month-picker/footer.tsx +++ b/web/app/components/base/date-and-time-picker/year-and-month-picker/footer.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { YearAndMonthPickerFooterProps } from '../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '../../button' diff --git a/web/app/components/base/date-and-time-picker/year-and-month-picker/header.tsx b/web/app/components/base/date-and-time-picker/year-and-month-picker/header.tsx index 8e1af95550..043598884a 100644 --- a/web/app/components/base/date-and-time-picker/year-and-month-picker/header.tsx +++ b/web/app/components/base/date-and-time-picker/year-and-month-picker/header.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { YearAndMonthPickerHeaderProps } from '../types' import { RiArrowUpSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useMonths } from '../hooks' const Header: FC<YearAndMonthPickerHeaderProps> = ({ diff --git a/web/app/components/base/date-and-time-picker/year-and-month-picker/options.tsx b/web/app/components/base/date-and-time-picker/year-and-month-picker/options.tsx index e0f5f5387f..9b0d602a3e 100644 --- a/web/app/components/base/date-and-time-picker/year-and-month-picker/options.tsx +++ b/web/app/components/base/date-and-time-picker/year-and-month-picker/options.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { YearAndMonthPickerOptionsProps } from '../types' -import React from 'react' +import * as React from 'react' import OptionListItem from '../common/option-list-item' import { useMonths, useYearOptions } from '../hooks' diff --git a/web/app/components/base/divider/index.tsx b/web/app/components/base/divider/index.tsx index 1a34be7397..cde3189ed0 100644 --- a/web/app/components/base/divider/index.tsx +++ b/web/app/components/base/divider/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties, FC } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' const dividerVariants = cva('', { diff --git a/web/app/components/base/drawer-plus/index.tsx b/web/app/components/base/drawer-plus/index.tsx index 71f4372d35..0b59997885 100644 --- a/web/app/components/base/drawer-plus/index.tsx +++ b/web/app/components/base/drawer-plus/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/drawer/index.spec.tsx b/web/app/components/base/drawer/index.spec.tsx index dae574332c..51cf0fa55c 100644 --- a/web/app/components/base/drawer/index.spec.tsx +++ b/web/app/components/base/drawer/index.spec.tsx @@ -1,6 +1,6 @@ import type { IDrawerProps } from './index' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Drawer from './index' // Capture dialog onClose for testing diff --git a/web/app/components/base/effect/index.tsx b/web/app/components/base/effect/index.tsx index 85fc5a7cd8..dd3415dc07 100644 --- a/web/app/components/base/effect/index.tsx +++ b/web/app/components/base/effect/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type EffectProps = { diff --git a/web/app/components/base/emoji-picker/Inner.tsx b/web/app/components/base/emoji-picker/Inner.tsx index 4add3de9ba..f125cfa63b 100644 --- a/web/app/components/base/emoji-picker/Inner.tsx +++ b/web/app/components/base/emoji-picker/Inner.tsx @@ -8,7 +8,8 @@ import { MagnifyingGlassIcon, } from '@heroicons/react/24/outline' import { init } from 'emoji-mart' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import Divider from '@/app/components/base/divider' import Input from '@/app/components/base/input' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index 0c04f25fb5..53bef278f6 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { noop } from 'lodash-es' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/base/error-boundary/index.tsx b/web/app/components/base/error-boundary/index.tsx index d72621a754..b041bba4d6 100644 --- a/web/app/components/base/error-boundary/index.tsx +++ b/web/app/components/base/error-boundary/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { ErrorInfo, ReactNode } from 'react' import { RiAlertLine, RiBugLine } from '@remixicon/react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import Button from '@/app/components/base/button' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx index 5d562315b1..fbeffa2422 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx @@ -4,7 +4,7 @@ import { RiEditLine, RiFileEditLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx index 5e46e4d478..9050da6194 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { AnnotationReplyConfig } from '@/models/debug' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/config-param.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/config-param.tsx index f1a92f5628..9642aed0a8 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/config-param.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/config-param.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Tooltip from '@/app/components/base/tooltip' export const Item: FC<{ title: string, tooltip: string, children: React.JSX.Element }> = ({ diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/index.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/index.tsx index b6d0235bfd..6acd6cad70 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/index.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/index.tsx @@ -3,7 +3,8 @@ import type { AnnotationReplyConfig } from '@/models/debug' import { RiEqualizer2Line, RiExternalLinkLine } from '@remixicon/react' import { produce } from 'immer' import { usePathname, useRouter } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/index.tsx b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/index.tsx index d04b56b228..a5fd2b75bf 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/index.tsx +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/score-slider/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Slider from '@/app/components/base/features/new-feature-panel/annotation-reply/score-slider/base-slider' diff --git a/web/app/components/base/features/new-feature-panel/annotation-reply/use-annotation-config.ts b/web/app/components/base/features/new-feature-panel/annotation-reply/use-annotation-config.ts index bd4de98daa..c74175846d 100644 --- a/web/app/components/base/features/new-feature-panel/annotation-reply/use-annotation-config.ts +++ b/web/app/components/base/features/new-feature-panel/annotation-reply/use-annotation-config.ts @@ -1,7 +1,8 @@ import type { EmbeddingModelConfig } from '@/app/components/app/annotation/type' import type { AnnotationReplyConfig } from '@/models/debug' import { produce } from 'immer' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { AnnotationEnableStatus, JobStatus } from '@/app/components/app/annotation/type' import { ANNOTATION_DEFAULT } from '@/config' import { useProviderContext } from '@/context/provider-context' diff --git a/web/app/components/base/features/new-feature-panel/citation.tsx b/web/app/components/base/features/new-feature-panel/citation.tsx index 321958bea4..e8854176f9 100644 --- a/web/app/components/base/features/new-feature-panel/citation.tsx +++ b/web/app/components/base/features/new-feature-panel/citation.tsx @@ -1,6 +1,7 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx index 0fef89fc33..3568ce64f5 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/index.tsx @@ -3,7 +3,8 @@ import type { InputVar } from '@/app/components/workflow/types' import type { PromptVariable } from '@/models/debug' import { RiEditLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx index cda5a25ae0..a1b66ae0fc 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -5,7 +5,8 @@ import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from import { useBoolean } from 'ahooks' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var' diff --git a/web/app/components/base/features/new-feature-panel/feature-bar.tsx b/web/app/components/base/features/new-feature-panel/feature-bar.tsx index 585c2d923b..94b278c5de 100644 --- a/web/app/components/base/features/new-feature-panel/feature-bar.tsx +++ b/web/app/components/base/features/new-feature-panel/feature-bar.tsx @@ -1,5 +1,6 @@ import { RiApps2AddLine, RiArrowRightLine, RiSparklingFill } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/feature-card.tsx b/web/app/components/base/features/new-feature-panel/feature-card.tsx index b2b530e8bd..7b7327517b 100644 --- a/web/app/components/base/features/new-feature-panel/feature-card.tsx +++ b/web/app/components/base/features/new-feature-panel/feature-card.tsx @@ -1,7 +1,7 @@ import { RiQuestionLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/base/features/new-feature-panel/file-upload/index.tsx b/web/app/components/base/features/new-feature-panel/file-upload/index.tsx index 66f138037c..1af70ddb19 100644 --- a/web/app/components/base/features/new-feature-panel/file-upload/index.tsx +++ b/web/app/components/base/features/new-feature-panel/file-upload/index.tsx @@ -1,7 +1,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { RiEqualizer2Line } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx b/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx index caa9a99e22..da306a6ac9 100644 --- a/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx +++ b/web/app/components/base/features/new-feature-panel/file-upload/setting-content.tsx @@ -2,7 +2,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import type { UploadFileSetting } from '@/app/components/workflow/types' import { RiCloseLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/follow-up.tsx b/web/app/components/base/features/new-feature-panel/follow-up.tsx index ad651ab1e0..d45476c680 100644 --- a/web/app/components/base/features/new-feature-panel/follow-up.tsx +++ b/web/app/components/base/features/new-feature-panel/follow-up.tsx @@ -1,6 +1,7 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' diff --git a/web/app/components/base/features/new-feature-panel/image-upload/index.tsx b/web/app/components/base/features/new-feature-panel/image-upload/index.tsx index 043d061512..9c2990fb72 100644 --- a/web/app/components/base/features/new-feature-panel/image-upload/index.tsx +++ b/web/app/components/base/features/new-feature-panel/image-upload/index.tsx @@ -1,7 +1,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { RiEqualizer2Line, RiImage2Fill } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import Button from '@/app/components/base/button' diff --git a/web/app/components/base/features/new-feature-panel/index.tsx b/web/app/components/base/features/new-feature-panel/index.tsx index 8ecbb2e42f..b6d260f03d 100644 --- a/web/app/components/base/features/new-feature-panel/index.tsx +++ b/web/app/components/base/features/new-feature-panel/index.tsx @@ -2,7 +2,7 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import type { InputVar } from '@/app/components/workflow/types' import type { PromptVariable } from '@/models/debug' import { RiCloseLine, RiInformation2Fill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AnnotationReply from '@/app/components/base/features/new-feature-panel/annotation-reply' diff --git a/web/app/components/base/features/new-feature-panel/moderation/index.tsx b/web/app/components/base/features/new-feature-panel/moderation/index.tsx index 9fdb684f1b..d1b11aa8c8 100644 --- a/web/app/components/base/features/new-feature-panel/moderation/index.tsx +++ b/web/app/components/base/features/new-feature-panel/moderation/index.tsx @@ -1,7 +1,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { RiEqualizer2Line } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/base/features/new-feature-panel/more-like-this.tsx b/web/app/components/base/features/new-feature-panel/more-like-this.tsx index 4e2fbe07ac..98eb82c885 100644 --- a/web/app/components/base/features/new-feature-panel/more-like-this.tsx +++ b/web/app/components/base/features/new-feature-panel/more-like-this.tsx @@ -1,7 +1,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { RiSparklingFill } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' diff --git a/web/app/components/base/features/new-feature-panel/speech-to-text.tsx b/web/app/components/base/features/new-feature-panel/speech-to-text.tsx index 136f7f32fa..6e9a6b06e7 100644 --- a/web/app/components/base/features/new-feature-panel/speech-to-text.tsx +++ b/web/app/components/base/features/new-feature-panel/speech-to-text.tsx @@ -1,6 +1,7 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import FeatureCard from '@/app/components/base/features/new-feature-panel/feature-card' diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech/index.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech/index.tsx index 482a3634cc..96a0eaa5ff 100644 --- a/web/app/components/base/features/new-feature-panel/text-to-speech/index.tsx +++ b/web/app/components/base/features/new-feature-panel/text-to-speech/index.tsx @@ -1,7 +1,8 @@ import type { OnFeaturesChange } from '@/app/components/base/features/types' import { RiEqualizer2Line } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx index 53117d7999..fc1052e172 100644 --- a/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx +++ b/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx @@ -6,7 +6,8 @@ import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' import { RiCloseLine } from '@remixicon/react' import { produce } from 'immer' import { usePathname } from 'next/navigation' -import React, { Fragment } from 'react' +import * as React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import AudioBtn from '@/app/components/base/audio-btn' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/file-thumb/image-render.tsx b/web/app/components/base/file-thumb/image-render.tsx index f21f0a84e5..bda092ffc3 100644 --- a/web/app/components/base/file-thumb/image-render.tsx +++ b/web/app/components/base/file-thumb/image-render.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' type ImageRenderProps = { sourceUrl: string diff --git a/web/app/components/base/file-thumb/index.tsx b/web/app/components/base/file-thumb/index.tsx index e3601c445a..09f88d5d3b 100644 --- a/web/app/components/base/file-thumb/index.tsx +++ b/web/app/components/base/file-thumb/index.tsx @@ -1,6 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' import { FileTypeIcon } from '../file-uploader' import { getFileAppearanceType } from '../file-uploader/utils' diff --git a/web/app/components/base/file-uploader/audio-preview.tsx b/web/app/components/base/file-uploader/audio-preview.tsx index 80ccc6225c..e8be22fc9f 100644 --- a/web/app/components/base/file-uploader/audio-preview.tsx +++ b/web/app/components/base/file-uploader/audio-preview.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { createPortal } from 'react-dom' import { useHotkeys } from 'react-hotkeys-hook' diff --git a/web/app/components/base/file-uploader/file-list-in-log.tsx b/web/app/components/base/file-uploader/file-list-in-log.tsx index d35e6e096a..279341c2bb 100644 --- a/web/app/components/base/file-uploader/file-list-in-log.tsx +++ b/web/app/components/base/file-uploader/file-list-in-log.tsx @@ -1,6 +1,7 @@ import type { FileEntity } from './types' import { RiArrowRightSLine } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { SupportUploadFileTypes } from '@/app/components/workflow/types' diff --git a/web/app/components/base/file-uploader/pdf-preview.tsx b/web/app/components/base/file-uploader/pdf-preview.tsx index 30aeccfbeb..04a90a414c 100644 --- a/web/app/components/base/file-uploader/pdf-preview.tsx +++ b/web/app/components/base/file-uploader/pdf-preview.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiCloseLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react' import { t } from 'i18next' import { noop } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useHotkeys } from 'react-hotkeys-hook' import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter' diff --git a/web/app/components/base/file-uploader/video-preview.tsx b/web/app/components/base/file-uploader/video-preview.tsx index ef0c5c17b1..94d9a94c58 100644 --- a/web/app/components/base/file-uploader/video-preview.tsx +++ b/web/app/components/base/file-uploader/video-preview.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { createPortal } from 'react-dom' import { useHotkeys } from 'react-hotkeys-hook' diff --git a/web/app/components/base/form/components/field/file-uploader.tsx b/web/app/components/base/form/components/field/file-uploader.tsx index 278e8e0fa5..b498cfb951 100644 --- a/web/app/components/base/form/components/field/file-uploader.tsx +++ b/web/app/components/base/form/components/field/file-uploader.tsx @@ -1,7 +1,7 @@ import type { FileUploaderInAttachmentWrapperProps } from '../../../file-uploader/file-uploader-in-attachment' import type { FileEntity } from '../../../file-uploader/types' import type { LabelProps } from '../label' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { useFieldContext } from '../..' import FileUploaderInAttachmentWrapper from '../../../file-uploader/file-uploader-in-attachment' diff --git a/web/app/components/base/form/components/field/input-type-select/option.tsx b/web/app/components/base/form/components/field/input-type-select/option.tsx index f0b9ee3068..d678a3e93d 100644 --- a/web/app/components/base/form/components/field/input-type-select/option.tsx +++ b/web/app/components/base/form/components/field/input-type-select/option.tsx @@ -1,5 +1,5 @@ import type { FileTypeSelectOption } from './types' -import React from 'react' +import * as React from 'react' import Badge from '@/app/components/base/badge' type OptionProps = { diff --git a/web/app/components/base/form/components/field/input-type-select/trigger.tsx b/web/app/components/base/form/components/field/input-type-select/trigger.tsx index bdd6796f70..eb26a49be9 100644 --- a/web/app/components/base/form/components/field/input-type-select/trigger.tsx +++ b/web/app/components/base/form/components/field/input-type-select/trigger.tsx @@ -1,6 +1,6 @@ import type { FileTypeSelectOption } from './types' import { RiArrowDownSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/form/components/field/number-input.tsx b/web/app/components/base/form/components/field/number-input.tsx index dcc4fd6663..a7844983ae 100644 --- a/web/app/components/base/form/components/field/number-input.tsx +++ b/web/app/components/base/form/components/field/number-input.tsx @@ -1,6 +1,6 @@ import type { InputNumberProps } from '../../../input-number' import type { LabelProps } from '../label' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { useFieldContext } from '../..' import { InputNumber } from '../../../input-number' diff --git a/web/app/components/base/form/components/field/text-area.tsx b/web/app/components/base/form/components/field/text-area.tsx index 16979d3748..295482e56a 100644 --- a/web/app/components/base/form/components/field/text-area.tsx +++ b/web/app/components/base/form/components/field/text-area.tsx @@ -1,6 +1,6 @@ import type { TextareaProps } from '../../../textarea' import type { LabelProps } from '../label' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { useFieldContext } from '../..' import Textarea from '../../../textarea' diff --git a/web/app/components/base/form/components/field/text.tsx b/web/app/components/base/form/components/field/text.tsx index 470033b58a..c9b46919a4 100644 --- a/web/app/components/base/form/components/field/text.tsx +++ b/web/app/components/base/form/components/field/text.tsx @@ -1,6 +1,6 @@ import type { InputProps } from '../../../input' import type { LabelProps } from '../label' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { useFieldContext } from '../..' import Input from '../../../input' diff --git a/web/app/components/base/form/form-scenarios/base/field.tsx b/web/app/components/base/form/form-scenarios/base/field.tsx index d41499bbb2..6fc59d9062 100644 --- a/web/app/components/base/form/form-scenarios/base/field.tsx +++ b/web/app/components/base/form/form-scenarios/base/field.tsx @@ -1,6 +1,6 @@ import type { BaseConfiguration } from './types' import { useStore } from '@tanstack/react-form' -import React from 'react' +import * as React from 'react' import { withForm } from '../..' import { BaseFieldType } from './types' diff --git a/web/app/components/base/form/form-scenarios/base/index.tsx b/web/app/components/base/form/form-scenarios/base/index.tsx index 4f154cc2e5..af552e4a81 100644 --- a/web/app/components/base/form/form-scenarios/base/index.tsx +++ b/web/app/components/base/form/form-scenarios/base/index.tsx @@ -1,5 +1,6 @@ import type { BaseFormProps } from './types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useAppForm } from '../..' import BaseField from './field' import { generateZodSchema } from './utils' diff --git a/web/app/components/base/form/form-scenarios/input-field/field.tsx b/web/app/components/base/form/form-scenarios/input-field/field.tsx index 0892cc2f45..e82ae8f914 100644 --- a/web/app/components/base/form/form-scenarios/input-field/field.tsx +++ b/web/app/components/base/form/form-scenarios/input-field/field.tsx @@ -1,6 +1,6 @@ import type { InputFieldConfiguration } from './types' import { useStore } from '@tanstack/react-form' -import React from 'react' +import * as React from 'react' import { withForm } from '../..' import { InputFieldType } from './types' diff --git a/web/app/components/base/form/form-scenarios/node-panel/field.tsx b/web/app/components/base/form/form-scenarios/node-panel/field.tsx index 5e2755a14c..6597d8fa32 100644 --- a/web/app/components/base/form/form-scenarios/node-panel/field.tsx +++ b/web/app/components/base/form/form-scenarios/node-panel/field.tsx @@ -1,6 +1,6 @@ import type { InputFieldConfiguration } from './types' import { useStore } from '@tanstack/react-form' -import React from 'react' +import * as React from 'react' import { withForm } from '../..' import { InputFieldType } from './types' diff --git a/web/app/components/base/ga/index.tsx b/web/app/components/base/ga/index.tsx index 91614c34e0..2d5fe101d0 100644 --- a/web/app/components/base/ga/index.tsx +++ b/web/app/components/base/ga/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { headers } from 'next/headers' import Script from 'next/script' -import React from 'react' +import * as React from 'react' import { IS_CE_EDITION } from '@/config' export enum GaType { diff --git a/web/app/components/base/icons/IconBase.spec.tsx b/web/app/components/base/icons/IconBase.spec.tsx index badbbc5212..ba5efd8429 100644 --- a/web/app/components/base/icons/IconBase.spec.tsx +++ b/web/app/components/base/icons/IconBase.spec.tsx @@ -1,6 +1,6 @@ import type { IconData } from './IconBase' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import IconBase from './IconBase' import * as utils from './utils' diff --git a/web/app/components/base/icons/icon-gallery.stories.tsx b/web/app/components/base/icons/icon-gallery.stories.tsx index de7d251a1f..55322a7ea3 100644 --- a/web/app/components/base/icons/icon-gallery.stories.tsx +++ b/web/app/components/base/icons/icon-gallery.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/nextjs' -import React from 'react' +import * as React from 'react' declare const require: any diff --git a/web/app/components/base/icons/utils.ts b/web/app/components/base/icons/utils.ts index 76be8ecc46..9a15a0816d 100644 --- a/web/app/components/base/icons/utils.ts +++ b/web/app/components/base/icons/utils.ts @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' export type AbstractNode = { name: string diff --git a/web/app/components/base/image-gallery/index.tsx b/web/app/components/base/image-gallery/index.tsx index 438fcc5b11..8ca8be0fc7 100644 --- a/web/app/components/base/image-gallery/index.tsx +++ b/web/app/components/base/image-gallery/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import ImagePreview from '@/app/components/base/image-uploader/image-preview' import { cn } from '@/utils/classnames' import s from './style.module.css' diff --git a/web/app/components/base/image-uploader/image-preview.tsx b/web/app/components/base/image-uploader/image-preview.tsx index 37170fe1d9..bfabe5e247 100644 --- a/web/app/components/base/image-uploader/image-preview.tsx +++ b/web/app/components/base/image-uploader/image-preview.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiAddBoxLine, RiCloseLine, RiDownloadCloud2Line, RiFileCopyLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react' import { t } from 'i18next' import { noop } from 'lodash-es' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { useHotkeys } from 'react-hotkeys-hook' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/base/inline-delete-confirm/index.spec.tsx b/web/app/components/base/inline-delete-confirm/index.spec.tsx index d42e482775..8b360929b5 100644 --- a/web/app/components/base/inline-delete-confirm/index.spec.tsx +++ b/web/app/components/base/inline-delete-confirm/index.spec.tsx @@ -1,5 +1,5 @@ import { cleanup, fireEvent, render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import InlineDeleteConfirm from './index' // Mock react-i18next diff --git a/web/app/components/base/input-with-copy/index.spec.tsx b/web/app/components/base/input-with-copy/index.spec.tsx index b90e52adb9..782b2bab25 100644 --- a/web/app/components/base/input-with-copy/index.spec.tsx +++ b/web/app/components/base/input-with-copy/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import InputWithCopy from './index' // Create a mock function that we can track using vi.hoisted diff --git a/web/app/components/base/input-with-copy/index.tsx b/web/app/components/base/input-with-copy/index.tsx index 57848cb6bd..151fa435e7 100644 --- a/web/app/components/base/input-with-copy/index.tsx +++ b/web/app/components/base/input-with-copy/index.tsx @@ -3,7 +3,8 @@ import type { InputProps } from '../input' import { RiClipboardFill, RiClipboardLine } from '@remixicon/react' import copy from 'copy-to-clipboard' import { debounce } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import ActionButton from '../action-button' diff --git a/web/app/components/base/input/index.spec.tsx b/web/app/components/base/input/index.spec.tsx index 7409559df1..226217a146 100644 --- a/web/app/components/base/input/index.spec.tsx +++ b/web/app/components/base/input/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Input, { inputVariants } from './index' // Mock the i18n hook diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index 948c15456d..98529a26bc 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -3,7 +3,7 @@ import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react' import { cva } from 'class-variance-authority' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { CopyFeedbackNew } from '../copy-feedback' diff --git a/web/app/components/base/linked-apps-panel/index.tsx b/web/app/components/base/linked-apps-panel/index.tsx index 00a562046a..adc8ccf729 100644 --- a/web/app/components/base/linked-apps-panel/index.tsx +++ b/web/app/components/base/linked-apps-panel/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { RelatedApp } from '@/models/datasets' import { RiArrowRightUpLine } from '@remixicon/react' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import AppIcon from '@/app/components/base/app-icon' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/list-empty/index.tsx b/web/app/components/base/list-empty/index.tsx index ab1c968682..6d728e3aec 100644 --- a/web/app/components/base/list-empty/index.tsx +++ b/web/app/components/base/list-empty/index.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import { Variable02 } from '../icons/src/vender/solid/development' import HorizontalLine from './horizontal-line' import VerticalLine from './vertical-line' diff --git a/web/app/components/base/loading/index.spec.tsx b/web/app/components/base/loading/index.spec.tsx index 0003247586..5140f1216b 100644 --- a/web/app/components/base/loading/index.spec.tsx +++ b/web/app/components/base/loading/index.spec.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Loading from './index' describe('Loading Component', () => { diff --git a/web/app/components/base/loading/index.tsx b/web/app/components/base/loading/index.tsx index 385d59b599..072b761833 100644 --- a/web/app/components/base/loading/index.tsx +++ b/web/app/components/base/loading/index.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import './style.css' diff --git a/web/app/components/base/markdown-blocks/audio-block.tsx b/web/app/components/base/markdown-blocks/audio-block.tsx index 09001f105b..8633c908d5 100644 --- a/web/app/components/base/markdown-blocks/audio-block.tsx +++ b/web/app/components/base/markdown-blocks/audio-block.tsx @@ -3,7 +3,8 @@ * Extracted from the main markdown renderer for modularity. * Uses the AudioGallery component to display audio players. */ -import React, { memo } from 'react' +import * as React from 'react' +import { memo } from 'react' import AudioGallery from '@/app/components/base/audio-gallery' const AudioBlock: any = memo(({ node }: any) => { diff --git a/web/app/components/base/markdown-blocks/form.tsx b/web/app/components/base/markdown-blocks/form.tsx index 19872738c7..2ccf216538 100644 --- a/web/app/components/base/markdown-blocks/form.tsx +++ b/web/app/components/base/markdown-blocks/form.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import Button from '@/app/components/base/button' import { useChatContext } from '@/app/components/base/chat/chat/context' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/base/markdown-blocks/img.tsx b/web/app/components/base/markdown-blocks/img.tsx index 33fce13f0b..57182828a2 100644 --- a/web/app/components/base/markdown-blocks/img.tsx +++ b/web/app/components/base/markdown-blocks/img.tsx @@ -3,7 +3,7 @@ * Extracted from the main markdown renderer for modularity. * Uses the ImageGallery component to display images. */ -import React from 'react' +import * as React from 'react' import ImageGallery from '@/app/components/base/image-gallery' const Img = ({ src }: any) => { diff --git a/web/app/components/base/markdown-blocks/link.tsx b/web/app/components/base/markdown-blocks/link.tsx index ec2518990e..717352609f 100644 --- a/web/app/components/base/markdown-blocks/link.tsx +++ b/web/app/components/base/markdown-blocks/link.tsx @@ -3,7 +3,7 @@ * Extracted from the main markdown renderer for modularity. * Handles special rendering for "abbr:" type links for interactive chat actions. */ -import React from 'react' +import * as React from 'react' import { useChatContext } from '@/app/components/base/chat/chat/context' import { isValidUrl } from './utils' diff --git a/web/app/components/base/markdown-blocks/paragraph.tsx b/web/app/components/base/markdown-blocks/paragraph.tsx index fb1612477a..adef509a31 100644 --- a/web/app/components/base/markdown-blocks/paragraph.tsx +++ b/web/app/components/base/markdown-blocks/paragraph.tsx @@ -3,7 +3,7 @@ * Extracted from the main markdown renderer for modularity. * Handles special rendering for paragraphs that directly contain an image. */ -import React from 'react' +import * as React from 'react' import ImageGallery from '@/app/components/base/image-gallery' const Paragraph = (paragraph: any) => { diff --git a/web/app/components/base/markdown-blocks/plugin-img.tsx b/web/app/components/base/markdown-blocks/plugin-img.tsx index 486f5900d2..259f49ca9b 100644 --- a/web/app/components/base/markdown-blocks/plugin-img.tsx +++ b/web/app/components/base/markdown-blocks/plugin-img.tsx @@ -4,7 +4,8 @@ import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' * Extracted from the main markdown renderer for modularity. * Uses the ImageGallery component to display images. */ -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import ImageGallery from '@/app/components/base/image-gallery' import { usePluginReadmeAsset } from '@/service/use-plugins' import { getMarkdownImageURL } from './utils' diff --git a/web/app/components/base/markdown-blocks/plugin-paragraph.tsx b/web/app/components/base/markdown-blocks/plugin-paragraph.tsx index dca4ee52a2..73189289f3 100644 --- a/web/app/components/base/markdown-blocks/plugin-paragraph.tsx +++ b/web/app/components/base/markdown-blocks/plugin-paragraph.tsx @@ -1,5 +1,6 @@ import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' /** * @fileoverview Paragraph component for rendering <p> tags in Markdown. * Extracted from the main markdown renderer for modularity. diff --git a/web/app/components/base/markdown-blocks/pre-code.tsx b/web/app/components/base/markdown-blocks/pre-code.tsx index 9d10c42d3c..efce56a158 100644 --- a/web/app/components/base/markdown-blocks/pre-code.tsx +++ b/web/app/components/base/markdown-blocks/pre-code.tsx @@ -3,7 +3,8 @@ * Extracted from the main markdown renderer for modularity. * This is a simple wrapper around the HTML <pre> element. */ -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' function PreCode(props: { children: any }) { const ref = useRef<HTMLPreElement>(null) diff --git a/web/app/components/base/markdown-blocks/think-block.tsx b/web/app/components/base/markdown-blocks/think-block.tsx index 20d2c5cc05..a2b0b3c293 100644 --- a/web/app/components/base/markdown-blocks/think-block.tsx +++ b/web/app/components/base/markdown-blocks/think-block.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { useChatContext } from '../chat/chat/context' diff --git a/web/app/components/base/markdown-blocks/video-block.tsx b/web/app/components/base/markdown-blocks/video-block.tsx index 9f1a36f678..13ea490499 100644 --- a/web/app/components/base/markdown-blocks/video-block.tsx +++ b/web/app/components/base/markdown-blocks/video-block.tsx @@ -3,7 +3,8 @@ * Extracted from the main markdown renderer for modularity. * Uses the VideoGallery component to display videos. */ -import React, { memo } from 'react' +import * as React from 'react' +import { memo } from 'react' import VideoGallery from '@/app/components/base/video-gallery' const VideoBlock: any = memo(({ node }: any) => { diff --git a/web/app/components/base/markdown/error-boundary.tsx b/web/app/components/base/markdown/error-boundary.tsx index 9347491aea..fd11af5b1c 100644 --- a/web/app/components/base/markdown/error-boundary.tsx +++ b/web/app/components/base/markdown/error-boundary.tsx @@ -5,7 +5,8 @@ * logs those errors, and displays a fallback UI instead of the crashed component tree. * Primarily used around complex rendering logic like ECharts or SVG within Markdown. */ -import React, { Component } from 'react' +import * as React from 'react' +import { Component } from 'react' // **Add an ECharts runtime error handler // Avoid error #7832 (Crash when ECharts accesses undefined objects) // This can happen when a component attempts to access an undefined object that references an unregistered map, causing the program to crash. diff --git a/web/app/components/base/mermaid/index.tsx b/web/app/components/base/mermaid/index.tsx index 540df44a56..7721b8316e 100644 --- a/web/app/components/base/mermaid/index.tsx +++ b/web/app/components/base/mermaid/index.tsx @@ -2,7 +2,8 @@ import type { MermaidConfig } from 'mermaid' import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' import { MoonIcon, SunIcon } from '@heroicons/react/24/solid' import mermaid from 'mermaid' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' import ImagePreview from '@/app/components/base/image-uploader/image-preview' diff --git a/web/app/components/base/modal-like-wrap/index.tsx b/web/app/components/base/modal-like-wrap/index.tsx index 65c2873582..4c45ff2f7c 100644 --- a/web/app/components/base/modal-like-wrap/index.tsx +++ b/web/app/components/base/modal-like-wrap/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Button from '../button' diff --git a/web/app/components/base/node-status/index.tsx b/web/app/components/base/node-status/index.tsx index 118ab7181a..3c39fa1fb3 100644 --- a/web/app/components/base/node-status/index.tsx +++ b/web/app/components/base/node-status/index.tsx @@ -3,7 +3,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties } from 'react' import { RiErrorWarningFill } from '@remixicon/react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import AlertTriangle from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertTriangle' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/notion-connector/index.tsx b/web/app/components/base/notion-connector/index.tsx index 48c4b3a584..cec3a9076d 100644 --- a/web/app/components/base/notion-connector/index.tsx +++ b/web/app/components/base/notion-connector/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '../button' import { Notion } from '../icons/src/public/common' diff --git a/web/app/components/base/notion-page-selector/credential-selector/index.tsx b/web/app/components/base/notion-page-selector/credential-selector/index.tsx index 810ec1f4e7..a7bfa1053d 100644 --- a/web/app/components/base/notion-page-selector/credential-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/credential-selector/index.tsx @@ -1,7 +1,8 @@ 'use client' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' import { RiArrowDownSLine } from '@remixicon/react' -import React, { Fragment, useMemo } from 'react' +import * as React from 'react' +import { Fragment, useMemo } from 'react' import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' export type NotionCredential = { diff --git a/web/app/components/base/pagination/hook.ts b/web/app/components/base/pagination/hook.ts index 366e1db822..25fa24cb26 100644 --- a/web/app/components/base/pagination/hook.ts +++ b/web/app/components/base/pagination/hook.ts @@ -1,5 +1,6 @@ import type { IPaginationProps, IUsePagination } from './type' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' const usePagination = ({ currentPage, diff --git a/web/app/components/base/pagination/index.tsx b/web/app/components/base/pagination/index.tsx index 45aa97c986..d85b8082f9 100644 --- a/web/app/components/base/pagination/index.tsx +++ b/web/app/components/base/pagination/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiArrowLeftLine, RiArrowRightLine } from '@remixicon/react' import { useDebounceFn } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/base/pagination/pagination.tsx b/web/app/components/base/pagination/pagination.tsx index 599df58dc4..25bdf5a31b 100644 --- a/web/app/components/base/pagination/pagination.tsx +++ b/web/app/components/base/pagination/pagination.tsx @@ -5,7 +5,7 @@ import type { PageButtonProps, } from './type' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import usePagination from './hook' diff --git a/web/app/components/base/param-item/score-threshold-item.tsx b/web/app/components/base/param-item/score-threshold-item.tsx index 17feea2678..d7fe82e392 100644 --- a/web/app/components/base/param-item/score-threshold-item.tsx +++ b/web/app/components/base/param-item/score-threshold-item.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ParamItem from '.' diff --git a/web/app/components/base/param-item/top-k-item.tsx b/web/app/components/base/param-item/top-k-item.tsx index cfae5cf6a9..fcfd5a9f6d 100644 --- a/web/app/components/base/param-item/top-k-item.tsx +++ b/web/app/components/base/param-item/top-k-item.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ParamItem from '.' diff --git a/web/app/components/base/portal-to-follow-elem/index.spec.tsx b/web/app/components/base/portal-to-follow-elem/index.spec.tsx index 2b7cb83a53..f320cd2a74 100644 --- a/web/app/components/base/portal-to-follow-elem/index.spec.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.spec.tsx @@ -1,5 +1,5 @@ import { cleanup, fireEvent, render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '.' const useFloatingMock = vi.fn() diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index d2964e4c8a..a656ab5308 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -16,7 +16,8 @@ import { useRole, } from '@floating-ui/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { cn } from '@/utils/classnames' export type PortalToFollowElemOptions = { diff --git a/web/app/components/base/premium-badge/index.tsx b/web/app/components/base/premium-badge/index.tsx index 130729e135..50a5832a28 100644 --- a/web/app/components/base/premium-badge/index.tsx +++ b/web/app/components/base/premium-badge/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties, ReactNode } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { Highlight } from '@/app/components/base/icons/src/public/common' import { cn } from '@/utils/classnames' import './index.css' diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index be1b6b79a5..99e3a3325c 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -26,7 +26,8 @@ import { $getRoot, TextNode, } from 'lexical' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useEventEmitterContextContext } from '@/context/event-emitter' import { cn } from '@/utils/classnames' import { diff --git a/web/app/components/base/qrcode/index.tsx b/web/app/components/base/qrcode/index.tsx index 6f6752d2ae..664a4f104c 100644 --- a/web/app/components/base/qrcode/index.tsx +++ b/web/app/components/base/qrcode/index.tsx @@ -3,7 +3,8 @@ import { RiQrCodeLine, } from '@remixicon/react' import { QRCodeCanvas as QRCode } from 'qrcode.react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/base/radio-card/index.tsx b/web/app/components/base/radio-card/index.tsx index 7eb84ff2e4..ae8bb00099 100644 --- a/web/app/components/base/radio-card/index.tsx +++ b/web/app/components/base/radio-card/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/base/radio-card/simple/index.tsx b/web/app/components/base/radio-card/simple/index.tsx index 3a542599e1..d5f2f0ab9c 100644 --- a/web/app/components/base/radio-card/simple/index.tsx +++ b/web/app/components/base/radio-card/simple/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import s from './style.module.css' diff --git a/web/app/components/base/radio/index.tsx b/web/app/components/base/radio/index.tsx index 0a07600746..f5f18bac01 100644 --- a/web/app/components/base/radio/index.tsx +++ b/web/app/components/base/radio/index.tsx @@ -1,4 +1,4 @@ -import type React from 'react' +import type * as React from 'react' import type { IRadioProps } from './component/radio' import Group from './component/group' import RadioComps from './component/radio' diff --git a/web/app/components/base/radio/ui.tsx b/web/app/components/base/radio/ui.tsx index 2b2770c104..e836dddc96 100644 --- a/web/app/components/base/radio/ui.tsx +++ b/web/app/components/base/radio/ui.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/base/segmented-control/index.tsx b/web/app/components/base/segmented-control/index.tsx index b9c749befb..89a15a1f65 100644 --- a/web/app/components/base/segmented-control/index.tsx +++ b/web/app/components/base/segmented-control/index.tsx @@ -1,7 +1,7 @@ import type { RemixiconComponentType } from '@remixicon/react' import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import Divider from '../divider' import './index.css' diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index 8ab8925366..2d134bf6df 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions, Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react' import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid' import { RiCheckLine, RiLoader4Line } from '@remixicon/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/base/spinner/index.spec.tsx b/web/app/components/base/spinner/index.spec.tsx index d6e8fde823..652d061206 100644 --- a/web/app/components/base/spinner/index.spec.tsx +++ b/web/app/components/base/spinner/index.spec.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Spinner from './index' describe('Spinner component', () => { diff --git a/web/app/components/base/spinner/index.tsx b/web/app/components/base/spinner/index.tsx index f3810f4cff..1c66a127f6 100644 --- a/web/app/components/base/spinner/index.tsx +++ b/web/app/components/base/spinner/index.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' type Props = { loading?: boolean diff --git a/web/app/components/base/svg/index.tsx b/web/app/components/base/svg/index.tsx index e8e0fad9bd..4ebfa5f3d0 100644 --- a/web/app/components/base/svg/index.tsx +++ b/web/app/components/base/svg/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import ActionButton from '../action-button' import s from './style.module.css' diff --git a/web/app/components/base/switch/index.tsx b/web/app/components/base/switch/index.tsx index 4e32b22440..6296a33141 100644 --- a/web/app/components/base/switch/index.tsx +++ b/web/app/components/base/switch/index.tsx @@ -1,6 +1,7 @@ 'use client' import { Switch as OriginalSwitch } from '@headlessui/react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { cn } from '@/utils/classnames' type SwitchProps = { diff --git a/web/app/components/base/tab-header/index.tsx b/web/app/components/base/tab-header/index.tsx index b4dfd1f526..e762e23232 100644 --- a/web/app/components/base/tab-header/index.tsx +++ b/web/app/components/base/tab-header/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Item = { diff --git a/web/app/components/base/tab-slider-plain/index.tsx b/web/app/components/base/tab-slider-plain/index.tsx index 4ef4d916c9..5b8eb270ee 100644 --- a/web/app/components/base/tab-slider-plain/index.tsx +++ b/web/app/components/base/tab-slider-plain/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Option = { diff --git a/web/app/components/base/tag-management/panel.tsx b/web/app/components/base/tag-management/panel.tsx index 42414e4d3d..854de012a5 100644 --- a/web/app/components/base/tag-management/panel.tsx +++ b/web/app/components/base/tag-management/panel.tsx @@ -4,7 +4,8 @@ import type { Tag } from '@/app/components/base/tag-management/constant' import { RiAddLine, RiPriceTag3Line } from '@remixicon/react' import { useUnmount } from 'ahooks' import { noop } from 'lodash-es' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/base/tag-management/trigger.tsx b/web/app/components/base/tag-management/trigger.tsx index 84d3af55d3..471e8cfa3d 100644 --- a/web/app/components/base/tag-management/trigger.tsx +++ b/web/app/components/base/tag-management/trigger.tsx @@ -1,5 +1,5 @@ import { RiPriceTag3Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type TriggerProps = { diff --git a/web/app/components/base/tag/index.tsx b/web/app/components/base/tag/index.tsx index 38a686fa8e..fa225e0f84 100644 --- a/web/app/components/base/tag/index.tsx +++ b/web/app/components/base/tag/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' export type ITagProps = { diff --git a/web/app/components/base/textarea/index.tsx b/web/app/components/base/textarea/index.tsx index 039703d828..bea9a7bd41 100644 --- a/web/app/components/base/textarea/index.tsx +++ b/web/app/components/base/textarea/index.tsx @@ -1,7 +1,7 @@ import type { VariantProps } from 'class-variance-authority' import type { CSSProperties } from 'react' import { cva } from 'class-variance-authority' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' const textareaVariants = cva( diff --git a/web/app/components/base/timezone-label/__tests__/index.test.tsx b/web/app/components/base/timezone-label/__tests__/index.test.tsx index 9fb1c6127b..15aa4ee685 100644 --- a/web/app/components/base/timezone-label/__tests__/index.test.tsx +++ b/web/app/components/base/timezone-label/__tests__/index.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import TimezoneLabel from '../index' // Mock the convertTimezoneToOffsetStr function diff --git a/web/app/components/base/timezone-label/index.tsx b/web/app/components/base/timezone-label/index.tsx index 2904555e5b..f614280b3e 100644 --- a/web/app/components/base/timezone-label/index.tsx +++ b/web/app/components/base/timezone-label/index.tsx @@ -1,4 +1,5 @@ -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { convertTimezoneToOffsetStr } from '@/app/components/base/date-and-time-picker/utils/dayjs' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/toast/index.spec.tsx b/web/app/components/base/toast/index.spec.tsx index 94b3a81d54..d32619f59a 100644 --- a/web/app/components/base/toast/index.spec.tsx +++ b/web/app/components/base/toast/index.spec.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react' import { act, render, screen, waitFor } from '@testing-library/react' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import Toast, { ToastProvider, useToastContext } from '.' const TestComponent = () => { diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index f120bdeb7c..a016778996 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -8,7 +8,8 @@ import { RiInformation2Fill, } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' import { createContext, useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/base/tooltip/index.spec.tsx b/web/app/components/base/tooltip/index.spec.tsx index 8f77db71f6..66d3157ddc 100644 --- a/web/app/components/base/tooltip/index.spec.tsx +++ b/web/app/components/base/tooltip/index.spec.tsx @@ -1,5 +1,5 @@ import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Tooltip from './index' afterEach(cleanup) diff --git a/web/app/components/base/tooltip/index.tsx b/web/app/components/base/tooltip/index.tsx index db570ac6ca..3bd379b318 100644 --- a/web/app/components/base/tooltip/index.tsx +++ b/web/app/components/base/tooltip/index.tsx @@ -3,7 +3,8 @@ import type { OffsetOptions, Placement } from '@floating-ui/react' import type { FC } from 'react' import { RiQuestionLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' import { cn } from '@/utils/classnames' import { tooltipManager } from './TooltipManager' diff --git a/web/app/components/base/video-gallery/VideoPlayer.tsx b/web/app/components/base/video-gallery/VideoPlayer.tsx index f94b97ed90..8adaf71f58 100644 --- a/web/app/components/base/video-gallery/VideoPlayer.tsx +++ b/web/app/components/base/video-gallery/VideoPlayer.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import styles from './VideoPlayer.module.css' type VideoPlayerProps = { diff --git a/web/app/components/base/video-gallery/index.tsx b/web/app/components/base/video-gallery/index.tsx index 87133b94b9..b058b0b08a 100644 --- a/web/app/components/base/video-gallery/index.tsx +++ b/web/app/components/base/video-gallery/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import VideoPlayer from './VideoPlayer' type Props = { diff --git a/web/app/components/base/with-input-validation/index.tsx b/web/app/components/base/with-input-validation/index.tsx index f8ea354208..9a78dbb298 100644 --- a/web/app/components/base/with-input-validation/index.tsx +++ b/web/app/components/base/with-input-validation/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { ZodSchema } from 'zod' -import React from 'react' +import * as React from 'react' function withValidation<T extends Record<string, unknown>, K extends keyof T>( WrappedComponent: React.ComponentType<T>, diff --git a/web/app/components/billing/annotation-full/index.tsx b/web/app/components/billing/annotation-full/index.tsx index 090e383e22..fc770a9357 100644 --- a/web/app/components/billing/annotation-full/index.tsx +++ b/web/app/components/billing/annotation-full/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import GridMask from '@/app/components/base/grid-mask' import { cn } from '@/utils/classnames' diff --git a/web/app/components/billing/annotation-full/modal.tsx b/web/app/components/billing/annotation-full/modal.tsx index 0474fc0722..0e4fbfee32 100644 --- a/web/app/components/billing/annotation-full/modal.tsx +++ b/web/app/components/billing/annotation-full/modal.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import GridMask from '@/app/components/base/grid-mask' import { cn } from '@/utils/classnames' diff --git a/web/app/components/billing/annotation-full/usage.tsx b/web/app/components/billing/annotation-full/usage.tsx index 7ab984d04f..eb2b79cc89 100644 --- a/web/app/components/billing/annotation-full/usage.tsx +++ b/web/app/components/billing/annotation-full/usage.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useProviderContext } from '@/context/provider-context' import { MessageFastPlus } from '../../base/icons/src/vender/line/communication' diff --git a/web/app/components/billing/apps-full-in-dialog/index.tsx b/web/app/components/billing/apps-full-in-dialog/index.tsx index dbcbe49081..c69378c2f1 100644 --- a/web/app/components/billing/apps-full-in-dialog/index.tsx +++ b/web/app/components/billing/apps-full-in-dialog/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import ProgressBar from '@/app/components/billing/progress-bar' diff --git a/web/app/components/billing/billing-page/index.tsx b/web/app/components/billing/billing-page/index.tsx index c754386a3f..ee68cafe2e 100644 --- a/web/app/components/billing/billing-page/index.tsx +++ b/web/app/components/billing/billing-page/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiArrowRightUpLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' diff --git a/web/app/components/billing/header-billing-btn/index.tsx b/web/app/components/billing/header-billing-btn/index.tsx index 049ee214c0..862093c210 100644 --- a/web/app/components/billing/header-billing-btn/index.tsx +++ b/web/app/components/billing/header-billing-btn/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useProviderContext } from '@/context/provider-context' import { cn } from '@/utils/classnames' import { Plan } from '../type' diff --git a/web/app/components/billing/partner-stack/index.tsx b/web/app/components/billing/partner-stack/index.tsx index 06a7905475..e7b954a576 100644 --- a/web/app/components/billing/partner-stack/index.tsx +++ b/web/app/components/billing/partner-stack/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { IS_CLOUD_EDITION } from '@/config' import usePSInfo from './use-ps-info' diff --git a/web/app/components/billing/plan-upgrade-modal/index.spec.tsx b/web/app/components/billing/plan-upgrade-modal/index.spec.tsx index 215218d42a..9dbe115a89 100644 --- a/web/app/components/billing/plan-upgrade-modal/index.spec.tsx +++ b/web/app/components/billing/plan-upgrade-modal/index.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import PlanUpgradeModal from './index' const mockSetShowPricingModal = vi.fn() diff --git a/web/app/components/billing/plan-upgrade-modal/index.tsx b/web/app/components/billing/plan-upgrade-modal/index.tsx index 7bf8c1bb0d..0d5dccdd73 100644 --- a/web/app/components/billing/plan-upgrade-modal/index.tsx +++ b/web/app/components/billing/plan-upgrade-modal/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/billing/plan/assets/sandbox.spec.tsx b/web/app/components/billing/plan/assets/sandbox.spec.tsx index 0c70f979df..024213cf5a 100644 --- a/web/app/components/billing/plan/assets/sandbox.spec.tsx +++ b/web/app/components/billing/plan/assets/sandbox.spec.tsx @@ -1,5 +1,5 @@ import { render } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Sandbox from './sandbox' describe('Sandbox Icon Component', () => { diff --git a/web/app/components/billing/plan/assets/sandbox.tsx b/web/app/components/billing/plan/assets/sandbox.tsx index 22230f1c3d..0839e8d979 100644 --- a/web/app/components/billing/plan/assets/sandbox.tsx +++ b/web/app/components/billing/plan/assets/sandbox.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' const Sandbox = () => { return ( diff --git a/web/app/components/billing/plan/index.tsx b/web/app/components/billing/plan/index.tsx index b08a9037ca..9f826f5255 100644 --- a/web/app/components/billing/plan/index.tsx +++ b/web/app/components/billing/plan/index.tsx @@ -8,7 +8,8 @@ import { } from '@remixicon/react' import { useUnmountedRef } from 'ahooks' import { usePathname, useRouter } from 'next/navigation' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { ApiAggregate, TriggerAll } from '@/app/components/base/icons/src/vender/workflow' diff --git a/web/app/components/billing/pricing/footer.tsx b/web/app/components/billing/pricing/footer.tsx index f7842327a2..48b4a9f4bd 100644 --- a/web/app/components/billing/pricing/footer.tsx +++ b/web/app/components/billing/pricing/footer.tsx @@ -1,7 +1,7 @@ import type { Category } from '.' import { RiArrowRightUpLine } from '@remixicon/react' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { CategoryEnum } from '.' diff --git a/web/app/components/billing/pricing/header.tsx b/web/app/components/billing/pricing/header.tsx index 55f9960eee..c282664e9c 100644 --- a/web/app/components/billing/pricing/header.tsx +++ b/web/app/components/billing/pricing/header.tsx @@ -1,5 +1,5 @@ import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '../../base/button' import DifyLogo from '../../base/logo/dify-logo' diff --git a/web/app/components/billing/pricing/index.tsx b/web/app/components/billing/pricing/index.tsx index d726fa7256..2b58158146 100644 --- a/web/app/components/billing/pricing/index.tsx +++ b/web/app/components/billing/pricing/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useKeyPress } from 'ahooks' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useAppContext } from '@/context/app-context' import { useGetPricingPageLanguage } from '@/context/i18n' diff --git a/web/app/components/billing/pricing/plan-switcher/index.tsx b/web/app/components/billing/pricing/plan-switcher/index.tsx index 3a25c2a569..416a645438 100644 --- a/web/app/components/billing/pricing/plan-switcher/index.tsx +++ b/web/app/components/billing/pricing/plan-switcher/index.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { Category } from '../index' import type { PlanRange } from './plan-range-switcher' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { Cloud, SelfHosted } from '../assets' diff --git a/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx index 770a19505b..080ae42fbe 100644 --- a/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx +++ b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '../../../base/switch' diff --git a/web/app/components/billing/pricing/plan-switcher/tab.tsx b/web/app/components/billing/pricing/plan-switcher/tab.tsx index fab24addf8..db1632e8df 100644 --- a/web/app/components/billing/pricing/plan-switcher/tab.tsx +++ b/web/app/components/billing/pricing/plan-switcher/tab.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' type TabProps<T> = { diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/button.spec.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/button.spec.tsx index ed6c7e4efb..d57f1c022d 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/button.spec.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/button.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { Plan } from '../../../type' import Button from './button' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/button.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/button.tsx index 8cf7351790..9cdef8e333 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/button.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/button.tsx @@ -1,6 +1,6 @@ import type { BasicPlan } from '../../../type' import { RiArrowRightLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { Plan } from '../../../type' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx index c2a17b0d04..f6df314917 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { useAppContext } from '@/context/app-context' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { fetchBillingUrl, fetchSubscriptionUrls } from '@/service/billing' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx index e104e9cdd3..57c85cf297 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { BasicPlan } from '../../../type' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.spec.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.spec.tsx index 5e2ba2a994..bc33798482 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.spec.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { Plan } from '../../../../type' import List from './index' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx index 600a3c0653..1ba8b2c9bc 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/list/index.tsx @@ -1,5 +1,5 @@ import type { BasicPlan } from '../../../../type' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { ALL_PLANS, NUM_INFINITE } from '../../../../config' diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/index.tsx index 95e232fefe..df93a5fb0e 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Tooltip from './tooltip' type ItemProps = { diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/tooltip.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/tooltip.tsx index 34341ed97a..bfac36e529 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/tooltip.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/list/item/tooltip.tsx @@ -1,5 +1,5 @@ import { RiInfoI } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type TooltipProps = { content: string diff --git a/web/app/components/billing/pricing/plans/index.spec.tsx b/web/app/components/billing/pricing/plans/index.spec.tsx index 166f75e941..3accaee345 100644 --- a/web/app/components/billing/pricing/plans/index.spec.tsx +++ b/web/app/components/billing/pricing/plans/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import type { UsagePlanInfo } from '../../type' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { Plan } from '../../type' import { PlanRange } from '../plan-switcher/plan-range-switcher' import cloudPlanItem from './cloud-plan-item' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.spec.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.spec.tsx index 193e16be45..35a484e7c3 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.spec.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.spec.tsx @@ -1,6 +1,6 @@ import type { MockedFunction } from 'vitest' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' import { SelfHostedPlan } from '../../../type' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx index 148420a85a..544141a6a5 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx @@ -1,5 +1,6 @@ import { RiArrowRightLine } from '@remixicon/react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { AwsMarketplaceDark, AwsMarketplaceLight } from '@/app/components/base/icons/src/public/billing' import useTheme from '@/hooks/use-theme' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.spec.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.spec.tsx index aee3b7e8d1..3a687de5c1 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.spec.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.spec.tsx @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { useAppContext } from '@/context/app-context' import Toast from '../../../../base/toast' import { contactSalesUrl, getStartedWithCommunityUrl, getWithPremiumUrl } from '../../../config' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx index db1f44b23a..b89d0c6941 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Azure, GoogleCloud } from '@/app/components/base/icons/src/public/billing' import { useAppContext } from '@/context/app-context' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.spec.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.spec.tsx index 6188ac3e0a..97329e47e5 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.spec.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { SelfHostedPlan } from '@/app/components/billing/type' import List from './index' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx index 137b14db6b..4ed307d36e 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx @@ -1,5 +1,5 @@ import type { SelfHostedPlan } from '@/app/components/billing/type' -import React from 'react' +import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import Item from './item' diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.spec.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.spec.tsx index eda8527445..2f2957fb9d 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.spec.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Item from './item' describe('SelfHostedPlanItem/List/Item', () => { diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.tsx index 8d219f30c2..ee14117d24 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/item.tsx @@ -1,5 +1,5 @@ import { RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type ItemProps = { label: string diff --git a/web/app/components/billing/trigger-events-limit-modal/index.tsx b/web/app/components/billing/trigger-events-limit-modal/index.tsx index 35d5e8bf65..421ec752dd 100644 --- a/web/app/components/billing/trigger-events-limit-modal/index.tsx +++ b/web/app/components/billing/trigger-events-limit-modal/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal' diff --git a/web/app/components/billing/upgrade-btn/index.tsx b/web/app/components/billing/upgrade-btn/index.tsx index 10ec04e4de..0f23022b35 100644 --- a/web/app/components/billing/upgrade-btn/index.tsx +++ b/web/app/components/billing/upgrade-btn/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { CSSProperties, FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { SparklesSoft } from '@/app/components/base/icons/src/public/common' diff --git a/web/app/components/billing/usage-info/apps-info.tsx b/web/app/components/billing/usage-info/apps-info.tsx index 79ebd31a5f..024f3c47b1 100644 --- a/web/app/components/billing/usage-info/apps-info.tsx +++ b/web/app/components/billing/usage-info/apps-info.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiApps2Line, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useProviderContext } from '@/context/provider-context' import UsageInfo from '../usage-info' diff --git a/web/app/components/billing/usage-info/index.tsx b/web/app/components/billing/usage-info/index.tsx index 7fa9efab2a..3681be9a20 100644 --- a/web/app/components/billing/usage-info/index.tsx +++ b/web/app/components/billing/usage-info/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/billing/usage-info/vector-space-info.tsx b/web/app/components/billing/usage-info/vector-space-info.tsx index b946a2580f..1da573c708 100644 --- a/web/app/components/billing/usage-info/vector-space-info.tsx +++ b/web/app/components/billing/usage-info/vector-space-info.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiHardDrive3Line, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useProviderContext } from '@/context/provider-context' import UsageInfo from '../usage-info' diff --git a/web/app/components/billing/vector-space-full/index.tsx b/web/app/components/billing/vector-space-full/index.tsx index 67d4cd8c34..21a856b660 100644 --- a/web/app/components/billing/vector-space-full/index.tsx +++ b/web/app/components/billing/vector-space-full/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import GridMask from '@/app/components/base/grid-mask' import { cn } from '@/utils/classnames' diff --git a/web/app/components/custom/custom-page/index.spec.tsx b/web/app/components/custom/custom-page/index.spec.tsx index 3fa4479450..0eea48fb6e 100644 --- a/web/app/components/custom/custom-page/index.spec.tsx +++ b/web/app/components/custom/custom-page/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { createMockProviderContextValue } from '@/__mocks__/provider-context' import { contactSalesUrl } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' diff --git a/web/app/components/datasets/api/index.tsx b/web/app/components/datasets/api/index.tsx index 3ca84c3e11..801070030c 100644 --- a/web/app/components/datasets/api/index.tsx +++ b/web/app/components/datasets/api/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' const index = () => { return ( diff --git a/web/app/components/datasets/common/chunking-mode-label.tsx b/web/app/components/datasets/common/chunking-mode-label.tsx index 5147d44ad8..1a265855a4 100644 --- a/web/app/components/datasets/common/chunking-mode-label.tsx +++ b/web/app/components/datasets/common/chunking-mode-label.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import { GeneralChunk, ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge' diff --git a/web/app/components/datasets/common/credential-icon.tsx b/web/app/components/datasets/common/credential-icon.tsx index 27dea86993..a97cf7d410 100644 --- a/web/app/components/datasets/common/credential-icon.tsx +++ b/web/app/components/datasets/common/credential-icon.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { cn } from '@/utils/classnames' type CredentialIconProps = { diff --git a/web/app/components/datasets/common/document-file-icon.tsx b/web/app/components/datasets/common/document-file-icon.tsx index d4ce8878ba..d831cd78aa 100644 --- a/web/app/components/datasets/common/document-file-icon.tsx +++ b/web/app/components/datasets/common/document-file-icon.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { FileAppearanceType } from '@/app/components/base/file-uploader/types' -import React from 'react' +import * as React from 'react' import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' import FileTypeIcon from '../../base/file-uploader/file-type-icon' diff --git a/web/app/components/datasets/common/document-picker/document-list.tsx b/web/app/components/datasets/common/document-picker/document-list.tsx index 966bc0e4c8..574792ee14 100644 --- a/web/app/components/datasets/common/document-picker/document-list.tsx +++ b/web/app/components/datasets/common/document-picker/document-list.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { DocumentItem } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' import FileIcon from '../document-file-icon' diff --git a/web/app/components/datasets/common/document-picker/index.spec.tsx b/web/app/components/datasets/common/document-picker/index.spec.tsx index d236cf4e62..2ed29a0e23 100644 --- a/web/app/components/datasets/common/document-picker/index.spec.tsx +++ b/web/app/components/datasets/common/document-picker/index.spec.tsx @@ -1,7 +1,7 @@ import type { ParentMode, SimpleDocumentDetail } from '@/models/datasets' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ChunkingMode, DataSourceType } from '@/models/datasets' import DocumentPicker from './index' diff --git a/web/app/components/datasets/common/document-picker/index.tsx b/web/app/components/datasets/common/document-picker/index.tsx index fe5b96a50b..14acd1db6c 100644 --- a/web/app/components/datasets/common/document-picker/index.tsx +++ b/web/app/components/datasets/common/document-picker/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { DocumentItem, ParentMode, SimpleDocumentDetail } from '@/models/datasets' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { GeneralChunk, ParentChildChunk } from '@/app/components/base/icons/src/vender/knowledge' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/datasets/common/document-picker/preview-document-picker.spec.tsx b/web/app/components/datasets/common/document-picker/preview-document-picker.spec.tsx index 5a70cada88..65272b77be 100644 --- a/web/app/components/datasets/common/document-picker/preview-document-picker.spec.tsx +++ b/web/app/components/datasets/common/document-picker/preview-document-picker.spec.tsx @@ -1,6 +1,6 @@ import type { DocumentItem } from '@/models/datasets' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import PreviewDocumentPicker from './preview-document-picker' // Override shared i18n mock for custom translations diff --git a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx index 8b7cf8340b..4403cf29f2 100644 --- a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx +++ b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { DocumentItem } from '@/models/datasets' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { diff --git a/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx b/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx index 3fceccb481..8cb4d4e22b 100644 --- a/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx +++ b/web/app/components/datasets/common/document-status-with-action/auto-disabled-document.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { useAutoDisabledDocuments, useDocumentEnable, useInvalidDisabledDocument } from '@/service/knowledge/use-document' diff --git a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx index 4fafbd8997..1635720037 100644 --- a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx +++ b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { IndexingStatusResponse } from '@/models/datasets' import { noop } from 'lodash-es' -import React, { useEffect, useReducer } from 'react' +import * as React from 'react' +import { useEffect, useReducer } from 'react' import { useTranslation } from 'react-i18next' import { retryErrorDocs } from '@/service/datasets' import { useDatasetErrorDocs } from '@/service/knowledge/use-dataset' diff --git a/web/app/components/datasets/common/document-status-with-action/status-with-action.tsx b/web/app/components/datasets/common/document-status-with-action/status-with-action.tsx index b4eedd0cfe..9d42d40b1a 100644 --- a/web/app/components/datasets/common/document-status-with-action/status-with-action.tsx +++ b/web/app/components/datasets/common/document-status-with-action/status-with-action.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiAlertFill, RiCheckboxCircleFill, RiErrorWarningFill, RiInformation2Fill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Divider from '@/app/components/base/divider' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx b/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx index ec6878cd64..83f2911093 100644 --- a/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { VectorSearch } from '@/app/components/base/icons/src/vender/knowledge' import { RETRIEVE_METHOD } from '@/types/app' diff --git a/web/app/components/datasets/common/image-list/more.tsx b/web/app/components/datasets/common/image-list/more.tsx index 6b809ee5c5..be6b53a5a5 100644 --- a/web/app/components/datasets/common/image-list/more.tsx +++ b/web/app/components/datasets/common/image-list/more.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' type MoreProps = { count: number diff --git a/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-input.tsx b/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-input.tsx index e7e198fa86..63d3656dda 100644 --- a/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-input.tsx +++ b/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-input.tsx @@ -1,5 +1,5 @@ import { RiUploadCloud2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { ACCEPT_TYPES } from '../constants' diff --git a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx index 5525913d3c..390e1f1ed4 100644 --- a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx +++ b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx @@ -1,5 +1,5 @@ import { RiImageAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { ACCEPT_TYPES } from '../constants' diff --git a/web/app/components/datasets/common/retrieval-method-config/index.spec.tsx b/web/app/components/datasets/common/retrieval-method-config/index.spec.tsx index da07401d0c..ec6da2b160 100644 --- a/web/app/components/datasets/common/retrieval-method-config/index.spec.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/index.spec.tsx @@ -1,6 +1,6 @@ import type { RetrievalConfig } from '@/types/app' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { DEFAULT_WEIGHTED_SCORE, RerankingModeEnum, diff --git a/web/app/components/datasets/common/retrieval-method-config/index.tsx b/web/app/components/datasets/common/retrieval-method-config/index.tsx index cd700f55f1..255f51f23e 100644 --- a/web/app/components/datasets/common/retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { FullTextSearch, HybridSearch, VectorSearch } from '@/app/components/base/icons/src/vender/knowledge' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' diff --git a/web/app/components/datasets/common/retrieval-method-info/index.tsx b/web/app/components/datasets/common/retrieval-method-info/index.tsx index ee388a9580..df8a93f666 100644 --- a/web/app/components/datasets/common/retrieval-method-info/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-info/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' import Image from 'next/image' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import RadioCard from '@/app/components/base/radio-card' import { RETRIEVE_METHOD } from '@/types/app' diff --git a/web/app/components/datasets/common/retrieval-param-config/index.tsx b/web/app/components/datasets/common/retrieval-param-config/index.tsx index 19653ebad7..2517bb842b 100644 --- a/web/app/components/datasets/common/retrieval-param-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' import Image from 'next/image' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/header.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/header.tsx index 7e36043318..5b2fc33e3e 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/header.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/header.tsx @@ -1,5 +1,5 @@ import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type HeaderProps = { diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.tsx index c6ec52c0f8..7024233dd6 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal' import Item from './item' diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/item.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/item.tsx index f734e1ab90..87af4714bd 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/item.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/item.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type ItemProps = { diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx index 669068ab6e..4d5c06d523 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/uploader.tsx @@ -5,7 +5,8 @@ import { RiNodeTree, RiUploadCloud2Line, } from '@remixicon/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/datasets/create-from-pipeline/footer.tsx b/web/app/components/datasets/create-from-pipeline/footer.tsx index df29d06ce5..a744a8a5ca 100644 --- a/web/app/components/datasets/create-from-pipeline/footer.tsx +++ b/web/app/components/datasets/create-from-pipeline/footer.tsx @@ -1,6 +1,7 @@ import { RiFileUploadLine } from '@remixicon/react' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' import Divider from '../../base/divider' diff --git a/web/app/components/datasets/create-from-pipeline/header.tsx b/web/app/components/datasets/create-from-pipeline/header.tsx index dd43998453..690f78958c 100644 --- a/web/app/components/datasets/create-from-pipeline/header.tsx +++ b/web/app/components/datasets/create-from-pipeline/header.tsx @@ -1,6 +1,6 @@ import { RiArrowLeftLine } from '@remixicon/react' import Link from 'next/link' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '../../base/button' diff --git a/web/app/components/datasets/create-from-pipeline/list/create-card.tsx b/web/app/components/datasets/create-from-pipeline/list/create-card.tsx index 144926d97d..57ea9202bb 100644 --- a/web/app/components/datasets/create-from-pipeline/list/create-card.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/create-card.tsx @@ -1,6 +1,7 @@ import { RiAddCircleLine } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/actions.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/actions.tsx index c7f1b742a8..a621862a08 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/actions.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/actions.tsx @@ -1,5 +1,5 @@ import { RiAddLine, RiArrowRightUpLine, RiMoreFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import CustomPopover from '@/app/components/base/popover' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx index 85873d97ca..8452d20d2d 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx @@ -1,5 +1,5 @@ import type { ChunkingMode, IconInfo } from '@/models/datasets' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { General } from '@/app/components/base/icons/src/public/knowledge/dataset-card' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/details/chunk-structure-card.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/details/chunk-structure-card.tsx index 39d03ae28d..4a6efa29a2 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/details/chunk-structure-card.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/details/chunk-structure-card.tsx @@ -1,5 +1,5 @@ import type { Option } from './types' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { EffectColor } from './types' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx index 9f0aebf6ff..0d48576f10 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx @@ -1,6 +1,7 @@ import type { AppIconType } from '@/types/app' import { RiAddLine, RiCloseLine } from '@remixicon/react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx index 46d09b8477..1c1d4f0a8c 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/edit-pipeline-info.tsx @@ -1,7 +1,8 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { PipelineTemplate } from '@/models/pipeline' import { RiCloseLine } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import AppIconPicker from '@/app/components/base/app-icon-picker' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx index 877e96bd1d..61192c6430 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/index.tsx @@ -1,6 +1,7 @@ import type { PipelineTemplate } from '@/models/pipeline' import { useRouter } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/operations.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/operations.tsx index a6ab47faec..8fc33f2178 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/operations.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/operations.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/create/embedding-process/index.tsx b/web/app/components/datasets/create/embedding-process/index.tsx index 5cc376207f..541eb62b60 100644 --- a/web/app/components/datasets/create/embedding-process/index.tsx +++ b/web/app/components/datasets/create/embedding-process/index.tsx @@ -16,7 +16,8 @@ import { import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/index.spec.tsx b/web/app/components/datasets/create/empty-dataset-creation-modal/index.spec.tsx index 708ccc4ba3..cef945c968 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/index.spec.tsx +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/index.spec.tsx @@ -1,6 +1,6 @@ import type { MockedFunction } from 'vitest' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { createEmptyDataset } from '@/service/datasets' import { useInvalidDatasetList } from '@/service/knowledge/use-dataset' import EmptyDatasetCreationModal from './index' diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx index 4427009b06..c88eb0b47d 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx @@ -1,6 +1,7 @@ 'use client' import { useRouter } from 'next/navigation' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { trackEvent } from '@/app/components/base/amplitude' diff --git a/web/app/components/datasets/create/file-preview/index.tsx b/web/app/components/datasets/create/file-preview/index.tsx index dd174837c4..3212f61d0c 100644 --- a/web/app/components/datasets/create/file-preview/index.tsx +++ b/web/app/components/datasets/create/file-preview/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { CustomFile as File } from '@/models/datasets' import { XMarkIcon } from '@heroicons/react/20/solid' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { fetchFilePreview } from '@/service/common' diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index b0680905d1..97d1625c10 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { CustomFile as File, FileItem } from '@/models/datasets' import { RiDeleteBinLine, RiUploadCloud2Line } from '@remixicon/react' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { getFileUploadErrorMessage } from '@/app/components/base/file-uploader/utils' diff --git a/web/app/components/datasets/create/index.spec.tsx b/web/app/components/datasets/create/index.spec.tsx index a0248d59a1..66a642db3a 100644 --- a/web/app/components/datasets/create/index.spec.tsx +++ b/web/app/components/datasets/create/index.spec.tsx @@ -1,7 +1,7 @@ import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' import type { DataSet } from '@/models/datasets' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { DataSourceProvider } from '@/models/common' import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets' import { RETRIEVE_METHOD } from '@/types/app' diff --git a/web/app/components/datasets/create/index.tsx b/web/app/components/datasets/create/index.tsx index 357b2256fc..3e4e870628 100644 --- a/web/app/components/datasets/create/index.tsx +++ b/web/app/components/datasets/create/index.tsx @@ -2,7 +2,8 @@ import type { NotionPage } from '@/models/common' import type { CrawlOptions, CrawlResultItem, createDocumentResponse, FileItem } from '@/models/datasets' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/datasets/create/notion-page-preview/index.tsx b/web/app/components/datasets/create/notion-page-preview/index.tsx index ac590cc0c3..e245cd3490 100644 --- a/web/app/components/datasets/create/notion-page-preview/index.tsx +++ b/web/app/components/datasets/create/notion-page-preview/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { NotionPage } from '@/models/common' import { XMarkIcon } from '@heroicons/react/20/solid' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import NotionIcon from '@/app/components/base/notion-icon' diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index ddbfb05e7d..ff99c218b2 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -4,7 +4,8 @@ import type { DataSourceProvider, NotionPage } from '@/models/common' import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets' import { RiArrowRightLine, RiFolder6Line } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import NotionConnector from '@/app/components/base/notion-connector' diff --git a/web/app/components/datasets/create/step-one/upgrade-card.tsx b/web/app/components/datasets/create/step-one/upgrade-card.tsx index a4fcca73c0..a354e91e56 100644 --- a/web/app/components/datasets/create/step-one/upgrade-card.tsx +++ b/web/app/components/datasets/create/step-one/upgrade-card.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import UpgradeBtn from '@/app/components/billing/upgrade-btn' import { useModalContext } from '@/context/modal-context' diff --git a/web/app/components/datasets/create/step-three/index.tsx b/web/app/components/datasets/create/step-three/index.tsx index 56ff55de84..b4c122990f 100644 --- a/web/app/components/datasets/create/step-three/index.tsx +++ b/web/app/components/datasets/create/step-three/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { createDocumentResponse, FullDocumentDetail } from '@/models/datasets' import { RiBookOpenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index b2f1a221f6..981b6c5a8f 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -12,7 +12,8 @@ import { import { noop } from 'lodash-es' import Image from 'next/image' import Link from 'next/link' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { trackEvent } from '@/app/components/base/amplitude' diff --git a/web/app/components/datasets/create/step-two/language-select/index.spec.tsx b/web/app/components/datasets/create/step-two/language-select/index.spec.tsx index ba1097ecb4..a2f0d96d80 100644 --- a/web/app/components/datasets/create/step-two/language-select/index.spec.tsx +++ b/web/app/components/datasets/create/step-two/language-select/index.spec.tsx @@ -1,6 +1,6 @@ import type { ILanguageSelectProps } from './index' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { languages } from '@/i18n-config/language' import LanguageSelect from './index' diff --git a/web/app/components/datasets/create/step-two/language-select/index.tsx b/web/app/components/datasets/create/step-two/language-select/index.tsx index 9de71c12b8..b54db21970 100644 --- a/web/app/components/datasets/create/step-two/language-select/index.tsx +++ b/web/app/components/datasets/create/step-two/language-select/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Popover from '@/app/components/base/popover' import { languages } from '@/i18n-config/language' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/create/step-two/preview-item/index.spec.tsx b/web/app/components/datasets/create/step-two/preview-item/index.spec.tsx index 911982b7b3..c4cdf75480 100644 --- a/web/app/components/datasets/create/step-two/preview-item/index.spec.tsx +++ b/web/app/components/datasets/create/step-two/preview-item/index.spec.tsx @@ -1,6 +1,6 @@ import type { IPreviewItemProps } from './index' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import PreviewItem, { PreviewType } from './index' // Test data builder for props diff --git a/web/app/components/datasets/create/step-two/preview-item/index.tsx b/web/app/components/datasets/create/step-two/preview-item/index.tsx index 67706fae4c..c1c95f4e62 100644 --- a/web/app/components/datasets/create/step-two/preview-item/index.tsx +++ b/web/app/components/datasets/create/step-two/preview-item/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' export type IPreviewItemProps = { diff --git a/web/app/components/datasets/create/stop-embedding-modal/index.tsx b/web/app/components/datasets/create/stop-embedding-modal/index.tsx index cb3a572b32..5a65692a27 100644 --- a/web/app/components/datasets/create/stop-embedding-modal/index.tsx +++ b/web/app/components/datasets/create/stop-embedding-modal/index.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/datasets/create/website/base/checkbox-with-label.tsx b/web/app/components/datasets/create/website/base/checkbox-with-label.tsx index 214cc9dc04..182645d5bc 100644 --- a/web/app/components/datasets/create/website/base/checkbox-with-label.tsx +++ b/web/app/components/datasets/create/website/base/checkbox-with-label.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/create/website/base/crawled-result-item.tsx b/web/app/components/datasets/create/website/base/crawled-result-item.tsx index 47fdda193e..4cc1e16a5f 100644 --- a/web/app/components/datasets/create/website/base/crawled-result-item.tsx +++ b/web/app/components/datasets/create/website/base/crawled-result-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/create/website/base/crawled-result.tsx b/web/app/components/datasets/create/website/base/crawled-result.tsx index 987958c5c5..c922a77169 100644 --- a/web/app/components/datasets/create/website/base/crawled-result.tsx +++ b/web/app/components/datasets/create/website/base/crawled-result.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlResultItem } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import CheckboxWithLabel from './checkbox-with-label' diff --git a/web/app/components/datasets/create/website/base/crawling.tsx b/web/app/components/datasets/create/website/base/crawling.tsx index 80642ad2f4..a9e28985f1 100644 --- a/web/app/components/datasets/create/website/base/crawling.tsx +++ b/web/app/components/datasets/create/website/base/crawling.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { RowStruct } from '@/app/components/base/icons/src/public/other' diff --git a/web/app/components/datasets/create/website/base/error-message.tsx b/web/app/components/datasets/create/website/base/error-message.tsx index d021d1431c..97b18d00c1 100644 --- a/web/app/components/datasets/create/website/base/error-message.tsx +++ b/web/app/components/datasets/create/website/base/error-message.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/create/website/base/field.tsx b/web/app/components/datasets/create/website/base/field.tsx index 76671b65f7..43f9e4bb37 100644 --- a/web/app/components/datasets/create/website/base/field.tsx +++ b/web/app/components/datasets/create/website/base/field.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' import Input from './input' diff --git a/web/app/components/datasets/create/website/base/header.tsx b/web/app/components/datasets/create/website/base/header.tsx index 92f50a0989..cf4d537e3f 100644 --- a/web/app/components/datasets/create/website/base/header.tsx +++ b/web/app/components/datasets/create/website/base/header.tsx @@ -1,5 +1,5 @@ import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/create/website/base/input.tsx b/web/app/components/datasets/create/website/base/input.tsx index 64288f2872..aff683c0e4 100644 --- a/web/app/components/datasets/create/website/base/input.tsx +++ b/web/app/components/datasets/create/website/base/input.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' type Props = { value: string | number diff --git a/web/app/components/datasets/create/website/base/options-wrap.tsx b/web/app/components/datasets/create/website/base/options-wrap.tsx index 50701251e1..4b6d9a5522 100644 --- a/web/app/components/datasets/create/website/base/options-wrap.tsx +++ b/web/app/components/datasets/create/website/base/options-wrap.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiEqualizer2Line } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/create/website/base/url-input.tsx b/web/app/components/datasets/create/website/base/url-input.tsx index 1137c8d1c4..c23655dcfa 100644 --- a/web/app/components/datasets/create/website/base/url-input.tsx +++ b/web/app/components/datasets/create/website/base/url-input.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/datasets/create/website/firecrawl/index.tsx b/web/app/components/datasets/create/website/firecrawl/index.tsx index c1146e8add..0a79b8f660 100644 --- a/web/app/components/datasets/create/website/firecrawl/index.tsx +++ b/web/app/components/datasets/create/website/firecrawl/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions, CrawlResultItem } from '@/models/datasets' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/datasets/create/website/firecrawl/options.tsx b/web/app/components/datasets/create/website/firecrawl/options.tsx index caf1895e64..59b23d34b0 100644 --- a/web/app/components/datasets/create/website/firecrawl/options.tsx +++ b/web/app/components/datasets/create/website/firecrawl/options.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import CheckboxWithLabel from '../base/checkbox-with-label' diff --git a/web/app/components/datasets/create/website/index.tsx b/web/app/components/datasets/create/website/index.tsx index 3d0d79dc77..2014631155 100644 --- a/web/app/components/datasets/create/website/index.tsx +++ b/web/app/components/datasets/create/website/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types' import type { CrawlOptions, CrawlResultItem } from '@/models/datasets' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config' diff --git a/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx b/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx index 0e20a76d1a..71a5037e71 100644 --- a/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx +++ b/web/app/components/datasets/create/website/jina-reader/base/url-input.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/datasets/create/website/jina-reader/index.tsx b/web/app/components/datasets/create/website/jina-reader/index.tsx index 44556a4bcb..953f869c44 100644 --- a/web/app/components/datasets/create/website/jina-reader/index.tsx +++ b/web/app/components/datasets/create/website/jina-reader/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions, CrawlResultItem } from '@/models/datasets' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/datasets/create/website/jina-reader/options.tsx b/web/app/components/datasets/create/website/jina-reader/options.tsx index 67991055df..c9aeae9ee5 100644 --- a/web/app/components/datasets/create/website/jina-reader/options.tsx +++ b/web/app/components/datasets/create/website/jina-reader/options.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import CheckboxWithLabel from '../base/checkbox-with-label' diff --git a/web/app/components/datasets/create/website/no-data.tsx b/web/app/components/datasets/create/website/no-data.tsx index f01ec18f1a..ad3e1d4010 100644 --- a/web/app/components/datasets/create/website/no-data.tsx +++ b/web/app/components/datasets/create/website/no-data.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { Icon3Dots } from '@/app/components/base/icons/src/vender/line/others' diff --git a/web/app/components/datasets/create/website/preview.tsx b/web/app/components/datasets/create/website/preview.tsx index f9213e3c89..cb1c5822ba 100644 --- a/web/app/components/datasets/create/website/preview.tsx +++ b/web/app/components/datasets/create/website/preview.tsx @@ -1,7 +1,7 @@ 'use client' import type { CrawlResultItem } from '@/models/datasets' import { XMarkIcon } from '@heroicons/react/20/solid' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import s from '../file-preview/index.module.css' diff --git a/web/app/components/datasets/create/website/watercrawl/index.tsx b/web/app/components/datasets/create/website/watercrawl/index.tsx index 938d1dd813..9f3a130419 100644 --- a/web/app/components/datasets/create/website/watercrawl/index.tsx +++ b/web/app/components/datasets/create/website/watercrawl/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions, CrawlResultItem } from '@/models/datasets' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/datasets/create/website/watercrawl/options.tsx b/web/app/components/datasets/create/website/watercrawl/options.tsx index 0858647c60..5af2a5e7bb 100644 --- a/web/app/components/datasets/create/website/watercrawl/options.tsx +++ b/web/app/components/datasets/create/website/watercrawl/options.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CrawlOptions } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import CheckboxWithLabel from '../base/checkbox-with-label' diff --git a/web/app/components/datasets/documents/create-from-pipeline/actions/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/actions/index.spec.tsx index 077b4fee3a..cbb74bb796 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/actions/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/actions/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Actions from './index' // ========================================== diff --git a/web/app/components/datasets/documents/create-from-pipeline/actions/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/actions/index.tsx index ad860e0f59..fc2759cbda 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/actions/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/actions/index.tsx @@ -1,7 +1,8 @@ import { RiArrowRightLine } from '@remixicon/react' import Link from 'next/link' import { useParams } from 'next/navigation' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source-options/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source-options/index.spec.tsx index f5c56995b8..57b73e9222 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source-options/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source-options/index.spec.tsx @@ -3,7 +3,7 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-so import type { Node } from '@/app/components/workflow/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, fireEvent, render, renderHook, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { BlockEnum } from '@/app/components/workflow/types' import DatasourceIcon from './datasource-icon' import { useDatasourceIcon } from './hooks' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source-options/option-card.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source-options/option-card.tsx index 6e8d1d4105..b938afa950 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source-options/option-card.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source-options/option-card.tsx @@ -1,5 +1,5 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import DatasourceIcon from './datasource-icon' import { useDatasourceIcon } from './hooks' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.spec.tsx index 1477fe71e9..da5075ec8a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.spec.tsx @@ -1,7 +1,7 @@ import type { CredentialSelectorProps } from './index' import type { DataSourceCredential } from '@/types/pipeline' import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CredentialSelector from './index' // Mock CredentialTypeEnum to avoid deep import chain issues diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx index abeff83ebf..2f14b0f3b8 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/index.tsx @@ -1,6 +1,7 @@ import type { DataSourceCredential } from '@/types/pipeline' import { useBoolean } from 'ahooks' -import React, { useCallback, useEffect, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx index 65ea951798..4d54a04d1f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/item.tsx @@ -1,6 +1,7 @@ import type { DataSourceCredential } from '@/types/pipeline' import { RiCheckLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' type ItemProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx index d90feaf2c0..09988a42d5 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/list.tsx @@ -1,5 +1,5 @@ import type { DataSourceCredential } from '@/types/pipeline' -import React from 'react' +import * as React from 'react' import Item from './item' type ListProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx index 7bac6afd35..ed68eaef5d 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/credential-selector/trigger.tsx @@ -1,6 +1,6 @@ import type { DataSourceCredential } from '@/types/pipeline' import { RiArrowDownSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { CredentialIcon } from '@/app/components/datasets/common/credential-icon' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.spec.tsx index cadfbdae0f..31be2cdba6 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.spec.tsx @@ -1,6 +1,6 @@ import type { DataSourceCredential } from '@/types/pipeline' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Header from './header' // Mock CredentialTypeEnum to avoid deep import chain issues diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx index cbdd24e5b9..c08e39937f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/base/header.tsx @@ -1,6 +1,6 @@ import type { CredentialSelectorProps } from './credential-selector' import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx index ffa5ed6bb8..31570ef4cf 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx @@ -3,7 +3,8 @@ import type { CustomFile as File, FileItem } from '@/models/datasets' import { RiDeleteBinLine, RiErrorWarningFill, RiUploadCloud2Line } from '@remixicon/react' import { produce } from 'immer' import dynamic from 'next/dynamic' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { getFileUploadErrorMessage } from '@/app/components/base/file-uploader/utils' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.spec.tsx index 80109c738a..543d53ac39 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.spec.tsx @@ -1,7 +1,7 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' import type { DataSourceNotionWorkspace, NotionPage } from '@/models/common' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { VarKindType } from '@/app/components/workflow/nodes/_base/types' import OnlineDocuments from './index' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.spec.tsx index c1fe9a8cda..60da0e7c9f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.spec.tsx @@ -1,7 +1,7 @@ import type { NotionPageTreeItem, NotionPageTreeMap } from './index' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import PageSelector from './index' import { recursivePushInParentDescendants } from './utils' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx index bc494d93aa..99ecb84ddd 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx @@ -1,7 +1,7 @@ import type { ListChildComponentProps } from 'react-window' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { areEqual } from 'react-window' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/title.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/title.tsx index c9f48d0539..376274ba44 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/title.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/title.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type TitleProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/bucket.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/bucket.tsx index 06e4dc8386..ae84b21027 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/bucket.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/bucket.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { BucketsGray } from '@/app/components/base/icons/src/public/knowledge/online-drive' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/drive.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/drive.tsx index 91884ac2c8..208658ab5b 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/drive.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/drive.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.spec.tsx index 174e5f6287..13abce1c81 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Dropdown from './index' // ========================================== diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.tsx index 5b4948241f..f6eda7f7af 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/index.tsx @@ -1,5 +1,6 @@ import { RiMoreFill } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/item.tsx index 59ad8a6e10..864cade85c 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/item.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' type ItemProps = { name: string diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/menu.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/menu.tsx index 9c5b15cb47..44af10cd95 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/menu.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/dropdown/menu.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Item from './item' type MenuProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.spec.tsx index 24500822c6..b7e53ed1be 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Breadcrumbs from './index' // ========================================== diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.tsx index 4657b79c19..a85137927a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/index.tsx @@ -1,4 +1,5 @@ -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useDataSourceStore, useDataSourceStoreWithSelector } from '../../../../store' import Bucket from './bucket' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/item.tsx index fa019642f3..1bf32ab769 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/breadcrumbs/item.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' type BreadcrumbItemProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.spec.tsx index ff2bdb2769..3c836465b8 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.spec.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Header from './index' // ========================================== diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.tsx index cda916a4e8..8f0d169f1b 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/header/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Breadcrumbs from './breadcrumbs' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/index.spec.tsx index 0e69a18574..2ad62aae8e 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/index.spec.tsx @@ -1,6 +1,6 @@ import type { OnlineDriveFile } from '@/models/pipeline' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { OnlineDriveFileType } from '@/models/pipeline' import FileList from './index' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-folder.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-folder.tsx index 595e976ba3..304210ca8f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-folder.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-folder.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const EmptyFolder = () => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-search-result.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-search-result.tsx index d435f38e64..b2266b7bb3 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-search-result.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/empty-search-result.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { SearchMenu } from '@/app/components/base/icons/src/vender/knowledge' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/file-icon.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/file-icon.tsx index 1c25532884..ee87390892 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/file-icon.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/file-icon.tsx @@ -1,4 +1,5 @@ -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon' import { BucketsBlue, Folder } from '@/app/components/base/icons/src/public/knowledge/online-drive' import { OnlineDriveFileType } from '@/models/pipeline' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.spec.tsx index 29683bcfa9..0a8066bdc7 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.spec.tsx @@ -1,7 +1,7 @@ import type { Mock } from 'vitest' import type { OnlineDriveFile } from '@/models/pipeline' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { OnlineDriveFileType } from '@/models/pipeline' import List from './index' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.tsx index ecf28026d3..977001dbdd 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/index.tsx @@ -1,6 +1,7 @@ import type { OnlineDriveFile } from '@/models/pipeline' import { RiLoader2Line } from '@remixicon/react' -import React, { useEffect, useRef } from 'react' +import * as React from 'react' +import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useDataSourceStore } from '../../../store' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/item.tsx index 8672a1841a..07ee21486a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/file-list/list/item.tsx @@ -1,6 +1,7 @@ import type { Placement } from '@floating-ui/react' import type { OnlineDriveFile } from '@/models/pipeline' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import Radio from '@/app/components/base/radio/ui' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/header.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/header.tsx index 4092a5b80c..3a6f294e09 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/header.tsx @@ -1,5 +1,5 @@ import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/index.spec.tsx index ff65ad1385..7bf1d123f6 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-drive/index.spec.tsx @@ -2,7 +2,7 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-so import type { OnlineDriveFile } from '@/models/pipeline' import type { OnlineDriveData } from '@/types/pipeline' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline' import Header from './header' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx index f109737a41..17dfa37569 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx index 871be218b3..7ca7c03d97 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result-item.tsx @@ -1,6 +1,7 @@ 'use client' import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx index ecd5c709b3..b10b1e8457 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawled-result.tsx @@ -1,6 +1,7 @@ 'use client' import type { CrawlResultItem } from '@/models/datasets' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import CheckboxWithLabel from './checkbox-with-label' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawling.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawling.tsx index 65eb2b2c76..3b98ec76a0 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawling.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/crawling.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/error-message.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/error-message.tsx index 1423ab03a6..f0a1fb64a9 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/error-message.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/error-message.tsx @@ -1,5 +1,5 @@ import { RiErrorWarningFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type ErrorMessageProps = { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/index.spec.tsx index a544d90c39..94de64d791 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/index.spec.tsx @@ -1,6 +1,6 @@ import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CheckboxWithLabel from './checkbox-with-label' import CrawledResult from './crawled-result' import CrawledResultItem from './crawled-result-item' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.spec.tsx index 4f92d85ec7..b89114c84b 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.spec.tsx @@ -1,7 +1,7 @@ import type { MockInstance } from 'vitest' import type { RAGPipelineVariables } from '@/models/pipeline' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' import Toast from '@/app/components/base/toast' import { CrawlStep } from '@/models/datasets' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.spec.tsx index 0c38208db9..493dd25730 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.spec.tsx @@ -1,7 +1,7 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' import type { CrawlResultItem } from '@/models/datasets' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { CrawlStep } from '@/models/datasets' import WebsiteCrawl from './index' diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx index 30fa81b608..2a1141cf9e 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/index.tsx @@ -6,7 +6,8 @@ import type { DataSourceNodeErrorResponse, DataSourceNodeProcessingResponse, } from '@/types/pipeline' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' diff --git a/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx b/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx index 1760286b04..2b30c79022 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/left-header.tsx @@ -2,7 +2,7 @@ import type { Step } from './step-indicator' import { RiArrowLeftLine } from '@remixicon/react' import Link from 'next/link' import { useParams } from 'next/navigation' -import React from 'react' +import * as React from 'react' import Button from '@/app/components/base/button' import Effect from '@/app/components/base/effect' import StepIndicator from './step-indicator' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.spec.tsx index 29584b5da5..f055c90df8 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.spec.tsx @@ -2,7 +2,7 @@ import type { NotionPage } from '@/models/common' import type { CrawlResultItem, CustomFile, FileIndexingEstimateResponse } from '@/models/datasets' import type { OnlineDriveFile } from '@/models/pipeline' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ChunkingMode } from '@/models/datasets' import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline' import ChunkPreview from './chunk-preview' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.tsx index d2a28feef9..6a137fa98c 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/chunk-preview.tsx @@ -2,7 +2,8 @@ import type { NotionPage } from '@/models/common' import type { CrawlResultItem, CustomFile, DocumentItem, FileIndexingEstimateResponse } from '@/models/datasets' import type { OnlineDriveFile } from '@/models/pipeline' import { RiSearchEyeLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.spec.tsx index e5aaa27895..6f040ffb00 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.spec.tsx @@ -1,6 +1,6 @@ import type { CustomFile as File } from '@/models/datasets' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import FilePreview from './file-preview' // Uses global react-i18next mock from web/vitest.setup.ts diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.tsx index 6962d63567..53427a60a6 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/file-preview.tsx @@ -1,7 +1,8 @@ 'use client' import type { CustomFile as File } from '@/models/datasets' import { RiCloseLine } from '@remixicon/react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useFilePreview } from '@/service/use-common' import { formatFileSize, formatNumberAbbreviated } from '@/utils/format' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/loading.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/loading.tsx index a367f3675c..dedc9d6a99 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/loading.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/loading.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { SkeletonContainer, SkeletonRectangle } from '@/app/components/base/skeleton' const Loading = () => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.spec.tsx index cd16ed3bbc..5375a0197c 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.spec.tsx @@ -1,6 +1,6 @@ import type { NotionPage } from '@/models/common' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import Toast from '@/app/components/base/toast' import OnlineDocumentPreview from './online-document-preview' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.tsx index 3582eed5df..6c25218421 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/online-document-preview.tsx @@ -1,7 +1,8 @@ 'use client' import type { NotionPage } from '@/models/common' import { RiCloseLine } from '@remixicon/react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Notion } from '@/app/components/base/icons/src/public/common' import { Markdown } from '@/app/components/base/markdown' diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.spec.tsx index cfe58de56b..2cfb14f42a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.spec.tsx @@ -1,6 +1,6 @@ import type { CrawlResultItem } from '@/models/datasets' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import WebsitePreview from './web-preview' // Uses global react-i18next mock from web/vitest.setup.ts diff --git a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx index c68ede7734..22179bad05 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/preview/web-preview.tsx @@ -1,7 +1,7 @@ 'use client' import type { CrawlResultItem } from '@/models/datasets' import { RiCloseLine, RiGlobalLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { formatNumberAbbreviated } from '@/utils/format' diff --git a/web/app/components/datasets/documents/create-from-pipeline/process-documents/actions.tsx b/web/app/components/datasets/documents/create-from-pipeline/process-documents/actions.tsx index a49a8e9964..f4f4898b1f 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/process-documents/actions.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/process-documents/actions.tsx @@ -1,5 +1,5 @@ import { RiArrowLeftLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/documents/create-from-pipeline/process-documents/components.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/process-documents/components.spec.tsx index 2bd80ea60c..322e6edd49 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/process-documents/components.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/process-documents/components.spec.tsx @@ -1,6 +1,6 @@ import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { z } from 'zod' import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/datasets/documents/create-from-pipeline/process-documents/header.tsx b/web/app/components/datasets/documents/create-from-pipeline/process-documents/header.tsx index 0b6300310a..ac1f3b0fa0 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/process-documents/header.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/process-documents/header.tsx @@ -1,5 +1,5 @@ import { RiSearchEyeLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.spec.tsx index 2b0fd7f0d0..318a6c2cba 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.spec.tsx @@ -1,6 +1,6 @@ import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' import { useConfigurations, useInitialData } from '@/app/components/rag-pipeline/hooks/use-input-fields' import { useInputVariables } from './hooks' diff --git a/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.tsx index 4556d7ab85..770c6c820b 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/process-documents/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { generateZodSchema } from '@/app/components/base/form/form-scenarios/base/utils' import { useConfigurations, useInitialData } from '@/app/components/rag-pipeline/hooks/use-input-fields' import Actions from './actions' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.spec.tsx index 1626f4f707..81e97a79a1 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.spec.tsx @@ -2,7 +2,7 @@ import type { Mock } from 'vitest' import type { DocumentIndexingStatus, IndexingStatusResponse } from '@/models/datasets' import type { InitialDocumentDetail } from '@/models/pipeline' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { Plan } from '@/app/components/billing/type' import { IndexingType } from '@/app/components/datasets/create/step-two' import { DatasourceType } from '@/models/pipeline' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.tsx index 98f83f7458..3c4dbb6e0a 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/index.tsx @@ -12,7 +12,8 @@ import { } from '@remixicon/react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.spec.tsx index 17d7d8305b..9831896b90 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.spec.tsx @@ -1,6 +1,6 @@ import type { ProcessRuleResponse } from '@/models/datasets' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { IndexingType } from '@/app/components/datasets/create/step-two' import { ProcessMode } from '@/models/datasets' import { RETRIEVE_METHOD } from '@/types/app' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx index a96a47a569..a16e284bcf 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx @@ -1,6 +1,7 @@ import type { ProcessRuleResponse } from '@/models/datasets' import Image from 'next/image' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { indexMethodIcon, retrievalIcon } from '@/app/components/datasets/create/icons' import { IndexingType } from '@/app/components/datasets/create/step-two' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/index.spec.tsx index 948e3ba118..9d7a3e7b08 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/index.spec.tsx @@ -1,7 +1,7 @@ import type { DocumentIndexingStatus } from '@/models/datasets' import type { InitialDocumentDetail } from '@/models/pipeline' import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { DatasourceType } from '@/models/pipeline' import Processing from './index' diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/index.tsx index c57221f8a4..09458dde89 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { InitialDocumentDetail } from '@/models/pipeline' import { RiBookOpenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx b/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx index 82bc9e9b31..755526df79 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/step-indicator.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' export type Step = { diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx index 7cf0890c43..b6f0cbff10 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader, diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx index d127471e28..3e55da0a90 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx @@ -4,7 +4,8 @@ import type { FileItem } from '@/models/datasets' import { RiDeleteBinLine, } from '@remixicon/react' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/documents/detail/batch-modal/index.tsx b/web/app/components/datasets/documents/detail/batch-modal/index.tsx index cc3e9455d8..091d5c493e 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/index.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { ChunkingMode, FileItem } from '@/models/datasets' import { RiCloseLine } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx index 135d791fb3..f7166ca4dc 100644 --- a/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/child-segment-detail.tsx @@ -5,7 +5,8 @@ import { RiCollapseDiagonalLine, RiExpandDiagonalLine, } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { useEventEmitterContextContext } from '@/context/event-emitter' diff --git a/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx b/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx index 69f7ee7889..49a7524a8e 100644 --- a/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/action-buttons.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import { useKeyPress } from 'ahooks' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' diff --git a/web/app/components/datasets/documents/detail/completed/common/add-another.tsx b/web/app/components/datasets/documents/detail/completed/common/add-another.tsx index 3c7eb83533..e6103b8ffe 100644 --- a/web/app/components/datasets/documents/detail/completed/common/add-another.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/add-another.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/detail/completed/common/batch-action.tsx b/web/app/components/datasets/documents/detail/completed/common/batch-action.tsx index ed8b6ac562..6cc60453dd 100644 --- a/web/app/components/datasets/documents/detail/completed/common/batch-action.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/batch-action.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiArchive2Line, RiCheckboxCircleLine, RiCloseCircleLine, RiDeleteBinLine, RiDraftLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx index cb00903016..03ef530b11 100644 --- a/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/chunk-content.tsx @@ -1,5 +1,6 @@ import type { ComponentProps, FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Markdown } from '@/app/components/base/markdown' import { ChunkingMode } from '@/models/datasets' diff --git a/web/app/components/datasets/documents/detail/completed/common/dot.tsx b/web/app/components/datasets/documents/detail/completed/common/dot.tsx index 3ec98cb64f..d0a3543851 100644 --- a/web/app/components/datasets/documents/detail/completed/common/dot.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/dot.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' const Dot = () => { return ( diff --git a/web/app/components/datasets/documents/detail/completed/common/drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx index a3f30b0ebd..dc1b7192c3 100644 --- a/web/app/components/datasets/documents/detail/completed/common/drawer.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/drawer.tsx @@ -1,5 +1,6 @@ import { useKeyPress } from 'ahooks' -import React, { useCallback, useEffect, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef } from 'react' import { createPortal } from 'react-dom' import { cn } from '@/utils/classnames' import { useSegmentListContext } from '..' diff --git a/web/app/components/datasets/documents/detail/completed/common/empty.tsx b/web/app/components/datasets/documents/detail/completed/common/empty.tsx index 04ce92a693..48a730076e 100644 --- a/web/app/components/datasets/documents/detail/completed/common/empty.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/empty.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiFileList2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type IEmptyProps = { diff --git a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx index 9293dc862d..5f62bf0185 100644 --- a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx @@ -1,5 +1,5 @@ import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import Drawer from './drawer' diff --git a/web/app/components/datasets/documents/detail/completed/common/keywords.tsx b/web/app/components/datasets/documents/detail/completed/common/keywords.tsx index be7da98cd9..e62f2fd09d 100644 --- a/web/app/components/datasets/documents/detail/completed/common/keywords.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/keywords.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { SegmentDetailModel } from '@/models/datasets' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import TagInput from '@/app/components/base/tag-input' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx index 77518b2fe4..4957104e25 100644 --- a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiLoader2Line } from '@remixicon/react' import { useCountDown } from 'ahooks' import { noop } from 'lodash-es' -import React, { useRef, useState } from 'react' +import * as React from 'react' +import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx b/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx index 2a837f66a7..a263ca55c8 100644 --- a/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/segment-index-tag.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { Chunk } from '@/app/components/base/icons/src/vender/knowledge' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/documents/detail/completed/common/tag.tsx b/web/app/components/datasets/documents/detail/completed/common/tag.tsx index 66bc0bbeaf..f78cbf1c3f 100644 --- a/web/app/components/datasets/documents/detail/completed/common/tag.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/tag.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' const Tag = ({ text, className }: { text: string, className?: string }) => { diff --git a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx index 2f1212acdf..444907311a 100644 --- a/web/app/components/datasets/documents/detail/completed/display-toggle.tsx +++ b/web/app/components/datasets/documents/detail/completed/display-toggle.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiLineHeight } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Collapse } from '@/app/components/base/icons/src/vender/knowledge' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 8c2587969d..1b4aadfa50 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -6,7 +6,8 @@ import type { ChildChunkDetail, SegmentDetailModel, SegmentUpdater } from '@/mod import { useDebounceFn } from 'ahooks' import { noop } from 'lodash-es' import { usePathname } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { createContext, useContext, useContextSelector } from 'use-context-selector' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx b/web/app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx index 11c73349de..dda2d9bf80 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card/chunk-content.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { Markdown } from '@/app/components/base/markdown' import { cn } from '@/utils/classnames' import { useSegmentListContext } from '..' diff --git a/web/app/components/datasets/documents/detail/completed/segment-card/index.spec.tsx b/web/app/components/datasets/documents/detail/completed/segment-card/index.spec.tsx index 31f2e45ab4..536b7af338 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card/index.spec.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card/index.spec.tsx @@ -2,7 +2,7 @@ import type { SegmentListContextValue } from '@/app/components/datasets/document import type { DocumentContextValue } from '@/app/components/datasets/documents/detail/context' import type { Attachment, ChildChunkDetail, ParentMode, SegmentDetailModel } from '@/models/datasets' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { ChunkingMode } from '@/models/datasets' import SegmentCard from './index' diff --git a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx index f0f24ec372..2393324d55 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-card/index.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { ChildChunkDetail, SegmentDetailModel } from '@/models/datasets' import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx index 175c08133d..1ba64176ad 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-detail.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-detail.tsx @@ -6,7 +6,8 @@ import { RiCollapseDiagonalLine, RiExpandDiagonalLine, } from '@remixicon/react' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { v4 as uuid4 } from 'uuid' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/documents/detail/completed/segment-list.tsx b/web/app/components/datasets/documents/detail/completed/segment-list.tsx index a0153ae76c..9e5f0ab2fe 100644 --- a/web/app/components/datasets/documents/detail/completed/segment-list.tsx +++ b/web/app/components/datasets/documents/detail/completed/segment-list.tsx @@ -1,5 +1,6 @@ import type { ChildChunkDetail, SegmentDetailModel } from '@/models/datasets' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import Checkbox from '@/app/components/base/checkbox' import Divider from '@/app/components/base/divider' import { ChunkingMode } from '@/models/datasets' diff --git a/web/app/components/datasets/documents/detail/completed/skeleton/full-doc-list-skeleton.tsx b/web/app/components/datasets/documents/detail/completed/skeleton/full-doc-list-skeleton.tsx index e1d7231214..9f9f51b55b 100644 --- a/web/app/components/datasets/documents/detail/completed/skeleton/full-doc-list-skeleton.tsx +++ b/web/app/components/datasets/documents/detail/completed/skeleton/full-doc-list-skeleton.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' const Slice = React.memo(() => { return ( diff --git a/web/app/components/datasets/documents/detail/completed/skeleton/general-list-skeleton.tsx b/web/app/components/datasets/documents/detail/completed/skeleton/general-list-skeleton.tsx index 416a5e15d5..118a39ef56 100644 --- a/web/app/components/datasets/documents/detail/completed/skeleton/general-list-skeleton.tsx +++ b/web/app/components/datasets/documents/detail/completed/skeleton/general-list-skeleton.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Divider from '@/app/components/base/divider' import { diff --git a/web/app/components/datasets/documents/detail/completed/skeleton/paragraph-list-skeleton.tsx b/web/app/components/datasets/documents/detail/completed/skeleton/paragraph-list-skeleton.tsx index aa33bfbf17..9af0543fb6 100644 --- a/web/app/components/datasets/documents/detail/completed/skeleton/paragraph-list-skeleton.tsx +++ b/web/app/components/datasets/documents/detail/completed/skeleton/paragraph-list-skeleton.tsx @@ -1,5 +1,5 @@ import { RiArrowRightSLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Divider from '@/app/components/base/divider' import { diff --git a/web/app/components/datasets/documents/detail/completed/skeleton/parent-chunk-card-skeleton.tsx b/web/app/components/datasets/documents/detail/completed/skeleton/parent-chunk-card-skeleton.tsx index 4495547edb..be1a1696b2 100644 --- a/web/app/components/datasets/documents/detail/completed/skeleton/parent-chunk-card-skeleton.tsx +++ b/web/app/components/datasets/documents/detail/completed/skeleton/parent-chunk-card-skeleton.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { SkeletonContainer, diff --git a/web/app/components/datasets/documents/detail/completed/status-item.tsx b/web/app/components/datasets/documents/detail/completed/status-item.tsx index ce038742ff..34fc8bf0cb 100644 --- a/web/app/components/datasets/documents/detail/completed/status-item.tsx +++ b/web/app/components/datasets/documents/detail/completed/status-item.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { Item } from '@/app/components/base/select' import { RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type IStatusItemProps = { item: Item diff --git a/web/app/components/datasets/documents/detail/embedding/index.tsx b/web/app/components/datasets/documents/detail/embedding/index.tsx index 8eb5d197cb..db83d89c40 100644 --- a/web/app/components/datasets/documents/detail/embedding/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/index.tsx @@ -3,7 +3,8 @@ import type { CommonResponse } from '@/models/common' import type { IndexingStatusResponse, ProcessRuleResponse } from '@/models/datasets' import { RiLoader2Line, RiPauseCircleLine, RiPlayCircleLine } from '@remixicon/react' import Image from 'next/image' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/documents/detail/embedding/skeleton/index.tsx b/web/app/components/datasets/documents/detail/embedding/skeleton/index.tsx index eda512f38e..469d928eaa 100644 --- a/web/app/components/datasets/documents/detail/embedding/skeleton/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/skeleton/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Divider from '@/app/components/base/divider' import { SkeletonContainer, diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index 88b9fb5153..afb2d47c5b 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { DataSourceInfo, FileItem, LegacyDataSourceInfo } from '@/models/datasets' import { RiArrowLeftLine, RiLayoutLeft2Line, RiLayoutRight2Line } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import FloatRightContainer from '@/app/components/base/float-right-container' diff --git a/web/app/components/datasets/documents/detail/metadata/index.tsx b/web/app/components/datasets/documents/detail/metadata/index.tsx index 73e8357e10..87d136c3fe 100644 --- a/web/app/components/datasets/documents/detail/metadata/index.tsx +++ b/web/app/components/datasets/documents/detail/metadata/index.tsx @@ -5,7 +5,8 @@ import type { CommonResponse } from '@/models/common' import type { DocType, FullDocumentDetail } from '@/models/datasets' import { PencilIcon } from '@heroicons/react/24/outline' import { get } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AutoHeightTextarea from '@/app/components/base/auto-height-textarea' diff --git a/web/app/components/datasets/documents/detail/segment-add/index.tsx b/web/app/components/datasets/documents/detail/segment-add/index.tsx index eed48eac6a..d8c4ab5c69 100644 --- a/web/app/components/datasets/documents/detail/segment-add/index.tsx +++ b/web/app/components/datasets/documents/detail/segment-add/index.tsx @@ -7,7 +7,8 @@ import { RiLoader2Line, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import Popover from '@/app/components/base/popover' diff --git a/web/app/components/datasets/documents/detail/settings/document-settings.tsx b/web/app/components/datasets/documents/detail/settings/document-settings.tsx index 96ac687d7a..6046829514 100644 --- a/web/app/components/datasets/documents/detail/settings/document-settings.tsx +++ b/web/app/components/datasets/documents/detail/settings/document-settings.tsx @@ -11,7 +11,8 @@ import type { } from '@/models/datasets' import { useBoolean } from 'ahooks' import { useRouter } from 'next/navigation' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import AppUnavailable from '@/app/components/base/app-unavailable' diff --git a/web/app/components/datasets/documents/detail/settings/index.tsx b/web/app/components/datasets/documents/detail/settings/index.tsx index ba1c1eb197..45b885fb06 100644 --- a/web/app/components/datasets/documents/detail/settings/index.tsx +++ b/web/app/components/datasets/documents/detail/settings/index.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import DocumentSettings from './document-settings' import PipelineSettings from './pipeline-settings' diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/left-header.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/left-header.tsx index fb7d1356c1..547dc8f53b 100644 --- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/left-header.tsx +++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/left-header.tsx @@ -1,6 +1,7 @@ import { RiArrowLeftLine } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Effect from '@/app/components/base/effect' diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/process-documents/actions.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/process-documents/actions.tsx index c14a722ade..2cd379fa5f 100644 --- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/process-documents/actions.tsx +++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/process-documents/actions.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index 6a8a8ca563..5592c56224 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -6,7 +6,8 @@ import { PlusIcon } from '@heroicons/react/24/solid' import { RiDraftLine, RiExternalLinkLine } from '@remixicon/react' import { useDebounce, useDebounceFn } from 'ahooks' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index 3c95874c46..0b06d5fe15 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -11,7 +11,8 @@ import { import { useBoolean } from 'ahooks' import { pick, uniq } from 'lodash-es' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import NotionIcon from '@/app/components/base/notion-icon' diff --git a/web/app/components/datasets/documents/operations.tsx b/web/app/components/datasets/documents/operations.tsx index 561771dc89..825a315178 100644 --- a/web/app/components/datasets/documents/operations.tsx +++ b/web/app/components/datasets/documents/operations.tsx @@ -13,7 +13,8 @@ import { import { useBoolean, useDebounceFn } from 'ahooks' import { noop } from 'lodash-es' import { useRouter } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { DataSourceType, DocumentActionType } from '@/models/datasets' diff --git a/web/app/components/datasets/documents/rename-modal.tsx b/web/app/components/datasets/documents/rename-modal.tsx index ee1b7a5a82..cd4acf8eab 100644 --- a/web/app/components/datasets/documents/rename-modal.tsx +++ b/web/app/components/datasets/documents/rename-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useBoolean } from 'ahooks' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/datasets/documents/status-item/index.tsx b/web/app/components/datasets/documents/status-item/index.tsx index f152e498ad..415b413a26 100644 --- a/web/app/components/datasets/documents/status-item/index.tsx +++ b/web/app/components/datasets/documents/status-item/index.tsx @@ -3,7 +3,8 @@ import type { ColorMap, IndicatorProps } from '@/app/components/header/indicator import type { CommonResponse } from '@/models/common' import type { DocumentDisplayStatus } from '@/models/datasets' import { useDebounceFn } from 'ahooks' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/datasets/external-api/external-api-modal/Form.tsx b/web/app/components/datasets/external-api/external-api-modal/Form.tsx index 875475f3e4..558ea1414e 100644 --- a/web/app/components/datasets/external-api/external-api-modal/Form.tsx +++ b/web/app/components/datasets/external-api/external-api-modal/Form.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { CreateExternalAPIReq, FormSchema } from '../declarations' import { RiBookOpenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/datasets/external-api/external-api-panel/index.tsx b/web/app/components/datasets/external-api/external-api-panel/index.tsx index def26fe00b..0cfe7657b1 100644 --- a/web/app/components/datasets/external-api/external-api-panel/index.tsx +++ b/web/app/components/datasets/external-api/external-api-panel/index.tsx @@ -3,7 +3,7 @@ import { RiBookOpenLine, RiCloseLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx b/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx index af95e6771a..f4158fc462 100644 --- a/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx +++ b/web/app/components/datasets/external-api/external-knowledge-api-card/index.tsx @@ -4,7 +4,8 @@ import { RiDeleteBinLine, RiEditLine, } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/external-knowledge-base/connector/index.tsx b/web/app/components/datasets/external-knowledge-base/connector/index.tsx index 5184bdd888..1545c0d232 100644 --- a/web/app/components/datasets/external-knowledge-base/connector/index.tsx +++ b/web/app/components/datasets/external-knowledge-base/connector/index.tsx @@ -2,7 +2,8 @@ import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' import { useRouter } from 'next/navigation' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { trackEvent } from '@/app/components/base/amplitude' import { useToastContext } from '@/app/components/base/toast' import ExternalKnowledgeBaseCreate from '@/app/components/datasets/external-knowledge-base/create' diff --git a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx index 2035f6709a..b07d1091e2 100644 --- a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx @@ -3,7 +3,8 @@ import { RiArrowDownSLine, } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context' diff --git a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx index 68231b46d4..6f4bfed1ba 100644 --- a/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/ExternalApiSelection.tsx @@ -2,7 +2,8 @@ import { RiAddLine } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx b/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx index 280e8ac864..e3cddc2c69 100644 --- a/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' diff --git a/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx b/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx index a7de114a2d..36085c5f33 100644 --- a/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' import TopKItem from '@/app/components/base/param-item/top-k-item' diff --git a/web/app/components/datasets/external-knowledge-base/create/index.spec.tsx b/web/app/components/datasets/external-knowledge-base/create/index.spec.tsx index 0284a924c0..2fce096cd5 100644 --- a/web/app/components/datasets/external-knowledge-base/create/index.spec.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/index.spec.tsx @@ -1,7 +1,7 @@ import type { ExternalAPIItem } from '@/models/datasets' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import ExternalKnowledgeBaseCreate from './index' import RetrievalSettings from './RetrievalSettings' diff --git a/web/app/components/datasets/extra-info/index.tsx b/web/app/components/datasets/extra-info/index.tsx index 5b46c92798..d0f74fd288 100644 --- a/web/app/components/datasets/extra-info/index.tsx +++ b/web/app/components/datasets/extra-info/index.tsx @@ -1,5 +1,5 @@ import type { RelatedAppResponse } from '@/models/datasets' -import React from 'react' +import * as React from 'react' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { useDatasetApiBaseUrl } from '@/service/knowledge/use-dataset' import ServiceApi from './service-api' diff --git a/web/app/components/datasets/extra-info/service-api/card.tsx b/web/app/components/datasets/extra-info/service-api/card.tsx index 0452ee4da1..e5de8f66a5 100644 --- a/web/app/components/datasets/extra-info/service-api/card.tsx +++ b/web/app/components/datasets/extra-info/service-api/card.tsx @@ -1,6 +1,7 @@ import { RiBookOpenLine, RiKey2Line } from '@remixicon/react' import Link from 'next/link' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import CopyFeedback from '@/app/components/base/copy-feedback' diff --git a/web/app/components/datasets/extra-info/service-api/index.tsx b/web/app/components/datasets/extra-info/service-api/index.tsx index c653f2cc70..e8a0fbcb5a 100644 --- a/web/app/components/datasets/extra-info/service-api/index.tsx +++ b/web/app/components/datasets/extra-info/service-api/index.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ApiAggregate } from '@/app/components/base/icons/src/vender/knowledge' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' diff --git a/web/app/components/datasets/extra-info/statistics.tsx b/web/app/components/datasets/extra-info/statistics.tsx index c867bade83..4982fffcb0 100644 --- a/web/app/components/datasets/extra-info/statistics.tsx +++ b/web/app/components/datasets/extra-info/statistics.tsx @@ -1,6 +1,6 @@ import type { RelatedAppResponse } from '@/models/datasets' import { RiInformation2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' diff --git a/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx b/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx index 680e848185..bca73994bd 100644 --- a/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx +++ b/web/app/components/datasets/hit-testing/components/child-chunks-item.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { HitTestingChildChunk } from '@/models/datasets' -import React from 'react' +import * as React from 'react' import { SliceContent } from '../../formatted-text/flavours/shared' import Score from './score' diff --git a/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx index b7468ee08c..942fe46ffb 100644 --- a/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx +++ b/web/app/components/datasets/hit-testing/components/chunk-detail-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' import type { HitTesting } from '@/models/datasets' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import FileIcon from '@/app/components/base/file-uploader/file-type-icon' import { Markdown } from '@/app/components/base/markdown' diff --git a/web/app/components/datasets/hit-testing/components/empty-records.tsx b/web/app/components/datasets/hit-testing/components/empty-records.tsx index 1a93439e73..dccc1f3f19 100644 --- a/web/app/components/datasets/hit-testing/components/empty-records.tsx +++ b/web/app/components/datasets/hit-testing/components/empty-records.tsx @@ -1,5 +1,5 @@ import { RiHistoryLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const EmptyRecords = () => { diff --git a/web/app/components/datasets/hit-testing/components/mask.tsx b/web/app/components/datasets/hit-testing/components/mask.tsx index 0bf329a3ff..703644bf9d 100644 --- a/web/app/components/datasets/hit-testing/components/mask.tsx +++ b/web/app/components/datasets/hit-testing/components/mask.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type MaskProps = { diff --git a/web/app/components/datasets/hit-testing/components/query-input/index.tsx b/web/app/components/datasets/hit-testing/components/query-input/index.tsx index abaed302c8..959e7f3425 100644 --- a/web/app/components/datasets/hit-testing/components/query-input/index.tsx +++ b/web/app/components/datasets/hit-testing/components/query-input/index.tsx @@ -15,7 +15,8 @@ import { RiPlayCircleLine, } from '@remixicon/react' import Image from 'next/image' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { v4 as uuid4 } from 'uuid' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/hit-testing/components/query-input/textarea.tsx b/web/app/components/datasets/hit-testing/components/query-input/textarea.tsx index a3478d5deb..c74bdd4492 100644 --- a/web/app/components/datasets/hit-testing/components/query-input/textarea.tsx +++ b/web/app/components/datasets/hit-testing/components/query-input/textarea.tsx @@ -1,5 +1,5 @@ import type { ChangeEvent } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Corner } from '@/app/components/base/icons/src/vender/solid/shapes' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/hit-testing/components/records.tsx b/web/app/components/datasets/hit-testing/components/records.tsx index 37eea71625..5de5391cc0 100644 --- a/web/app/components/datasets/hit-testing/components/records.tsx +++ b/web/app/components/datasets/hit-testing/components/records.tsx @@ -1,6 +1,7 @@ import type { Attachment, HitTestingRecord, Query } from '@/models/datasets' import { RiApps2Line, RiArrowDownLine, RiFocus2Line } from '@remixicon/react' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import useTimestamp from '@/hooks/use-timestamp' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/hit-testing/components/result-item-external.tsx b/web/app/components/datasets/hit-testing/components/result-item-external.tsx index 43d0709994..d4a6f2b002 100644 --- a/web/app/components/datasets/hit-testing/components/result-item-external.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item-external.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { ExternalKnowledgeBaseHitTesting } from '@/models/datasets' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/datasets/hit-testing/components/result-item-footer.tsx b/web/app/components/datasets/hit-testing/components/result-item-footer.tsx index ad2d07d98e..1c62828cf3 100644 --- a/web/app/components/datasets/hit-testing/components/result-item-footer.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item-footer.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader/types' import { RiArrowRightUpLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import FileIcon from '@/app/components/base/file-uploader/file-type-icon' diff --git a/web/app/components/datasets/hit-testing/components/result-item-meta.tsx b/web/app/components/datasets/hit-testing/components/result-item-meta.tsx index 558333a103..6277d9af8e 100644 --- a/web/app/components/datasets/hit-testing/components/result-item-meta.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item-meta.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Dot from '../../documents/detail/completed/common/dot' diff --git a/web/app/components/datasets/hit-testing/components/result-item.tsx b/web/app/components/datasets/hit-testing/components/result-item.tsx index 65ea47f348..0df8c6d560 100644 --- a/web/app/components/datasets/hit-testing/components/result-item.tsx +++ b/web/app/components/datasets/hit-testing/components/result-item.tsx @@ -3,7 +3,8 @@ import type { FileAppearanceTypeEnum } from '@/app/components/base/file-uploader import type { HitTesting } from '@/models/datasets' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Markdown } from '@/app/components/base/markdown' import Tag from '@/app/components/datasets/documents/detail/completed/common/tag' diff --git a/web/app/components/datasets/hit-testing/components/score.tsx b/web/app/components/datasets/hit-testing/components/score.tsx index 20113a403e..ed5496740d 100644 --- a/web/app/components/datasets/hit-testing/components/score.tsx +++ b/web/app/components/datasets/hit-testing/components/score.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx index 2ae5e303e2..e75ef48abf 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -10,7 +10,8 @@ import type { } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { useBoolean } from 'ahooks' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Drawer from '@/app/components/base/drawer' diff --git a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx index 801b62340c..7c7d4c4da2 100644 --- a/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx +++ b/web/app/components/datasets/hit-testing/modify-retrieval-modal.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { IndexingType } from '../create/step-two' import type { RetrievalConfig } from '@/types/app' import { RiCloseLine } from '@remixicon/react' -import React, { useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' diff --git a/web/app/components/datasets/list/dataset-card/index.tsx b/web/app/components/datasets/list/dataset-card/index.tsx index 4da265b43c..8087b80fda 100644 --- a/web/app/components/datasets/list/dataset-card/index.tsx +++ b/web/app/components/datasets/list/dataset-card/index.tsx @@ -4,7 +4,8 @@ import type { DataSet } from '@/models/datasets' import { RiFileTextFill, RiMoreFill, RiRobot2Fill } from '@remixicon/react' import { useHover } from 'ahooks' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/list/dataset-card/operation-item.tsx b/web/app/components/datasets/list/dataset-card/operation-item.tsx index c5c11afe45..afa0f174e8 100644 --- a/web/app/components/datasets/list/dataset-card/operation-item.tsx +++ b/web/app/components/datasets/list/dataset-card/operation-item.tsx @@ -1,5 +1,5 @@ import type { RemixiconComponentType } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type OperationItemProps = { Icon: RemixiconComponentType diff --git a/web/app/components/datasets/list/dataset-card/operations.tsx b/web/app/components/datasets/list/dataset-card/operations.tsx index e6ecbf76b9..d83ed1d396 100644 --- a/web/app/components/datasets/list/dataset-card/operations.tsx +++ b/web/app/components/datasets/list/dataset-card/operations.tsx @@ -1,5 +1,5 @@ import { RiDeleteBinLine, RiEditLine, RiFileDownloadLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import OperationItem from './operation-item' diff --git a/web/app/components/datasets/list/dataset-footer/index.tsx b/web/app/components/datasets/list/dataset-footer/index.tsx index 425ca0df26..233fd26456 100644 --- a/web/app/components/datasets/list/dataset-footer/index.tsx +++ b/web/app/components/datasets/list/dataset-footer/index.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const DatasetFooter = () => { diff --git a/web/app/components/datasets/list/new-dataset-card/index.tsx b/web/app/components/datasets/list/new-dataset-card/index.tsx index edc05e9919..0f8aa52586 100644 --- a/web/app/components/datasets/list/new-dataset-card/index.tsx +++ b/web/app/components/datasets/list/new-dataset-card/index.tsx @@ -3,7 +3,7 @@ import { RiAddLine, RiFunctionAddLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import Option from './option' diff --git a/web/app/components/datasets/list/new-dataset-card/option.tsx b/web/app/components/datasets/list/new-dataset-card/option.tsx index 97a9e88b16..e862b5c11e 100644 --- a/web/app/components/datasets/list/new-dataset-card/option.tsx +++ b/web/app/components/datasets/list/new-dataset-card/option.tsx @@ -1,5 +1,5 @@ import Link from 'next/link' -import React from 'react' +import * as React from 'react' type OptionProps = { Icon: React.ComponentType<{ className?: string }> diff --git a/web/app/components/datasets/metadata/add-metadata-button.tsx b/web/app/components/datasets/metadata/add-metadata-button.tsx index 3908c8935e..4bb28e0b32 100644 --- a/web/app/components/datasets/metadata/add-metadata-button.tsx +++ b/web/app/components/datasets/metadata/add-metadata-button.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Button from '../../base/button' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/add-row.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/add-row.tsx index f3a05fb22d..20fad9769a 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/add-row.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/add-row.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { MetadataItemWithEdit } from '../types' import { RiIndeterminateCircleLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import InputCombined from './input-combined' import Label from './label' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/edit-row.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/edit-row.tsx index 907ff127fb..1fdf8c2ef7 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/edit-row.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/edit-row.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { MetadataItemWithEdit } from '../types' import { RiDeleteBinLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { UpdateType } from '../types' import EditedBeacon from './edited-beacon' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/edited-beacon.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/edited-beacon.tsx index 34f4e43a33..b6b8c2a7d0 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/edited-beacon.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/edited-beacon.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiResetLeftLine } from '@remixicon/react' import { useHover } from 'ahooks' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/input-combined.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/input-combined.tsx index 51dd81b1fa..aec74bcfef 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/input-combined.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/input-combined.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Input from '@/app/components/base/input' import { InputNumber } from '@/app/components/base/input-number' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/input-has-set-multiple-value.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/input-has-set-multiple-value.tsx index 412ef419e9..cf475898d3 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/input-has-set-multiple-value.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/input-has-set-multiple-value.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/label.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/label.tsx index 009b61f0b8..08f886be18 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/label.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/label.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx index 8cec39b1d2..253d271a96 100644 --- a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx +++ b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { BuiltInMetadataItem, MetadataItemInBatchEdit, MetadataItemWithEdit } from '../types' import { RiQuestionLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx index f2eba083fb..d31e9d7957 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiArrowLeftLine } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import ModalLikeWrap from '../../../base/modal-like-wrap' diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx index 9ee326fd53..713804a541 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/create-metadata-modal.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Props as CreateContentProps } from './create-content' -import React from 'react' +import * as React from 'react' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../../base/portal-to-follow-elem' import CreateContent from './create-content' diff --git a/web/app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx b/web/app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx index bfdda3dd65..f94e6e136f 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/dataset-metadata-drawer.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { BuiltInMetadataItem, MetadataItemWithValueLength } from '../types' import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' import { useBoolean, useHover } from 'ahooks' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/datasets/metadata/metadata-dataset/field.tsx b/web/app/components/datasets/metadata/metadata-dataset/field.tsx index 8fb57bac34..4e8e3e02d8 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/field.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/field.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' type Props = { className?: string diff --git a/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx b/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx index be0fbe9fab..eb7189a4cd 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/select-metadata-modal.tsx @@ -3,7 +3,8 @@ import type { Placement } from '@floating-ui/react' import type { FC } from 'react' import type { MetadataItem } from '../types' import type { Props as CreateContentProps } from './create-content' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useDatasetMetaData } from '@/service/knowledge/use-metadata' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../../base/portal-to-follow-elem' import CreateContent from './create-content' diff --git a/web/app/components/datasets/metadata/metadata-dataset/select-metadata.tsx b/web/app/components/datasets/metadata/metadata-dataset/select-metadata.tsx index 3540939205..0422c7a51a 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/select-metadata.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/select-metadata.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { MetadataItem } from '../types' import { RiAddLine, RiArrowRightUpLine } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import SearchInput from '@/app/components/base/search-input' import { getIcon } from '../utils/get-icon' diff --git a/web/app/components/datasets/metadata/metadata-document/field.tsx b/web/app/components/datasets/metadata/metadata-document/field.tsx index 46c7598d5d..f6f5fdf8f1 100644 --- a/web/app/components/datasets/metadata/metadata-document/field.tsx +++ b/web/app/components/datasets/metadata/metadata-document/field.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' type Props = { label: string diff --git a/web/app/components/datasets/metadata/metadata-document/index.tsx b/web/app/components/datasets/metadata/metadata-document/index.tsx index beb88a8b82..4e8c931417 100644 --- a/web/app/components/datasets/metadata/metadata-document/index.tsx +++ b/web/app/components/datasets/metadata/metadata-document/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { FullDocumentDetail } from '@/models/datasets' import { RiEditLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/metadata/metadata-document/info-group.tsx b/web/app/components/datasets/metadata/metadata-document/info-group.tsx index afa490c344..5137859bed 100644 --- a/web/app/components/datasets/metadata/metadata-document/info-group.tsx +++ b/web/app/components/datasets/metadata/metadata-document/info-group.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { MetadataItemWithValue } from '../types' import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/metadata/metadata-document/no-data.tsx b/web/app/components/datasets/metadata/metadata-document/no-data.tsx index e52f69b590..81021ea51b 100644 --- a/web/app/components/datasets/metadata/metadata-document/no-data.tsx +++ b/web/app/components/datasets/metadata/metadata-document/no-data.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiArrowRightLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/no-linked-apps-panel.tsx b/web/app/components/datasets/no-linked-apps-panel.tsx index 5ab9689a12..b0a49abcdb 100644 --- a/web/app/components/datasets/no-linked-apps-panel.tsx +++ b/web/app/components/datasets/no-linked-apps-panel.tsx @@ -1,5 +1,5 @@ import { RiApps2AddLine, RiBookOpenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/datasets/settings/chunk-structure/index.tsx b/web/app/components/datasets/settings/chunk-structure/index.tsx index 2b9073672e..977620c4c1 100644 --- a/web/app/components/datasets/settings/chunk-structure/index.tsx +++ b/web/app/components/datasets/settings/chunk-structure/index.tsx @@ -1,5 +1,5 @@ import type { ChunkingMode } from '@/models/datasets' -import React from 'react' +import * as React from 'react' import OptionCard from '../option-card' import { useChunkStructure } from './hooks' diff --git a/web/app/components/datasets/settings/index-method/keyword-number.tsx b/web/app/components/datasets/settings/index-method/keyword-number.tsx index c754c2a48c..994d98f14a 100644 --- a/web/app/components/datasets/settings/index-method/keyword-number.tsx +++ b/web/app/components/datasets/settings/index-method/keyword-number.tsx @@ -1,5 +1,6 @@ import { RiQuestionLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { InputNumber } from '@/app/components/base/input-number' import Slider from '@/app/components/base/slider' diff --git a/web/app/components/datasets/settings/option-card.tsx b/web/app/components/datasets/settings/option-card.tsx index 8aa255746f..d17542935b 100644 --- a/web/app/components/datasets/settings/option-card.tsx +++ b/web/app/components/datasets/settings/option-card.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/settings/permission-selector/index.tsx b/web/app/components/datasets/settings/permission-selector/index.tsx index 85c69a46a6..ffbc3e4a1c 100644 --- a/web/app/components/datasets/settings/permission-selector/index.tsx +++ b/web/app/components/datasets/settings/permission-selector/index.tsx @@ -1,7 +1,8 @@ import type { Member } from '@/models/common' import { RiArrowDownSLine, RiGroup2Line, RiLock2Line } from '@remixicon/react' import { useDebounceFn } from 'ahooks' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Avatar from '@/app/components/base/avatar' import Input from '@/app/components/base/input' diff --git a/web/app/components/datasets/settings/permission-selector/member-item.tsx b/web/app/components/datasets/settings/permission-selector/member-item.tsx index f70faa3553..9c1c3da70c 100644 --- a/web/app/components/datasets/settings/permission-selector/member-item.tsx +++ b/web/app/components/datasets/settings/permission-selector/member-item.tsx @@ -1,5 +1,5 @@ import { RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/datasets/settings/permission-selector/permission-item.tsx b/web/app/components/datasets/settings/permission-selector/permission-item.tsx index f926e8287c..c5847896f7 100644 --- a/web/app/components/datasets/settings/permission-selector/permission-item.tsx +++ b/web/app/components/datasets/settings/permission-selector/permission-item.tsx @@ -1,5 +1,5 @@ import { RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type PermissionItemProps = { leftIcon: React.ReactNode diff --git a/web/app/components/develop/secret-key/input-copy.tsx b/web/app/components/develop/secret-key/input-copy.tsx index af7edea3c5..8f12d579bc 100644 --- a/web/app/components/develop/secret-key/input-copy.tsx +++ b/web/app/components/develop/secret-key/input-copy.tsx @@ -1,7 +1,8 @@ 'use client' import copy from 'copy-to-clipboard' import { t } from 'i18next' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import CopyFeedback from '@/app/components/base/copy-feedback' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/explore/app-card/index.spec.tsx b/web/app/components/explore/app-card/index.spec.tsx index 6247e8adc7..769b317929 100644 --- a/web/app/components/explore/app-card/index.spec.tsx +++ b/web/app/components/explore/app-card/index.spec.tsx @@ -1,7 +1,7 @@ import type { AppCardProps } from './index' import type { App } from '@/models/explore' import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { AppModeEnum } from '@/types/app' import AppCard from './index' diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 859a4c081d..585c4e60c1 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -3,7 +3,8 @@ import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import type { App } from '@/models/explore' import { useDebounceFn } from 'ahooks' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useContext } from 'use-context-selector' diff --git a/web/app/components/explore/category.tsx b/web/app/components/explore/category.tsx index 6d44c9f46b..eba883d849 100644 --- a/web/app/components/explore/category.tsx +++ b/web/app/components/explore/category.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { AppCategory } from '@/models/explore' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { ThumbsUp } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' import exploreI18n from '@/i18n/en-US/explore' diff --git a/web/app/components/explore/create-app-modal/index.spec.tsx b/web/app/components/explore/create-app-modal/index.spec.tsx index 55ee7f9064..6bc1e1e9a0 100644 --- a/web/app/components/explore/create-app-modal/index.spec.tsx +++ b/web/app/components/explore/create-app-modal/index.spec.tsx @@ -1,7 +1,7 @@ import type { CreateAppModalProps } from './index' import type { UsagePlanInfo } from '@/app/components/billing/type' import { act, fireEvent, render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { createMockPlan, createMockPlanTotal, createMockPlanUsage } from '@/__mocks__/provider-context' import { Plan } from '@/app/components/billing/type' import { AppModeEnum } from '@/types/app' diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index ccd1280201..dac89bc776 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -3,7 +3,8 @@ import type { AppIconType } from '@/types/app' import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react' import { useDebounceFn, useKeyPress } from 'ahooks' import { noop } from 'lodash-es' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' diff --git a/web/app/components/explore/index.tsx b/web/app/components/explore/index.tsx index d9919e90d9..a405fe0d28 100644 --- a/web/app/components/explore/index.tsx +++ b/web/app/components/explore/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { InstalledApp } from '@/models/explore' import { useRouter } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Sidebar from '@/app/components/explore/sidebar' import { useAppContext } from '@/context/app-context' diff --git a/web/app/components/explore/installed-app/index.tsx b/web/app/components/explore/installed-app/index.tsx index cd8bc468fb..def66c0260 100644 --- a/web/app/components/explore/installed-app/index.tsx +++ b/web/app/components/explore/installed-app/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { AppData } from '@/models/share' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useContext } from 'use-context-selector' import ChatWithHistory from '@/app/components/base/chat/chat-with-history' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/explore/item-operation/index.tsx b/web/app/components/explore/item-operation/index.tsx index bc145a633a..3703c0d4c0 100644 --- a/web/app/components/explore/item-operation/index.tsx +++ b/web/app/components/explore/item-operation/index.tsx @@ -5,7 +5,8 @@ import { RiEditLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' diff --git a/web/app/components/explore/sidebar/app-nav-item/index.tsx b/web/app/components/explore/sidebar/app-nav-item/index.tsx index 37163de42a..3347efeb3f 100644 --- a/web/app/components/explore/sidebar/app-nav-item/index.tsx +++ b/web/app/components/explore/sidebar/app-nav-item/index.tsx @@ -3,7 +3,8 @@ import type { AppIconType } from '@/types/app' import { useHover } from 'ahooks' import { useRouter } from 'next/navigation' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import AppIcon from '@/app/components/base/app-icon' import ItemOperation from '@/app/components/explore/item-operation' import { cn } from '@/utils/classnames' diff --git a/web/app/components/explore/sidebar/index.tsx b/web/app/components/explore/sidebar/index.tsx index d178e65cbe..2d370ef153 100644 --- a/web/app/components/explore/sidebar/index.tsx +++ b/web/app/components/explore/sidebar/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import Link from 'next/link' import { useSelectedLayoutSegments } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/goto-anything/actions/commands/account.tsx b/web/app/components/goto-anything/actions/commands/account.tsx index 82fc24ccb9..82025191b1 100644 --- a/web/app/components/goto-anything/actions/commands/account.tsx +++ b/web/app/components/goto-anything/actions/commands/account.tsx @@ -1,6 +1,6 @@ import type { SlashCommandHandler } from './types' import { RiUser3Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import i18n from '@/i18n-config/i18next-config' import { registerCommands, unregisterCommands } from './command-bus' diff --git a/web/app/components/goto-anything/actions/commands/community.tsx b/web/app/components/goto-anything/actions/commands/community.tsx index 3327c44c20..95ca9f89f3 100644 --- a/web/app/components/goto-anything/actions/commands/community.tsx +++ b/web/app/components/goto-anything/actions/commands/community.tsx @@ -1,6 +1,6 @@ import type { SlashCommandHandler } from './types' import { RiDiscordLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import i18n from '@/i18n-config/i18next-config' import { registerCommands, unregisterCommands } from './command-bus' diff --git a/web/app/components/goto-anything/actions/commands/docs.tsx b/web/app/components/goto-anything/actions/commands/docs.tsx index d802379570..8a95b5a836 100644 --- a/web/app/components/goto-anything/actions/commands/docs.tsx +++ b/web/app/components/goto-anything/actions/commands/docs.tsx @@ -1,6 +1,6 @@ import type { SlashCommandHandler } from './types' import { RiBookOpenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { defaultDocBaseUrl } from '@/context/i18n' import i18n from '@/i18n-config/i18next-config' import { getDocLanguage } from '@/i18n-config/language' diff --git a/web/app/components/goto-anything/actions/commands/forum.tsx b/web/app/components/goto-anything/actions/commands/forum.tsx index 50fc55211c..2156642bcf 100644 --- a/web/app/components/goto-anything/actions/commands/forum.tsx +++ b/web/app/components/goto-anything/actions/commands/forum.tsx @@ -1,6 +1,6 @@ import type { SlashCommandHandler } from './types' import { RiFeedbackLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import i18n from '@/i18n-config/i18next-config' import { registerCommands, unregisterCommands } from './command-bus' diff --git a/web/app/components/goto-anything/actions/commands/theme.tsx b/web/app/components/goto-anything/actions/commands/theme.tsx index c70e4378d6..dc8ca46bc0 100644 --- a/web/app/components/goto-anything/actions/commands/theme.tsx +++ b/web/app/components/goto-anything/actions/commands/theme.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from 'react' import type { CommandSearchResult } from '../types' import type { SlashCommandHandler } from './types' import { RiComputerLine, RiMoonLine, RiSunLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import i18n from '@/i18n-config/i18next-config' import { registerCommands, unregisterCommands } from './command-bus' diff --git a/web/app/components/goto-anything/actions/commands/zen.tsx b/web/app/components/goto-anything/actions/commands/zen.tsx index e8ee4c8087..9fa055a8cc 100644 --- a/web/app/components/goto-anything/actions/commands/zen.tsx +++ b/web/app/components/goto-anything/actions/commands/zen.tsx @@ -1,6 +1,6 @@ import type { SlashCommandHandler } from './types' import { RiFullscreenLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { isInWorkflowPage } from '@/app/components/workflow/constants' import i18n from '@/i18n-config/i18next-config' import { registerCommands, unregisterCommands } from './command-bus' diff --git a/web/app/components/goto-anything/command-selector.spec.tsx b/web/app/components/goto-anything/command-selector.spec.tsx index 40e67789cb..0ee2086058 100644 --- a/web/app/components/goto-anything/command-selector.spec.tsx +++ b/web/app/components/goto-anything/command-selector.spec.tsx @@ -2,7 +2,7 @@ import type { ActionItem } from './actions/types' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Command } from 'cmdk' -import React from 'react' +import * as React from 'react' import CommandSelector from './command-selector' vi.mock('next/navigation', () => ({ diff --git a/web/app/components/goto-anything/context.spec.tsx b/web/app/components/goto-anything/context.spec.tsx index 6922e83af1..ec979e1f88 100644 --- a/web/app/components/goto-anything/context.spec.tsx +++ b/web/app/components/goto-anything/context.spec.tsx @@ -1,5 +1,5 @@ import { render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { GotoAnythingProvider, useGotoAnythingContext } from './context' let pathnameMock = '/' diff --git a/web/app/components/goto-anything/context.tsx b/web/app/components/goto-anything/context.tsx index e0802d07c5..5c2bf3cb6b 100644 --- a/web/app/components/goto-anything/context.tsx +++ b/web/app/components/goto-anything/context.tsx @@ -2,7 +2,8 @@ import type { ReactNode } from 'react' import { usePathname } from 'next/navigation' -import React, { createContext, useContext, useEffect, useState } from 'react' +import * as React from 'react' +import { createContext, useContext, useEffect, useState } from 'react' import { isInWorkflowPage } from '../workflow/constants' /** diff --git a/web/app/components/goto-anything/index.spec.tsx b/web/app/components/goto-anything/index.spec.tsx index 29f6a6be99..7a8c1ead11 100644 --- a/web/app/components/goto-anything/index.spec.tsx +++ b/web/app/components/goto-anything/index.spec.tsx @@ -1,7 +1,7 @@ import type { ActionItem, SearchResult } from './actions/types' import { act, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import GotoAnything from './index' const routerPush = vi.fn() diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx index 7897e6da94..d139ab39df 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { DataSourceNotion as TDataSourceNotion } from '@/models/common' import { noop } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import NotionIcon from '@/app/components/base/notion-icon' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx index 0d30ed1d85..54a4f4e9cf 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { FirecrawlConfig } from '@/models/common' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx index e02a49e5e1..74392d30da 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx index 7405991f83..92a2f7b806 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-website/config-watercrawl-modal.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { WatercrawlConfig } from '@/models/common' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx index 4fccd064f9..5ad75a9466 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { DataSourceItem } from '@/models/common' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import s from '@/app/components/datasets/create/website/index.module.css' diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx index a6bcb3adbb..b98dd7933d 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx @@ -4,7 +4,7 @@ import { RiDeleteBinLine, } from '@remixicon/react' import { noop } from 'lodash-es' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Indicator from '../../../indicator' diff --git a/web/app/components/header/account-setting/data-source-page/panel/index.tsx b/web/app/components/header/account-setting/data-source-page/panel/index.tsx index 0cdaadbd30..49ef183136 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { ConfigItemType } from './config-item' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx index cee4e2b466..7d8169e4c4 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx @@ -1,5 +1,6 @@ import { RiArrowDownSLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Check } from '@/app/components/base/icons/src/vender/line/general' import { diff --git a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx index 48776cdfef..825c225a51 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/invitation-link.tsx @@ -2,7 +2,8 @@ import type { SuccessInvitationResult } from '.' import copy from 'copy-to-clipboard' import { t } from 'i18next' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import Tooltip from '@/app/components/base/tooltip' import s from './index.module.css' diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx index 6ad18d3830..2c6a33dc1f 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx @@ -1,6 +1,7 @@ import { RiCloseLine } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx index dae7731799..043fa13aa0 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/member-selector.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import { RiArrowDownSLine, } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Avatar from '@/app/components/base/avatar' import Input from '@/app/components/base/input' diff --git a/web/app/components/header/app-back/index.tsx b/web/app/components/header/app-back/index.tsx index 716a37b94e..5f76880fd0 100644 --- a/web/app/components/header/app-back/index.tsx +++ b/web/app/components/header/app-back/index.tsx @@ -2,7 +2,8 @@ import type { AppDetailResponse } from '@/models/app' import { ArrowLeftIcon, Squares2X2Icon } from '@heroicons/react/24/solid' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/header/github-star/index.spec.tsx b/web/app/components/header/github-star/index.spec.tsx index 78a0017b03..f60ced4147 100644 --- a/web/app/components/header/github-star/index.spec.tsx +++ b/web/app/components/header/github-star/index.spec.tsx @@ -1,7 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import nock from 'nock' -import React from 'react' +import * as React from 'react' import GithubStar from './index' const GITHUB_HOST = 'https://api.github.com' diff --git a/web/app/components/header/header-wrapper.tsx b/web/app/components/header/header-wrapper.tsx index 69d9e1d421..1b81c1152c 100644 --- a/web/app/components/header/header-wrapper.tsx +++ b/web/app/components/header/header-wrapper.tsx @@ -1,6 +1,7 @@ 'use client' import { usePathname } from 'next/navigation' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useEventEmitterContextContext } from '@/context/event-emitter' import { cn } from '@/utils/classnames' import s from './index.module.css' diff --git a/web/app/components/header/nav/index.tsx b/web/app/components/header/nav/index.tsx index a3820fcc49..83e75b8513 100644 --- a/web/app/components/header/nav/index.tsx +++ b/web/app/components/header/nav/index.tsx @@ -3,7 +3,8 @@ import type { INavSelectorProps } from './nav-selector' import Link from 'next/link' import { usePathname, useSearchParams, useSelectedLayoutSegment } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useStore as useAppStore } from '@/app/components/app/store' import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' import { cn } from '@/utils/classnames' diff --git a/web/app/components/i18n-server.tsx b/web/app/components/i18n-server.tsx index a81d137c59..01dc5f0f13 100644 --- a/web/app/components/i18n-server.tsx +++ b/web/app/components/i18n-server.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { getLocaleOnServer } from '@/i18n-config/server' import { ToastProvider } from './base/toast' import I18N from './i18n' diff --git a/web/app/components/i18n.tsx b/web/app/components/i18n.tsx index 5bd9de617e..8a95363c15 100644 --- a/web/app/components/i18n.tsx +++ b/web/app/components/i18n.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Locale } from '@/i18n-config' import { usePrefetchQuery } from '@tanstack/react-query' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import I18NContext from '@/context/i18n' import { setLocaleOnClient } from '@/i18n-config' import { getSystemFeatures } from '@/service/common' diff --git a/web/app/components/plugins/base/badges/icon-with-tooltip.tsx b/web/app/components/plugins/base/badges/icon-with-tooltip.tsx index 0a75334b5f..fc2aaaa572 100644 --- a/web/app/components/plugins/base/badges/icon-with-tooltip.tsx +++ b/web/app/components/plugins/base/badges/icon-with-tooltip.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Tooltip from '@/app/components/base/tooltip' import { Theme } from '@/types/app' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/base/deprecation-notice.tsx b/web/app/components/plugins/base/deprecation-notice.tsx index 3359474669..8832c77961 100644 --- a/web/app/components/plugins/base/deprecation-notice.tsx +++ b/web/app/components/plugins/base/deprecation-notice.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiAlertFill } from '@remixicon/react' import { camelCase } from 'lodash-es' import Link from 'next/link' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { Trans } from 'react-i18next' import { cn } from '@/utils/classnames' import { useMixedTranslation } from '../marketplace/hooks' diff --git a/web/app/components/plugins/base/key-value-item.tsx b/web/app/components/plugins/base/key-value-item.tsx index 07f5193773..5f1732cc0b 100644 --- a/web/app/components/plugins/base/key-value-item.tsx +++ b/web/app/components/plugins/base/key-value-item.tsx @@ -4,7 +4,8 @@ import { RiClipboardLine, } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/card/base/description.tsx b/web/app/components/plugins/card/base/description.tsx index 9b9d7e3471..79e77c7e6f 100644 --- a/web/app/components/plugins/card/base/description.tsx +++ b/web/app/components/plugins/card/base/description.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/plugins/card/base/download-count.tsx b/web/app/components/plugins/card/base/download-count.tsx index 653b595dde..91541cb931 100644 --- a/web/app/components/plugins/card/base/download-count.tsx +++ b/web/app/components/plugins/card/base/download-count.tsx @@ -1,5 +1,5 @@ import { RiInstallLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { formatNumber } from '@/utils/format' type Props = { diff --git a/web/app/components/plugins/card/card-more-info.tsx b/web/app/components/plugins/card/card-more-info.tsx index d81c941e96..33f819f31a 100644 --- a/web/app/components/plugins/card/card-more-info.tsx +++ b/web/app/components/plugins/card/card-more-info.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import DownloadCount from './base/download-count' type Props = { diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index af3468629e..1cb15bf70b 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { Plugin } from '../types' import { RiAlertFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' diff --git a/web/app/components/plugins/install-plugin/base/installed.tsx b/web/app/components/plugins/install-plugin/base/installed.tsx index aa4ca7c982..2c5a5cd088 100644 --- a/web/app/components/plugins/install-plugin/base/installed.tsx +++ b/web/app/components/plugins/install-plugin/base/installed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Badge, { BadgeState } from '@/app/components/base/badge/index' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/install-plugin/base/loading-error.tsx b/web/app/components/plugins/install-plugin/base/loading-error.tsx index f0067ed4fd..dd156fe7ef 100644 --- a/web/app/components/plugins/install-plugin/base/loading-error.tsx +++ b/web/app/components/plugins/install-plugin/base/loading-error.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import { LoadingPlaceholder } from '@/app/components/plugins/card/base/placeholder' diff --git a/web/app/components/plugins/install-plugin/base/loading.tsx b/web/app/components/plugins/install-plugin/base/loading.tsx index 973b574c8f..7416311dc8 100644 --- a/web/app/components/plugins/install-plugin/base/loading.tsx +++ b/web/app/components/plugins/install-plugin/base/loading.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Placeholder from '../../card/base/placeholder' diff --git a/web/app/components/plugins/install-plugin/base/version.tsx b/web/app/components/plugins/install-plugin/base/version.tsx index ad91c91af7..d7bf042ab5 100644 --- a/web/app/components/plugins/install-plugin/base/version.tsx +++ b/web/app/components/plugins/install-plugin/base/version.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { VersionProps } from '../../types' -import React from 'react' +import * as React from 'react' import Badge, { BadgeState } from '@/app/components/base/badge/index' const Version: FC<VersionProps> = ({ diff --git a/web/app/components/plugins/install-plugin/install-bundle/index.tsx b/web/app/components/plugins/install-plugin/install-bundle/index.tsx index 0e32ea7164..6c8a060116 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Dependency } from '../../types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx index 382f5473ca..a25c2f5978 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { GitHubItemAndMarketPlaceDependency, Plugin } from '../../../types' import type { VersionProps } from '@/app/components/plugins/types' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useUploadGitHub } from '@/service/use-plugins' import Loading from '../../base/loading' import { pluginManifestToCardPluginProps } from '../../utils' diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx index 22c48cf55c..29f4a44ddb 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Plugin, VersionProps } from '../../../types' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import { MARKETPLACE_API_PREFIX } from '@/config' import Card from '../../../card' diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx index 1502fd0d65..d02cb0f74f 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { Plugin } from '../../../types' import type { VersionProps } from '@/app/components/plugins/types' -import React from 'react' +import * as React from 'react' import Loading from '../../base/loading' import LoadedItem from './loaded-item' diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx index 84cee52ccc..63880bde5f 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/item/package-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { PackageDependency, Plugin } from '../../../types' import type { VersionProps } from '@/app/components/plugins/types' -import React from 'react' +import * as React from 'react' import LoadingError from '../../base/loading-error' import { pluginManifestToCardPluginProps } from '../../utils' import LoadedItem from './loaded-item' diff --git a/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx index f10556924c..2310063367 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Dependency, InstallStatus, Plugin } from '../../types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { InstallStep } from '../../types' import Install from './steps/install' import Installed from './steps/installed' diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx index f908b4f1c1..1b08ca5a04 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-multi.tsx @@ -1,7 +1,8 @@ 'use client' import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types' import { produce } from 'immer' -import React, { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' import { useGlobalPublicStore } from '@/context/global-public-context' import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx index 67c3bde8bc..0373e255a0 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Dependency, InstallStatus, InstallStatusResponse, Plugin, VersionInfo } from '../../../types' import type { ExposeRefs } from './install-multi' import { RiLoader2Line } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx index 2fb7aab9d2..48096a13d3 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { InstallStatus, Plugin } from '../../../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Badge, { BadgeState } from '@/app/components/base/badge/index' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index 291e5d4eca..4a15b263b7 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -3,7 +3,8 @@ import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types' import type { Item } from '@/app/components/base/select' import type { InstallState } from '@/app/components/plugins/types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx index 27cc8b7498..3bff22816b 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx @@ -2,7 +2,8 @@ import type { Plugin, PluginDeclaration, UpdateFromGitHubPayload } from '../../../types' import { RiLoader2Line } from '@remixicon/react' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx index 8fdd0c8b8a..a5fc79c50b 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx @@ -2,7 +2,7 @@ import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types' import type { Item } from '@/app/components/base/select' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalSelect } from '@/app/components/base/select' diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx index f07005a253..7d39675162 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx @@ -1,6 +1,6 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx index 4c0f26a2b9..b2390e38f3 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { Dependency, PluginDeclaration } from '../../types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx index 5afe9ccf90..b6f4e9d3ce 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { PluginDeclaration } from '../../types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { InstallStep } from '../../types' import Installed from '../base/installed' import useRefreshPluginList from '../hooks/use-refresh-plugin-list' diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx index b8a6891d64..86dda07639 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { PluginDeclaration } from '../../../types' import { RiLoader2Line } from '@remixicon/react' -import React, { useEffect, useMemo } from 'react' +import * as React from 'react' +import { useEffect, useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' import { gte } from 'semver' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx index fb591064ed..67b2505394 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { Dependency, PluginDeclaration } from '../../../types' import { RiLoader2Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { uploadFile } from '@/service/plugins' diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index 72ec874d6f..22ef6cb53a 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -1,7 +1,8 @@ 'use client' import type { Dependency, Plugin, PluginManifestInMarket } from '../../types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index 8a8af380c3..99ee173490 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Plugin, PluginManifestInMarket } from '../../../types' import { RiLoader2Line } from '@remixicon/react' -import React, { useEffect, useMemo } from 'react' +import * as React from 'react' +import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { gte } from 'semver' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index 9ca005fe52..159107eb97 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -3,7 +3,8 @@ import type { Plugin } from '@/app/components/plugins/types' import { RiArrowRightUpLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import { useTheme } from 'next-themes' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import Button from '@/app/components/base/button' import Card from '@/app/components/plugins/card' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' diff --git a/web/app/components/plugins/marketplace/search-box/trigger/marketplace.tsx b/web/app/components/plugins/marketplace/search-box/trigger/marketplace.tsx index 2cc12614ee..e38c9199c4 100644 --- a/web/app/components/plugins/marketplace/search-box/trigger/marketplace.tsx +++ b/web/app/components/plugins/marketplace/search-box/trigger/marketplace.tsx @@ -1,6 +1,6 @@ import type { Tag } from '../../../hooks' import { RiArrowDownSLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import { useMixedTranslation } from '../../hooks' diff --git a/web/app/components/plugins/marketplace/search-box/trigger/tool-selector.tsx b/web/app/components/plugins/marketplace/search-box/trigger/tool-selector.tsx index 762a31a4fd..4e1fbebd46 100644 --- a/web/app/components/plugins/marketplace/search-box/trigger/tool-selector.tsx +++ b/web/app/components/plugins/marketplace/search-box/trigger/tool-selector.tsx @@ -1,6 +1,6 @@ import type { Tag } from '../../../hooks' import { RiCloseCircleFill, RiPriceTag3Line } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type ToolSelectorTriggerProps = { diff --git a/web/app/components/plugins/plugin-detail-panel/action-list.tsx b/web/app/components/plugins/plugin-detail-panel/action-list.tsx index 96e25bbc54..a1cb1198a1 100644 --- a/web/app/components/plugins/plugin-detail-panel/action-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/action-list.tsx @@ -1,5 +1,6 @@ import type { PluginDetail } from '@/app/components/plugins/types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ToolItem from '@/app/components/tools/provider/tool-item' import { diff --git a/web/app/components/plugins/plugin-detail-panel/agent-strategy-list.tsx b/web/app/components/plugins/plugin-detail-panel/agent-strategy-list.tsx index afc75226ea..e1114853e5 100644 --- a/web/app/components/plugins/plugin-detail-panel/agent-strategy-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/agent-strategy-list.tsx @@ -1,5 +1,6 @@ import type { PluginDetail } from '@/app/components/plugins/types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import StrategyItem from '@/app/components/plugins/plugin-detail-panel/strategy-item' import { diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx index 2fc94bd00d..897bc91707 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-inputs-panel.tsx @@ -1,7 +1,8 @@ 'use client' import type { FileUpload } from '@/app/components/base/features/types' import type { App } from '@/types/app' -import React, { useMemo, useRef } from 'react' +import * as React from 'react' +import { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx index ca916cf662..f46588b5e3 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-picker.tsx @@ -5,7 +5,8 @@ import type { } from '@floating-ui/react' import type { FC } from 'react' import type { App } from '@/types/app' -import React, { useCallback, useEffect, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Input from '@/app/components/base/input' diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx index 54dc1562fd..2841864aa1 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/app-trigger.tsx @@ -3,7 +3,7 @@ import type { App } from '@/types/app' import { RiArrowDownSLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx index 965ae4e47a..ad7281e88e 100644 --- a/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/app-selector/index.tsx @@ -5,7 +5,8 @@ import type { } from '@floating-ui/react' import type { FC } from 'react' import type { App } from '@/types/app' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx b/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx index 003829b9ff..e9f5d88a29 100644 --- a/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx @@ -5,7 +5,8 @@ // import ToolItem from '@/app/components/tools/provider/tool-item' // import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' import type { PluginDetail } from '@/app/components/plugins/types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { transformDataSourceToTool } from '@/app/components/workflow/block-selector/utils' import { useDataSourceList } from '@/service/use-pipeline' diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 6fe439b631..0dcd69c01b 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -6,7 +6,8 @@ import { RiHardDrive3Line, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { trackEvent } from '@/app/components/base/amplitude' diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx index ba4d26d3dd..9ddaef1d81 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx @@ -2,7 +2,8 @@ import type { EndpointListItem, PluginDetail } from '../types' import { RiClipboardLine, RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx index a1ccbcd446..7acc065371 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-list.tsx @@ -5,7 +5,8 @@ import { RiBookOpenLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx index e2d7da6257..5e84dc3abc 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-modal.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { FormSchema } from '../../base/form/types' import type { PluginDetail } from '../types' import { RiArrowRightUpLine, RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/plugin-detail-panel/model-list.tsx b/web/app/components/plugins/plugin-detail-panel/model-list.tsx index 385c128b86..49d5780451 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-list.tsx @@ -1,5 +1,5 @@ import type { PluginDetail } from '@/app/components/plugins/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon' import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name' diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/llm-params-panel.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/llm-params-panel.tsx index 3b26b40fb6..2d3d07cfef 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-selector/llm-params-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-selector/llm-params-panel.tsx @@ -3,7 +3,8 @@ import type { ModelParameterRule, } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ParameterValue } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import ParameterItem from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item' diff --git a/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx b/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx index db208d349e..cc4f4d4881 100644 --- a/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx +++ b/web/app/components/plugins/plugin-detail-panel/model-selector/tts-params-panel.tsx @@ -1,4 +1,5 @@ -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { PortalSelect } from '@/app/components/base/select' import { languages } from '@/i18n-config/language' diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx index e0013089b0..b3d841e86a 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx @@ -5,7 +5,7 @@ import { RiAddLine, RiQuestionLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx index e9d794964d..15e4d6df67 100644 --- a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' // import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/plugin-detail-panel/strategy-detail.tsx b/web/app/components/plugins/plugin-detail-panel/strategy-detail.tsx index 061e44d208..50c803b81b 100644 --- a/web/app/components/plugins/plugin-detail-panel/strategy-detail.tsx +++ b/web/app/components/plugins/plugin-detail-panel/strategy-detail.tsx @@ -8,7 +8,8 @@ import { RiArrowLeftLine, RiCloseLine, } from '@remixicon/react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/plugins/plugin-detail-panel/strategy-item.tsx b/web/app/components/plugins/plugin-detail-panel/strategy-item.tsx index 19adbd707d..280f1bce49 100644 --- a/web/app/components/plugins/plugin-detail-panel/strategy-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/strategy-item.tsx @@ -3,7 +3,8 @@ import type { StrategyDetail, } from '@/app/components/plugins/types' import type { Locale } from '@/i18n-config' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useRenderI18nObject } from '@/hooks/use-i18n' import { cn } from '@/utils/classnames' import StrategyDetailPanel from './strategy-detail' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx index 51b661d2ba..16a789e67b 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx @@ -4,7 +4,8 @@ import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block import type { BuildTriggerSubscriptionPayload } from '@/service/use-triggers' import { RiLoader2Line } from '@remixicon/react' import { debounce } from 'lodash-es' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' // import { CopyFeedbackNew } from '@/app/components/base/copy-feedback' import { EncryptedBottom } from '@/app/components/base/encrypted-bottom' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx index d4c1b79c16..6c1094559e 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx @@ -6,7 +6,8 @@ import { RiClipboardLine, RiInformation2Fill, } from '@remixicon/react' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { BaseForm } from '@/app/components/base/form/components/base' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx index fd007409a4..628f561ca2 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx index d042653a1b..f7ba8d8c35 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx @@ -8,7 +8,8 @@ import { RiFileCopyLine, } from '@remixicon/react' import dayjs from 'dayjs' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx index b98b8cc202..4fd1cdd7d2 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx @@ -1,7 +1,8 @@ 'use client' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' import { RiCheckLine, RiDeleteBinLine, RiWebhookLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index bbfcc1fa09..2402a2af64 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -8,7 +8,8 @@ import type { Node } from 'reactflow' import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' import type { NodeOutPutVar } from '@/app/components/workflow/types' import Link from 'next/link' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx index 6801a6df88..7b19707bb0 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import VisualEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor' diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx index 342d91a084..eadbb4104c 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form.tsx @@ -4,7 +4,8 @@ import type { Collection } from '@/app/components/tools/types' import { RiArrowRightUpLine, } from '@remixicon/react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx index 09d0eb898f..4cea66e505 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx @@ -4,7 +4,8 @@ import { RiEqualizer2Line, RiErrorWarningFill, } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx index bfad698fff..e7c8d1e3e0 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger.tsx @@ -4,7 +4,7 @@ import { RiArrowDownSLine, RiEqualizer2Line, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import BlockIcon from '@/app/components/workflow/block-icon' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/plugins/plugin-item/action.tsx b/web/app/components/plugins/plugin-item/action.tsx index 87830a745b..3644dee76f 100644 --- a/web/app/components/plugins/plugin-item/action.tsx +++ b/web/app/components/plugins/plugin-item/action.tsx @@ -4,7 +4,8 @@ import type { MetaData } from '../types' import type { PluginCategoryEnum } from '@/app/components/plugins/types' import { RiDeleteBinLine, RiInformation2Line, RiLoopLeftLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { useModalContext } from '@/context/modal-context' diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index fe884f69b2..b2ee45bf68 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -8,7 +8,8 @@ import { RiHardDrive3Line, RiLoginCircleLine, } from '@remixicon/react' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { gte } from 'semver' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/plugins/plugin-mutation-model/index.tsx b/web/app/components/plugins/plugin-mutation-model/index.tsx index 09eb72a656..2ac5368346 100644 --- a/web/app/components/plugins/plugin-mutation-model/index.tsx +++ b/web/app/components/plugins/plugin-mutation-model/index.tsx @@ -1,7 +1,8 @@ import type { UseMutationResult } from '@tanstack/react-query' import type { FC, ReactNode } from 'react' import type { Plugin } from '../types' -import React, { memo } from 'react' +import * as React from 'react' +import { memo } from 'react' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import Card from '@/app/components/plugins/card' diff --git a/web/app/components/plugins/plugin-page/debug-info.tsx b/web/app/components/plugins/plugin-page/debug-info.tsx index 69b22001c9..ea6d0afccf 100644 --- a/web/app/components/plugins/plugin-page/debug-info.tsx +++ b/web/app/components/plugins/plugin-page/debug-info.tsx @@ -4,7 +4,7 @@ import { RiArrowRightUpLine, RiBugLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index 83b67c2320..4d8904d293 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -1,6 +1,7 @@ 'use client' import { noop } from 'lodash-es' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { Group } from '@/app/components/base/icons/src/vender/other' diff --git a/web/app/components/plugins/plugin-page/filter-management/index.tsx b/web/app/components/plugins/plugin-page/filter-management/index.tsx index cc4d64759e..ad1b3329c6 100644 --- a/web/app/components/plugins/plugin-page/filter-management/index.tsx +++ b/web/app/components/plugins/plugin-page/filter-management/index.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { usePluginPageContext } from '../context' import CategoriesFilter from './category-filter' import SearchBox from './search-box' diff --git a/web/app/components/plugins/plugin-page/plugin-info.tsx b/web/app/components/plugins/plugin-page/plugin-info.tsx index cc41fb88b8..fdc09b91c2 100644 --- a/web/app/components/plugins/plugin-page/plugin-info.tsx +++ b/web/app/components/plugins/plugin-page/plugin-info.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Modal from '../../base/modal' import KeyValueItem from '../base/key-value-item' diff --git a/web/app/components/plugins/provider-card.tsx b/web/app/components/plugins/provider-card.tsx index ad3f0f005d..3470d28495 100644 --- a/web/app/components/plugins/provider-card.tsx +++ b/web/app/components/plugins/provider-card.tsx @@ -4,7 +4,8 @@ import type { Plugin } from './types' import { RiArrowRightUpLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import { useTheme } from 'next-themes' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' diff --git a/web/app/components/plugins/readme-panel/entrance.tsx b/web/app/components/plugins/readme-panel/entrance.tsx index 611796d70d..9060041476 100644 --- a/web/app/components/plugins/readme-panel/entrance.tsx +++ b/web/app/components/plugins/readme-panel/entrance.tsx @@ -1,6 +1,6 @@ import type { PluginDetail } from '../types' import { RiBookReadLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { BUILTIN_TOOLS_ARRAY } from './constants' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/index.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/index.tsx index 05b826f22d..92ff0d8786 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/index.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { AutoUpdateConfig } from './types' import type { TriggerParams } from '@/app/components/base/date-and-time-picker/types' import { RiTimeLine } from '@remixicon/react' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' import { convertTimezoneToOffsetStr } from '@/app/components/base/date-and-time-picker/utils/dayjs' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-data-placeholder.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-data-placeholder.tsx index 8e2b00dab7..84f78c45cd 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-data-placeholder.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-data-placeholder.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' import { Group } from '@/app/components/base/icons/src/vender/other' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-plugin-selected.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-plugin-selected.tsx index 4e1301e92f..0f3f6e8b8c 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-plugin-selected.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/no-plugin-selected.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { AUTO_UPDATE_MODE } from './types' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-picker.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-picker.tsx index 7a3f864ac4..ead9472606 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-picker.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-picker.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import { RiAddLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import NoPluginSelected from './no-plugin-selected' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-selected.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-selected.tsx index 72b5b7398a..0284ec8a7f 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-selected.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/plugins-selected.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import Icon from '@/app/components/plugins/card/base/card-icon' import { MARKETPLACE_API_PREFIX } from '@/config' import { cn } from '@/utils/classnames' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-item.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-item.tsx index 48f7ff37ab..5a6dc08181 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-item.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-item.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { PluginDetail } from '@/app/components/plugins/types' -import React from 'react' +import * as React from 'react' import Checkbox from '@/app/components/base/checkbox' import Icon from '@/app/components/plugins/card/base/card-icon' import { MARKETPLACE_API_PREFIX } from '@/config' diff --git a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx index 3020bd96f1..b063277400 100644 --- a/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx +++ b/web/app/components/plugins/reference-setting-modal/auto-update-setting/tool-picker.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { diff --git a/web/app/components/plugins/reference-setting-modal/label.tsx b/web/app/components/plugins/reference-setting-modal/label.tsx index dc39c12b28..720361d79a 100644 --- a/web/app/components/plugins/reference-setting-modal/label.tsx +++ b/web/app/components/plugins/reference-setting-modal/label.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/plugins/reference-setting-modal/modal.tsx b/web/app/components/plugins/reference-setting-modal/modal.tsx index 7c6ff5df8d..bc28960157 100644 --- a/web/app/components/plugins/reference-setting-modal/modal.tsx +++ b/web/app/components/plugins/reference-setting-modal/modal.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { AutoUpdateConfig } from './auto-update-setting/types' import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/plugins/update-plugin/from-github.tsx b/web/app/components/plugins/update-plugin/from-github.tsx index 437837d8d9..c4bf1a8a0a 100644 --- a/web/app/components/plugins/update-plugin/from-github.tsx +++ b/web/app/components/plugins/update-plugin/from-github.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { UpdateFromGitHubPayload } from '../types' -import React from 'react' +import * as React from 'react' import InstallFromGitHub from '../install-plugin/install-from-github' type Props = { diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index d2802db54d..b45f8d24d8 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { UpdateFromMarketPlacePayload } from '../types' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Badge, { BadgeState } from '@/app/components/base/badge/index' import Button from '@/app/components/base/button' diff --git a/web/app/components/plugins/update-plugin/index.tsx b/web/app/components/plugins/update-plugin/index.tsx index 2ed3cef9e4..f8f77e845a 100644 --- a/web/app/components/plugins/update-plugin/index.tsx +++ b/web/app/components/plugins/update-plugin/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { UpdatePluginModalType } from '../types' -import React from 'react' +import * as React from 'react' import { PluginSource } from '../types' import UpdateFromGitHub from './from-github' import UpdateFromMarketplace from './from-market-place' diff --git a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx index 3c18d7fc0b..6562acdb6b 100644 --- a/web/app/components/plugins/update-plugin/plugin-version-picker.tsx +++ b/web/app/components/plugins/update-plugin/plugin-version-picker.tsx @@ -4,7 +4,8 @@ import type { Placement, } from '@floating-ui/react' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { lt } from 'semver' import Badge from '@/app/components/base/badge' diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx b/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx index 62e90dbf1c..7efe1ff76a 100644 --- a/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx +++ b/web/app/components/rag-pipeline/components/chunk-card-list/chunk-card.tsx @@ -1,6 +1,7 @@ import type { QAChunk } from './types' import type { ParentMode } from '@/models/datasets' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Dot from '@/app/components/datasets/documents/detail/completed/common/dot' import SegmentIndexTag from '@/app/components/datasets/documents/detail/completed/common/segment-index-tag' diff --git a/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx b/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx index 4a34222e1d..bd317a2ce4 100644 --- a/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx +++ b/web/app/components/rag-pipeline/components/chunk-card-list/q-a-item.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { QAItemType } from './types' type QAItemProps = { diff --git a/web/app/components/rag-pipeline/components/conversion.tsx b/web/app/components/rag-pipeline/components/conversion.tsx index 3e55350d4c..8e20df54b4 100644 --- a/web/app/components/rag-pipeline/components/conversion.tsx +++ b/web/app/components/rag-pipeline/components/conversion.tsx @@ -1,5 +1,6 @@ import { useParams } from 'next/navigation' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx index 4d052f39cd..f7b35e7916 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/hidden-fields.tsx @@ -1,5 +1,5 @@ import { useStore } from '@tanstack/react-form' -import React from 'react' +import * as React from 'react' import { withForm } from '@/app/components/base/form' import InputField from '@/app/components/base/form/form-scenarios/input-field/field' import { useHiddenConfigurations } from './hooks' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx index 9c6a053509..1cd6e24789 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/initial-fields.tsx @@ -1,4 +1,5 @@ -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { withForm } from '@/app/components/base/form' import InputField from '@/app/components/base/form/form-scenarios/input-field/field' import { useConfigurations } from './hooks' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx index 2a27710cd0..ac8fa0f715 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/editor/form/show-all-settings.tsx @@ -1,6 +1,6 @@ import { RiArrowRightSLine } from '@remixicon/react' import { useStore } from '@tanstack/react-form' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { withForm } from '@/app/components/base/form' import { useHiddenFieldNames } from './hooks' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx index a76a4db7ba..9b566b916c 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-item.tsx @@ -7,7 +7,8 @@ import { RiEditLine, } from '@remixicon/react' import { useHover } from 'ahooks' -import React, { useCallback, useRef } from 'react' +import * as React from 'react' +import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx index 30d05bef40..0449f7c9a4 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/index.tsx @@ -1,6 +1,7 @@ import type { InputVar } from '@/models/pipeline' import { RiAddLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import ActionButton from '@/app/components/base/action-button' import RemoveEffectVarConfirm from '@/app/components/workflow/nodes/_base/components/remove-effect-var-confirm' import { cn } from '@/utils/classnames' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx b/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx index a173f36230..4ee55861ec 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/footer-tip.tsx @@ -1,5 +1,5 @@ import { RiDragDropLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' const FooterTip = () => { return ( diff --git a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/datasource.tsx b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/datasource.tsx index 3f3481b511..aecb250809 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/datasource.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/datasource.tsx @@ -1,5 +1,5 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' -import React from 'react' +import * as React from 'react' import BlockIcon from '@/app/components/workflow/block-icon' import { useToolIcon } from '@/app/components/workflow/hooks' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx index ead0f3c851..601efe3221 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/preview/data-source.tsx b/web/app/components/rag-pipeline/components/panel/input-field/preview/data-source.tsx index cc216a6aaa..e13336e773 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/preview/data-source.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/preview/data-source.tsx @@ -1,5 +1,5 @@ import type { Datasource } from '../../test-run/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/workflow/store' import { useDraftPipelinePreProcessingParams } from '@/service/use-pipeline' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/preview/process-documents.tsx b/web/app/components/rag-pipeline/components/panel/input-field/preview/process-documents.tsx index c473e13add..9beb2c364e 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/preview/process-documents.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/preview/process-documents.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/workflow/store' import { useDraftPipelineProcessingParams } from '@/service/use-pipeline' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/header.tsx b/web/app/components/rag-pipeline/components/panel/test-run/header.tsx index a7949dd68f..bc139201bb 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/header.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/header.tsx @@ -1,5 +1,6 @@ import { RiCloseLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useWorkflowInteractions } from '@/app/components/workflow/hooks' import { useWorkflowStore } from '@/app/components/workflow/store' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/actions/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/actions/index.tsx index 2e4e97c665..4556031f31 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/actions/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/actions/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/data-source-options/option-card.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/data-source-options/option-card.tsx index 1074276552..7198d4d988 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/data-source-options/option-card.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/data-source-options/option-card.tsx @@ -1,5 +1,6 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import BlockIcon from '@/app/components/workflow/block-icon' import { useToolIcon } from '@/app/components/workflow/hooks' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/actions.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/actions.tsx index 47f06557d4..a9f0c6bff6 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/actions.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/actions.tsx @@ -1,5 +1,5 @@ import type { CustomActionsProps } from '@/app/components/base/form/components/form/actions' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useStore } from '@/app/components/workflow/store' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx index c4ad280dce..3186e5bb7d 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx @@ -1,5 +1,6 @@ import type { CustomActionsProps } from '@/app/components/base/form/components/form/actions' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { generateZodSchema } from '@/app/components/base/form/form-scenarios/base/utils' import { useConfigurations, useInitialData } from '@/app/components/rag-pipeline/hooks/use-input-fields' import Actions from './actions' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/footer-tips.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/footer-tips.tsx index 58ea748994..a2502838ea 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/footer-tips.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/footer-tips.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const FooterTips = () => { diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx index b5ec39d985..bf4f74493b 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/index.tsx @@ -1,5 +1,6 @@ import type { Datasource } from '../types' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useShallow } from 'zustand/react/shallow' import { trackEvent } from '@/app/components/base/amplitude' import LocalFile from '@/app/components/datasets/documents/create-from-pipeline/data-source/local-file' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/preparation/step-indicator.tsx b/web/app/components/rag-pipeline/components/panel/test-run/preparation/step-indicator.tsx index 9ab7015e07..93c4af8047 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/preparation/step-indicator.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/preparation/step-indicator.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import Divider from '@/app/components/base/divider' import { cn } from '@/utils/classnames' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx index 395fdc09e9..92932dbc5f 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/result-preview/index.tsx @@ -1,5 +1,6 @@ import { RiLoader2Line } from '@remixicon/react' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { RAG_PIPELINE_PREVIEW_CHUNK_NUM } from '@/config' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/index.tsx b/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/index.tsx index 4e4d51eb02..7a64810710 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/index.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/index.tsx @@ -1,5 +1,5 @@ import type { WorkflowRunningData } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tab from './tab' diff --git a/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/tab.tsx b/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/tab.tsx index 640a3feb17..ee9e3f5564 100644 --- a/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/tab.tsx +++ b/web/app/components/rag-pipeline/components/panel/test-run/result/tabs/tab.tsx @@ -1,5 +1,6 @@ import type { WorkflowRunningData } from '@/app/components/workflow/types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' type TabProps = { diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx index 4d5066a285..be5b4c11bd 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/run-mode.tsx @@ -1,5 +1,6 @@ import { RiCloseLine, RiDatabase2Line, RiLoader2Line, RiPlayLargeLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { useWorkflowRun, useWorkflowStartRun } from '@/app/components/workflow/hooks' diff --git a/web/app/components/rag-pipeline/components/screenshot.tsx b/web/app/components/rag-pipeline/components/screenshot.tsx index dae6f04161..3138b846d9 100644 --- a/web/app/components/rag-pipeline/components/screenshot.tsx +++ b/web/app/components/rag-pipeline/components/screenshot.tsx @@ -1,5 +1,5 @@ import Image from 'next/image' -import React from 'react' +import * as React from 'react' import useTheme from '@/hooks/use-theme' import { basePath } from '@/utils/var' diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 5c58635e0f..157ed123d1 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -15,7 +15,8 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import { useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import SavedItems from '@/app/components/app/text-generate/saved-items' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/share/text-generation/info-modal.tsx b/web/app/components/share/text-generation/info-modal.tsx index e206e6d6c9..9ee6557cef 100644 --- a/web/app/components/share/text-generation/info-modal.tsx +++ b/web/app/components/share/text-generation/info-modal.tsx @@ -1,5 +1,5 @@ import type { SiteInfo } from '@/models/share' -import React from 'react' +import * as React from 'react' import AppIcon from '@/app/components/base/app-icon' import Modal from '@/app/components/base/modal' import { appDefaultIconBackground } from '@/config' diff --git a/web/app/components/share/text-generation/menu-dropdown.tsx b/web/app/components/share/text-generation/menu-dropdown.tsx index 270789f2c6..c96c9ffa32 100644 --- a/web/app/components/share/text-generation/menu-dropdown.tsx +++ b/web/app/components/share/text-generation/menu-dropdown.tsx @@ -6,7 +6,8 @@ import { RiEqualizer2Line, } from '@remixicon/react' import { usePathname, useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { diff --git a/web/app/components/share/text-generation/no-data/index.spec.tsx b/web/app/components/share/text-generation/no-data/index.spec.tsx index 14a86a3c1b..41de9907fd 100644 --- a/web/app/components/share/text-generation/no-data/index.spec.tsx +++ b/web/app/components/share/text-generation/no-data/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import NoData from './index' describe('NoData', () => { diff --git a/web/app/components/share/text-generation/no-data/index.tsx b/web/app/components/share/text-generation/no-data/index.tsx index 5e10c821f8..f99c0af57d 100644 --- a/web/app/components/share/text-generation/no-data/index.tsx +++ b/web/app/components/share/text-generation/no-data/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import { RiSparklingFill, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' export type INoDataProps = {} diff --git a/web/app/components/share/text-generation/result/content.tsx b/web/app/components/share/text-generation/result/content.tsx index ec7c439ea9..01161d6dcd 100644 --- a/web/app/components/share/text-generation/result/content.tsx +++ b/web/app/components/share/text-generation/result/content.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { FeedbackType } from '@/app/components/base/chat/chat/type' -import React from 'react' +import * as React from 'react' import { format } from '@/service/base' import Header from './header' diff --git a/web/app/components/share/text-generation/result/header.tsx b/web/app/components/share/text-generation/result/header.tsx index b2cacfc3c5..409aad5cef 100644 --- a/web/app/components/share/text-generation/result/header.tsx +++ b/web/app/components/share/text-generation/result/header.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { FeedbackType } from '@/app/components/base/chat/chat/type' import { ClipboardDocumentIcon, HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' import copy from 'copy-to-clipboard' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx index 1b40b48d45..902da881ae 100644 --- a/web/app/components/share/text-generation/result/index.tsx +++ b/web/app/components/share/text-generation/result/index.tsx @@ -11,7 +11,8 @@ import { RiLoader2Line } from '@remixicon/react' import { useBoolean } from 'ahooks' import { t } from 'i18next' import { produce } from 'immer' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import TextGenerationRes from '@/app/components/app/text-generate/item' import Button from '@/app/components/base/button' import { diff --git a/web/app/components/share/text-generation/run-batch/csv-download/index.spec.tsx b/web/app/components/share/text-generation/run-batch/csv-download/index.spec.tsx index 385ccb91df..120e3ed0c2 100644 --- a/web/app/components/share/text-generation/run-batch/csv-download/index.spec.tsx +++ b/web/app/components/share/text-generation/run-batch/csv-download/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CSVDownload from './index' const mockType = { Link: 'mock-link' } diff --git a/web/app/components/share/text-generation/run-batch/csv-download/index.tsx b/web/app/components/share/text-generation/run-batch/csv-download/index.tsx index a7e6d5bf2c..eb1f9aa86a 100644 --- a/web/app/components/share/text-generation/run-batch/csv-download/index.tsx +++ b/web/app/components/share/text-generation/run-batch/csv-download/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader, diff --git a/web/app/components/share/text-generation/run-batch/csv-reader/index.spec.tsx b/web/app/components/share/text-generation/run-batch/csv-reader/index.spec.tsx index 1b93e7b9f9..83e89a0a04 100644 --- a/web/app/components/share/text-generation/run-batch/csv-reader/index.spec.tsx +++ b/web/app/components/share/text-generation/run-batch/csv-reader/index.spec.tsx @@ -1,5 +1,5 @@ import { act, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import CSVReader from './index' let mockAcceptedFile: { name: string } | null = null diff --git a/web/app/components/share/text-generation/run-batch/csv-reader/index.tsx b/web/app/components/share/text-generation/run-batch/csv-reader/index.tsx index 895d472297..95488c1e85 100644 --- a/web/app/components/share/text-generation/run-batch/csv-reader/index.tsx +++ b/web/app/components/share/text-generation/run-batch/csv-reader/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useCSVReader, diff --git a/web/app/components/share/text-generation/run-batch/index.spec.tsx b/web/app/components/share/text-generation/run-batch/index.spec.tsx index a3c3bbfd40..4359a66a58 100644 --- a/web/app/components/share/text-generation/run-batch/index.spec.tsx +++ b/web/app/components/share/text-generation/run-batch/index.spec.tsx @@ -1,6 +1,6 @@ import type { Mock } from 'vitest' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import RunBatch from './index' diff --git a/web/app/components/share/text-generation/run-batch/index.tsx b/web/app/components/share/text-generation/run-batch/index.tsx index 74e77e6165..793817f191 100644 --- a/web/app/components/share/text-generation/run-batch/index.tsx +++ b/web/app/components/share/text-generation/run-batch/index.tsx @@ -4,7 +4,7 @@ import { RiLoader2Line, RiPlayLargeLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' diff --git a/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx b/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx index 2a4ec0b3c1..b71b252345 100644 --- a/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx +++ b/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import ResDownload from './index' const mockType = { Link: 'mock-link' } diff --git a/web/app/components/share/text-generation/run-batch/res-download/index.tsx b/web/app/components/share/text-generation/run-batch/res-download/index.tsx index cdc2b8f41b..d7c42362fd 100644 --- a/web/app/components/share/text-generation/run-batch/res-download/index.tsx +++ b/web/app/components/share/text-generation/run-batch/res-download/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiDownloadLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader, diff --git a/web/app/components/share/text-generation/run-once/index.spec.tsx b/web/app/components/share/text-generation/run-once/index.spec.tsx index 4283409c1b..abead21c07 100644 --- a/web/app/components/share/text-generation/run-once/index.spec.tsx +++ b/web/app/components/share/text-generation/run-once/index.spec.tsx @@ -2,7 +2,8 @@ import type { PromptConfig, PromptVariable } from '@/models/debug' import type { SiteInfo } from '@/models/share' import type { VisionSettings } from '@/types/app' import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { Resolution, TransferMethod } from '@/types/app' import RunOnce from './index' diff --git a/web/app/components/share/text-generation/run-once/index.tsx b/web/app/components/share/text-generation/run-once/index.tsx index 09bd4872cb..eea2b1592c 100644 --- a/web/app/components/share/text-generation/run-once/index.tsx +++ b/web/app/components/share/text-generation/run-once/index.tsx @@ -6,7 +6,8 @@ import { RiLoader2Line, RiPlayLargeLine, } from '@remixicon/react' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' diff --git a/web/app/components/splash.tsx b/web/app/components/splash.tsx index 031b9b3230..e4103e8c93 100644 --- a/web/app/components/splash.tsx +++ b/web/app/components/splash.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, PropsWithChildren } from 'react' -import React from 'react' +import * as React from 'react' import { useIsLogin } from '@/service/use-common' import Loading from './base/loading' diff --git a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx index 19fa90fc25..1fa5e23347 100644 --- a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Credential } from '@/app/components/tools/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Drawer from '@/app/components/base/drawer-plus' diff --git a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx index fa4f45e75f..4ecee282f9 100644 --- a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx @@ -5,7 +5,8 @@ import { RiArrowDownSLine, } from '@remixicon/react' import { useClickAway } from 'ahooks' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/tools/edit-custom-collection-modal/index.tsx b/web/app/components/tools/edit-custom-collection-modal/index.tsx index 276230aa1b..93ef9142d9 100644 --- a/web/app/components/tools/edit-custom-collection-modal/index.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/index.tsx @@ -4,7 +4,8 @@ import type { Credential, CustomCollectionBackend, CustomParamSchema, Emoji } fr import { RiSettings2Line } from '@remixicon/react' import { useDebounce, useGetState } from 'ahooks' import { produce } from 'immer' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 29cd543e1e..8aacb7ad07 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Credential, CustomCollectionBackend, CustomParamSchema } from '@/app/components/tools/types' import { RiSettings2Line } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Button from '@/app/components/base/button' diff --git a/web/app/components/tools/marketplace/index.spec.tsx b/web/app/components/tools/marketplace/index.spec.tsx index 1056fe9d84..dcdda15588 100644 --- a/web/app/components/tools/marketplace/index.spec.tsx +++ b/web/app/components/tools/marketplace/index.spec.tsx @@ -2,7 +2,7 @@ import type { Plugin } from '@/app/components/plugins/types' import type { Collection } from '@/app/components/tools/types' import { act, render, renderHook, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { SCROLL_BOTTOM_THRESHOLD } from '@/app/components/plugins/marketplace/constants' import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils' import { PluginCategoryEnum } from '@/app/components/plugins/types' diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index bf57515f7d..417d8857f0 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -8,7 +8,8 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' -import React, { useCallback, useEffect } from 'react' +import * as React from 'react' +import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' diff --git a/web/app/components/tools/mcp/detail/list-loading.tsx b/web/app/components/tools/mcp/detail/list-loading.tsx index f770728715..636865fcce 100644 --- a/web/app/components/tools/mcp/detail/list-loading.tsx +++ b/web/app/components/tools/mcp/detail/list-loading.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' const ListLoading = () => { diff --git a/web/app/components/tools/mcp/detail/operation-dropdown.tsx b/web/app/components/tools/mcp/detail/operation-dropdown.tsx index 4ec80c5998..8955a5b50d 100644 --- a/web/app/components/tools/mcp/detail/operation-dropdown.tsx +++ b/web/app/components/tools/mcp/detail/operation-dropdown.tsx @@ -5,7 +5,8 @@ import { RiEditLine, RiMoreFill, } from '@remixicon/react' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { diff --git a/web/app/components/tools/mcp/detail/provider-detail.tsx b/web/app/components/tools/mcp/detail/provider-detail.tsx index d2f23b1887..f70006cd05 100644 --- a/web/app/components/tools/mcp/detail/provider-detail.tsx +++ b/web/app/components/tools/mcp/detail/provider-detail.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { ToolWithProvider } from '../../../workflow/types' -import React from 'react' +import * as React from 'react' import Drawer from '@/app/components/base/drawer' import { cn } from '@/utils/classnames' import MCPDetailContent from './content' diff --git a/web/app/components/tools/mcp/detail/tool-item.tsx b/web/app/components/tools/mcp/detail/tool-item.tsx index 83e58817f4..6e7673b3cd 100644 --- a/web/app/components/tools/mcp/detail/tool-item.tsx +++ b/web/app/components/tools/mcp/detail/tool-item.tsx @@ -1,6 +1,6 @@ 'use client' import type { Tool } from '@/app/components/tools/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/tools/mcp/headers-input.tsx b/web/app/components/tools/mcp/headers-input.tsx index 18ee9b1cc6..d6bb00bdb9 100644 --- a/web/app/components/tools/mcp/headers-input.tsx +++ b/web/app/components/tools/mcp/headers-input.tsx @@ -1,6 +1,6 @@ 'use client' import { RiAddLine, RiDeleteBinLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { v4 as uuid } from 'uuid' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/tools/mcp/mcp-server-modal.tsx b/web/app/components/tools/mcp/mcp-server-modal.tsx index 15fd2a573b..e0036a3dc4 100644 --- a/web/app/components/tools/mcp/mcp-server-modal.tsx +++ b/web/app/components/tools/mcp/mcp-server-modal.tsx @@ -3,7 +3,7 @@ import type { MCPServerDetail, } from '@/app/components/tools/types' import { RiCloseLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx index 3d99cc5bad..d951f19caa 100644 --- a/web/app/components/tools/mcp/mcp-server-param-item.tsx +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Textarea from '@/app/components/base/textarea' diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 521e93222b..07aa5e0168 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -2,7 +2,8 @@ import type { AppDetailResponse } from '@/models/app' import type { AppSSO } from '@/types/app' import { RiEditLine, RiLoopLeftLine } from '@remixicon/react' -import React, { useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index a739964ee3..eb8f280484 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -6,7 +6,8 @@ import type { AppIconType } from '@/types/app' import { RiCloseLine, RiEditLine } from '@remixicon/react' import { useHover } from 'ahooks' import { noop } from 'lodash-es' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { getDomain } from 'tldts' import { v4 as uuid } from 'uuid' diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx index e881dd3997..c4b65f353d 100644 --- a/web/app/components/tools/provider/detail.tsx +++ b/web/app/components/tools/provider/detail.tsx @@ -3,7 +3,8 @@ import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderReq import { RiCloseLine, } from '@remixicon/react' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/tools/provider/tool-item.tsx b/web/app/components/tools/provider/tool-item.tsx index 3248e3c024..b240bf6a41 100644 --- a/web/app/components/tools/provider/tool-item.tsx +++ b/web/app/components/tools/provider/tool-item.tsx @@ -1,6 +1,7 @@ 'use client' import type { Collection, Tool } from '../types' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useContext } from 'use-context-selector' import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool' import I18n from '@/context/i18n' diff --git a/web/app/components/tools/setting/build-in/config-credentials.tsx b/web/app/components/tools/setting/build-in/config-credentials.tsx index 783d4a6476..43383cdb51 100644 --- a/web/app/components/tools/setting/build-in/config-credentials.tsx +++ b/web/app/components/tools/setting/build-in/config-credentials.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Collection } from '../../types' import { noop } from 'lodash-es' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Drawer from '@/app/components/base/drawer-plus' diff --git a/web/app/components/tools/workflow-tool/configure-button.tsx b/web/app/components/tools/workflow-tool/configure-button.tsx index 0d28576de5..f142989ff6 100644 --- a/web/app/components/tools/workflow-tool/configure-button.tsx +++ b/web/app/components/tools/workflow-tool/configure-button.tsx @@ -4,7 +4,8 @@ import type { InputVar, Variable } from '@/app/components/workflow/types' import type { PublishWorkflowParams } from '@/types/workflow' import { RiArrowRightUpLine, RiHammerLine } from '@remixicon/react' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx index 972ac8c882..a03860d952 100644 --- a/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx +++ b/web/app/components/tools/workflow-tool/confirm-modal/index.spec.tsx @@ -1,6 +1,6 @@ import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import ConfirmModal from './index' // Test utilities diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx index af226e7071..8804a4128d 100644 --- a/web/app/components/tools/workflow-tool/index.tsx +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Emoji, WorkflowToolProviderOutputParameter, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types' import { RiErrorWarningLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx index b7a2cefd1c..6dac82a642 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.spec.tsx @@ -1,6 +1,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { BlockEnum } from '@/app/components/workflow/types' import WorkflowOnboardingModal from './index' diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx index 0fa5c9f13d..9c77ebfdfe 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-option.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import StartNodeOption from './start-node-option' describe('StartNodeOption', () => { diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx index 8c59ec3b82..43d8c1a8e1 100644 --- a/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/start-node-selection-panel.spec.tsx @@ -1,6 +1,6 @@ import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import React from 'react' +import * as React from 'react' import { BlockEnum } from '@/app/components/workflow/types' import StartNodeSelectionPanel from './start-node-selection-panel' diff --git a/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx b/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx index 6bf7e8f542..d3c3d235fe 100644 --- a/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx +++ b/web/app/components/workflow/__tests__/trigger-status-sync.test.tsx @@ -2,7 +2,8 @@ import type { MockedFunction } from 'vitest' import type { EntryNodeStatus } from '../store/trigger-status' import type { BlockEnum } from '../types' import { act, render } from '@testing-library/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTriggerStatusStore } from '../store/trigger-status' import { isTriggerNode } from '../types' diff --git a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx index a23ca32b50..bb98f1043f 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/action.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/action.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import { RiMoreFill } from '@remixicon/react' import { useQueryClient } from '@tanstack/react-query' import { useTheme } from 'next-themes' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' // import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx index 18d8629260..545eaca41f 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { Plugin } from '@/app/components/plugins/types.ts' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx index e9255917aa..f090fc8e7c 100644 --- a/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx +++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/index.tsx @@ -4,7 +4,8 @@ import type { ViewType } from '@/app/components/workflow/block-selector/view-typ import type { OnSelectBlock } from '@/app/components/workflow/types' import { RiMoreLine } from '@remixicon/react' import Link from 'next/link' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx index 9a351c4eff..125b307ae0 100644 --- a/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx +++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/uninstalled-item.tsx @@ -1,7 +1,7 @@ 'use client' import type { Plugin } from '@/app/components/plugins/types' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index cbb8d5a01e..235144ae78 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -8,7 +8,8 @@ import type { ToolDefaultValue, ToolValue } from './types' import type { CustomCollectionBackend } from '@/app/components/tools/types' import type { BlockEnum, OnSelectBlock } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { PortalToFollowElem, diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index 60fac5e701..6c1c2465ed 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { ToolWithProvider } from '../../types' import type { ToolDefaultValue } from '../types' import type { Tool } from '@/app/components/tools/types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx index 54eb050e06..a911ea23c3 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { ViewType } from '../../view-type-select' import Tool from '../tool' diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx index 64a376e394..308baa45e7 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' -import React from 'react' +import * as React from 'react' import { ViewType } from '../../view-type-select' import Tool from '../tool' diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx index 0f790ab036..2b85121e4d 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { BlockEnum, ToolWithProvider } from '../../../types' import type { ToolDefaultValue, ToolValue } from '../../types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { AGENT_GROUP_NAME, CUSTOM_GROUP_NAME, WORKFLOW_GROUP_NAME } from '../../index-bar' import Item from './item' diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 366059b311..337742365e 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -5,7 +5,8 @@ import type { ToolWithProvider } from '../../types' import type { ToolDefaultValue, ToolValue } from '../types' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useHover } from 'ahooks' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Mcp } from '@/app/components/base/icons/src/vender/other' import { useGetLanguage } from '@/context/i18n' diff --git a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx index c0edec474a..92ee677362 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { TriggerDefaultValue, TriggerWithProvider } from '../types' import type { Event } from '@/app/components/tools/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { useGetLanguage } from '@/context/i18n' diff --git a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx index a4de7b30dd..9e3ec77790 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' -import React, { useEffect, useMemo, useRef } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { CollectionType } from '@/app/components/tools/types' import BlockIcon from '@/app/components/workflow/block-icon' diff --git a/web/app/components/workflow/block-selector/use-sticky-scroll.ts b/web/app/components/workflow/block-selector/use-sticky-scroll.ts index 67ea70d94e..4960eea74f 100644 --- a/web/app/components/workflow/block-selector/use-sticky-scroll.ts +++ b/web/app/components/workflow/block-selector/use-sticky-scroll.ts @@ -1,5 +1,5 @@ import { useThrottleFn } from 'ahooks' -import React from 'react' +import * as React from 'react' export enum ScrollPosition { belowTheWrap = 'belowTheWrap', diff --git a/web/app/components/workflow/block-selector/view-type-select.tsx b/web/app/components/workflow/block-selector/view-type-select.tsx index a4830d8e81..c81d09c6dd 100644 --- a/web/app/components/workflow/block-selector/view-type-select.tsx +++ b/web/app/components/workflow/block-selector/view-type-select.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiNodeTree, RiSortAlphabetAsc } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' export enum ViewType { diff --git a/web/app/components/workflow/dsl-export-confirm-modal.tsx b/web/app/components/workflow/dsl-export-confirm-modal.tsx index 63100876c6..b616ec5fb5 100644 --- a/web/app/components/workflow/dsl-export-confirm-modal.tsx +++ b/web/app/components/workflow/dsl-export-confirm-modal.tsx @@ -2,7 +2,8 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiCloseLine, RiLock2Line } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/workflow/header/run-mode.tsx b/web/app/components/workflow/header/run-mode.tsx index 82e33b5c30..21195d489a 100644 --- a/web/app/components/workflow/header/run-mode.tsx +++ b/web/app/components/workflow/header/run-mode.tsx @@ -1,6 +1,7 @@ import type { TestRunMenuRef, TriggerOption } from './test-run-menu' import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react' -import React, { useCallback, useEffect, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' diff --git a/web/app/components/workflow/header/version-history-button.tsx b/web/app/components/workflow/header/version-history-button.tsx index b29608a022..3be0cfbe32 100644 --- a/web/app/components/workflow/header/version-history-button.tsx +++ b/web/app/components/workflow/header/version-history-button.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import { RiHistoryLine } from '@remixicon/react' import { useKeyPress } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import useTheme from '@/hooks/use-theme' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/add-button.tsx b/web/app/components/workflow/nodes/_base/components/add-button.tsx index 99ccc61fe5..95a0a963fe 100644 --- a/web/app/components/workflow/nodes/_base/components/add-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/add-button.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiAddLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import Button from '@/app/components/base/button' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx index db32627dc2..6e71e1a356 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/bool-input.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index c33deae438..5cc68c6048 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -6,7 +6,8 @@ import { RiDeleteBinLine, } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { Line3 } from '@/app/components/base/icons/src/public/common' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx index e45f001924..0f695bb884 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { InputVar } from '../../../../types' import { produce } from 'immer' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import AddButton from '@/app/components/base/button/add-button' import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants' import { InputVarType } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index 9957d79cf6..f5870cede1 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -4,7 +4,8 @@ import type { Props as FormProps } from './form' import type { Emoji } from '@/app/components/tools/types' import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel' import type { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types' -import React, { useEffect, useRef } from 'react' +import * as React from 'react' +import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { getProcessedFiles } from '@/app/components/base/file-uploader/utils' diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx index 61e614bb9e..4d05194314 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/panel-wrap.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiCloseLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' const i18nPrefix = 'workflow.singleRun' diff --git a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx index 6888bff96c..96b9fe7a84 100644 --- a/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/code-generator-button.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { CodeLanguage } from '../../code/types' import type { GenRes } from '@/service/debug' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { GetCodeGeneratorResModal } from '@/app/components/app/configuration/config/code-generator/get-code-generator-res' import { ActionButton } from '@/app/components/base/action-button' import { Generator } from '@/app/components/base/icons/src/vender/other' diff --git a/web/app/components/workflow/nodes/_base/components/config-vision.tsx b/web/app/components/workflow/nodes/_base/components/config-vision.tsx index 3c2cc217a7..f5f47c4012 100644 --- a/web/app/components/workflow/nodes/_base/components/config-vision.tsx +++ b/web/app/components/workflow/nodes/_base/components/config-vision.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { ValueSelector, Var, VisionSetting } from '@/app/components/workflow/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index 95aabc0ec0..3d5ca5f78a 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -4,7 +4,8 @@ import type { CodeLanguage } from '../../../code/types' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { Node, NodeOutPutVar } from '@/app/components/workflow/types' import copy from 'copy-to-clipboard' -import React, { useCallback, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useRef, useState } from 'react' import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap' import ActionButton from '@/app/components/base/action-button' import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log' diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx index d4d43ae796..e5cb8f258a 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Props as EditorProps } from '.' import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index b98e9085de..fa7aef90a5 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import Editor, { loader } from '@monaco-editor/react' import { noop } from 'lodash-es' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { getFilesInLogs, } from '@/app/components/base/file-uploader/utils' diff --git a/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx b/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx index 6fa3c2adfd..888c1c7017 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/text-editor.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import Base from './base' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/editor/wrap.tsx b/web/app/components/workflow/nodes/_base/components/editor/wrap.tsx index 700f5a4317..4ebedfe596 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/wrap.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/wrap.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useStore } from '@/app/components/workflow/store' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index 9f46546700..93fd331f94 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -4,7 +4,7 @@ import { RiArrowDownSLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx index 3dc1e7b132..b354d35276 100644 --- a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { noop } from 'lodash-es' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' import { FileTypeIcon } from '@/app/components/base/file-uploader' diff --git a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx index c6330daf4d..bcbb9b6373 100644 --- a/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-upload-setting.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { UploadFileSetting } from '../../../types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Field from '@/app/components/app/configuration/config-var/config-modal/field' import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks' diff --git a/web/app/components/workflow/nodes/_base/components/info-panel.tsx b/web/app/components/workflow/nodes/_base/components/info-panel.tsx index cc2426c24f..885adab0fd 100644 --- a/web/app/components/workflow/nodes/_base/components/info-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/info-panel.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, ReactNode } from 'react' -import React from 'react' +import * as React from 'react' type Props = { title: string diff --git a/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx b/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx index 0a021402ba..cb0dc9064c 100644 --- a/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-number-with-slider.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import Slider from '@/app/components/base/slider' export type InputNumberWithSliderProps = { diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index c06fe55375..348bb23302 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -6,7 +6,8 @@ import type { } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' import { noop } from 'lodash-es' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import PromptEditor from '@/app/components/base/prompt-editor' diff --git a/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx b/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx index 7e529013cb..586149d727 100644 --- a/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-var-type-icon.tsx @@ -10,7 +10,7 @@ import { RiHashtag, RiTextSnippet, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { InputVarType } from '../../../types' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx b/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx index 98e2dc4f29..c3a30a3ac3 100644 --- a/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx +++ b/web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' type Props = { children: React.ReactNode diff --git a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx index 33da12239b..49a8f1e406 100644 --- a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx +++ b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiAlertFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/nodes/_base/components/memory-config.tsx b/web/app/components/workflow/nodes/_base/components/memory-config.tsx index 1272f132a6..70dfdde71f 100644 --- a/web/app/components/workflow/nodes/_base/components/memory-config.tsx +++ b/web/app/components/workflow/nodes/_base/components/memory-config.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Memory } from '../../../types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Slider from '@/app/components/base/slider' diff --git a/web/app/components/workflow/nodes/_base/components/option-card.tsx b/web/app/components/workflow/nodes/_base/components/option-card.tsx index 7c3a06daeb..d9deb2ab7b 100644 --- a/web/app/components/workflow/nodes/_base/components/option-card.tsx +++ b/web/app/components/workflow/nodes/_base/components/option-card.tsx @@ -2,7 +2,8 @@ import type { VariantProps } from 'class-variance-authority' import type { FC } from 'react' import { cva } from 'class-variance-authority' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/output-vars.tsx b/web/app/components/workflow/nodes/_base/components/output-vars.tsx index 2c646148b3..7ec18032ae 100644 --- a/web/app/components/workflow/nodes/_base/components/output-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/output-vars.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC, ReactNode } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index c2bc6481ff..c0dbb181e2 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -11,7 +11,8 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' -import React, { useCallback, useRef } from 'react' +import * as React from 'react' +import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' diff --git a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx index fbec61d516..b9a980ae01 100644 --- a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { VariableLabelInText, } from '@/app/components/workflow/nodes/_base/components/variable/variable-label' diff --git a/web/app/components/workflow/nodes/_base/components/remove-button.tsx b/web/app/components/workflow/nodes/_base/components/remove-button.tsx index 7b77f956d3..962a3c1828 100644 --- a/web/app/components/workflow/nodes/_base/components/remove-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/remove-button.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiDeleteBinLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import ActionButton from '@/app/components/base/action-button' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/remove-effect-var-confirm.tsx b/web/app/components/workflow/nodes/_base/components/remove-effect-var-confirm.tsx index bf3fd86865..02f61ad178 100644 --- a/web/app/components/workflow/nodes/_base/components/remove-effect-var-confirm.tsx +++ b/web/app/components/workflow/nodes/_base/components/remove-effect-var-confirm.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/workflow/nodes/_base/components/selector.tsx b/web/app/components/workflow/nodes/_base/components/selector.tsx index 58b1ecfa31..60498a4a6a 100644 --- a/web/app/components/workflow/nodes/_base/components/selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/selector.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { useBoolean, useClickAway } from 'ahooks' -import React from 'react' +import * as React from 'react' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' import { Check } from '@/app/components/base/icons/src/vender/line/general' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/split.tsx b/web/app/components/workflow/nodes/_base/components/split.tsx index fa5ea3adc1..5cb5153c95 100644 --- a/web/app/components/workflow/nodes/_base/components/split.tsx +++ b/web/app/components/workflow/nodes/_base/components/split.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx index d3753bb5ff..377e1d8bc5 100644 --- a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import VarHighlight from '@/app/components/app/configuration/base/var-highlight' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx b/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx index 116825ae95..73b221a912 100644 --- a/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx +++ b/web/app/components/workflow/nodes/_base/components/toggle-expand-btn.tsx @@ -4,7 +4,8 @@ import { RiCollapseDiagonalLine, RiExpandDiagonalLine, } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import ActionButton from '@/app/components/base/action-button' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx index 7907c83fc3..9e3aba9bf0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/assigned-var-reference-popup.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ListEmpty from '@/app/components/base/list-empty' import VarReferenceVars from './var-reference-vars' diff --git a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx index 310e82ff12..f0d7a04294 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/constant-field.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Var } from '@/app/components/workflow/types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { SimpleSelect } from '@/app/components/base/select' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx index f533c33108..18d84db571 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { Field as FieldType } from '../../../../../llm/types' import type { ValueSelector } from '@/app/components/workflow/types' import { RiMoreFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx index baf3cfcbd2..b29b1a6992 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { StructuredOutput } from '../../../../../llm/types' import type { ValueSelector } from '@/app/components/workflow/types' import { useHover } from 'ahooks' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import { cn } from '@/utils/classnames' import Field from './field' diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx index fc23cda205..d028bd2c16 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/field.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { Field as FieldType } from '../../../../../llm/types' import { RiArrowDropDownLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { Type } from '../../../../../llm/types' diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx index deaab09e6c..c0049a54e8 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { StructuredOutput } from '../../../../../llm/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Field from './field' diff --git a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx index 9e45a4cccc..786875ddd1 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/tree-indent-line.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx index 7eccbe23de..44df18ddf2 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx @@ -5,7 +5,8 @@ import type { ToastHandle } from '@/app/components/base/toast' import type { VarType } from '@/app/components/workflow/types' import { useDebounceFn } from 'ahooks' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx index 744567daae..b2c63be8b6 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-full-path-panel.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Field, StructuredOutput, TypeWithArray } from '../../../llm/types' -import React from 'react' +import * as React from 'react' import BlockIcon from '@/app/components/workflow/block-icon' import { PickerPanelMain as Panel } from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker' import { BlockEnum } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index c9cad91236..2d96baaf28 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -5,7 +5,8 @@ import type { ValueSelector, Var, Variable } from '@/app/components/workflow/typ import { RiDraggable } from '@remixicon/react' import { useDebounceFn } from 'ahooks' import { produce } from 'immer' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 3692cb6413..05e2c913ce 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -13,7 +13,8 @@ import { } from '@remixicon/react' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNodes, diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx index 45ad5d9f8c..22ea741174 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ListEmpty from '@/app/components/base/list-empty' import { useStore } from '@/app/components/workflow/store' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 5482eea996..dd0dfa8682 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -5,7 +5,8 @@ import type { Field } from '@/app/components/workflow/nodes/llm/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useHover } from 'ahooks' import { noop } from 'lodash-es' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx index b6b08bc799..3af95587cb 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { RiArrowDownSLine } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { Check } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index 3624e10bb1..8e684afa87 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -7,7 +7,8 @@ import { RiPlayLargeLine, } from '@remixicon/react' import { debounce } from 'lodash-es' -import React, { +import * as React from 'react' +import { cloneElement, memo, useCallback, diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx index 93d5debb51..31a7e3b9fd 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { ResultPanelProps } from '@/app/components/workflow/run/result-panel' import type { NodeTracing } from '@/types/workflow' import { RiLoader2Line } from '@remixicon/react' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useHooksStore } from '@/app/components/workflow/hooks-store' import ResultPanel from '@/app/components/workflow/run/result-panel' import { NodeRunningStatus } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx index 4ae6ccd31f..11422bf858 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/no-data.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiPlayLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time' diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx index 53ae913a8e..7878f0d476 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import TabHeader from '@/app/components/base/tab-header' diff --git a/web/app/components/workflow/nodes/answer/node.tsx b/web/app/components/workflow/nodes/answer/node.tsx index 12c7b10a1a..80fd07a900 100644 --- a/web/app/components/workflow/nodes/answer/node.tsx +++ b/web/app/components/workflow/nodes/answer/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { AnswerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import InfoPanel from '../_base/components/info-panel' import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' diff --git a/web/app/components/workflow/nodes/answer/panel.tsx b/web/app/components/workflow/nodes/answer/panel.tsx index 170cd17bf8..8d539b9216 100644 --- a/web/app/components/workflow/nodes/answer/panel.tsx +++ b/web/app/components/workflow/nodes/answer/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { AnswerNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx index 3f99121835..422cd5a486 100644 --- a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx @@ -5,7 +5,8 @@ import type { ValueSelector, Var } from '@/app/components/workflow/types' import { RiDeleteBinLine } from '@remixicon/react' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Input from '@/app/components/base/input' diff --git a/web/app/components/workflow/nodes/assigner/node.tsx b/web/app/components/workflow/nodes/assigner/node.tsx index c7777c4541..be30104242 100644 --- a/web/app/components/workflow/nodes/assigner/node.tsx +++ b/web/app/components/workflow/nodes/assigner/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { AssignerNodeType } from './types' import type { Node, NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' import Badge from '@/app/components/base/badge' diff --git a/web/app/components/workflow/nodes/assigner/panel.tsx b/web/app/components/workflow/nodes/assigner/panel.tsx index 04da330fd4..b680ba2631 100644 --- a/web/app/components/workflow/nodes/assigner/panel.tsx +++ b/web/app/components/workflow/nodes/assigner/panel.tsx @@ -4,7 +4,7 @@ import type { NodePanelProps } from '@/app/components/workflow/types' import { RiAddLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import VarList from './components/var-list' diff --git a/web/app/components/workflow/nodes/code/dependency-picker.tsx b/web/app/components/workflow/nodes/code/dependency-picker.tsx index 2e0e0c0d59..1c30ce0818 100644 --- a/web/app/components/workflow/nodes/code/dependency-picker.tsx +++ b/web/app/components/workflow/nodes/code/dependency-picker.tsx @@ -4,7 +4,8 @@ import { RiArrowDownSLine, } from '@remixicon/react' import { t } from 'i18next' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { Check } from '@/app/components/base/icons/src/vender/line/general' import Input from '@/app/components/base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' diff --git a/web/app/components/workflow/nodes/code/node.tsx b/web/app/components/workflow/nodes/code/node.tsx index 5fa002913d..66e83dbb71 100644 --- a/web/app/components/workflow/nodes/code/node.tsx +++ b/web/app/components/workflow/nodes/code/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { CodeNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' const Node: FC<NodeProps<CodeNodeType>> = () => { return ( diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index 261195c4c5..62c2f55834 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { CodeNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AddButton from '@/app/components/base/button/add-button' import SyncButton from '@/app/components/base/button/sync-button' diff --git a/web/app/components/workflow/nodes/data-source/before-run-form.tsx b/web/app/components/workflow/nodes/data-source/before-run-form.tsx index a091211fa5..172570c802 100644 --- a/web/app/components/workflow/nodes/data-source/before-run-form.tsx +++ b/web/app/components/workflow/nodes/data-source/before-run-form.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { CustomRunFormProps } from './types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import LocalFile from '@/app/components/datasets/documents/create-from-pipeline/data-source/local-file' diff --git a/web/app/components/workflow/nodes/document-extractor/node.tsx b/web/app/components/workflow/nodes/document-extractor/node.tsx index c092cd353a..f1e61b8353 100644 --- a/web/app/components/workflow/nodes/document-extractor/node.tsx +++ b/web/app/components/workflow/nodes/document-extractor/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { DocExtractorNodeType } from './types' import type { Node, NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/workflow/nodes/document-extractor/panel.tsx b/web/app/components/workflow/nodes/document-extractor/panel.tsx index b7cfddea4b..87a6ab7a37 100644 --- a/web/app/components/workflow/nodes/document-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/document-extractor/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { DocExtractorNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/end/node.tsx b/web/app/components/workflow/nodes/end/node.tsx index 26e7b7d022..f6bace7058 100644 --- a/web/app/components/workflow/nodes/end/node.tsx +++ b/web/app/components/workflow/nodes/end/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { EndNodeType } from './types' import type { NodeProps, Variable } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useIsChatMode, useWorkflow, diff --git a/web/app/components/workflow/nodes/end/panel.tsx b/web/app/components/workflow/nodes/end/panel.tsx index 3970dc8efe..b07df2ef92 100644 --- a/web/app/components/workflow/nodes/end/panel.tsx +++ b/web/app/components/workflow/nodes/end/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { EndNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AddButton from '@/app/components/base/button/add-button' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/http/components/api-input.tsx b/web/app/components/workflow/nodes/http/components/api-input.tsx index a72fc9fde0..eeb9128827 100644 --- a/web/app/components/workflow/nodes/http/components/api-input.tsx +++ b/web/app/components/workflow/nodes/http/components/api-input.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Var } from '../../../types' import { RiArrowDownSLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/http/components/authorization/index.tsx b/web/app/components/workflow/nodes/http/components/authorization/index.tsx index 50505fd4c8..5293df5597 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/index.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Authorization as AuthorizationPayloadType } from '../../types' import type { Var } from '@/app/components/workflow/types' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import BaseInput from '@/app/components/base/input' diff --git a/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx b/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx index 6edd325b18..48ad4066b9 100644 --- a/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx +++ b/web/app/components/workflow/nodes/http/components/authorization/radio-group.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { cn } from '@/utils/classnames' type Option = { diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx index 2710fd3c5d..ef4ce45f38 100644 --- a/web/app/components/workflow/nodes/http/components/curl-panel.tsx +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { HttpNodeType } from '../types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx index 1770d01ef5..c475f1234a 100644 --- a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx +++ b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx @@ -4,7 +4,8 @@ import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { produce } from 'immer' import { uniqueId } from 'lodash-es' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor' import { VarType } from '@/app/components/workflow/types' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx index ea43c726e2..2a5b9484f7 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/bulk-edit/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { LayoutGrid02 } from '@/app/components/base/icons/src/vender/line/layout' import TextEditor from '@/app/components/workflow/nodes/_base/components/editor/text-editor' diff --git a/web/app/components/workflow/nodes/http/components/key-value/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/index.tsx index 0191cb0c7a..02ba7c641d 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { KeyValue } from '../../types' -import React from 'react' +import * as React from 'react' import KeyValueEdit from './key-value-edit' type Props = { diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx index 61d6292e06..cba9e82b37 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { KeyValue } from '../../../types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import KeyValueItem from './item' diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx index 2f1857f7af..f463388dad 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/input-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Var } from '@/app/components/workflow/types' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' diff --git a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx index 365367fd97..a03f09d18a 100644 --- a/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx +++ b/web/app/components/workflow/nodes/http/components/key-value/key-value-edit/item.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { KeyValue } from '../../../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { PortalSelect } from '@/app/components/base/select' import { VarType } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/http/components/timeout/index.tsx b/web/app/components/workflow/nodes/http/components/timeout/index.tsx index 11edfa8c93..0bac909f69 100644 --- a/web/app/components/workflow/nodes/http/components/timeout/index.tsx +++ b/web/app/components/workflow/nodes/http/components/timeout/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Timeout as TimeoutPayloadType } from '../../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' diff --git a/web/app/components/workflow/nodes/http/node.tsx b/web/app/components/workflow/nodes/http/node.tsx index 332a28c44c..7ade3a691b 100644 --- a/web/app/components/workflow/nodes/http/node.tsx +++ b/web/app/components/workflow/nodes/http/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { HttpNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import ReadonlyInputWithSelectVar from '../_base/components/readonly-input-with-select-var' const Node: FC<NodeProps<HttpNodeType>> = ({ diff --git a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx index b9f80cb9f0..cdcd7561db 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx @@ -8,7 +8,8 @@ import { RiDraggable, } from '@remixicon/react' import { noop } from 'lodash-es' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow/nodes/if-else/node.tsx b/web/app/components/workflow/nodes/if-else/node.tsx index 41a7ec5d46..83f61f1f8e 100644 --- a/web/app/components/workflow/nodes/if-else/node.tsx +++ b/web/app/components/workflow/nodes/if-else/node.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { NodeProps } from 'reactflow' import type { Condition, IfElseNodeType } from './types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { VarType } from '../../types' import { NodeSourceHandle } from '../_base/components/node-handle' diff --git a/web/app/components/workflow/nodes/iteration/panel.tsx b/web/app/components/workflow/nodes/iteration/panel.tsx index 5cffb356a2..404fb20b82 100644 --- a/web/app/components/workflow/nodes/iteration/panel.tsx +++ b/web/app/components/workflow/nodes/iteration/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { IterationNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Select from '@/app/components/base/select' diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx index 82af5f8d2f..8f288364c8 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { AddChunks } from '@/app/components/base/icons/src/vender/knowledge' import { useDocLink } from '@/context/i18n' diff --git a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx index a2a3835be6..c4eab5d370 100644 --- a/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx +++ b/web/app/components/workflow/nodes/knowledge-base/components/chunk-structure/instruction/line.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' type LineProps = { type?: 'vertical' | 'horizontal' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx index b51d085113..a513280dec 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/add-dataset.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { DataSet } from '@/models/datasets' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import SelectDataset from '@/app/components/app/configuration/dataset-config/select-dataset' import AddButton from '@/app/components/base/button/add-button' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx index 440aa9189a..b3f2701524 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx @@ -6,7 +6,8 @@ import { RiEditLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx index a804ae8953..8554fdf3e3 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { DataSet } from '@/models/datasets' import { produce } from 'immer' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useSelector as useAppContextSelector } from '@/context/app-context' import { hasEditPermissionForDataset } from '@/utils/permission' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx index ced1bfcdae..02ae01ba16 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx @@ -5,7 +5,8 @@ import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' import type { DataSet } from '@/models/datasets' import type { DatasetConfigs } from '@/models/debug' import { RiEqualizer2Line } from '@remixicon/react' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ConfigRetrievalContent from '@/app/components/app/configuration/dataset-config/params-config/config-content' import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx index 55715f2fb0..9f5fe1f31c 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/node.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { KnowledgeRetrievalNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' import type { DataSet } from '@/models/datasets' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import AppIcon from '@/app/components/base/app-icon' import { useDatasetsDetailStore } from '../../datasets-detail-store/store' diff --git a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx index 7d4b472fd3..c9a6151d72 100644 --- a/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/extract-input.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Var } from '../../../types' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' diff --git a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx index d77a0c3eb3..8dd817a5ad 100644 --- a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Condition } from '../types' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { SimpleSelect as Select } from '@/app/components/base/select' import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' diff --git a/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx b/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx index f7356b58aa..1793deb293 100644 --- a/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/limit-config.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Limit } from '../types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx index ee8703ca6c..52669526c6 100644 --- a/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/sub-variable-picker.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { Item } from '@/app/components/base/select' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { SimpleSelect as Select } from '@/app/components/base/select' diff --git a/web/app/components/workflow/nodes/list-operator/node.tsx b/web/app/components/workflow/nodes/list-operator/node.tsx index 4d02595fcf..29b79636dd 100644 --- a/web/app/components/workflow/nodes/list-operator/node.tsx +++ b/web/app/components/workflow/nodes/list-operator/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ListFilterNodeType } from './types' import type { Node, NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index be1d79dcad..f9d279f966 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ListFilterNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index a712d6e408..776ad6804c 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import type { ModelConfig, PromptItem, Variable } from '../../../types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx index 856b88ac00..228156f009 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { ModelConfig, PromptItem, ValueSelector, Var, Variable } from '../../../types' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx index 72620ee233..0e1aac8a32 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { Editor } from '@monaco-editor/react' import { RiClipboardLine, RiIndentIncrease } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import useTheme from '@/hooks/use-theme' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx index 5cb2a421d5..041894fee6 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiErrorWarningFill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type ErrorMessageProps = { diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx index b7a0a40f32..66ea3bfc59 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { SchemaRoot } from '../../types' -import React from 'react' +import * as React from 'react' import Modal from '../../../../../base/modal' import JsonSchemaConfig from './json-schema-config' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx index 0110756b47..b69ce186f9 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-importer.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx index c5eaea6efd..38c6539d89 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { SchemaRoot } from '../../types' import { RiBracesLine, RiCloseLine, RiExternalLinkLine, RiTimelineView } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx index 0e7d8c8d0c..5921976c41 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { SchemaRoot } from '../../../types' import { RiArrowLeftLine, RiCloseLine, RiSparklingLine } from '@remixicon/react' -import React, { useCallback, useMemo, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx index a4da5b69e3..6a34925275 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { SchemaRoot } from '../../../types' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { CompletionParams, Model } from '@/types/app' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx index 17641fdf37..b9e0938e9b 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Model } from '@/types/app' import { RiCloseLine, RiSparklingFill } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Textarea from '@/app/components/base/textarea' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx index 54753f08b4..fc95b4a998 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import LargeDataAlert from '@/app/components/workflow/variable-inspect/large-data-alert' import { cn } from '@/utils/classnames' import CodeEditor from './code-editor' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx index 54a3b6bb85..967481ac94 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/add-field.tsx @@ -1,5 +1,6 @@ import { RiAddCircleFill } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { useMittContext } from './context' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx index 3510498835..56d7c7dc31 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/card.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type CardProps = { diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx index a612701adc..fae1d2ab5a 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiAddCircleLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx index ee60195fdb..1555f20a83 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-actions.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { useKeyPress } from 'ahooks' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { getKeyboardKeyCodeBySystem, getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx index 28ea12d9a3..10e6e721a8 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/advanced-options.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import Textarea from '@/app/components/base/textarea' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx index 2dfaa88260..4f471ec38c 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx @@ -1,5 +1,6 @@ import type { FC } from 'react' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { cn } from '@/utils/classnames' type AutoWidthInputProps = { diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx index e3ae0d16ab..81899f6b69 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx @@ -3,7 +3,8 @@ import type { SchemaEnumType } from '../../../../types' import type { AdvancedOptionsType } from './advanced-options' import type { TypeItem } from './type-selector' import { useUnmount } from 'ahooks' -import React, { useCallback, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx index b84bdd0775..7ee68d1bcb 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/required-switch.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx index 23cd1ee477..0fdf5b4349 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { Field } from '../../../types' import { RiArrowDropDownLine, RiArrowDropRightLine } from '@remixicon/react' import { useDebounceFn } from 'ahooks' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import Divider from '@/app/components/base/divider' import { JSON_SCHEMA_MAX_DEPTH } from '@/config' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx b/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx index eb285ee389..da272b4b14 100644 --- a/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx +++ b/web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { ModelConfig } from '@/app/components/workflow/types' import type { GenRes } from '@/service/debug' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' import { ActionButton } from '@/app/components/base/action-button' import { Generator } from '@/app/components/base/icons/src/vender/other' diff --git a/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx b/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx index 147981e398..6a20c89315 100644 --- a/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx +++ b/web/app/components/workflow/nodes/llm/components/reasoning-format-config.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx index fe078ba6fa..e59c2764a0 100644 --- a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx +++ b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' import { Resolution } from '@/types/app' diff --git a/web/app/components/workflow/nodes/llm/components/structure-output.tsx b/web/app/components/workflow/nodes/llm/components/structure-output.tsx index b97d5e20b7..c4db2d6637 100644 --- a/web/app/components/workflow/nodes/llm/components/structure-output.tsx +++ b/web/app/components/workflow/nodes/llm/components/structure-output.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { SchemaRoot, StructuredOutput } from '../types' import { RiEditLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import ShowPanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' diff --git a/web/app/components/workflow/nodes/llm/node.tsx b/web/app/components/workflow/nodes/llm/node.tsx index 9d44d49475..6a9574a10b 100644 --- a/web/app/components/workflow/nodes/llm/node.tsx +++ b/web/app/components/workflow/nodes/llm/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { LLMNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 4044989a07..fd20b1a2bb 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { LLMNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' import { RiAlertFill, RiQuestionLine } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import AddButton2 from '@/app/components/base/button/add-button' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx b/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx index 00f44ab244..72dfd92d51 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-wrap.tsx @@ -5,7 +5,8 @@ import type { Condition, HandleAddCondition, HandleAddSubVariableCondition, Hand import { RiAddLine, } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { PortalSelect as Select } from '@/app/components/base/select' diff --git a/web/app/components/workflow/nodes/loop/panel.tsx b/web/app/components/workflow/nodes/loop/panel.tsx index 45fec4030f..036abda6de 100644 --- a/web/app/components/workflow/nodes/loop/panel.tsx +++ b/web/app/components/workflow/nodes/loop/panel.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { LoopNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Field from '@/app/components/workflow/nodes/_base/components/field' import { LOOP_NODE_MAX_COUNT } from '@/config' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx index 6382b33154..317d2583e2 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx @@ -5,7 +5,7 @@ import { RiDeleteBinLine, RiEditLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx index 1cd1232983..94343dd5d7 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Param } from '../../types' import type { MoreInfo } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import ListNoDataPlaceholder from '../../../_base/components/list-no-data-placeholder' import Item from './item' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx index 3048a78118..288e486ea7 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Param } from '../../types' import type { MoreInfo } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Field from '@/app/components/app/configuration/config-var/config-modal/field' import ConfigSelect from '@/app/components/app/configuration/config-var/config-select' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx index dc5354a21a..7990bcc361 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Field from '../../_base/components/field' import OptionCard from '../../_base/components/option-card' diff --git a/web/app/components/workflow/nodes/parameter-extractor/node.tsx b/web/app/components/workflow/nodes/parameter-extractor/node.tsx index 014706810f..9e02d657ff 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/node.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ParameterExtractorNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' diff --git a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx index 563c124102..9603da5869 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ParameterExtractorNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' diff --git a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx index 336bd3463a..0a6b3dbbfb 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx index eb629a857c..2af2f8036a 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { Topic } from '../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { uniqueId } from 'lodash-es' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx index 387d60d671..8e61f918a5 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx @@ -5,7 +5,8 @@ import type { ValueSelector, Var } from '@/app/components/workflow/types' import { RiDraggable } from '@remixicon/react' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' diff --git a/web/app/components/workflow/nodes/question-classifier/node.tsx b/web/app/components/workflow/nodes/question-classifier/node.tsx index e0a330e110..e00eee9d41 100644 --- a/web/app/components/workflow/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/nodes/question-classifier/node.tsx @@ -2,7 +2,7 @@ import type { TFunction } from 'i18next' import type { FC } from 'react' import type { NodeProps } from 'reactflow' import type { QuestionClassifierNodeType } from './types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import { diff --git a/web/app/components/workflow/nodes/question-classifier/panel.tsx b/web/app/components/workflow/nodes/question-classifier/panel.tsx index 9496f90915..05b93c98b9 100644 --- a/web/app/components/workflow/nodes/question-classifier/panel.tsx +++ b/web/app/components/workflow/nodes/question-classifier/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { QuestionClassifierNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' diff --git a/web/app/components/workflow/nodes/start/components/var-item.tsx b/web/app/components/workflow/nodes/start/components/var-item.tsx index 317a733d9b..a506c51e31 100644 --- a/web/app/components/workflow/nodes/start/components/var-item.tsx +++ b/web/app/components/workflow/nodes/start/components/var-item.tsx @@ -6,7 +6,8 @@ import { } from '@remixicon/react' import { useBoolean, useHover } from 'ahooks' import { noop } from 'lodash-es' -import React, { useCallback, useRef } from 'react' +import * as React from 'react' +import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' import Badge from '@/app/components/base/badge' diff --git a/web/app/components/workflow/nodes/start/components/var-list.tsx b/web/app/components/workflow/nodes/start/components/var-list.tsx index 5ae45c7192..bda45ca5dd 100644 --- a/web/app/components/workflow/nodes/start/components/var-list.tsx +++ b/web/app/components/workflow/nodes/start/components/var-list.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { InputVar, MoreInfo } from '@/app/components/workflow/types' import { RiDraggable } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { ReactSortable } from 'react-sortablejs' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/workflow/nodes/start/node.tsx b/web/app/components/workflow/nodes/start/node.tsx index e8642ff616..cc772dfc6a 100644 --- a/web/app/components/workflow/nodes/start/node.tsx +++ b/web/app/components/workflow/nodes/start/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { StartNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import InputVarTypeIcon from '../_base/components/input-var-type-icon' diff --git a/web/app/components/workflow/nodes/start/panel.tsx b/web/app/components/workflow/nodes/start/panel.tsx index 5871ab1852..b29ece7266 100644 --- a/web/app/components/workflow/nodes/start/panel.tsx +++ b/web/app/components/workflow/nodes/start/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { StartNodeType } from './types' import type { InputVar, NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' import AddButton from '@/app/components/base/button/add-button' diff --git a/web/app/components/workflow/nodes/template-transform/node.tsx b/web/app/components/workflow/nodes/template-transform/node.tsx index 3a4c5c3319..4485d66258 100644 --- a/web/app/components/workflow/nodes/template-transform/node.tsx +++ b/web/app/components/workflow/nodes/template-transform/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { TemplateTransformNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' const Node: FC<NodeProps<TemplateTransformNodeType>> = () => { return ( diff --git a/web/app/components/workflow/nodes/template-transform/panel.tsx b/web/app/components/workflow/nodes/template-transform/panel.tsx index c8fc293329..7e2c39247c 100644 --- a/web/app/components/workflow/nodes/template-transform/panel.tsx +++ b/web/app/components/workflow/nodes/template-transform/panel.tsx @@ -4,7 +4,7 @@ import type { NodePanelProps } from '@/app/components/workflow/types' import { RiQuestionLine, } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import AddButton from '@/app/components/base/button/add-button' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars' diff --git a/web/app/components/workflow/nodes/tool/components/copy-id.tsx b/web/app/components/workflow/nodes/tool/components/copy-id.tsx index 39ffd7edd1..8e53970749 100644 --- a/web/app/components/workflow/nodes/tool/components/copy-id.tsx +++ b/web/app/components/workflow/nodes/tool/components/copy-id.tsx @@ -2,7 +2,8 @@ import { RiFileCopyLine } from '@remixicon/react' import copy from 'copy-to-clipboard' import { debounce } from 'lodash-es' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 4f5d5f24bc..8b1bd46eeb 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -6,7 +6,8 @@ import type { Tool } from '@/app/components/tools/types' import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' diff --git a/web/app/components/workflow/nodes/tool/node.tsx b/web/app/components/workflow/nodes/tool/node.tsx index e2bcd26bd2..0cf4f0ff58 100644 --- a/web/app/components/workflow/nodes/tool/node.tsx +++ b/web/app/components/workflow/nodes/tool/node.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { ToolNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' import { useNodePluginInstallation } from '@/app/components/workflow/hooks/use-node-plugin-installation' diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index 3e1b778a7a..559d42fd9f 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ToolNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import Field from '@/app/components/workflow/nodes/_base/components/field' diff --git a/web/app/components/workflow/nodes/trigger-plugin/node.tsx b/web/app/components/workflow/nodes/trigger-plugin/node.tsx index da4dc83d34..94f7d0a314 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/node.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/node.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { PluginTriggerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React, { useEffect, useMemo } from 'react' +import * as React from 'react' +import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import NodeStatus, { NodeStatusEnum } from '@/app/components/base/node-status' import { useNodeDataUpdate } from '@/app/components/workflow/hooks/use-node-data-update' diff --git a/web/app/components/workflow/nodes/trigger-plugin/panel.tsx b/web/app/components/workflow/nodes/trigger-plugin/panel.tsx index ffa3a5503c..a74639faf5 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-plugin/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { PluginTriggerNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import Split from '@/app/components/workflow/nodes/_base/components/split' import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx index b4f62de436..c257949ca2 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/frequency-selector.tsx @@ -1,5 +1,6 @@ import type { ScheduleFrequency } from '../types' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { SimpleSelect } from '@/app/components/base/select' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx index 724fedc7b2..7c1f4e8f9d 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx @@ -1,6 +1,6 @@ import type { ScheduleMode } from '../types' import { RiCalendarLine, RiCodeLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { SegmentedControl } from '@/app/components/base/segmented-control' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx index 35ffaff939..583c92ccaf 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/mode-toggle.tsx @@ -1,5 +1,5 @@ import type { ScheduleMode } from '../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { Asterisk, CalendarCheckLine } from '@/app/components/base/icons/src/vender/workflow' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx index e5a50522e1..16399bea27 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx @@ -1,5 +1,5 @@ import { RiQuestionLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx index c84bca483c..fe246f1c67 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/next-execution-times.tsx @@ -1,5 +1,5 @@ import type { ScheduleTriggerNodeType } from '../types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { getFormattedExecutionTimes } from '../utils/execution-time-calculator' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/on-minute-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/on-minute-selector.tsx index 992a111d19..f9eef3691d 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/on-minute-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/on-minute-selector.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Slider from '@/app/components/base/slider' diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/weekday-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/weekday-selector.tsx index 348fd53454..9255a6b485 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/weekday-selector.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/weekday-selector.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' type WeekdaySelectorProps = { diff --git a/web/app/components/workflow/nodes/trigger-schedule/node.tsx b/web/app/components/workflow/nodes/trigger-schedule/node.tsx index 45e9b2afdb..6e9226b0be 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/node.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ScheduleTriggerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { getNextExecutionTime } from './utils/execution-time-calculator' diff --git a/web/app/components/workflow/nodes/trigger-schedule/panel.tsx b/web/app/components/workflow/nodes/trigger-schedule/panel.tsx index 8daedc50a9..b4ca1860b5 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { ScheduleTriggerNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import TimePicker from '@/app/components/base/date-and-time-picker/time-picker' import Input from '@/app/components/base/input' diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx index a6644d7312..d85b622e10 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/generic-table.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC, ReactNode } from 'react' import { RiDeleteBinLine } from '@remixicon/react' -import React, { useCallback, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useMemo } from 'react' import Checkbox from '@/app/components/base/checkbox' import Input from '@/app/components/base/input' import { SimpleSelect } from '@/app/components/base/select' diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx index da54cac16a..b681eec9b1 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/header-table.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { WebhookHeader } from '../types' import type { ColumnConfig, GenericTableRow } from './generic-table' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import GenericTable from './generic-table' diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/paragraph-input.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/paragraph-input.tsx index b26238fdd1..c49ce9cd5d 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/paragraph-input.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/paragraph-input.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useRef } from 'react' +import * as React from 'react' +import { useRef } from 'react' import { cn } from '@/utils/classnames' type ParagraphInputProps = { diff --git a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx index 1fa038ff73..d4dc05f741 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/components/parameter-table.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { WebhookParameter } from '../types' import type { ColumnConfig, GenericTableRow } from './generic-table' -import React, { useMemo } from 'react' +import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { VarType } from '@/app/components/workflow/types' import { createParameterTypeOptions, normalizeParameterType } from '../utils/parameter-type-utils' diff --git a/web/app/components/workflow/nodes/trigger-webhook/node.tsx b/web/app/components/workflow/nodes/trigger-webhook/node.tsx index 77f42b6db2..2de1b30aee 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/node.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { WebhookTriggerNodeType } from './types' import type { NodeProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' const Node: FC<NodeProps<WebhookTriggerNodeType>> = ({ data, diff --git a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx index efc541bbb3..e5773e1afd 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/panel.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/panel.tsx @@ -3,7 +3,8 @@ import type { HttpMethod, WebhookTriggerNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' import copy from 'copy-to-clipboard' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { InputNumber } from '@/app/components/base/input-number' import InputWithCopy from '@/app/components/base/input-with-copy' diff --git a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx index 984ffc03dd..d58561b603 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx +++ b/web/app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { Variable } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' type OutputVariablesContentProps = { variables?: Variable[] diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx index 277c44744b..8fb1cfba61 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx @@ -7,7 +7,8 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Folder } from '@/app/components/base/icons/src/vender/line/files' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx index a767e704fe..19ead7ead1 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx @@ -3,7 +3,8 @@ import type { FC } from 'react' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { produce } from 'immer' import { noop } from 'lodash-es' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' diff --git a/web/app/components/workflow/nodes/variable-assigner/panel.tsx b/web/app/components/workflow/nodes/variable-assigner/panel.tsx index 0a6c1c3c84..c7edffc933 100644 --- a/web/app/components/workflow/nodes/variable-assigner/panel.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { VariableAssignerNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx index 3d22437830..e1ec55de12 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/array-bool-list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiAddLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx index e1025eda6a..42a1d3f247 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/array-value-list.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import { RiAddLine } from '@remixicon/react' import { produce } from 'immer' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx index 89b309db58..859a8bc728 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/bool-value.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import OptionCard from '../../../nodes/_base/components/option-card' type Props = { diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx index 1132434122..33235e2423 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx @@ -1,7 +1,8 @@ 'use client' import type { FC } from 'react' import { produce } from 'immer' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { ToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx index 0e39bfcfcc..0c2f8fc4f1 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/object-value-list.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import ObjectValueItem from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-item' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx index 1fe4e5fe5a..14cd1b3cf1 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal-trigger.tsx @@ -1,7 +1,7 @@ 'use client' import type { ConversationVariable } from '@/app/components/workflow/types' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx index e30da0fff3..33e2e07376 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx @@ -1,6 +1,7 @@ import type { ConversationVariable } from '@/app/components/workflow/types' import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react' -import React, { useCallback, useEffect, useMemo } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { v4 as uuid4 } from 'uuid' diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx index 1922374941..69ef1366b9 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-type-select.tsx @@ -1,6 +1,7 @@ 'use client' import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' -import React, { useState } from 'react' +import * as React from 'react' +import { useState } from 'react' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx index 117247901e..6e130180d0 100644 --- a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx @@ -6,7 +6,8 @@ import { RiCloseLine } from '@remixicon/react' import { useMount } from 'ahooks' import copy from 'copy-to-clipboard' import { capitalize, noop } from 'lodash-es' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Copy, diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx index 6cf193fe96..e253d6c27c 100644 --- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx @@ -1,6 +1,7 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiCloseLine } from '@remixicon/react' -import React, { useEffect } from 'react' +import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { v4 as uuid4 } from 'uuid' diff --git a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx index 30551562a7..448d6f1aa9 100644 --- a/web/app/components/workflow/panel/env-panel/variable-trigger.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-trigger.tsx @@ -1,7 +1,7 @@ 'use client' import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiAddLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { diff --git a/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx b/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx index 47dc68687d..225f4b08f8 100644 --- a/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/context-menu/index.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import { RiMoreFill } from '@remixicon/react' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import { diff --git a/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx b/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx index a307056148..938b8089ca 100644 --- a/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/context-menu/menu-item.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { VersionHistoryContextMenuOptions } from '../../../types' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type MenuItemProps = { diff --git a/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx b/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx index 3ad7d0dc8a..a879593698 100644 --- a/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx +++ b/web/app/components/workflow/panel/version-history-panel/delete-confirm-modal.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { VersionHistory } from '@/types/workflow' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/workflow/panel/version-history-panel/empty.tsx b/web/app/components/workflow/panel/version-history-panel/empty.tsx index c020c076ad..bc81fc7503 100644 --- a/web/app/components/workflow/panel/version-history-panel/empty.tsx +++ b/web/app/components/workflow/panel/version-history-panel/empty.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiHistoryLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx b/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx index c9a7c28112..d7a37caa5e 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/filter-item.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { WorkflowVersionFilterOptions } from '../../../types' import { RiCheckLine } from '@remixicon/react' -import React from 'react' +import * as React from 'react' type FilterItemProps = { item: { diff --git a/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx b/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx index 6db331338b..d6d79f9a6a 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/filter-switch.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx index 8def221926..83156be732 100644 --- a/web/app/components/workflow/panel/version-history-panel/filter/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/filter/index.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import { RiFilter3Line } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import Divider from '@/app/components/base/divider' import { PortalToFollowElem, diff --git a/web/app/components/workflow/panel/version-history-panel/index.tsx b/web/app/components/workflow/panel/version-history-panel/index.tsx index 0bdb608d94..06a27eb7c7 100644 --- a/web/app/components/workflow/panel/version-history-panel/index.tsx +++ b/web/app/components/workflow/panel/version-history-panel/index.tsx @@ -2,7 +2,8 @@ import type { VersionHistory } from '@/types/workflow' import { RiArrowDownDoubleLine, RiCloseLine, RiLoader2Line } from '@remixicon/react' import copy from 'copy-to-clipboard' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import VersionInfoModal from '@/app/components/app/app-publisher/version-info-modal' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/workflow/panel/version-history-panel/loading/item.tsx b/web/app/components/workflow/panel/version-history-panel/loading/item.tsx index c17d725fb3..58d5cc3bd4 100644 --- a/web/app/components/workflow/panel/version-history-panel/loading/item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/loading/item.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type ItemProps = { diff --git a/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx b/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx index 09bc5d79b4..a8dfb8b21c 100644 --- a/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx +++ b/web/app/components/workflow/panel/version-history-panel/restore-confirm-modal.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { VersionHistory } from '@/types/workflow' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx b/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx index 7739d10af2..4818c371a7 100644 --- a/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx +++ b/web/app/components/workflow/panel/version-history-panel/version-history-item.tsx @@ -1,7 +1,8 @@ import type { VersionHistoryContextMenuOptions } from '../../types' import type { VersionHistory } from '@/types/workflow' import dayjs from 'dayjs' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import { WorkflowVersion } from '../../types' diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 1bff24e2cc..378daa7b19 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -2,7 +2,8 @@ import type { FC } from 'react' import type { WorkflowRunDetailResponse } from '@/models/log' import type { NodeTracing } from '@/types/workflow' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx index 5933e897bd..12812aeef7 100644 --- a/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-log/iteration-result-panel.tsx @@ -7,7 +7,8 @@ import { RiErrorWarningLine, RiLoader2Line, } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Iteration } from '@/app/components/base/icons/src/vender/workflow' import TracingPanel from '@/app/components/workflow/run/tracing-panel' diff --git a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx index 758148d8c1..219888a56f 100644 --- a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx @@ -7,7 +7,8 @@ import { RiErrorWarningLine, RiLoader2Line, } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Loop } from '@/app/components/base/icons/src/vender/workflow' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' diff --git a/web/app/components/workflow/run/loop-result-panel.tsx b/web/app/components/workflow/run/loop-result-panel.tsx index 61ba6db115..8238be82f3 100644 --- a/web/app/components/workflow/run/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-result-panel.tsx @@ -5,7 +5,8 @@ import { RiArrowRightSLine, RiCloseLine, } from '@remixicon/react' -import React, { useCallback, useState } from 'react' +import * as React from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/run/tracing-panel.tsx b/web/app/components/workflow/run/tracing-panel.tsx index 0e1d6578ab..8931c8f7fe 100644 --- a/web/app/components/workflow/run/tracing-panel.tsx +++ b/web/app/components/workflow/run/tracing-panel.tsx @@ -5,9 +5,8 @@ import { RiArrowDownSLine, RiMenu4Line, } from '@remixicon/react' -import -React, -{ +import * as React from 'react' +import { useCallback, useState, } from 'react' diff --git a/web/app/components/workflow/variable-inspect/display-content.tsx b/web/app/components/workflow/variable-inspect/display-content.tsx index 901c0fa6dc..ebeaa17c42 100644 --- a/web/app/components/workflow/variable-inspect/display-content.tsx +++ b/web/app/components/workflow/variable-inspect/display-content.tsx @@ -2,7 +2,8 @@ import type { VarType } from '../types' import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list/types' import type { ParentMode } from '@/models/datasets' import { RiBracesLine, RiEyeLine } from '@remixicon/react' -import React, { useMemo, useState } from 'react' +import * as React from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { Markdown } from '@/app/components/base/markdown' import { SegmentedControl } from '@/app/components/base/segmented-control' diff --git a/web/app/components/workflow/variable-inspect/large-data-alert.tsx b/web/app/components/workflow/variable-inspect/large-data-alert.tsx index a2750c82e7..6ab3e65f41 100644 --- a/web/app/components/workflow/variable-inspect/large-data-alert.tsx +++ b/web/app/components/workflow/variable-inspect/large-data-alert.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiInformation2Fill } from '@remixicon/react' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/variable-inspect/value-content.tsx b/web/app/components/workflow/variable-inspect/value-content.tsx index 6d6a04434a..1d4f1cfd13 100644 --- a/web/app/components/workflow/variable-inspect/value-content.tsx +++ b/web/app/components/workflow/variable-inspect/value-content.tsx @@ -1,6 +1,7 @@ import type { VarInInspect } from '@/types/workflow' import { useDebounceFn } from 'ahooks' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' diff --git a/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx index b0a9e6903f..bac19c579f 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/if-else/node.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react' import type { NodeProps } from 'reactflow' import type { Condition, IfElseNodeType } from '@/app/components/workflow/nodes/if-else/types' -import React, { useCallback } from 'react' +import * as React from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import ConditionFilesListValue from '@/app/components/workflow/nodes/if-else/components/condition-files-list-value' import ConditionValue from '@/app/components/workflow/nodes/if-else/components/condition-value' diff --git a/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx b/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx index c164d624e4..2511483b36 100644 --- a/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/workflow-preview/components/nodes/question-classifier/node.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { NodeProps } from 'reactflow' import type { QuestionClassifierNodeType } from '@/app/components/workflow/nodes/question-classifier/types' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import InfoPanel from '@/app/components/workflow/nodes/_base/components/info-panel' import { NodeSourceHandle } from '../../node-handle' diff --git a/web/app/education-apply/expire-notice-modal.tsx b/web/app/education-apply/expire-notice-modal.tsx index 51a3ba66b1..6755de1241 100644 --- a/web/app/education-apply/expire-notice-modal.tsx +++ b/web/app/education-apply/expire-notice-modal.tsx @@ -2,7 +2,7 @@ import { RiExternalLinkLine } from '@remixicon/react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import React from 'react' +import * as React from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' diff --git a/web/app/education-apply/verify-state-modal.tsx b/web/app/education-apply/verify-state-modal.tsx index e4a5cd9bbe..5d4e89c92e 100644 --- a/web/app/education-apply/verify-state-modal.tsx +++ b/web/app/education-apply/verify-state-modal.tsx @@ -1,7 +1,8 @@ import { RiExternalLinkLine, } from '@remixicon/react' -import React, { useEffect, useRef, useState } from 'react' +import * as React from 'react' +import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/forgot-password/ForgotPasswordForm.tsx b/web/app/forgot-password/ForgotPasswordForm.tsx index 43aa1006d6..2b50c1c452 100644 --- a/web/app/forgot-password/ForgotPasswordForm.tsx +++ b/web/app/forgot-password/ForgotPasswordForm.tsx @@ -4,7 +4,8 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useRouter } from 'next/navigation' -import React, { useEffect, useState } from 'react' +import * as React from 'react' +import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { z } from 'zod' diff --git a/web/app/forgot-password/page.tsx b/web/app/forgot-password/page.tsx index 4c37e096ca..338f4eaf13 100644 --- a/web/app/forgot-password/page.tsx +++ b/web/app/forgot-password/page.tsx @@ -1,6 +1,6 @@ 'use client' import { useSearchParams } from 'next/navigation' -import React from 'react' +import * as React from 'react' import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' import { useGlobalPublicStore } from '@/context/global-public-context' import useDocumentTitle from '@/hooks/use-document-title' diff --git a/web/app/init/page.tsx b/web/app/init/page.tsx index c61457f984..7c1d849bdd 100644 --- a/web/app/init/page.tsx +++ b/web/app/init/page.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' import InitPasswordPopup from './InitPasswordPopup' diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index c3d9c1dfa6..60de8e0501 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -7,7 +7,8 @@ import { useDebounceFn } from 'ahooks' import Link from 'next/link' import { useRouter } from 'next/navigation' -import React, { useCallback, useEffect } from 'react' +import * as React from 'react' +import { useCallback, useEffect } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { z } from 'zod' diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index b9a770405f..db30d5bc5a 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' import { useGlobalPublicStore } from '@/context/global-public-context' import { cn } from '@/utils/classnames' import Header from '../signin/_header' diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index 5ef24cd03e..01135c2bf6 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -1,7 +1,7 @@ 'use client' import type { Locale } from '@/i18n-config' import dynamic from 'next/dynamic' -import React from 'react' +import * as React from 'react' import { useContext } from 'use-context-selector' import Divider from '@/app/components/base/divider' import LocaleSigninSelect from '@/app/components/base/select/locale-signin' diff --git a/web/app/signin/normal-form.tsx b/web/app/signin/normal-form.tsx index 6bc37e6dd3..a4e6e4607e 100644 --- a/web/app/signin/normal-form.tsx +++ b/web/app/signin/normal-form.tsx @@ -1,7 +1,8 @@ import { RiContractLine, RiDoorLockLine, RiErrorWarningFill } from '@remixicon/react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useCallback, useEffect, useState } from 'react' +import * as React from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' import { IS_CE_EDITION } from '@/config' diff --git a/web/app/signin/one-more-step.tsx b/web/app/signin/one-more-step.tsx index 80013e622f..76f707493b 100644 --- a/web/app/signin/one-more-step.tsx +++ b/web/app/signin/one-more-step.tsx @@ -2,7 +2,8 @@ import type { Reducer } from 'react' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' -import React, { useReducer } from 'react' +import * as React from 'react' +import { useReducer } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { SimpleSelect } from '@/app/components/base/select' diff --git a/web/app/signin/split.tsx b/web/app/signin/split.tsx index b6e848357c..370f108421 100644 --- a/web/app/signin/split.tsx +++ b/web/app/signin/split.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { cn } from '@/utils/classnames' type Props = { diff --git a/web/context/external-api-panel-context.tsx b/web/context/external-api-panel-context.tsx index 05ae5c45c1..e50420c169 100644 --- a/web/context/external-api-panel-context.tsx +++ b/web/context/external-api-panel-context.tsx @@ -1,6 +1,7 @@ 'use client' -import React, { createContext, useContext, useState } from 'react' +import * as React from 'react' +import { createContext, useContext, useState } from 'react' type ExternalApiPanelContextType = { showExternalApiPanel: boolean diff --git a/web/context/modal-context.test.tsx b/web/context/modal-context.test.tsx index 07a82939a0..4f41c19df6 100644 --- a/web/context/modal-context.test.tsx +++ b/web/context/modal-context.test.tsx @@ -1,5 +1,5 @@ import { act, render, screen, waitFor } from '@testing-library/react' -import React from 'react' +import * as React from 'react' import { defaultPlan } from '@/app/components/billing/config' import { Plan } from '@/app/components/billing/type' import { ModalContextProvider } from '@/context/modal-context' diff --git a/web/context/provider-context-mock.tsx b/web/context/provider-context-mock.tsx index b42847a9ec..174affca0d 100644 --- a/web/context/provider-context-mock.tsx +++ b/web/context/provider-context-mock.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import * as React from 'react' import { useProviderContext } from '@/context/provider-context' const ProviderContextMock: FC = () => { diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index da425efb62..9cd4d7831e 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -11,6 +11,7 @@ export default antfu( 'react/no-context-provider': 'off', 'react/no-forward-ref': 'off', 'react/no-use-context': 'off', + 'react/prefer-namespace-import': 'error', }, }, nextjs: true, @@ -54,7 +55,6 @@ export default antfu( 'test/no-identical-title': 'warn', 'test/prefer-hooks-in-order': 'warn', 'ts/no-empty-object-type': 'warn', - 'ts/no-require-imports': 'warn', 'unicorn/prefer-number-properties': 'warn', 'unused-imports/no-unused-vars': 'warn', }, diff --git a/web/hooks/use-breakpoints.ts b/web/hooks/use-breakpoints.ts index 99c2b75d67..e0bd45c01c 100644 --- a/web/hooks/use-breakpoints.ts +++ b/web/hooks/use-breakpoints.ts @@ -1,5 +1,5 @@ 'use client' -import React from 'react' +import * as React from 'react' export enum MediaType { mobile = 'mobile', diff --git a/web/service/demo/index.tsx b/web/service/demo/index.tsx index d538d6fda2..afce18d468 100644 --- a/web/service/demo/index.tsx +++ b/web/service/demo/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import React from 'react' +import * as React from 'react' import Loading from '@/app/components/base/loading' import { AppModeEnum } from '@/types/app' import { createApp, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps' diff --git a/web/utils/context.spec.ts b/web/utils/context.spec.ts index b70a15639a..40f39dda6a 100644 --- a/web/utils/context.spec.ts +++ b/web/utils/context.spec.ts @@ -9,7 +9,7 @@ import { renderHook } from '@testing-library/react' * - createCtx: Standard React context using useContext/createContext * - createSelectorCtx: Context with selector support using use-context-selector library */ -import React from 'react' +import * as React from 'react' import { createCtx, createSelectorCtx } from './context' describe('Context Utilities', () => { From efac8766a124011f121e0ea8c92116871c5be918 Mon Sep 17 00:00:00 2001 From: -LAN- <laipz8200@outlook.com> Date: Tue, 23 Dec 2025 18:14:39 +0800 Subject: [PATCH 44/71] fix: YAML URL import rewrite for GitHub attachments (#30003) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/services/app_dsl_service.py | 1 + .../test_app_dsl_service_import_yaml_url.py | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 api/tests/unit_tests/services/test_app_dsl_service_import_yaml_url.py diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index 1dd6faea5d..deba0b79e8 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -155,6 +155,7 @@ class AppDslService: parsed_url.scheme == "https" and parsed_url.netloc == "github.com" and parsed_url.path.endswith((".yml", ".yaml")) + and "/blob/" in parsed_url.path ): yaml_url = yaml_url.replace("https://github.com", "https://raw.githubusercontent.com") yaml_url = yaml_url.replace("/blob/", "/") diff --git a/api/tests/unit_tests/services/test_app_dsl_service_import_yaml_url.py b/api/tests/unit_tests/services/test_app_dsl_service_import_yaml_url.py new file mode 100644 index 0000000000..41c1d0ea2a --- /dev/null +++ b/api/tests/unit_tests/services/test_app_dsl_service_import_yaml_url.py @@ -0,0 +1,71 @@ +from unittest.mock import MagicMock + +import httpx + +from models import Account +from services import app_dsl_service +from services.app_dsl_service import AppDslService, ImportMode, ImportStatus + + +def _build_response(url: str, status_code: int, content: bytes = b"") -> httpx.Response: + request = httpx.Request("GET", url) + return httpx.Response(status_code=status_code, request=request, content=content) + + +def _pending_yaml_content(version: str = "99.0.0") -> bytes: + return (f'version: "{version}"\nkind: app\napp:\n name: Loop Test\n mode: workflow\n').encode() + + +def _account_mock() -> MagicMock: + account = MagicMock(spec=Account) + account.current_tenant_id = "tenant-1" + return account + + +def test_import_app_yaml_url_user_attachments_keeps_original_url(monkeypatch): + yaml_url = "https://github.com/user-attachments/files/24290802/loop-test.yml" + raw_url = "https://raw.githubusercontent.com/user-attachments/files/24290802/loop-test.yml" + yaml_bytes = _pending_yaml_content() + + def fake_get(url: str, **kwargs): + if url == raw_url: + return _build_response(url, status_code=404) + assert url == yaml_url + return _build_response(url, status_code=200, content=yaml_bytes) + + monkeypatch.setattr(app_dsl_service.ssrf_proxy, "get", fake_get) + + service = AppDslService(MagicMock()) + result = service.import_app( + account=_account_mock(), + import_mode=ImportMode.YAML_URL, + yaml_url=yaml_url, + ) + + assert result.status == ImportStatus.PENDING + assert result.imported_dsl_version == "99.0.0" + + +def test_import_app_yaml_url_github_blob_rewrites_to_raw(monkeypatch): + yaml_url = "https://github.com/acme/repo/blob/main/app.yml" + raw_url = "https://raw.githubusercontent.com/acme/repo/main/app.yml" + yaml_bytes = _pending_yaml_content() + + requested_urls: list[str] = [] + + def fake_get(url: str, **kwargs): + requested_urls.append(url) + assert url == raw_url + return _build_response(url, status_code=200, content=yaml_bytes) + + monkeypatch.setattr(app_dsl_service.ssrf_proxy, "get", fake_get) + + service = AppDslService(MagicMock()) + result = service.import_app( + account=_account_mock(), + import_mode=ImportMode.YAML_URL, + yaml_url=yaml_url, + ) + + assert result.status == ImportStatus.PENDING + assert requested_urls == [raw_url] From a3d4f4f3bdce880bed7ef67bb3b780955a4a11f2 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:26:02 +0800 Subject: [PATCH 45/71] chore: enable ts/no-explicit-any, remove no-unused-vars (#30042) --- web/eslint.config.mjs | 2 +- web/i18n-config/check-i18n.js | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 9cd4d7831e..b085ea3619 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -19,6 +19,7 @@ export default antfu( typescript: { overrides: { 'ts/consistent-type-definitions': ['error', 'type'], + 'ts/no-explicit-any': 'warn', }, }, test: { @@ -40,7 +41,6 @@ export default antfu( 'next/inline-script-id': 'warn', 'no-console': 'warn', 'no-irregular-whitespace': 'warn', - 'no-unused-vars': 'warn', 'node/prefer-global/buffer': 'warn', 'node/prefer-global/process': 'warn', 'react/no-create-ref': 'warn', diff --git a/web/i18n-config/check-i18n.js b/web/i18n-config/check-i18n.js index 096a4d7afc..d69885e6f0 100644 --- a/web/i18n-config/check-i18n.js +++ b/web/i18n-config/check-i18n.js @@ -184,24 +184,6 @@ async function getKeysFromLanguage(language) { }) } -function removeKeysFromObject(obj, keysToRemove, prefix = '') { - let modified = false - for (const key in obj) { - const fullKey = prefix ? `${prefix}.${key}` : key - - if (keysToRemove.includes(fullKey)) { - delete obj[key] - modified = true - console.log(`🗑️ Removed key: ${fullKey}`) - } - else if (typeof obj[key] === 'object' && obj[key] !== null) { - const subModified = removeKeysFromObject(obj[key], keysToRemove, fullKey) - modified = modified || subModified - } - } - return modified -} - async function removeExtraKeysFromFile(language, fileName, extraKeys) { const filePath = path.resolve(__dirname, '../i18n', language, `${fileName}.ts`) From b321511518a95efd68fedf33019a6cd9ec3d0231 Mon Sep 17 00:00:00 2001 From: wangxiaolei <fatelei@gmail.com> Date: Tue, 23 Dec 2025 18:56:38 +0800 Subject: [PATCH 46/71] feat: grace ful close the connection (#30039) --- api/core/mcp/client/sse_client.py | 11 +++ api/core/mcp/client/streamable_client.py | 113 ++++++++++++++++++----- 2 files changed, 102 insertions(+), 22 deletions(-) diff --git a/api/core/mcp/client/sse_client.py b/api/core/mcp/client/sse_client.py index 24ca59ee45..1de1d5a073 100644 --- a/api/core/mcp/client/sse_client.py +++ b/api/core/mcp/client/sse_client.py @@ -61,6 +61,7 @@ class SSETransport: self.timeout = timeout self.sse_read_timeout = sse_read_timeout self.endpoint_url: str | None = None + self.event_source: EventSource | None = None def _validate_endpoint_url(self, endpoint_url: str) -> bool: """Validate that the endpoint URL matches the connection origin. @@ -237,6 +238,9 @@ class SSETransport: write_queue: WriteQueue = queue.Queue() status_queue: StatusQueue = queue.Queue() + # Store event_source for graceful shutdown + self.event_source = event_source + # Start SSE reader thread executor.submit(self.sse_reader, event_source, read_queue, status_queue) @@ -296,6 +300,13 @@ def sse_client( logger.exception("Error connecting to SSE endpoint") raise finally: + # Close the SSE connection to unblock the reader thread + if transport.event_source is not None: + try: + transport.event_source.response.close() + except RuntimeError: + pass + # Clean up queues if read_queue: read_queue.put(None) diff --git a/api/core/mcp/client/streamable_client.py b/api/core/mcp/client/streamable_client.py index 805c16c838..f81e7cead8 100644 --- a/api/core/mcp/client/streamable_client.py +++ b/api/core/mcp/client/streamable_client.py @@ -8,6 +8,7 @@ and session management. import logging import queue +import threading from collections.abc import Callable, Generator from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager @@ -103,6 +104,9 @@ class StreamableHTTPTransport: CONTENT_TYPE: JSON, **self.headers, } + self.stop_event = threading.Event() + self._active_responses: list[httpx.Response] = [] + self._lock = threading.Lock() def _update_headers_with_session(self, base_headers: dict[str, str]) -> dict[str, str]: """Update headers with session ID if available.""" @@ -111,6 +115,30 @@ class StreamableHTTPTransport: headers[MCP_SESSION_ID] = self.session_id return headers + def _register_response(self, response: httpx.Response): + """Register a response for cleanup on shutdown.""" + with self._lock: + self._active_responses.append(response) + + def _unregister_response(self, response: httpx.Response): + """Unregister a response after it's closed.""" + with self._lock: + try: + self._active_responses.remove(response) + except ValueError as e: + logger.debug("Ignoring error during response unregister: %s", e) + + def close_active_responses(self): + """Close all active SSE connections to unblock threads.""" + with self._lock: + responses_to_close = list(self._active_responses) + self._active_responses.clear() + for response in responses_to_close: + try: + response.close() + except RuntimeError as e: + logger.debug("Ignoring error during active response close: %s", e) + def _is_initialization_request(self, message: JSONRPCMessage) -> bool: """Check if the message is an initialization request.""" return isinstance(message.root, JSONRPCRequest) and message.root.method == "initialize" @@ -195,11 +223,21 @@ class StreamableHTTPTransport: event_source.response.raise_for_status() logger.debug("GET SSE connection established") - for sse in event_source.iter_sse(): - self._handle_sse_event(sse, server_to_client_queue) + # Register response for cleanup + self._register_response(event_source.response) + + try: + for sse in event_source.iter_sse(): + if self.stop_event.is_set(): + logger.debug("GET stream received stop signal") + break + self._handle_sse_event(sse, server_to_client_queue) + finally: + self._unregister_response(event_source.response) except Exception as exc: - logger.debug("GET stream error (non-fatal): %s", exc) + if not self.stop_event.is_set(): + logger.debug("GET stream error (non-fatal): %s", exc) def _handle_resumption_request(self, ctx: RequestContext): """Handle a resumption request using GET with SSE.""" @@ -224,15 +262,24 @@ class StreamableHTTPTransport: event_source.response.raise_for_status() logger.debug("Resumption GET SSE connection established") - for sse in event_source.iter_sse(): - is_complete = self._handle_sse_event( - sse, - ctx.server_to_client_queue, - original_request_id, - ctx.metadata.on_resumption_token_update if ctx.metadata else None, - ) - if is_complete: - break + # Register response for cleanup + self._register_response(event_source.response) + + try: + for sse in event_source.iter_sse(): + if self.stop_event.is_set(): + logger.debug("Resumption stream received stop signal") + break + is_complete = self._handle_sse_event( + sse, + ctx.server_to_client_queue, + original_request_id, + ctx.metadata.on_resumption_token_update if ctx.metadata else None, + ) + if is_complete: + break + finally: + self._unregister_response(event_source.response) def _handle_post_request(self, ctx: RequestContext): """Handle a POST request with response processing.""" @@ -295,17 +342,27 @@ class StreamableHTTPTransport: def _handle_sse_response(self, response: httpx.Response, ctx: RequestContext): """Handle SSE response from the server.""" try: + # Register response for cleanup + self._register_response(response) + event_source = EventSource(response) - for sse in event_source.iter_sse(): - is_complete = self._handle_sse_event( - sse, - ctx.server_to_client_queue, - resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None), - ) - if is_complete: - break + try: + for sse in event_source.iter_sse(): + if self.stop_event.is_set(): + logger.debug("SSE response stream received stop signal") + break + is_complete = self._handle_sse_event( + sse, + ctx.server_to_client_queue, + resumption_callback=(ctx.metadata.on_resumption_token_update if ctx.metadata else None), + ) + if is_complete: + break + finally: + self._unregister_response(response) except Exception as e: - ctx.server_to_client_queue.put(e) + if not self.stop_event.is_set(): + ctx.server_to_client_queue.put(e) def _handle_unexpected_content_type( self, @@ -345,6 +402,11 @@ class StreamableHTTPTransport: """ while True: try: + # Check if we should stop + if self.stop_event.is_set(): + logger.debug("Post writer received stop signal") + break + # Read message from client queue with timeout to check stop_event periodically session_message = client_to_server_queue.get(timeout=DEFAULT_QUEUE_READ_TIMEOUT) if session_message is None: @@ -381,7 +443,8 @@ class StreamableHTTPTransport: except queue.Empty: continue except Exception as exc: - server_to_client_queue.put(exc) + if not self.stop_event.is_set(): + server_to_client_queue.put(exc) def terminate_session(self, client: httpx.Client): """Terminate the session by sending a DELETE request.""" @@ -465,6 +528,12 @@ def streamablehttp_client( transport.get_session_id, ) finally: + # Set stop event to signal all threads to stop + transport.stop_event.set() + + # Close all active SSE connections to unblock threads + transport.close_active_responses() + if transport.session_id and terminate_on_close: transport.terminate_session(client) From 3f27b3f0b49f29993677aa5a9da9942412ed5cdd Mon Sep 17 00:00:00 2001 From: ericko-being <erickokurniadi@beingcorp.co.jp> Date: Tue, 23 Dec 2025 20:00:17 +0900 Subject: [PATCH 47/71] fix(ops): correct LangSmith dotted_order timestamp format (#30022) --- api/core/ops/utils.py | 2 +- api/tests/unit_tests/core/ops/test_utils.py | 53 ++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/api/core/ops/utils.py b/api/core/ops/utils.py index c00f785034..631e3b77b2 100644 --- a/api/core/ops/utils.py +++ b/api/core/ops/utils.py @@ -54,7 +54,7 @@ def generate_dotted_order(run_id: str, start_time: Union[str, datetime], parent_ generate dotted_order for langsmith """ start_time = datetime.fromisoformat(start_time) if isinstance(start_time, str) else start_time - timestamp = start_time.strftime("%Y%m%dT%H%M%S%f")[:-3] + "Z" + timestamp = start_time.strftime("%Y%m%dT%H%M%S%f") + "Z" current_segment = f"{timestamp}{run_id}" if parent_dotted_order is None: diff --git a/api/tests/unit_tests/core/ops/test_utils.py b/api/tests/unit_tests/core/ops/test_utils.py index 7cc2772acf..e1084001b7 100644 --- a/api/tests/unit_tests/core/ops/test_utils.py +++ b/api/tests/unit_tests/core/ops/test_utils.py @@ -1,6 +1,9 @@ +import re +from datetime import datetime + import pytest -from core.ops.utils import validate_project_name, validate_url, validate_url_with_path +from core.ops.utils import generate_dotted_order, validate_project_name, validate_url, validate_url_with_path class TestValidateUrl: @@ -136,3 +139,51 @@ class TestValidateProjectName: """Test custom default name""" result = validate_project_name("", "Custom Default") assert result == "Custom Default" + + +class TestGenerateDottedOrder: + """Test cases for generate_dotted_order function""" + + def test_dotted_order_has_6_digit_microseconds(self): + """Test that timestamp includes full 6-digit microseconds for LangSmith API compatibility. + + LangSmith API expects timestamps in format: YYYYMMDDTHHMMSSffffffZ (6-digit microseconds). + Previously, the code truncated to 3 digits which caused API errors: + 'cannot parse .111 as .000000' + """ + start_time = datetime(2025, 12, 23, 4, 19, 55, 111000) + run_id = "test-run-id" + result = generate_dotted_order(run_id, start_time) + + # Extract timestamp portion (before the run_id) + timestamp_match = re.match(r"^(\d{8}T\d{6})(\d+)Z", result) + assert timestamp_match is not None, "Timestamp format should match YYYYMMDDTHHMMSSffffffZ" + + microseconds = timestamp_match.group(2) + assert len(microseconds) == 6, f"Microseconds should be 6 digits, got {len(microseconds)}: {microseconds}" + + def test_dotted_order_format_matches_langsmith_expected(self): + """Test that dotted_order format matches LangSmith API expected format.""" + start_time = datetime(2025, 1, 15, 10, 30, 45, 123456) + run_id = "abc123" + result = generate_dotted_order(run_id, start_time) + + # LangSmith expects: YYYYMMDDTHHMMSSffffffZ followed by run_id + assert result == "20250115T103045123456Zabc123" + + def test_dotted_order_with_parent(self): + """Test dotted_order generation with parent order uses dot separator.""" + start_time = datetime(2025, 12, 23, 4, 19, 55, 111000) + run_id = "child-run-id" + parent_order = "20251223T041955000000Zparent-run-id" + result = generate_dotted_order(run_id, start_time, parent_order) + + assert result == "20251223T041955000000Zparent-run-id.20251223T041955111000Zchild-run-id" + + def test_dotted_order_without_parent_has_no_dot(self): + """Test dotted_order generation without parent has no dot separator.""" + start_time = datetime(2025, 12, 23, 4, 19, 55, 111000) + run_id = "test-run-id" + result = generate_dotted_order(run_id, start_time, None) + + assert "." not in result From aea3a6f80c816aa67bae59150a1e49daebbf5e0c Mon Sep 17 00:00:00 2001 From: wangxiaolei <fatelei@gmail.com> Date: Tue, 23 Dec 2025 19:01:12 +0800 Subject: [PATCH 48/71] =?UTF-8?q?fix:=20when=20use=20forward=20proxy=20wit?= =?UTF-8?q?h=20httpx,=20httpx=20will=20overwrite=20the=20use=20=E2=80=A6?= =?UTF-8?q?=20(#30029)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/helper/ssrf_proxy.py | 34 +++- .../unit_tests/core/helper/test_ssrf_proxy.py | 154 +++++++++++++++--- 2 files changed, 165 insertions(+), 23 deletions(-) diff --git a/api/core/helper/ssrf_proxy.py b/api/core/helper/ssrf_proxy.py index 6c98aea1be..f2172e4e2f 100644 --- a/api/core/helper/ssrf_proxy.py +++ b/api/core/helper/ssrf_proxy.py @@ -72,6 +72,22 @@ def _get_ssrf_client(ssl_verify_enabled: bool) -> httpx.Client: ) +def _get_user_provided_host_header(headers: dict | None) -> str | None: + """ + Extract the user-provided Host header from the headers dict. + + This is needed because when using a forward proxy, httpx may override the Host header. + We preserve the user's explicit Host header to support virtual hosting and other use cases. + """ + if not headers: + return None + # Case-insensitive lookup for Host header + for key, value in headers.items(): + if key.lower() == "host": + return value + return None + + def make_request(method, url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs): if "allow_redirects" in kwargs: allow_redirects = kwargs.pop("allow_redirects") @@ -90,10 +106,26 @@ def make_request(method, url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs): verify_option = kwargs.pop("ssl_verify", dify_config.HTTP_REQUEST_NODE_SSL_VERIFY) client = _get_ssrf_client(verify_option) + # Preserve user-provided Host header + # When using a forward proxy, httpx may override the Host header based on the URL. + # We extract and preserve any explicitly set Host header to support virtual hosting. + headers = kwargs.get("headers", {}) + user_provided_host = _get_user_provided_host_header(headers) + retries = 0 while retries <= max_retries: try: - response = client.request(method=method, url=url, **kwargs) + # Build the request manually to preserve the Host header + # httpx may override the Host header when using a proxy, so we use + # the request API to explicitly set headers before sending + request = client.build_request(method=method, url=url, **kwargs) + + # If user explicitly provided a Host header, ensure it's preserved + if user_provided_host is not None: + request.headers["Host"] = user_provided_host + + response = client.send(request) + # Check for SSRF protection by Squid proxy if response.status_code in (401, 403): # Check if this is a Squid SSRF rejection diff --git a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py index 37749f0c66..d5bc3283fe 100644 --- a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py +++ b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py @@ -3,50 +3,160 @@ from unittest.mock import MagicMock, patch import pytest -from core.helper.ssrf_proxy import SSRF_DEFAULT_MAX_RETRIES, STATUS_FORCELIST, make_request +from core.helper.ssrf_proxy import ( + SSRF_DEFAULT_MAX_RETRIES, + STATUS_FORCELIST, + _get_user_provided_host_header, + make_request, +) -@patch("httpx.Client.request") -def test_successful_request(mock_request): +@patch("core.helper.ssrf_proxy._get_ssrf_client") +def test_successful_request(mock_get_client): + mock_client = MagicMock() + mock_request = MagicMock() mock_response = MagicMock() mock_response.status_code = 200 - mock_request.return_value = mock_response + mock_client.send.return_value = mock_response + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client response = make_request("GET", "http://example.com") assert response.status_code == 200 -@patch("httpx.Client.request") -def test_retry_exceed_max_retries(mock_request): +@patch("core.helper.ssrf_proxy._get_ssrf_client") +def test_retry_exceed_max_retries(mock_get_client): + mock_client = MagicMock() + mock_request = MagicMock() mock_response = MagicMock() mock_response.status_code = 500 - - side_effects = [mock_response] * SSRF_DEFAULT_MAX_RETRIES - mock_request.side_effect = side_effects + mock_client.send.return_value = mock_response + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client with pytest.raises(Exception) as e: make_request("GET", "http://example.com", max_retries=SSRF_DEFAULT_MAX_RETRIES - 1) assert str(e.value) == f"Reached maximum retries ({SSRF_DEFAULT_MAX_RETRIES - 1}) for URL http://example.com" -@patch("httpx.Client.request") -def test_retry_logic_success(mock_request): - side_effects = [] +@patch("core.helper.ssrf_proxy._get_ssrf_client") +def test_retry_logic_success(mock_get_client): + mock_client = MagicMock() + mock_request = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + side_effects = [] for _ in range(SSRF_DEFAULT_MAX_RETRIES): status_code = secrets.choice(STATUS_FORCELIST) - mock_response = MagicMock() - mock_response.status_code = status_code - side_effects.append(mock_response) + retry_response = MagicMock() + retry_response.status_code = status_code + side_effects.append(retry_response) - mock_response_200 = MagicMock() - mock_response_200.status_code = 200 - side_effects.append(mock_response_200) - - mock_request.side_effect = side_effects + side_effects.append(mock_response) + mock_client.send.side_effect = side_effects + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client response = make_request("GET", "http://example.com", max_retries=SSRF_DEFAULT_MAX_RETRIES) assert response.status_code == 200 - assert mock_request.call_count == SSRF_DEFAULT_MAX_RETRIES + 1 - assert mock_request.call_args_list[0][1].get("method") == "GET" + assert mock_client.send.call_count == SSRF_DEFAULT_MAX_RETRIES + 1 + assert mock_client.build_request.call_count == SSRF_DEFAULT_MAX_RETRIES + 1 + + +class TestGetUserProvidedHostHeader: + """Tests for _get_user_provided_host_header function.""" + + def test_returns_none_when_headers_is_none(self): + assert _get_user_provided_host_header(None) is None + + def test_returns_none_when_headers_is_empty(self): + assert _get_user_provided_host_header({}) is None + + def test_returns_none_when_host_header_not_present(self): + headers = {"Content-Type": "application/json", "Authorization": "Bearer token"} + assert _get_user_provided_host_header(headers) is None + + def test_returns_host_header_lowercase(self): + headers = {"host": "example.com"} + assert _get_user_provided_host_header(headers) == "example.com" + + def test_returns_host_header_uppercase(self): + headers = {"HOST": "example.com"} + assert _get_user_provided_host_header(headers) == "example.com" + + def test_returns_host_header_mixed_case(self): + headers = {"HoSt": "example.com"} + assert _get_user_provided_host_header(headers) == "example.com" + + def test_returns_host_header_from_multiple_headers(self): + headers = {"Content-Type": "application/json", "Host": "api.example.com", "Authorization": "Bearer token"} + assert _get_user_provided_host_header(headers) == "api.example.com" + + def test_returns_first_host_header_when_duplicates(self): + headers = {"host": "first.com", "Host": "second.com"} + # Should return the first one encountered (iteration order is preserved in dict) + result = _get_user_provided_host_header(headers) + assert result in ("first.com", "second.com") + + +@patch("core.helper.ssrf_proxy._get_ssrf_client") +def test_host_header_preservation_without_user_header(mock_get_client): + """Test that when no Host header is provided, the default behavior is maintained.""" + mock_client = MagicMock() + mock_request = MagicMock() + mock_request.headers = {} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_client.send.return_value = mock_response + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client + + response = make_request("GET", "http://example.com") + + assert response.status_code == 200 + # build_request should be called without headers dict containing Host + mock_client.build_request.assert_called_once() + # Host should not be set if not provided by user + assert "Host" not in mock_request.headers or mock_request.headers.get("Host") is None + + +@patch("core.helper.ssrf_proxy._get_ssrf_client") +def test_host_header_preservation_with_user_header(mock_get_client): + """Test that user-provided Host header is preserved in the request.""" + mock_client = MagicMock() + mock_request = MagicMock() + mock_request.headers = {} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_client.send.return_value = mock_response + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client + + custom_host = "custom.example.com:8080" + response = make_request("GET", "http://example.com", headers={"Host": custom_host}) + + assert response.status_code == 200 + # Verify build_request was called + mock_client.build_request.assert_called_once() + # Verify the Host header was set on the request object + assert mock_request.headers.get("Host") == custom_host + mock_client.send.assert_called_once_with(mock_request) + + +@patch("core.helper.ssrf_proxy._get_ssrf_client") +@pytest.mark.parametrize("host_key", ["host", "HOST"]) +def test_host_header_preservation_case_insensitive(mock_get_client, host_key): + """Test that Host header is preserved regardless of case.""" + mock_client = MagicMock() + mock_request = MagicMock() + mock_request.headers = {} + mock_response = MagicMock() + mock_response.status_code = 200 + mock_client.send.return_value = mock_response + mock_client.build_request.return_value = mock_request + mock_get_client.return_value = mock_client + response = make_request("GET", "http://example.com", headers={host_key: "api.example.com"}) + assert mock_request.headers.get("Host") == "api.example.com" From 870a6427c99b8c69803aee147a7dd6162732014a Mon Sep 17 00:00:00 2001 From: wangxiaolei <fatelei@gmail.com> Date: Tue, 23 Dec 2025 19:01:29 +0800 Subject: [PATCH 49/71] feat: allow user close the tab to sync the draft (#30034) --- web/app/components/workflow/index.tsx | 16 +++++-- .../store/workflow/workflow-draft-slice.ts | 47 +++++++++++++------ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index ab31f36406..1d0c594c23 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -224,23 +224,31 @@ export const Workflow: FC<WorkflowProps> = memo(({ return () => { handleSyncWorkflowDraft(true, true) } - }, []) + }, [handleSyncWorkflowDraft]) const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() const handleSyncWorkflowDraftWhenPageClose = useCallback(() => { if (document.visibilityState === 'hidden') syncWorkflowDraftWhenPageClose() + else if (document.visibilityState === 'visible') setTimeout(() => handleRefreshWorkflowDraft(), 500) - }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft]) + }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft, workflowStore]) + + // Also add beforeunload handler as additional safety net for tab close + const handleBeforeUnload = useCallback(() => { + syncWorkflowDraftWhenPageClose() + }, [syncWorkflowDraftWhenPageClose]) useEffect(() => { document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose) + window.addEventListener('beforeunload', handleBeforeUnload) return () => { document.removeEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose) + window.removeEventListener('beforeunload', handleBeforeUnload) } - }, [handleSyncWorkflowDraftWhenPageClose]) + }, [handleSyncWorkflowDraftWhenPageClose, handleBeforeUnload]) useEventListener('keydown', (e) => { if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey)) @@ -419,7 +427,7 @@ export const Workflow: FC<WorkflowProps> = memo(({ onPaneContextMenu={handlePaneContextMenu} onSelectionContextMenu={handleSelectionContextMenu} connectionLineComponent={CustomConnectionLine} - // TODO: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same? + // NOTE: For LOOP node, how to distinguish between ITERATION and LOOP here? Maybe both are the same? connectionLineContainerStyle={{ zIndex: ITERATION_CHILDREN_Z_INDEX }} defaultViewport={viewport} multiSelectionKeyCode={null} diff --git a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts index 6c08c50e4a..83792e84a6 100644 --- a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts @@ -7,6 +7,12 @@ import type { } from '@/app/components/workflow/types' import { debounce } from 'lodash-es' +type DebouncedFunc = { + (fn: () => void): void + cancel?: () => void + flush?: () => void +} + export type WorkflowDraftSliceShape = { backupDraft?: { nodes: Node[] @@ -16,7 +22,7 @@ export type WorkflowDraftSliceShape = { environmentVariables: EnvironmentVariable[] } setBackupDraft: (backupDraft?: WorkflowDraftSliceShape['backupDraft']) => void - debouncedSyncWorkflowDraft: (fn: () => void) => void + debouncedSyncWorkflowDraft: DebouncedFunc syncWorkflowDraftHash: string setSyncWorkflowDraftHash: (hash: string) => void isSyncingWorkflowDraft: boolean @@ -25,20 +31,31 @@ export type WorkflowDraftSliceShape = { setIsWorkflowDataLoaded: (loaded: boolean) => void nodes: Node[] setNodes: (nodes: Node[]) => void + flushPendingSync: () => void } -export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = set => ({ - backupDraft: undefined, - setBackupDraft: backupDraft => set(() => ({ backupDraft })), - debouncedSyncWorkflowDraft: debounce((syncWorkflowDraft) => { +export const createWorkflowDraftSlice: StateCreator<WorkflowDraftSliceShape> = (set) => { + // Create the debounced function and store it with access to cancel/flush methods + const debouncedFn = debounce((syncWorkflowDraft) => { syncWorkflowDraft() - }, 5000), - syncWorkflowDraftHash: '', - setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })), - isSyncingWorkflowDraft: false, - setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })), - isWorkflowDataLoaded: false, - setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })), - nodes: [], - setNodes: nodes => set(() => ({ nodes })), -}) + }, 5000) + + return { + backupDraft: undefined, + setBackupDraft: backupDraft => set(() => ({ backupDraft })), + debouncedSyncWorkflowDraft: debouncedFn, + syncWorkflowDraftHash: '', + setSyncWorkflowDraftHash: syncWorkflowDraftHash => set(() => ({ syncWorkflowDraftHash })), + isSyncingWorkflowDraft: false, + setIsSyncingWorkflowDraft: isSyncingWorkflowDraft => set(() => ({ isSyncingWorkflowDraft })), + isWorkflowDataLoaded: false, + setIsWorkflowDataLoaded: loaded => set(() => ({ isWorkflowDataLoaded: loaded })), + nodes: [], + setNodes: nodes => set(() => ({ nodes })), + flushPendingSync: () => { + // Flush any pending debounced sync operations + if (debouncedFn.flush) + debouncedFn.flush() + }, + } +} From de021ff3e0b56704878d3a3f062cd34f4eaa0d80 Mon Sep 17 00:00:00 2001 From: Asuka Minato <i@asukaminato.eu.org> Date: Tue, 23 Dec 2025 21:30:30 +0900 Subject: [PATCH 50/71] refactor: split changes for api/controllers/web/remote_files.py (#29853) --- api/controllers/web/remote_files.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/api/controllers/web/remote_files.py b/api/controllers/web/remote_files.py index dac4b3da94..c1f976829f 100644 --- a/api/controllers/web/remote_files.py +++ b/api/controllers/web/remote_files.py @@ -1,7 +1,8 @@ import urllib.parse import httpx -from flask_restx import marshal_with, reqparse +from flask_restx import marshal_with +from pydantic import BaseModel, Field, HttpUrl import services from controllers.common import helpers @@ -10,14 +11,23 @@ from controllers.common.errors import ( RemoteFileUploadError, UnsupportedFileTypeError, ) -from controllers.web import web_ns -from controllers.web.wraps import WebApiResource from core.file import helpers as file_helpers from core.helper import ssrf_proxy from extensions.ext_database import db from fields.file_fields import build_file_with_signed_url_model, build_remote_file_info_model from services.file_service import FileService +from ..common.schema import register_schema_models +from . import web_ns +from .wraps import WebApiResource + + +class RemoteFileUploadPayload(BaseModel): + url: HttpUrl = Field(description="Remote file URL") + + +register_schema_models(web_ns, RemoteFileUploadPayload) + @web_ns.route("/remote-files/<path:url>") class RemoteFileInfoApi(WebApiResource): @@ -97,10 +107,8 @@ class RemoteFileUploadApi(WebApiResource): FileTooLargeError: File exceeds size limit UnsupportedFileTypeError: File type not supported """ - parser = reqparse.RequestParser().add_argument("url", type=str, required=True, help="URL is required") - args = parser.parse_args() - - url = args["url"] + payload = RemoteFileUploadPayload.model_validate(web_ns.payload or {}) + url = str(payload.url) try: resp = ssrf_proxy.head(url=url) From 4d48791f3cda0d9ef207be52613d77f4134de168 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:24:38 +0800 Subject: [PATCH 51/71] refactor: nodejs sdk (#30036) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- sdks/nodejs-client/.gitignore | 58 +- sdks/nodejs-client/LICENSE | 22 + sdks/nodejs-client/README.md | 118 +- sdks/nodejs-client/babel.config.cjs | 12 - sdks/nodejs-client/eslint.config.js | 45 + sdks/nodejs-client/index.d.ts | 107 - sdks/nodejs-client/index.js | 351 --- sdks/nodejs-client/index.test.js | 141 - sdks/nodejs-client/jest.config.cjs | 6 - sdks/nodejs-client/package.json | 62 +- sdks/nodejs-client/pnpm-lock.yaml | 2802 +++++++++++++++++ sdks/nodejs-client/scripts/publish.sh | 261 ++ sdks/nodejs-client/src/client/base.test.js | 175 + sdks/nodejs-client/src/client/base.ts | 284 ++ sdks/nodejs-client/src/client/chat.test.js | 239 ++ sdks/nodejs-client/src/client/chat.ts | 377 +++ .../src/client/completion.test.js | 83 + sdks/nodejs-client/src/client/completion.ts | 111 + .../src/client/knowledge-base.test.js | 249 ++ .../src/client/knowledge-base.ts | 706 +++++ .../src/client/validation.test.js | 91 + sdks/nodejs-client/src/client/validation.ts | 136 + .../nodejs-client/src/client/workflow.test.js | 119 + sdks/nodejs-client/src/client/workflow.ts | 165 + .../src/client/workspace.test.js | 21 + sdks/nodejs-client/src/client/workspace.ts | 16 + .../src/errors/dify-error.test.js | 37 + sdks/nodejs-client/src/errors/dify-error.ts | 75 + sdks/nodejs-client/src/http/client.test.js | 304 ++ sdks/nodejs-client/src/http/client.ts | 368 +++ sdks/nodejs-client/src/http/form-data.test.js | 23 + sdks/nodejs-client/src/http/form-data.ts | 31 + sdks/nodejs-client/src/http/retry.test.js | 38 + sdks/nodejs-client/src/http/retry.ts | 40 + sdks/nodejs-client/src/http/sse.test.js | 76 + sdks/nodejs-client/src/http/sse.ts | 133 + sdks/nodejs-client/src/index.test.js | 227 ++ sdks/nodejs-client/src/index.ts | 103 + sdks/nodejs-client/src/types/annotation.ts | 18 + sdks/nodejs-client/src/types/chat.ts | 17 + sdks/nodejs-client/src/types/common.ts | 71 + sdks/nodejs-client/src/types/completion.ts | 13 + .../nodejs-client/src/types/knowledge-base.ts | 184 ++ sdks/nodejs-client/src/types/workflow.ts | 12 + sdks/nodejs-client/src/types/workspace.ts | 2 + sdks/nodejs-client/tests/test-utils.js | 30 + sdks/nodejs-client/tsconfig.json | 17 + sdks/nodejs-client/tsup.config.ts | 12 + sdks/nodejs-client/vitest.config.ts | 14 + 49 files changed, 7901 insertions(+), 701 deletions(-) create mode 100644 sdks/nodejs-client/LICENSE delete mode 100644 sdks/nodejs-client/babel.config.cjs create mode 100644 sdks/nodejs-client/eslint.config.js delete mode 100644 sdks/nodejs-client/index.d.ts delete mode 100644 sdks/nodejs-client/index.js delete mode 100644 sdks/nodejs-client/index.test.js delete mode 100644 sdks/nodejs-client/jest.config.cjs create mode 100644 sdks/nodejs-client/pnpm-lock.yaml create mode 100755 sdks/nodejs-client/scripts/publish.sh create mode 100644 sdks/nodejs-client/src/client/base.test.js create mode 100644 sdks/nodejs-client/src/client/base.ts create mode 100644 sdks/nodejs-client/src/client/chat.test.js create mode 100644 sdks/nodejs-client/src/client/chat.ts create mode 100644 sdks/nodejs-client/src/client/completion.test.js create mode 100644 sdks/nodejs-client/src/client/completion.ts create mode 100644 sdks/nodejs-client/src/client/knowledge-base.test.js create mode 100644 sdks/nodejs-client/src/client/knowledge-base.ts create mode 100644 sdks/nodejs-client/src/client/validation.test.js create mode 100644 sdks/nodejs-client/src/client/validation.ts create mode 100644 sdks/nodejs-client/src/client/workflow.test.js create mode 100644 sdks/nodejs-client/src/client/workflow.ts create mode 100644 sdks/nodejs-client/src/client/workspace.test.js create mode 100644 sdks/nodejs-client/src/client/workspace.ts create mode 100644 sdks/nodejs-client/src/errors/dify-error.test.js create mode 100644 sdks/nodejs-client/src/errors/dify-error.ts create mode 100644 sdks/nodejs-client/src/http/client.test.js create mode 100644 sdks/nodejs-client/src/http/client.ts create mode 100644 sdks/nodejs-client/src/http/form-data.test.js create mode 100644 sdks/nodejs-client/src/http/form-data.ts create mode 100644 sdks/nodejs-client/src/http/retry.test.js create mode 100644 sdks/nodejs-client/src/http/retry.ts create mode 100644 sdks/nodejs-client/src/http/sse.test.js create mode 100644 sdks/nodejs-client/src/http/sse.ts create mode 100644 sdks/nodejs-client/src/index.test.js create mode 100644 sdks/nodejs-client/src/index.ts create mode 100644 sdks/nodejs-client/src/types/annotation.ts create mode 100644 sdks/nodejs-client/src/types/chat.ts create mode 100644 sdks/nodejs-client/src/types/common.ts create mode 100644 sdks/nodejs-client/src/types/completion.ts create mode 100644 sdks/nodejs-client/src/types/knowledge-base.ts create mode 100644 sdks/nodejs-client/src/types/workflow.ts create mode 100644 sdks/nodejs-client/src/types/workspace.ts create mode 100644 sdks/nodejs-client/tests/test-utils.js create mode 100644 sdks/nodejs-client/tsconfig.json create mode 100644 sdks/nodejs-client/tsup.config.ts create mode 100644 sdks/nodejs-client/vitest.config.ts diff --git a/sdks/nodejs-client/.gitignore b/sdks/nodejs-client/.gitignore index 1d40ff2ece..33cfbd3b18 100644 --- a/sdks/nodejs-client/.gitignore +++ b/sdks/nodejs-client/.gitignore @@ -1,48 +1,40 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# Dependencies +node_modules/ -# dependencies -/node_modules -/.pnp -.pnp.js +# Build output +dist/ -# testing -/coverage +# Testing +coverage/ -# next.js -/.next/ -/out/ +# IDE +.idea/ +.vscode/ +*.swp +*.swo -# production -/build - -# misc +# OS .DS_Store -*.pem +Thumbs.db -# debug +# Debug logs npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* +pnpm-debug.log* -# local env files -.env*.local +# Environment +.env +.env.local +.env.*.local -# vercel -.vercel - -# typescript +# TypeScript *.tsbuildinfo -next-env.d.ts -# npm +# Lock files (use pnpm-lock.yaml in CI if needed) package-lock.json +yarn.lock -# yarn -.pnp.cjs -.pnp.loader.mjs -.yarn/ -.yarnrc.yml - -# pmpm -pnpm-lock.yaml +# Misc +*.pem +*.tgz diff --git a/sdks/nodejs-client/LICENSE b/sdks/nodejs-client/LICENSE new file mode 100644 index 0000000000..ff17417e1f --- /dev/null +++ b/sdks/nodejs-client/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 LangGenius + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/sdks/nodejs-client/README.md b/sdks/nodejs-client/README.md index 3a5688bcbe..f8c2803c08 100644 --- a/sdks/nodejs-client/README.md +++ b/sdks/nodejs-client/README.md @@ -13,54 +13,92 @@ npm install dify-client After installing the SDK, you can use it in your project like this: ```js -import { DifyClient, ChatClient, CompletionClient } from 'dify-client' +import { + DifyClient, + ChatClient, + CompletionClient, + WorkflowClient, + KnowledgeBaseClient, + WorkspaceClient +} from 'dify-client' -const API_KEY = 'your-api-key-here' -const user = `random-user-id` +const API_KEY = 'your-app-api-key' +const DATASET_API_KEY = 'your-dataset-api-key' +const user = 'random-user-id' const query = 'Please tell me a short story in 10 words or less.' -const remote_url_files = [{ - type: 'image', - transfer_method: 'remote_url', - url: 'your_url_address' -}] -// Create a completion client -const completionClient = new CompletionClient(API_KEY) -// Create a completion message -completionClient.createCompletionMessage({'query': query}, user) -// Create a completion message with vision model -completionClient.createCompletionMessage({'query': 'Describe the picture.'}, user, false, remote_url_files) - -// Create a chat client const chatClient = new ChatClient(API_KEY) -// Create a chat message in stream mode -const response = await chatClient.createChatMessage({}, query, user, true, null) -const stream = response.data; -stream.on('data', data => { - console.log(data); -}); -stream.on('end', () => { - console.log('stream done'); -}); -// Create a chat message with vision model -chatClient.createChatMessage({}, 'Describe the picture.', user, false, null, remote_url_files) -// Fetch conversations -chatClient.getConversations(user) -// Fetch conversation messages -chatClient.getConversationMessages(conversationId, user) -// Rename conversation -chatClient.renameConversation(conversationId, name, user) - - +const completionClient = new CompletionClient(API_KEY) +const workflowClient = new WorkflowClient(API_KEY) +const kbClient = new KnowledgeBaseClient(DATASET_API_KEY) +const workspaceClient = new WorkspaceClient(DATASET_API_KEY) const client = new DifyClient(API_KEY) -// Fetch application parameters -client.getApplicationParameters(user) -// Provide feedback for a message -client.messageFeedback(messageId, rating, user) + +// App core +await client.getApplicationParameters(user) +await client.messageFeedback('message-id', 'like', user) + +// Completion (blocking) +await completionClient.createCompletionMessage({ + inputs: { query }, + user, + response_mode: 'blocking' +}) + +// Chat (streaming) +const stream = await chatClient.createChatMessage({ + inputs: {}, + query, + user, + response_mode: 'streaming' +}) +for await (const event of stream) { + console.log(event.event, event.data) +} + +// Chatflow (advanced chat via workflow_id) +await chatClient.createChatMessage({ + inputs: {}, + query, + user, + workflow_id: 'workflow-id', + response_mode: 'blocking' +}) + +// Workflow run (blocking or streaming) +await workflowClient.run({ + inputs: { query }, + user, + response_mode: 'blocking' +}) + +// Knowledge base (dataset token required) +await kbClient.listDatasets({ page: 1, limit: 20 }) +await kbClient.createDataset({ name: 'KB', indexing_technique: 'economy' }) + +// RAG pipeline (may require service API route registration) +const pipelineStream = await kbClient.runPipeline('dataset-id', { + inputs: {}, + datasource_type: 'online_document', + datasource_info_list: [], + start_node_id: 'start-node-id', + is_published: true, + response_mode: 'streaming' +}) +for await (const event of pipelineStream) { + console.log(event.data) +} + +// Workspace models (dataset token required) +await workspaceClient.getModelsByType('text-embedding') ``` -Replace 'your-api-key-here' with your actual Dify API key.Replace 'your-app-id-here' with your actual Dify APP ID. +Notes: + +- App endpoints use an app API token; knowledge base and workspace endpoints use a dataset API token. +- Chat/completion require a stable `user` identifier in the request payload. +- For streaming responses, iterate the returned AsyncIterable. Use `stream.toText()` to collect text. ## License diff --git a/sdks/nodejs-client/babel.config.cjs b/sdks/nodejs-client/babel.config.cjs deleted file mode 100644 index 392abb66d8..0000000000 --- a/sdks/nodejs-client/babel.config.cjs +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - presets: [ - [ - "@babel/preset-env", - { - targets: { - node: "current", - }, - }, - ], - ], -}; diff --git a/sdks/nodejs-client/eslint.config.js b/sdks/nodejs-client/eslint.config.js new file mode 100644 index 0000000000..9e659f5d28 --- /dev/null +++ b/sdks/nodejs-client/eslint.config.js @@ -0,0 +1,45 @@ +import js from "@eslint/js"; +import tsParser from "@typescript-eslint/parser"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +const tsconfigRootDir = path.dirname(fileURLToPath(import.meta.url)); +const typeCheckedRules = + tsPlugin.configs["recommended-type-checked"]?.rules ?? + tsPlugin.configs.recommendedTypeChecked?.rules ?? + {}; + +export default [ + { + ignores: ["dist", "node_modules", "scripts", "tests", "**/*.test.*", "**/*.spec.*"], + }, + js.configs.recommended, + { + files: ["src/**/*.ts"], + languageOptions: { + parser: tsParser, + ecmaVersion: "latest", + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir, + sourceType: "module", + }, + }, + plugins: { + "@typescript-eslint": tsPlugin, + }, + rules: { + ...tsPlugin.configs.recommended.rules, + ...typeCheckedRules, + "no-undef": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unsafe-call": "error", + "@typescript-eslint/no-unsafe-return": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { prefer: "type-imports", fixStyle: "separate-type-imports" }, + ], + }, + }, +]; diff --git a/sdks/nodejs-client/index.d.ts b/sdks/nodejs-client/index.d.ts deleted file mode 100644 index 3ea4b9d153..0000000000 --- a/sdks/nodejs-client/index.d.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Types.d.ts -export const BASE_URL: string; - -export type RequestMethods = 'GET' | 'POST' | 'PATCH' | 'DELETE'; - -interface Params { - [key: string]: any; -} - -interface HeaderParams { - [key: string]: string; -} - -interface User { -} - -interface DifyFileBase { - type: "image" -} - -export interface DifyRemoteFile extends DifyFileBase { - transfer_method: "remote_url" - url: string -} - -export interface DifyLocalFile extends DifyFileBase { - transfer_method: "local_file" - upload_file_id: string -} - -export type DifyFile = DifyRemoteFile | DifyLocalFile; - -export declare class DifyClient { - constructor(apiKey: string, baseUrl?: string); - - updateApiKey(apiKey: string): void; - - sendRequest( - method: RequestMethods, - endpoint: string, - data?: any, - params?: Params, - stream?: boolean, - headerParams?: HeaderParams - ): Promise<any>; - - messageFeedback(message_id: string, rating: number, user: User): Promise<any>; - - getApplicationParameters(user: User): Promise<any>; - - fileUpload(data: FormData): Promise<any>; - - textToAudio(text: string ,user: string, streaming?: boolean): Promise<any>; - - getMeta(user: User): Promise<any>; -} - -export declare class CompletionClient extends DifyClient { - createCompletionMessage( - inputs: any, - user: User, - stream?: boolean, - files?: DifyFile[] | null - ): Promise<any>; -} - -export declare class ChatClient extends DifyClient { - createChatMessage( - inputs: any, - query: string, - user: User, - stream?: boolean, - conversation_id?: string | null, - files?: DifyFile[] | null - ): Promise<any>; - - getSuggested(message_id: string, user: User): Promise<any>; - - stopMessage(task_id: string, user: User) : Promise<any>; - - - getConversations( - user: User, - first_id?: string | null, - limit?: number | null, - pinned?: boolean | null - ): Promise<any>; - - getConversationMessages( - user: User, - conversation_id?: string, - first_id?: string | null, - limit?: number | null - ): Promise<any>; - - renameConversation(conversation_id: string, name: string, user: User,auto_generate:boolean): Promise<any>; - - deleteConversation(conversation_id: string, user: User): Promise<any>; - - audioToText(data: FormData): Promise<any>; -} - -export declare class WorkflowClient extends DifyClient { - run(inputs: any, user: User, stream?: boolean,): Promise<any>; - - stop(task_id: string, user: User): Promise<any>; -} diff --git a/sdks/nodejs-client/index.js b/sdks/nodejs-client/index.js deleted file mode 100644 index 9743ae358c..0000000000 --- a/sdks/nodejs-client/index.js +++ /dev/null @@ -1,351 +0,0 @@ -import axios from "axios"; -export const BASE_URL = "https://api.dify.ai/v1"; - -export const routes = { - // app's - feedback: { - method: "POST", - url: (message_id) => `/messages/${message_id}/feedbacks`, - }, - application: { - method: "GET", - url: () => `/parameters`, - }, - fileUpload: { - method: "POST", - url: () => `/files/upload`, - }, - textToAudio: { - method: "POST", - url: () => `/text-to-audio`, - }, - getMeta: { - method: "GET", - url: () => `/meta`, - }, - - // completion's - createCompletionMessage: { - method: "POST", - url: () => `/completion-messages`, - }, - - // chat's - createChatMessage: { - method: "POST", - url: () => `/chat-messages`, - }, - getSuggested:{ - method: "GET", - url: (message_id) => `/messages/${message_id}/suggested`, - }, - stopChatMessage: { - method: "POST", - url: (task_id) => `/chat-messages/${task_id}/stop`, - }, - getConversations: { - method: "GET", - url: () => `/conversations`, - }, - getConversationMessages: { - method: "GET", - url: () => `/messages`, - }, - renameConversation: { - method: "POST", - url: (conversation_id) => `/conversations/${conversation_id}/name`, - }, - deleteConversation: { - method: "DELETE", - url: (conversation_id) => `/conversations/${conversation_id}`, - }, - audioToText: { - method: "POST", - url: () => `/audio-to-text`, - }, - - // workflow‘s - runWorkflow: { - method: "POST", - url: () => `/workflows/run`, - }, - stopWorkflow: { - method: "POST", - url: (task_id) => `/workflows/tasks/${task_id}/stop`, - } - -}; - -export class DifyClient { - constructor(apiKey, baseUrl = BASE_URL) { - this.apiKey = apiKey; - this.baseUrl = baseUrl; - } - - updateApiKey(apiKey) { - this.apiKey = apiKey; - } - - async sendRequest( - method, - endpoint, - data = null, - params = null, - stream = false, - headerParams = {} - ) { - const isFormData = - (typeof FormData !== "undefined" && data instanceof FormData) || - (data && data.constructor && data.constructor.name === "FormData"); - const headers = { - Authorization: `Bearer ${this.apiKey}`, - ...(isFormData ? {} : { "Content-Type": "application/json" }), - ...headerParams, - }; - - const url = `${this.baseUrl}${endpoint}`; - let response; - if (stream) { - response = await axios({ - method, - url, - data, - params, - headers, - responseType: "stream", - }); - } else { - response = await axios({ - method, - url, - ...(method !== "GET" && { data }), - params, - headers, - responseType: "json", - }); - } - - return response; - } - - messageFeedback(message_id, rating, user) { - const data = { - rating, - user, - }; - return this.sendRequest( - routes.feedback.method, - routes.feedback.url(message_id), - data - ); - } - - getApplicationParameters(user) { - const params = { user }; - return this.sendRequest( - routes.application.method, - routes.application.url(), - null, - params - ); - } - - fileUpload(data) { - return this.sendRequest( - routes.fileUpload.method, - routes.fileUpload.url(), - data - ); - } - - textToAudio(text, user, streaming = false) { - const data = { - text, - user, - streaming - }; - return this.sendRequest( - routes.textToAudio.method, - routes.textToAudio.url(), - data, - null, - streaming - ); - } - - getMeta(user) { - const params = { user }; - return this.sendRequest( - routes.getMeta.method, - routes.getMeta.url(), - null, - params - ); - } -} - -export class CompletionClient extends DifyClient { - createCompletionMessage(inputs, user, stream = false, files = null) { - const data = { - inputs, - user, - response_mode: stream ? "streaming" : "blocking", - files, - }; - return this.sendRequest( - routes.createCompletionMessage.method, - routes.createCompletionMessage.url(), - data, - null, - stream - ); - } - - runWorkflow(inputs, user, stream = false, files = null) { - const data = { - inputs, - user, - response_mode: stream ? "streaming" : "blocking", - }; - return this.sendRequest( - routes.runWorkflow.method, - routes.runWorkflow.url(), - data, - null, - stream - ); - } -} - -export class ChatClient extends DifyClient { - createChatMessage( - inputs, - query, - user, - stream = false, - conversation_id = null, - files = null - ) { - const data = { - inputs, - query, - user, - response_mode: stream ? "streaming" : "blocking", - files, - }; - if (conversation_id) data.conversation_id = conversation_id; - - return this.sendRequest( - routes.createChatMessage.method, - routes.createChatMessage.url(), - data, - null, - stream - ); - } - - getSuggested(message_id, user) { - const data = { user }; - return this.sendRequest( - routes.getSuggested.method, - routes.getSuggested.url(message_id), - data - ); - } - - stopMessage(task_id, user) { - const data = { user }; - return this.sendRequest( - routes.stopChatMessage.method, - routes.stopChatMessage.url(task_id), - data - ); - } - - getConversations(user, first_id = null, limit = null, pinned = null) { - const params = { user, first_id: first_id, limit, pinned }; - return this.sendRequest( - routes.getConversations.method, - routes.getConversations.url(), - null, - params - ); - } - - getConversationMessages( - user, - conversation_id = "", - first_id = null, - limit = null - ) { - const params = { user }; - - if (conversation_id) params.conversation_id = conversation_id; - - if (first_id) params.first_id = first_id; - - if (limit) params.limit = limit; - - return this.sendRequest( - routes.getConversationMessages.method, - routes.getConversationMessages.url(), - null, - params - ); - } - - renameConversation(conversation_id, name, user, auto_generate) { - const data = { name, user, auto_generate }; - return this.sendRequest( - routes.renameConversation.method, - routes.renameConversation.url(conversation_id), - data - ); - } - - deleteConversation(conversation_id, user) { - const data = { user }; - return this.sendRequest( - routes.deleteConversation.method, - routes.deleteConversation.url(conversation_id), - data - ); - } - - - audioToText(data) { - return this.sendRequest( - routes.audioToText.method, - routes.audioToText.url(), - data - ); - } - -} - -export class WorkflowClient extends DifyClient { - run(inputs,user,stream) { - const data = { - inputs, - response_mode: stream ? "streaming" : "blocking", - user - }; - - return this.sendRequest( - routes.runWorkflow.method, - routes.runWorkflow.url(), - data, - null, - stream - ); - } - - stop(task_id, user) { - const data = { user }; - return this.sendRequest( - routes.stopWorkflow.method, - routes.stopWorkflow.url(task_id), - data - ); - } -} diff --git a/sdks/nodejs-client/index.test.js b/sdks/nodejs-client/index.test.js deleted file mode 100644 index e3a1715238..0000000000 --- a/sdks/nodejs-client/index.test.js +++ /dev/null @@ -1,141 +0,0 @@ -import { DifyClient, WorkflowClient, BASE_URL, routes } from "."; - -import axios from 'axios' - -jest.mock('axios') - -afterEach(() => { - jest.resetAllMocks() -}) - -describe('Client', () => { - let difyClient - beforeEach(() => { - difyClient = new DifyClient('test') - }) - - test('should create a client', () => { - expect(difyClient).toBeDefined(); - }) - // test updateApiKey - test('should update the api key', () => { - difyClient.updateApiKey('test2'); - expect(difyClient.apiKey).toBe('test2'); - }) -}); - -describe('Send Requests', () => { - let difyClient - - beforeEach(() => { - difyClient = new DifyClient('test') - }) - - it('should make a successful request to the application parameter', async () => { - const method = 'GET' - const endpoint = routes.application.url() - const expectedResponse = { data: 'response' } - axios.mockResolvedValue(expectedResponse) - - await difyClient.sendRequest(method, endpoint) - - expect(axios).toHaveBeenCalledWith({ - method, - url: `${BASE_URL}${endpoint}`, - params: null, - headers: { - Authorization: `Bearer ${difyClient.apiKey}`, - 'Content-Type': 'application/json', - }, - responseType: 'json', - }) - - }) - - it('should handle errors from the API', async () => { - const method = 'GET' - const endpoint = '/test-endpoint' - const errorMessage = 'Request failed with status code 404' - axios.mockRejectedValue(new Error(errorMessage)) - - await expect(difyClient.sendRequest(method, endpoint)).rejects.toThrow( - errorMessage - ) - }) - - it('uses the getMeta route configuration', async () => { - axios.mockResolvedValue({ data: 'ok' }) - await difyClient.getMeta('end-user') - - expect(axios).toHaveBeenCalledWith({ - method: routes.getMeta.method, - url: `${BASE_URL}${routes.getMeta.url()}`, - params: { user: 'end-user' }, - headers: { - Authorization: `Bearer ${difyClient.apiKey}`, - 'Content-Type': 'application/json', - }, - responseType: 'json', - }) - }) -}) - -describe('File uploads', () => { - let difyClient - const OriginalFormData = global.FormData - - beforeAll(() => { - global.FormData = class FormDataMock {} - }) - - afterAll(() => { - global.FormData = OriginalFormData - }) - - beforeEach(() => { - difyClient = new DifyClient('test') - }) - - it('does not override multipart boundary headers for FormData', async () => { - const form = new FormData() - axios.mockResolvedValue({ data: 'ok' }) - - await difyClient.fileUpload(form) - - expect(axios).toHaveBeenCalledWith({ - method: routes.fileUpload.method, - url: `${BASE_URL}${routes.fileUpload.url()}`, - data: form, - params: null, - headers: { - Authorization: `Bearer ${difyClient.apiKey}`, - }, - responseType: 'json', - }) - }) -}) - -describe('Workflow client', () => { - let workflowClient - - beforeEach(() => { - workflowClient = new WorkflowClient('test') - }) - - it('uses tasks stop path for workflow stop', async () => { - axios.mockResolvedValue({ data: 'stopped' }) - await workflowClient.stop('task-1', 'end-user') - - expect(axios).toHaveBeenCalledWith({ - method: routes.stopWorkflow.method, - url: `${BASE_URL}${routes.stopWorkflow.url('task-1')}`, - data: { user: 'end-user' }, - params: null, - headers: { - Authorization: `Bearer ${workflowClient.apiKey}`, - 'Content-Type': 'application/json', - }, - responseType: 'json', - }) - }) -}) diff --git a/sdks/nodejs-client/jest.config.cjs b/sdks/nodejs-client/jest.config.cjs deleted file mode 100644 index ea0fb34ad1..0000000000 --- a/sdks/nodejs-client/jest.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - testEnvironment: "node", - transform: { - "^.+\\.[tj]sx?$": "babel-jest", - }, -}; diff --git a/sdks/nodejs-client/package.json b/sdks/nodejs-client/package.json index c6bb0a9c1f..554cb909ef 100644 --- a/sdks/nodejs-client/package.json +++ b/sdks/nodejs-client/package.json @@ -1,30 +1,70 @@ { "name": "dify-client", - "version": "2.3.2", + "version": "3.0.0", "description": "This is the Node.js SDK for the Dify.AI API, which allows you to easily integrate Dify.AI into your Node.js applications.", - "main": "index.js", "type": "module", - "types":"index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], "keywords": [ "Dify", "Dify.AI", - "LLM" + "LLM", + "AI", + "SDK", + "API" ], - "author": "Joel", + "author": "LangGenius", "contributors": [ - "<crazywoola> <<427733928@qq.com>> (https://github.com/crazywoola)" + "Joel <iamjoel007@gmail.com> (https://github.com/iamjoel)", + "lyzno1 <yuanyouhuilyz@gmail.com> (https://github.com/lyzno1)", + "crazywoola <427733928@qq.com> (https://github.com/crazywoola)" ], + "repository": { + "type": "git", + "url": "https://github.com/langgenius/dify.git", + "directory": "sdks/nodejs-client" + }, + "bugs": { + "url": "https://github.com/langgenius/dify/issues" + }, + "homepage": "https://dify.ai", "license": "MIT", "scripts": { - "test": "jest" + "build": "tsup", + "lint": "eslint", + "lint:fix": "eslint --fix", + "type-check": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "publish:check": "./scripts/publish.sh --dry-run", + "publish:npm": "./scripts/publish.sh" }, "dependencies": { "axios": "^1.3.5" }, "devDependencies": { - "@babel/core": "^7.21.8", - "@babel/preset-env": "^7.21.5", - "babel-jest": "^29.5.0", - "jest": "^29.5.0" + "@eslint/js": "^9.2.0", + "@types/node": "^20.11.30", + "@typescript-eslint/eslint-plugin": "^8.50.1", + "@typescript-eslint/parser": "^8.50.1", + "@vitest/coverage-v8": "1.6.1", + "eslint": "^9.2.0", + "tsup": "^8.5.1", + "typescript": "^5.4.5", + "vitest": "^1.5.0" } } diff --git a/sdks/nodejs-client/pnpm-lock.yaml b/sdks/nodejs-client/pnpm-lock.yaml new file mode 100644 index 0000000000..3e4011c580 --- /dev/null +++ b/sdks/nodejs-client/pnpm-lock.yaml @@ -0,0 +1,2802 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + axios: + specifier: ^1.3.5 + version: 1.13.2 + devDependencies: + '@eslint/js': + specifier: ^9.2.0 + version: 9.39.2 + '@types/node': + specifier: ^20.11.30 + version: 20.19.27 + '@typescript-eslint/eslint-plugin': + specifier: ^8.50.1 + version: 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.50.1 + version: 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@vitest/coverage-v8': + specifier: 1.6.1 + version: 1.6.1(vitest@1.6.1(@types/node@20.19.27)) + eslint: + specifier: ^9.2.0 + version: 9.39.2 + tsup: + specifier: ^8.5.1 + version: 8.5.1(postcss@8.5.6)(typescript@5.9.3) + typescript: + specifier: ^5.4.5 + version: 5.9.3 + vitest: + specifier: ^1.5.0 + version: 1.6.1(@types/node@20.19.27) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@20.19.27': + resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + + '@typescript-eslint/eslint-plugin@8.50.1': + resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.50.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.50.1': + resolution: {integrity: sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.50.1': + resolution: {integrity: sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.50.1': + resolution: {integrity: sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.50.1': + resolution: {integrity: sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.50.1': + resolution: {integrity: sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.50.1': + resolution: {integrity: sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.50.1': + resolution: {integrity: sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.50.1': + resolution: {integrity: sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.50.1': + resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/coverage-v8@1.6.1': + resolution: {integrity: sha512-6YeRZwuO4oTGKxD3bijok756oktHSIm3eczVVzNe3scqzuhLwltIF3S9ZL/vwOVIpURmU6SnZhziXXAfw8/Qlw==} + peerDependencies: + vitest: 1.6.1 + + '@vitest/expect@1.6.1': + resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} + + '@vitest/runner@1.6.1': + resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==} + + '@vitest/snapshot@1.6.1': + resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==} + + '@vitest/spy@1.6.1': + resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==} + + '@vitest/utils@1.6.1': + resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@2.1.1: + resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + engines: {node: '>=14.0.0'} + + tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@1.6.1: + resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.6.1 + '@vitest/ui': 1.6.1 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@istanbuljs/schema@0.1.3': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + + '@sinclair/typebox@0.27.8': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@20.19.27': + dependencies: + undici-types: 6.21.0 + + '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/type-utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.1 + eslint: 9.39.2 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.50.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.1 + debug: 4.4.3 + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.50.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) + '@typescript-eslint/types': 8.50.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.50.1': + dependencies: + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/visitor-keys': 8.50.1 + + '@typescript-eslint/tsconfig-utils@8.50.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.50.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.50.1': {} + + '@typescript-eslint/typescript-estree@8.50.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.50.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.50.1(typescript@5.9.3) + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/visitor-keys': 8.50.1 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.50.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.50.1': + dependencies: + '@typescript-eslint/types': 8.50.1 + eslint-visitor-keys: 4.2.1 + + '@vitest/coverage-v8@1.6.1(vitest@1.6.1(@types/node@20.19.27))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + picocolors: 1.1.1 + std-env: 3.10.0 + strip-literal: 2.1.1 + test-exclude: 6.0.0 + vitest: 1.6.1(@types/node@20.19.27) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@1.6.1': + dependencies: + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + chai: 4.5.0 + + '@vitest/runner@1.6.1': + dependencies: + '@vitest/utils': 1.6.1 + p-limit: 5.0.0 + pathe: 1.1.2 + + '@vitest/snapshot@1.6.1': + dependencies: + magic-string: 0.30.21 + pathe: 1.1.2 + pretty-format: 29.7.0 + + '@vitest/spy@1.6.1': + dependencies: + tinyspy: 2.2.1 + + '@vitest/utils@1.6.1': + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + argparse@2.0.1: {} + + assertion-error@1.1.0: {} + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + bundle-require@5.1.0(esbuild@0.27.2): + dependencies: + esbuild: 0.27.2 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + diff-sequences@29.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.0 + rollup: 4.54.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-escaper@2.0.2: {} + + human-signals@5.0.0: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-stream@3.0.0: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + joycon@3.1.1: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + local-pkg@0.5.1: + dependencies: + mlly: 1.8.0 + pkg-types: 1.3.1 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@4.0.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-assign@4.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@5.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + pathval@1.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.6 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + react-is@18.3.1: {} + + readdirp@4.1.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-literal@2.1.1: + dependencies: + js-tokens: 9.0.1 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@0.8.4: {} + + tinyspy@2.2.1: {} + + tree-kill@1.2.2: {} + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsup@8.5.1(postcss@8.5.6)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.2 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.6) + resolve-from: 5.0.0 + rollup: 4.54.0 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.1.0: {} + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + undici-types@6.21.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@1.6.1(@types/node@20.19.27): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@20.19.27) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@20.19.27): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.54.0 + optionalDependencies: + '@types/node': 20.19.27 + fsevents: 2.3.3 + + vitest@1.6.1(@types/node@20.19.27): + dependencies: + '@vitest/expect': 1.6.1 + '@vitest/runner': 1.6.1 + '@vitest/snapshot': 1.6.1 + '@vitest/spy': 1.6.1 + '@vitest/utils': 1.6.1 + acorn-walk: 8.3.4 + chai: 4.5.0 + debug: 4.4.3 + execa: 8.0.1 + local-pkg: 0.5.1 + magic-string: 0.30.21 + pathe: 1.1.2 + picocolors: 1.1.1 + std-env: 3.10.0 + strip-literal: 2.1.1 + tinybench: 2.9.0 + tinypool: 0.8.4 + vite: 5.4.21(@types/node@20.19.27) + vite-node: 1.6.1(@types/node@20.19.27) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.27 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} diff --git a/sdks/nodejs-client/scripts/publish.sh b/sdks/nodejs-client/scripts/publish.sh new file mode 100755 index 0000000000..043cac046d --- /dev/null +++ b/sdks/nodejs-client/scripts/publish.sh @@ -0,0 +1,261 @@ +#!/usr/bin/env bash +# +# Dify Node.js SDK Publish Script +# ================================ +# A beautiful and reliable script to publish the SDK to npm +# +# Usage: +# ./scripts/publish.sh # Normal publish +# ./scripts/publish.sh --dry-run # Test without publishing +# ./scripts/publish.sh --skip-tests # Skip tests (not recommended) +# + +set -euo pipefail + +# ============================================================================ +# Colors and Formatting +# ============================================================================ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +CYAN='\033[0;36m' +BOLD='\033[1m' +DIM='\033[2m' +NC='\033[0m' # No Color + +# ============================================================================ +# Helper Functions +# ============================================================================ +print_banner() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════╗" + echo "║ ║" + echo "║ 🚀 Dify Node.js SDK Publish Script 🚀 ║" + echo "║ ║" + echo "╚═══════════════════════════════════════════════════════════════╝" + echo -e "${NC}" +} + +info() { + echo -e "${BLUE}ℹ ${NC}$1" +} + +success() { + echo -e "${GREEN}✔ ${NC}$1" +} + +warning() { + echo -e "${YELLOW}⚠ ${NC}$1" +} + +error() { + echo -e "${RED}✖ ${NC}$1" +} + +step() { + echo -e "\n${MAGENTA}▶ ${BOLD}$1${NC}" +} + +divider() { + echo -e "${DIM}─────────────────────────────────────────────────────────────────${NC}" +} + +# ============================================================================ +# Configuration +# ============================================================================ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +DRY_RUN=false +SKIP_TESTS=false + +# Parse arguments +for arg in "$@"; do + case $arg in + --dry-run) + DRY_RUN=true + ;; + --skip-tests) + SKIP_TESTS=true + ;; + --help|-h) + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " --dry-run Run without actually publishing" + echo " --skip-tests Skip running tests (not recommended)" + echo " --help, -h Show this help message" + exit 0 + ;; + esac +done + +# ============================================================================ +# Main Script +# ============================================================================ +main() { + print_banner + cd "$PROJECT_DIR" + + # Show mode + if [[ "$DRY_RUN" == true ]]; then + warning "Running in DRY-RUN mode - no actual publish will occur" + divider + fi + + # ======================================================================== + # Step 1: Environment Check + # ======================================================================== + step "Step 1/6: Checking environment..." + + # Check Node.js + if ! command -v node &> /dev/null; then + error "Node.js is not installed" + exit 1 + fi + NODE_VERSION=$(node -v) + success "Node.js: $NODE_VERSION" + + # Check npm + if ! command -v npm &> /dev/null; then + error "npm is not installed" + exit 1 + fi + NPM_VERSION=$(npm -v) + success "npm: v$NPM_VERSION" + + # Check pnpm (optional, for local dev) + if command -v pnpm &> /dev/null; then + PNPM_VERSION=$(pnpm -v) + success "pnpm: v$PNPM_VERSION" + else + info "pnpm not found (optional)" + fi + + # Check npm login status + if ! npm whoami &> /dev/null; then + error "Not logged in to npm. Run 'npm login' first." + exit 1 + fi + NPM_USER=$(npm whoami) + success "Logged in as: ${BOLD}$NPM_USER${NC}" + + # ======================================================================== + # Step 2: Read Package Info + # ======================================================================== + step "Step 2/6: Reading package info..." + + PACKAGE_NAME=$(node -p "require('./package.json').name") + PACKAGE_VERSION=$(node -p "require('./package.json').version") + + success "Package: ${BOLD}$PACKAGE_NAME${NC}" + success "Version: ${BOLD}$PACKAGE_VERSION${NC}" + + # Check if version already exists on npm + if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version &> /dev/null; then + error "Version $PACKAGE_VERSION already exists on npm!" + echo "" + info "Current published versions:" + npm view "$PACKAGE_NAME" versions --json 2>/dev/null | tail -5 + echo "" + warning "Please update the version in package.json before publishing." + exit 1 + fi + success "Version $PACKAGE_VERSION is available" + + # ======================================================================== + # Step 3: Install Dependencies + # ======================================================================== + step "Step 3/6: Installing dependencies..." + + if command -v pnpm &> /dev/null; then + pnpm install --frozen-lockfile 2>/dev/null || pnpm install + else + npm ci 2>/dev/null || npm install + fi + success "Dependencies installed" + + # ======================================================================== + # Step 4: Run Tests + # ======================================================================== + step "Step 4/6: Running tests..." + + if [[ "$SKIP_TESTS" == true ]]; then + warning "Skipping tests (--skip-tests flag)" + else + if command -v pnpm &> /dev/null; then + pnpm test + else + npm test + fi + success "All tests passed" + fi + + # ======================================================================== + # Step 5: Build + # ======================================================================== + step "Step 5/6: Building package..." + + # Clean previous build + rm -rf dist + + if command -v pnpm &> /dev/null; then + pnpm run build + else + npm run build + fi + success "Build completed" + + # Verify build output + if [[ ! -f "dist/index.js" ]]; then + error "Build failed - dist/index.js not found" + exit 1 + fi + if [[ ! -f "dist/index.d.ts" ]]; then + error "Build failed - dist/index.d.ts not found" + exit 1 + fi + success "Build output verified" + + # ======================================================================== + # Step 6: Publish + # ======================================================================== + step "Step 6/6: Publishing to npm..." + + divider + echo -e "${CYAN}Package contents:${NC}" + npm pack --dry-run 2>&1 | head -30 + divider + + if [[ "$DRY_RUN" == true ]]; then + warning "DRY-RUN: Skipping actual publish" + echo "" + info "To publish for real, run without --dry-run flag" + else + echo "" + echo -e "${YELLOW}About to publish ${BOLD}$PACKAGE_NAME@$PACKAGE_VERSION${NC}${YELLOW} to npm${NC}" + echo -e "${DIM}Press Enter to continue, or Ctrl+C to cancel...${NC}" + read -r + + npm publish --access public + + echo "" + success "🎉 Successfully published ${BOLD}$PACKAGE_NAME@$PACKAGE_VERSION${NC} to npm!" + echo "" + echo -e "${GREEN}Install with:${NC}" + echo -e " ${CYAN}npm install $PACKAGE_NAME${NC}" + echo -e " ${CYAN}pnpm add $PACKAGE_NAME${NC}" + echo -e " ${CYAN}yarn add $PACKAGE_NAME${NC}" + echo "" + echo -e "${GREEN}View on npm:${NC}" + echo -e " ${CYAN}https://www.npmjs.com/package/$PACKAGE_NAME${NC}" + fi + + divider + echo -e "${GREEN}${BOLD}✨ All done!${NC}" +} + +# Run main function +main "$@" diff --git a/sdks/nodejs-client/src/client/base.test.js b/sdks/nodejs-client/src/client/base.test.js new file mode 100644 index 0000000000..5e1b21d0f1 --- /dev/null +++ b/sdks/nodejs-client/src/client/base.test.js @@ -0,0 +1,175 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { DifyClient } from "./base"; +import { ValidationError } from "../errors/dify-error"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("DifyClient base", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("getRoot calls root endpoint", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.getRoot(); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/", + }); + }); + + it("getApplicationParameters includes optional user", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.getApplicationParameters(); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/parameters", + query: undefined, + }); + + await dify.getApplicationParameters("user-1"); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/parameters", + query: { user: "user-1" }, + }); + }); + + it("getMeta includes optional user", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.getMeta("user-1"); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/meta", + query: { user: "user-1" }, + }); + }); + + it("getInfo and getSite support optional user", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.getInfo(); + await dify.getSite("user"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/info", + query: undefined, + }); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/site", + query: { user: "user" }, + }); + }); + + it("messageFeedback builds payload from request object", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.messageFeedback({ + messageId: "msg", + user: "user", + rating: "like", + content: "good", + }); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/messages/msg/feedbacks", + data: { user: "user", rating: "like", content: "good" }, + }); + }); + + it("fileUpload appends user to form data", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + const form = { append: vi.fn(), getHeaders: () => ({}) }; + + await dify.fileUpload(form, "user"); + + expect(form.append).toHaveBeenCalledWith("user", "user"); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/files/upload", + data: form, + }); + }); + + it("filePreview uses arraybuffer response", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.filePreview("file", "user", true); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/files/file/preview", + query: { user: "user", as_attachment: "true" }, + responseType: "arraybuffer", + }); + }); + + it("audioToText appends user and sends form", async () => { + const { client, request } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + const form = { append: vi.fn(), getHeaders: () => ({}) }; + + await dify.audioToText(form, "user"); + + expect(form.append).toHaveBeenCalledWith("user", "user"); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/audio-to-text", + data: form, + }); + }); + + it("textToAudio supports streaming and message id", async () => { + const { client, request, requestBinaryStream } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + await dify.textToAudio({ + user: "user", + message_id: "msg", + streaming: true, + }); + + expect(requestBinaryStream).toHaveBeenCalledWith({ + method: "POST", + path: "/text-to-audio", + data: { + user: "user", + message_id: "msg", + streaming: true, + }, + }); + + await dify.textToAudio("hello", "user", false, "voice"); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/text-to-audio", + data: { + text: "hello", + user: "user", + streaming: false, + voice: "voice", + }, + responseType: "arraybuffer", + }); + }); + + it("textToAudio requires text or message id", async () => { + const { client } = createHttpClientWithSpies(); + const dify = new DifyClient(client); + + expect(() => dify.textToAudio({ user: "user" })).toThrow(ValidationError); + }); +}); diff --git a/sdks/nodejs-client/src/client/base.ts b/sdks/nodejs-client/src/client/base.ts new file mode 100644 index 0000000000..0fa535a488 --- /dev/null +++ b/sdks/nodejs-client/src/client/base.ts @@ -0,0 +1,284 @@ +import type { + BinaryStream, + DifyClientConfig, + DifyResponse, + MessageFeedbackRequest, + QueryParams, + RequestMethod, + TextToAudioRequest, +} from "../types/common"; +import { HttpClient } from "../http/client"; +import { ensureNonEmptyString, ensureRating } from "./validation"; +import { FileUploadError, ValidationError } from "../errors/dify-error"; +import { isFormData } from "../http/form-data"; + +const toConfig = ( + init: string | DifyClientConfig, + baseUrl?: string +): DifyClientConfig => { + if (typeof init === "string") { + return { + apiKey: init, + baseUrl, + }; + } + return init; +}; + +const appendUserToFormData = (form: unknown, user: string): void => { + if (!isFormData(form)) { + throw new FileUploadError("FormData is required for file uploads"); + } + if (typeof form.append === "function") { + form.append("user", user); + } +}; + +export class DifyClient { + protected http: HttpClient; + + constructor(config: string | DifyClientConfig | HttpClient, baseUrl?: string) { + if (config instanceof HttpClient) { + this.http = config; + } else { + this.http = new HttpClient(toConfig(config, baseUrl)); + } + } + + updateApiKey(apiKey: string): void { + ensureNonEmptyString(apiKey, "apiKey"); + this.http.updateApiKey(apiKey); + } + + getHttpClient(): HttpClient { + return this.http; + } + + sendRequest( + method: RequestMethod, + endpoint: string, + data: unknown = null, + params: QueryParams | null = null, + stream = false, + headerParams: Record<string, string> = {} + ): ReturnType<HttpClient["requestRaw"]> { + return this.http.requestRaw({ + method, + path: endpoint, + data, + query: params ?? undefined, + headers: headerParams, + responseType: stream ? "stream" : "json", + }); + } + + getRoot(): Promise<DifyResponse<unknown>> { + return this.http.request({ + method: "GET", + path: "/", + }); + } + + getApplicationParameters(user?: string): Promise<DifyResponse<unknown>> { + if (user) { + ensureNonEmptyString(user, "user"); + } + return this.http.request({ + method: "GET", + path: "/parameters", + query: user ? { user } : undefined, + }); + } + + async getParameters(user?: string): Promise<DifyResponse<unknown>> { + return this.getApplicationParameters(user); + } + + getMeta(user?: string): Promise<DifyResponse<unknown>> { + if (user) { + ensureNonEmptyString(user, "user"); + } + return this.http.request({ + method: "GET", + path: "/meta", + query: user ? { user } : undefined, + }); + } + + messageFeedback( + request: MessageFeedbackRequest + ): Promise<DifyResponse<Record<string, unknown>>>; + messageFeedback( + messageId: string, + rating: "like" | "dislike" | null, + user: string, + content?: string + ): Promise<DifyResponse<Record<string, unknown>>>; + messageFeedback( + messageIdOrRequest: string | MessageFeedbackRequest, + rating?: "like" | "dislike" | null, + user?: string, + content?: string + ): Promise<DifyResponse<Record<string, unknown>>> { + let messageId: string; + const payload: Record<string, unknown> = {}; + + if (typeof messageIdOrRequest === "string") { + messageId = messageIdOrRequest; + ensureNonEmptyString(messageId, "messageId"); + ensureNonEmptyString(user, "user"); + payload.user = user; + if (rating !== undefined && rating !== null) { + ensureRating(rating); + payload.rating = rating; + } + if (content !== undefined) { + payload.content = content; + } + } else { + const request = messageIdOrRequest; + messageId = request.messageId; + ensureNonEmptyString(messageId, "messageId"); + ensureNonEmptyString(request.user, "user"); + payload.user = request.user; + if (request.rating !== undefined && request.rating !== null) { + ensureRating(request.rating); + payload.rating = request.rating; + } + if (request.content !== undefined) { + payload.content = request.content; + } + } + + return this.http.request({ + method: "POST", + path: `/messages/${messageId}/feedbacks`, + data: payload, + }); + } + + getInfo(user?: string): Promise<DifyResponse<unknown>> { + if (user) { + ensureNonEmptyString(user, "user"); + } + return this.http.request({ + method: "GET", + path: "/info", + query: user ? { user } : undefined, + }); + } + + getSite(user?: string): Promise<DifyResponse<unknown>> { + if (user) { + ensureNonEmptyString(user, "user"); + } + return this.http.request({ + method: "GET", + path: "/site", + query: user ? { user } : undefined, + }); + } + + fileUpload(form: unknown, user: string): Promise<DifyResponse<unknown>> { + if (!isFormData(form)) { + throw new FileUploadError("FormData is required for file uploads"); + } + ensureNonEmptyString(user, "user"); + appendUserToFormData(form, user); + return this.http.request({ + method: "POST", + path: "/files/upload", + data: form, + }); + } + + filePreview( + fileId: string, + user: string, + asAttachment?: boolean + ): Promise<DifyResponse<Buffer>> { + ensureNonEmptyString(fileId, "fileId"); + ensureNonEmptyString(user, "user"); + return this.http.request<Buffer>({ + method: "GET", + path: `/files/${fileId}/preview`, + query: { + user, + as_attachment: asAttachment ? "true" : undefined, + }, + responseType: "arraybuffer", + }); + } + + audioToText(form: unknown, user: string): Promise<DifyResponse<unknown>> { + if (!isFormData(form)) { + throw new FileUploadError("FormData is required for audio uploads"); + } + ensureNonEmptyString(user, "user"); + appendUserToFormData(form, user); + return this.http.request({ + method: "POST", + path: "/audio-to-text", + data: form, + }); + } + + textToAudio( + request: TextToAudioRequest + ): Promise<DifyResponse<Buffer> | BinaryStream>; + textToAudio( + text: string, + user: string, + streaming?: boolean, + voice?: string + ): Promise<DifyResponse<Buffer> | BinaryStream>; + textToAudio( + textOrRequest: string | TextToAudioRequest, + user?: string, + streaming = false, + voice?: string + ): Promise<DifyResponse<Buffer> | BinaryStream> { + let payload: TextToAudioRequest; + + if (typeof textOrRequest === "string") { + ensureNonEmptyString(textOrRequest, "text"); + ensureNonEmptyString(user, "user"); + payload = { + text: textOrRequest, + user, + streaming, + }; + if (voice) { + payload.voice = voice; + } + } else { + payload = { ...textOrRequest }; + ensureNonEmptyString(payload.user, "user"); + if (payload.text !== undefined && payload.text !== null) { + ensureNonEmptyString(payload.text, "text"); + } + if (payload.message_id !== undefined && payload.message_id !== null) { + ensureNonEmptyString(payload.message_id, "messageId"); + } + if (!payload.text && !payload.message_id) { + throw new ValidationError("text or message_id is required"); + } + payload.streaming = payload.streaming ?? false; + } + + if (payload.streaming) { + return this.http.requestBinaryStream({ + method: "POST", + path: "/text-to-audio", + data: payload, + }); + } + + return this.http.request<Buffer>({ + method: "POST", + path: "/text-to-audio", + data: payload, + responseType: "arraybuffer", + }); + } +} diff --git a/sdks/nodejs-client/src/client/chat.test.js b/sdks/nodejs-client/src/client/chat.test.js new file mode 100644 index 0000000000..a97c9d4a5c --- /dev/null +++ b/sdks/nodejs-client/src/client/chat.test.js @@ -0,0 +1,239 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { ChatClient } from "./chat"; +import { ValidationError } from "../errors/dify-error"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("ChatClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("creates chat messages in blocking mode", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.createChatMessage({ input: "x" }, "hello", "user", false, null); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/chat-messages", + data: { + inputs: { input: "x" }, + query: "hello", + user: "user", + response_mode: "blocking", + files: undefined, + }, + }); + }); + + it("creates chat messages in streaming mode", async () => { + const { client, requestStream } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.createChatMessage({ + inputs: { input: "x" }, + query: "hello", + user: "user", + response_mode: "streaming", + }); + + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/chat-messages", + data: { + inputs: { input: "x" }, + query: "hello", + user: "user", + response_mode: "streaming", + }, + }); + }); + + it("stops chat messages", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.stopChatMessage("task", "user"); + await chat.stopMessage("task", "user"); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/chat-messages/task/stop", + data: { user: "user" }, + }); + }); + + it("gets suggested questions", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.getSuggested("msg", "user"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/messages/msg/suggested", + query: { user: "user" }, + }); + }); + + it("submits message feedback", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.messageFeedback("msg", "like", "user", "good"); + await chat.messageFeedback({ + messageId: "msg", + user: "user", + rating: "dislike", + }); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/messages/msg/feedbacks", + data: { user: "user", rating: "like", content: "good" }, + }); + }); + + it("lists app feedbacks", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.getAppFeedbacks(2, 5); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/app/feedbacks", + query: { page: 2, limit: 5 }, + }); + }); + + it("lists conversations and messages", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.getConversations("user", "last", 10, "-updated_at"); + await chat.getConversationMessages("user", "conv", "first", 5); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/conversations", + query: { + user: "user", + last_id: "last", + limit: 10, + sort_by: "-updated_at", + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/messages", + query: { + user: "user", + conversation_id: "conv", + first_id: "first", + limit: 5, + }, + }); + }); + + it("renames conversations with optional auto-generate", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.renameConversation("conv", "name", "user", false); + await chat.renameConversation("conv", "user", { autoGenerate: true }); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/conversations/conv/name", + data: { user: "user", auto_generate: false, name: "name" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/conversations/conv/name", + data: { user: "user", auto_generate: true }, + }); + }); + + it("requires name when autoGenerate is false", async () => { + const { client } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + expect(() => + chat.renameConversation("conv", "", "user", false) + ).toThrow(ValidationError); + }); + + it("deletes conversations", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.deleteConversation("conv", "user"); + + expect(request).toHaveBeenCalledWith({ + method: "DELETE", + path: "/conversations/conv", + data: { user: "user" }, + }); + }); + + it("manages conversation variables", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.getConversationVariables("conv", "user", "last", 10, "name"); + await chat.updateConversationVariable("conv", "var", "user", "value"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/conversations/conv/variables", + query: { + user: "user", + last_id: "last", + limit: 10, + variable_name: "name", + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "PUT", + path: "/conversations/conv/variables/var", + data: { user: "user", value: "value" }, + }); + }); + + it("handles annotation APIs", async () => { + const { client, request } = createHttpClientWithSpies(); + const chat = new ChatClient(client); + + await chat.annotationReplyAction("enable", { + score_threshold: 0.5, + embedding_provider_name: "prov", + embedding_model_name: "model", + }); + await chat.getAnnotationReplyStatus("enable", "job"); + await chat.listAnnotations({ page: 1, limit: 10, keyword: "k" }); + await chat.createAnnotation({ question: "q", answer: "a" }); + await chat.updateAnnotation("id", { question: "q", answer: "a" }); + await chat.deleteAnnotation("id"); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/apps/annotation-reply/enable", + data: { + score_threshold: 0.5, + embedding_provider_name: "prov", + embedding_model_name: "model", + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/apps/annotation-reply/enable/status/job", + }); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/apps/annotations", + query: { page: 1, limit: 10, keyword: "k" }, + }); + }); +}); diff --git a/sdks/nodejs-client/src/client/chat.ts b/sdks/nodejs-client/src/client/chat.ts new file mode 100644 index 0000000000..745c999552 --- /dev/null +++ b/sdks/nodejs-client/src/client/chat.ts @@ -0,0 +1,377 @@ +import { DifyClient } from "./base"; +import type { ChatMessageRequest, ChatMessageResponse } from "../types/chat"; +import type { + AnnotationCreateRequest, + AnnotationListOptions, + AnnotationReplyActionRequest, + AnnotationResponse, +} from "../types/annotation"; +import type { + DifyResponse, + DifyStream, + QueryParams, +} from "../types/common"; +import { + ensureNonEmptyString, + ensureOptionalInt, + ensureOptionalString, +} from "./validation"; + +export class ChatClient extends DifyClient { + createChatMessage( + request: ChatMessageRequest + ): Promise<DifyResponse<ChatMessageResponse> | DifyStream<ChatMessageResponse>>; + createChatMessage( + inputs: Record<string, unknown>, + query: string, + user: string, + stream?: boolean, + conversationId?: string | null, + files?: Array<Record<string, unknown>> | null + ): Promise<DifyResponse<ChatMessageResponse> | DifyStream<ChatMessageResponse>>; + createChatMessage( + inputOrRequest: ChatMessageRequest | Record<string, unknown>, + query?: string, + user?: string, + stream = false, + conversationId?: string | null, + files?: Array<Record<string, unknown>> | null + ): Promise<DifyResponse<ChatMessageResponse> | DifyStream<ChatMessageResponse>> { + let payload: ChatMessageRequest; + let shouldStream = stream; + + if (query === undefined && "user" in (inputOrRequest as ChatMessageRequest)) { + payload = inputOrRequest as ChatMessageRequest; + shouldStream = payload.response_mode === "streaming"; + } else { + ensureNonEmptyString(query, "query"); + ensureNonEmptyString(user, "user"); + payload = { + inputs: inputOrRequest as Record<string, unknown>, + query, + user, + response_mode: stream ? "streaming" : "blocking", + files, + }; + if (conversationId) { + payload.conversation_id = conversationId; + } + } + + ensureNonEmptyString(payload.user, "user"); + ensureNonEmptyString(payload.query, "query"); + + if (shouldStream) { + return this.http.requestStream<ChatMessageResponse>({ + method: "POST", + path: "/chat-messages", + data: payload, + }); + } + + return this.http.request<ChatMessageResponse>({ + method: "POST", + path: "/chat-messages", + data: payload, + }); + } + + stopChatMessage( + taskId: string, + user: string + ): Promise<DifyResponse<ChatMessageResponse>> { + ensureNonEmptyString(taskId, "taskId"); + ensureNonEmptyString(user, "user"); + return this.http.request<ChatMessageResponse>({ + method: "POST", + path: `/chat-messages/${taskId}/stop`, + data: { user }, + }); + } + + stopMessage( + taskId: string, + user: string + ): Promise<DifyResponse<ChatMessageResponse>> { + return this.stopChatMessage(taskId, user); + } + + getSuggested( + messageId: string, + user: string + ): Promise<DifyResponse<ChatMessageResponse>> { + ensureNonEmptyString(messageId, "messageId"); + ensureNonEmptyString(user, "user"); + return this.http.request<ChatMessageResponse>({ + method: "GET", + path: `/messages/${messageId}/suggested`, + query: { user }, + }); + } + + // Note: messageFeedback is inherited from DifyClient + + getAppFeedbacks( + page?: number, + limit?: number + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureOptionalInt(page, "page"); + ensureOptionalInt(limit, "limit"); + return this.http.request({ + method: "GET", + path: "/app/feedbacks", + query: { + page, + limit, + }, + }); + } + + getConversations( + user: string, + lastId?: string | null, + limit?: number | null, + sortByOrPinned?: string | boolean | null + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(user, "user"); + ensureOptionalString(lastId, "lastId"); + ensureOptionalInt(limit, "limit"); + + const params: QueryParams = { user }; + if (lastId) { + params.last_id = lastId; + } + if (limit) { + params.limit = limit; + } + if (typeof sortByOrPinned === "string") { + params.sort_by = sortByOrPinned; + } else if (typeof sortByOrPinned === "boolean") { + params.pinned = sortByOrPinned; + } + + return this.http.request({ + method: "GET", + path: "/conversations", + query: params, + }); + } + + getConversationMessages( + user: string, + conversationId: string, + firstId?: string | null, + limit?: number | null + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(user, "user"); + ensureNonEmptyString(conversationId, "conversationId"); + ensureOptionalString(firstId, "firstId"); + ensureOptionalInt(limit, "limit"); + + const params: QueryParams = { user }; + params.conversation_id = conversationId; + if (firstId) { + params.first_id = firstId; + } + if (limit) { + params.limit = limit; + } + + return this.http.request({ + method: "GET", + path: "/messages", + query: params, + }); + } + + renameConversation( + conversationId: string, + name: string, + user: string, + autoGenerate?: boolean + ): Promise<DifyResponse<Record<string, unknown>>>; + renameConversation( + conversationId: string, + user: string, + options?: { name?: string | null; autoGenerate?: boolean } + ): Promise<DifyResponse<Record<string, unknown>>>; + renameConversation( + conversationId: string, + nameOrUser: string, + userOrOptions?: string | { name?: string | null; autoGenerate?: boolean }, + autoGenerate?: boolean + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(conversationId, "conversationId"); + + let name: string | null | undefined; + let user: string; + let resolvedAutoGenerate: boolean; + + if (typeof userOrOptions === "string" || userOrOptions === undefined) { + name = nameOrUser; + user = userOrOptions ?? ""; + resolvedAutoGenerate = autoGenerate ?? false; + } else { + user = nameOrUser; + name = userOrOptions.name; + resolvedAutoGenerate = userOrOptions.autoGenerate ?? false; + } + + ensureNonEmptyString(user, "user"); + if (!resolvedAutoGenerate) { + ensureNonEmptyString(name, "name"); + } + + const payload: Record<string, unknown> = { + user, + auto_generate: resolvedAutoGenerate, + }; + if (typeof name === "string" && name.trim().length > 0) { + payload.name = name; + } + + return this.http.request({ + method: "POST", + path: `/conversations/${conversationId}/name`, + data: payload, + }); + } + + deleteConversation( + conversationId: string, + user: string + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(conversationId, "conversationId"); + ensureNonEmptyString(user, "user"); + return this.http.request({ + method: "DELETE", + path: `/conversations/${conversationId}`, + data: { user }, + }); + } + + getConversationVariables( + conversationId: string, + user: string, + lastId?: string | null, + limit?: number | null, + variableName?: string | null + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(conversationId, "conversationId"); + ensureNonEmptyString(user, "user"); + ensureOptionalString(lastId, "lastId"); + ensureOptionalInt(limit, "limit"); + ensureOptionalString(variableName, "variableName"); + + return this.http.request({ + method: "GET", + path: `/conversations/${conversationId}/variables`, + query: { + user, + last_id: lastId ?? undefined, + limit: limit ?? undefined, + variable_name: variableName ?? undefined, + }, + }); + } + + updateConversationVariable( + conversationId: string, + variableId: string, + user: string, + value: unknown + ): Promise<DifyResponse<Record<string, unknown>>> { + ensureNonEmptyString(conversationId, "conversationId"); + ensureNonEmptyString(variableId, "variableId"); + ensureNonEmptyString(user, "user"); + return this.http.request({ + method: "PUT", + path: `/conversations/${conversationId}/variables/${variableId}`, + data: { + user, + value, + }, + }); + } + + annotationReplyAction( + action: "enable" | "disable", + request: AnnotationReplyActionRequest + ): Promise<DifyResponse<AnnotationResponse>> { + ensureNonEmptyString(action, "action"); + ensureNonEmptyString(request.embedding_provider_name, "embedding_provider_name"); + ensureNonEmptyString(request.embedding_model_name, "embedding_model_name"); + return this.http.request({ + method: "POST", + path: `/apps/annotation-reply/${action}`, + data: request, + }); + } + + getAnnotationReplyStatus( + action: "enable" | "disable", + jobId: string + ): Promise<DifyResponse<AnnotationResponse>> { + ensureNonEmptyString(action, "action"); + ensureNonEmptyString(jobId, "jobId"); + return this.http.request({ + method: "GET", + path: `/apps/annotation-reply/${action}/status/${jobId}`, + }); + } + + listAnnotations( + options?: AnnotationListOptions + ): Promise<DifyResponse<AnnotationResponse>> { + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + ensureOptionalString(options?.keyword, "keyword"); + return this.http.request({ + method: "GET", + path: "/apps/annotations", + query: { + page: options?.page, + limit: options?.limit, + keyword: options?.keyword ?? undefined, + }, + }); + } + + createAnnotation( + request: AnnotationCreateRequest + ): Promise<DifyResponse<AnnotationResponse>> { + ensureNonEmptyString(request.question, "question"); + ensureNonEmptyString(request.answer, "answer"); + return this.http.request({ + method: "POST", + path: "/apps/annotations", + data: request, + }); + } + + updateAnnotation( + annotationId: string, + request: AnnotationCreateRequest + ): Promise<DifyResponse<AnnotationResponse>> { + ensureNonEmptyString(annotationId, "annotationId"); + ensureNonEmptyString(request.question, "question"); + ensureNonEmptyString(request.answer, "answer"); + return this.http.request({ + method: "PUT", + path: `/apps/annotations/${annotationId}`, + data: request, + }); + } + + deleteAnnotation( + annotationId: string + ): Promise<DifyResponse<AnnotationResponse>> { + ensureNonEmptyString(annotationId, "annotationId"); + return this.http.request({ + method: "DELETE", + path: `/apps/annotations/${annotationId}`, + }); + } + + // Note: audioToText is inherited from DifyClient +} diff --git a/sdks/nodejs-client/src/client/completion.test.js b/sdks/nodejs-client/src/client/completion.test.js new file mode 100644 index 0000000000..b79cf3fb8f --- /dev/null +++ b/sdks/nodejs-client/src/client/completion.test.js @@ -0,0 +1,83 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { CompletionClient } from "./completion"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("CompletionClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("creates completion messages in blocking mode", async () => { + const { client, request } = createHttpClientWithSpies(); + const completion = new CompletionClient(client); + + await completion.createCompletionMessage({ input: "x" }, "user", false); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/completion-messages", + data: { + inputs: { input: "x" }, + user: "user", + files: undefined, + response_mode: "blocking", + }, + }); + }); + + it("creates completion messages in streaming mode", async () => { + const { client, requestStream } = createHttpClientWithSpies(); + const completion = new CompletionClient(client); + + await completion.createCompletionMessage({ + inputs: { input: "x" }, + user: "user", + response_mode: "streaming", + }); + + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/completion-messages", + data: { + inputs: { input: "x" }, + user: "user", + response_mode: "streaming", + }, + }); + }); + + it("stops completion messages", async () => { + const { client, request } = createHttpClientWithSpies(); + const completion = new CompletionClient(client); + + await completion.stopCompletionMessage("task", "user"); + await completion.stop("task", "user"); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/completion-messages/task/stop", + data: { user: "user" }, + }); + }); + + it("supports deprecated runWorkflow", async () => { + const { client, request, requestStream } = createHttpClientWithSpies(); + const completion = new CompletionClient(client); + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + + await completion.runWorkflow({ input: "x" }, "user", false); + await completion.runWorkflow({ input: "x" }, "user", true); + + expect(warn).toHaveBeenCalled(); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/run", + data: { inputs: { input: "x" }, user: "user", response_mode: "blocking" }, + }); + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/run", + data: { inputs: { input: "x" }, user: "user", response_mode: "streaming" }, + }); + }); +}); diff --git a/sdks/nodejs-client/src/client/completion.ts b/sdks/nodejs-client/src/client/completion.ts new file mode 100644 index 0000000000..9e39898e8b --- /dev/null +++ b/sdks/nodejs-client/src/client/completion.ts @@ -0,0 +1,111 @@ +import { DifyClient } from "./base"; +import type { CompletionRequest, CompletionResponse } from "../types/completion"; +import type { DifyResponse, DifyStream } from "../types/common"; +import { ensureNonEmptyString } from "./validation"; + +const warned = new Set<string>(); +const warnOnce = (message: string): void => { + if (warned.has(message)) { + return; + } + warned.add(message); + console.warn(message); +}; + +export class CompletionClient extends DifyClient { + createCompletionMessage( + request: CompletionRequest + ): Promise<DifyResponse<CompletionResponse> | DifyStream<CompletionResponse>>; + createCompletionMessage( + inputs: Record<string, unknown>, + user: string, + stream?: boolean, + files?: Array<Record<string, unknown>> | null + ): Promise<DifyResponse<CompletionResponse> | DifyStream<CompletionResponse>>; + createCompletionMessage( + inputOrRequest: CompletionRequest | Record<string, unknown>, + user?: string, + stream = false, + files?: Array<Record<string, unknown>> | null + ): Promise<DifyResponse<CompletionResponse> | DifyStream<CompletionResponse>> { + let payload: CompletionRequest; + let shouldStream = stream; + + if (user === undefined && "user" in (inputOrRequest as CompletionRequest)) { + payload = inputOrRequest as CompletionRequest; + shouldStream = payload.response_mode === "streaming"; + } else { + ensureNonEmptyString(user, "user"); + payload = { + inputs: inputOrRequest as Record<string, unknown>, + user, + files, + response_mode: stream ? "streaming" : "blocking", + }; + } + + ensureNonEmptyString(payload.user, "user"); + + if (shouldStream) { + return this.http.requestStream<CompletionResponse>({ + method: "POST", + path: "/completion-messages", + data: payload, + }); + } + + return this.http.request<CompletionResponse>({ + method: "POST", + path: "/completion-messages", + data: payload, + }); + } + + stopCompletionMessage( + taskId: string, + user: string + ): Promise<DifyResponse<CompletionResponse>> { + ensureNonEmptyString(taskId, "taskId"); + ensureNonEmptyString(user, "user"); + return this.http.request<CompletionResponse>({ + method: "POST", + path: `/completion-messages/${taskId}/stop`, + data: { user }, + }); + } + + stop( + taskId: string, + user: string + ): Promise<DifyResponse<CompletionResponse>> { + return this.stopCompletionMessage(taskId, user); + } + + runWorkflow( + inputs: Record<string, unknown>, + user: string, + stream = false + ): Promise<DifyResponse<Record<string, unknown>> | DifyStream<Record<string, unknown>>> { + warnOnce( + "CompletionClient.runWorkflow is deprecated. Use WorkflowClient.run instead." + ); + ensureNonEmptyString(user, "user"); + const payload = { + inputs, + user, + response_mode: stream ? "streaming" : "blocking", + }; + if (stream) { + return this.http.requestStream<Record<string, unknown>>({ + method: "POST", + path: "/workflows/run", + data: payload, + }); + } + return this.http.request<Record<string, unknown>>({ + method: "POST", + path: "/workflows/run", + data: payload, + }); + } +} diff --git a/sdks/nodejs-client/src/client/knowledge-base.test.js b/sdks/nodejs-client/src/client/knowledge-base.test.js new file mode 100644 index 0000000000..4381b39e56 --- /dev/null +++ b/sdks/nodejs-client/src/client/knowledge-base.test.js @@ -0,0 +1,249 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { KnowledgeBaseClient } from "./knowledge-base"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("KnowledgeBaseClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("handles dataset and tag operations", async () => { + const { client, request } = createHttpClientWithSpies(); + const kb = new KnowledgeBaseClient(client); + + await kb.listDatasets({ + page: 1, + limit: 2, + keyword: "k", + includeAll: true, + tagIds: ["t1"], + }); + await kb.createDataset({ name: "dataset" }); + await kb.getDataset("ds"); + await kb.updateDataset("ds", { name: "new" }); + await kb.deleteDataset("ds"); + await kb.updateDocumentStatus("ds", "enable", ["doc1"]); + + await kb.listTags(); + await kb.createTag({ name: "tag" }); + await kb.updateTag({ tag_id: "tag", name: "name" }); + await kb.deleteTag({ tag_id: "tag" }); + await kb.bindTags({ tag_ids: ["tag"], target_id: "doc" }); + await kb.unbindTags({ tag_id: "tag", target_id: "doc" }); + await kb.getDatasetTags("ds"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/datasets", + query: { + page: 1, + limit: 2, + keyword: "k", + include_all: true, + tag_ids: ["t1"], + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets", + data: { name: "dataset" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "PATCH", + path: "/datasets/ds", + data: { name: "new" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "PATCH", + path: "/datasets/ds/documents/status/enable", + data: { document_ids: ["doc1"] }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/tags/binding", + data: { tag_ids: ["tag"], target_id: "doc" }, + }); + }); + + it("handles document operations", async () => { + const { client, request } = createHttpClientWithSpies(); + const kb = new KnowledgeBaseClient(client); + const form = { append: vi.fn(), getHeaders: () => ({}) }; + + await kb.createDocumentByText("ds", { name: "doc", text: "text" }); + await kb.updateDocumentByText("ds", "doc", { name: "doc2" }); + await kb.createDocumentByFile("ds", form); + await kb.updateDocumentByFile("ds", "doc", form); + await kb.listDocuments("ds", { page: 1, limit: 20, keyword: "k" }); + await kb.getDocument("ds", "doc", { metadata: "all" }); + await kb.deleteDocument("ds", "doc"); + await kb.getDocumentIndexingStatus("ds", "batch"); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/document/create_by_text", + data: { name: "doc", text: "text" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/documents/doc/update_by_text", + data: { name: "doc2" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/document/create_by_file", + data: form, + }); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/datasets/ds/documents", + query: { page: 1, limit: 20, keyword: "k", status: undefined }, + }); + }); + + it("handles segments and child chunks", async () => { + const { client, request } = createHttpClientWithSpies(); + const kb = new KnowledgeBaseClient(client); + + await kb.createSegments("ds", "doc", { segments: [{ content: "x" }] }); + await kb.listSegments("ds", "doc", { page: 1, limit: 10, keyword: "k" }); + await kb.getSegment("ds", "doc", "seg"); + await kb.updateSegment("ds", "doc", "seg", { + segment: { content: "y" }, + }); + await kb.deleteSegment("ds", "doc", "seg"); + + await kb.createChildChunk("ds", "doc", "seg", { content: "c" }); + await kb.listChildChunks("ds", "doc", "seg", { page: 1, limit: 10 }); + await kb.updateChildChunk("ds", "doc", "seg", "child", { + content: "c2", + }); + await kb.deleteChildChunk("ds", "doc", "seg", "child"); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/documents/doc/segments", + data: { segments: [{ content: "x" }] }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/documents/doc/segments/seg", + data: { segment: { content: "y" } }, + }); + expect(request).toHaveBeenCalledWith({ + method: "PATCH", + path: "/datasets/ds/documents/doc/segments/seg/child_chunks/child", + data: { content: "c2" }, + }); + }); + + it("handles metadata and retrieval", async () => { + const { client, request } = createHttpClientWithSpies(); + const kb = new KnowledgeBaseClient(client); + + await kb.listMetadata("ds"); + await kb.createMetadata("ds", { name: "m", type: "string" }); + await kb.updateMetadata("ds", "mid", { name: "m2" }); + await kb.deleteMetadata("ds", "mid"); + await kb.listBuiltInMetadata("ds"); + await kb.updateBuiltInMetadata("ds", "enable"); + await kb.updateDocumentsMetadata("ds", { + operation_data: [ + { document_id: "doc", metadata_list: [{ id: "m", name: "n" }] }, + ], + }); + await kb.hitTesting("ds", { query: "q" }); + await kb.retrieve("ds", { query: "q" }); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/datasets/ds/metadata", + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/metadata", + data: { name: "m", type: "string" }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/hit-testing", + data: { query: "q" }, + }); + }); + + it("handles pipeline operations", async () => { + const { client, request, requestStream } = createHttpClientWithSpies(); + const kb = new KnowledgeBaseClient(client); + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + const form = { append: vi.fn(), getHeaders: () => ({}) }; + + await kb.listDatasourcePlugins("ds", { isPublished: true }); + await kb.runDatasourceNode("ds", "node", { + inputs: { input: "x" }, + datasource_type: "custom", + is_published: true, + }); + await kb.runPipeline("ds", { + inputs: { input: "x" }, + datasource_type: "custom", + datasource_info_list: [], + start_node_id: "start", + is_published: true, + response_mode: "streaming", + }); + await kb.runPipeline("ds", { + inputs: { input: "x" }, + datasource_type: "custom", + datasource_info_list: [], + start_node_id: "start", + is_published: true, + response_mode: "blocking", + }); + await kb.uploadPipelineFile(form); + + expect(warn).toHaveBeenCalled(); + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/datasets/ds/pipeline/datasource-plugins", + query: { is_published: true }, + }); + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/pipeline/datasource/nodes/node/run", + data: { + inputs: { input: "x" }, + datasource_type: "custom", + is_published: true, + }, + }); + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/pipeline/run", + data: { + inputs: { input: "x" }, + datasource_type: "custom", + datasource_info_list: [], + start_node_id: "start", + is_published: true, + response_mode: "streaming", + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/ds/pipeline/run", + data: { + inputs: { input: "x" }, + datasource_type: "custom", + datasource_info_list: [], + start_node_id: "start", + is_published: true, + response_mode: "blocking", + }, + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/datasets/pipeline/file-upload", + data: form, + }); + }); +}); diff --git a/sdks/nodejs-client/src/client/knowledge-base.ts b/sdks/nodejs-client/src/client/knowledge-base.ts new file mode 100644 index 0000000000..7a0e39898b --- /dev/null +++ b/sdks/nodejs-client/src/client/knowledge-base.ts @@ -0,0 +1,706 @@ +import { DifyClient } from "./base"; +import type { + DatasetCreateRequest, + DatasetListOptions, + DatasetTagBindingRequest, + DatasetTagCreateRequest, + DatasetTagDeleteRequest, + DatasetTagUnbindingRequest, + DatasetTagUpdateRequest, + DatasetUpdateRequest, + DocumentGetOptions, + DocumentListOptions, + DocumentStatusAction, + DocumentTextCreateRequest, + DocumentTextUpdateRequest, + SegmentCreateRequest, + SegmentListOptions, + SegmentUpdateRequest, + ChildChunkCreateRequest, + ChildChunkListOptions, + ChildChunkUpdateRequest, + MetadataCreateRequest, + MetadataOperationRequest, + MetadataUpdateRequest, + HitTestingRequest, + DatasourcePluginListOptions, + DatasourceNodeRunRequest, + PipelineRunRequest, + KnowledgeBaseResponse, + PipelineStreamEvent, +} from "../types/knowledge-base"; +import type { DifyResponse, DifyStream, QueryParams } from "../types/common"; +import { + ensureNonEmptyString, + ensureOptionalBoolean, + ensureOptionalInt, + ensureOptionalString, + ensureStringArray, +} from "./validation"; +import { FileUploadError, ValidationError } from "../errors/dify-error"; +import { isFormData } from "../http/form-data"; + +const warned = new Set<string>(); +const warnOnce = (message: string): void => { + if (warned.has(message)) { + return; + } + warned.add(message); + console.warn(message); +}; + +const ensureFormData = (form: unknown, context: string): void => { + if (!isFormData(form)) { + throw new FileUploadError(`${context} requires FormData`); + } +}; + +const ensureNonEmptyArray = (value: unknown, name: string): void => { + if (!Array.isArray(value) || value.length === 0) { + throw new ValidationError(`${name} must be a non-empty array`); + } +}; + +const warnPipelineRoutes = (): void => { + warnOnce( + "RAG pipeline endpoints may be unavailable unless the service API registers dataset/rag_pipeline routes." + ); +}; + +export class KnowledgeBaseClient extends DifyClient { + async listDatasets( + options?: DatasetListOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + ensureOptionalString(options?.keyword, "keyword"); + ensureOptionalBoolean(options?.includeAll, "includeAll"); + + const query: QueryParams = { + page: options?.page, + limit: options?.limit, + keyword: options?.keyword ?? undefined, + include_all: options?.includeAll ?? undefined, + }; + + if (options?.tagIds && options.tagIds.length > 0) { + ensureStringArray(options.tagIds, "tagIds"); + query.tag_ids = options.tagIds; + } + + return this.http.request({ + method: "GET", + path: "/datasets", + query, + }); + } + + async createDataset( + request: DatasetCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(request.name, "name"); + return this.http.request({ + method: "POST", + path: "/datasets", + data: request, + }); + } + + async getDataset(datasetId: string): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}`, + }); + } + + async updateDataset( + datasetId: string, + request: DatasetUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + if (request.name !== undefined && request.name !== null) { + ensureNonEmptyString(request.name, "name"); + } + return this.http.request({ + method: "PATCH", + path: `/datasets/${datasetId}`, + data: request, + }); + } + + async deleteDataset(datasetId: string): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "DELETE", + path: `/datasets/${datasetId}`, + }); + } + + async updateDocumentStatus( + datasetId: string, + action: DocumentStatusAction, + documentIds: string[] + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(action, "action"); + ensureStringArray(documentIds, "documentIds"); + return this.http.request({ + method: "PATCH", + path: `/datasets/${datasetId}/documents/status/${action}`, + data: { + document_ids: documentIds, + }, + }); + } + + async listTags(): Promise<DifyResponse<KnowledgeBaseResponse>> { + return this.http.request({ + method: "GET", + path: "/datasets/tags", + }); + } + + async createTag( + request: DatasetTagCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(request.name, "name"); + return this.http.request({ + method: "POST", + path: "/datasets/tags", + data: request, + }); + } + + async updateTag( + request: DatasetTagUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(request.tag_id, "tag_id"); + ensureNonEmptyString(request.name, "name"); + return this.http.request({ + method: "PATCH", + path: "/datasets/tags", + data: request, + }); + } + + async deleteTag( + request: DatasetTagDeleteRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(request.tag_id, "tag_id"); + return this.http.request({ + method: "DELETE", + path: "/datasets/tags", + data: request, + }); + } + + async bindTags( + request: DatasetTagBindingRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureStringArray(request.tag_ids, "tag_ids"); + ensureNonEmptyString(request.target_id, "target_id"); + return this.http.request({ + method: "POST", + path: "/datasets/tags/binding", + data: request, + }); + } + + async unbindTags( + request: DatasetTagUnbindingRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(request.tag_id, "tag_id"); + ensureNonEmptyString(request.target_id, "target_id"); + return this.http.request({ + method: "POST", + path: "/datasets/tags/unbinding", + data: request, + }); + } + + async getDatasetTags( + datasetId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/tags`, + }); + } + + async createDocumentByText( + datasetId: string, + request: DocumentTextCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(request.name, "name"); + ensureNonEmptyString(request.text, "text"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/document/create_by_text`, + data: request, + }); + } + + async updateDocumentByText( + datasetId: string, + documentId: string, + request: DocumentTextUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + if (request.name !== undefined && request.name !== null) { + ensureNonEmptyString(request.name, "name"); + } + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/${documentId}/update_by_text`, + data: request, + }); + } + + async createDocumentByFile( + datasetId: string, + form: unknown + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureFormData(form, "createDocumentByFile"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/document/create_by_file`, + data: form, + }); + } + + async updateDocumentByFile( + datasetId: string, + documentId: string, + form: unknown + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureFormData(form, "updateDocumentByFile"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/${documentId}/update_by_file`, + data: form, + }); + } + + async listDocuments( + datasetId: string, + options?: DocumentListOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + ensureOptionalString(options?.keyword, "keyword"); + ensureOptionalString(options?.status, "status"); + + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents`, + query: { + page: options?.page, + limit: options?.limit, + keyword: options?.keyword ?? undefined, + status: options?.status ?? undefined, + }, + }); + } + + async getDocument( + datasetId: string, + documentId: string, + options?: DocumentGetOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + if (options?.metadata) { + const allowed = new Set(["all", "only", "without"]); + if (!allowed.has(options.metadata)) { + throw new ValidationError("metadata must be one of all, only, without"); + } + } + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents/${documentId}`, + query: { + metadata: options?.metadata ?? undefined, + }, + }); + } + + async deleteDocument( + datasetId: string, + documentId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + return this.http.request({ + method: "DELETE", + path: `/datasets/${datasetId}/documents/${documentId}`, + }); + } + + async getDocumentIndexingStatus( + datasetId: string, + batch: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(batch, "batch"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents/${batch}/indexing-status`, + }); + } + + async createSegments( + datasetId: string, + documentId: string, + request: SegmentCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyArray(request.segments, "segments"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/${documentId}/segments`, + data: request, + }); + } + + async listSegments( + datasetId: string, + documentId: string, + options?: SegmentListOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + ensureOptionalString(options?.keyword, "keyword"); + if (options?.status && options.status.length > 0) { + ensureStringArray(options.status, "status"); + } + + const query: QueryParams = { + page: options?.page, + limit: options?.limit, + keyword: options?.keyword ?? undefined, + }; + if (options?.status && options.status.length > 0) { + query.status = options.status; + } + + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents/${documentId}/segments`, + query, + }); + } + + async getSegment( + datasetId: string, + documentId: string, + segmentId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + }); + } + + async updateSegment( + datasetId: string, + documentId: string, + segmentId: string, + request: SegmentUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + data: request, + }); + } + + async deleteSegment( + datasetId: string, + documentId: string, + segmentId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + return this.http.request({ + method: "DELETE", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}`, + }); + } + + async createChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + request: ChildChunkCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + ensureNonEmptyString(request.content, "content"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, + data: request, + }); + } + + async listChildChunks( + datasetId: string, + documentId: string, + segmentId: string, + options?: ChildChunkListOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + ensureOptionalString(options?.keyword, "keyword"); + + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks`, + query: { + page: options?.page, + limit: options?.limit, + keyword: options?.keyword ?? undefined, + }, + }); + } + + async updateChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + childChunkId: string, + request: ChildChunkUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + ensureNonEmptyString(childChunkId, "childChunkId"); + ensureNonEmptyString(request.content, "content"); + return this.http.request({ + method: "PATCH", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, + data: request, + }); + } + + async deleteChildChunk( + datasetId: string, + documentId: string, + segmentId: string, + childChunkId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(documentId, "documentId"); + ensureNonEmptyString(segmentId, "segmentId"); + ensureNonEmptyString(childChunkId, "childChunkId"); + return this.http.request({ + method: "DELETE", + path: `/datasets/${datasetId}/documents/${documentId}/segments/${segmentId}/child_chunks/${childChunkId}`, + }); + } + + async listMetadata( + datasetId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/metadata`, + }); + } + + async createMetadata( + datasetId: string, + request: MetadataCreateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(request.name, "name"); + ensureNonEmptyString(request.type, "type"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/metadata`, + data: request, + }); + } + + async updateMetadata( + datasetId: string, + metadataId: string, + request: MetadataUpdateRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(metadataId, "metadataId"); + ensureNonEmptyString(request.name, "name"); + return this.http.request({ + method: "PATCH", + path: `/datasets/${datasetId}/metadata/${metadataId}`, + data: request, + }); + } + + async deleteMetadata( + datasetId: string, + metadataId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(metadataId, "metadataId"); + return this.http.request({ + method: "DELETE", + path: `/datasets/${datasetId}/metadata/${metadataId}`, + }); + } + + async listBuiltInMetadata( + datasetId: string + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/metadata/built-in`, + }); + } + + async updateBuiltInMetadata( + datasetId: string, + action: "enable" | "disable" + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(action, "action"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/metadata/built-in/${action}`, + }); + } + + async updateDocumentsMetadata( + datasetId: string, + request: MetadataOperationRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyArray(request.operation_data, "operation_data"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/documents/metadata`, + data: request, + }); + } + + async hitTesting( + datasetId: string, + request: HitTestingRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + if (request.query !== undefined && request.query !== null) { + ensureOptionalString(request.query, "query"); + } + if (request.attachment_ids && request.attachment_ids.length > 0) { + ensureStringArray(request.attachment_ids, "attachment_ids"); + } + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/hit-testing`, + data: request, + }); + } + + async retrieve( + datasetId: string, + request: HitTestingRequest + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + ensureNonEmptyString(datasetId, "datasetId"); + return this.http.request({ + method: "POST", + path: `/datasets/${datasetId}/retrieve`, + data: request, + }); + } + + async listDatasourcePlugins( + datasetId: string, + options?: DatasourcePluginListOptions + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + warnPipelineRoutes(); + ensureNonEmptyString(datasetId, "datasetId"); + ensureOptionalBoolean(options?.isPublished, "isPublished"); + return this.http.request({ + method: "GET", + path: `/datasets/${datasetId}/pipeline/datasource-plugins`, + query: { + is_published: options?.isPublished ?? undefined, + }, + }); + } + + async runDatasourceNode( + datasetId: string, + nodeId: string, + request: DatasourceNodeRunRequest + ): Promise<DifyStream<PipelineStreamEvent>> { + warnPipelineRoutes(); + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(nodeId, "nodeId"); + ensureNonEmptyString(request.datasource_type, "datasource_type"); + return this.http.requestStream<PipelineStreamEvent>({ + method: "POST", + path: `/datasets/${datasetId}/pipeline/datasource/nodes/${nodeId}/run`, + data: request, + }); + } + + async runPipeline( + datasetId: string, + request: PipelineRunRequest + ): Promise<DifyResponse<KnowledgeBaseResponse> | DifyStream<PipelineStreamEvent>> { + warnPipelineRoutes(); + ensureNonEmptyString(datasetId, "datasetId"); + ensureNonEmptyString(request.datasource_type, "datasource_type"); + ensureNonEmptyString(request.start_node_id, "start_node_id"); + const shouldStream = request.response_mode === "streaming"; + if (shouldStream) { + return this.http.requestStream<PipelineStreamEvent>({ + method: "POST", + path: `/datasets/${datasetId}/pipeline/run`, + data: request, + }); + } + return this.http.request<KnowledgeBaseResponse>({ + method: "POST", + path: `/datasets/${datasetId}/pipeline/run`, + data: request, + }); + } + + async uploadPipelineFile( + form: unknown + ): Promise<DifyResponse<KnowledgeBaseResponse>> { + warnPipelineRoutes(); + ensureFormData(form, "uploadPipelineFile"); + return this.http.request({ + method: "POST", + path: "/datasets/pipeline/file-upload", + data: form, + }); + } +} diff --git a/sdks/nodejs-client/src/client/validation.test.js b/sdks/nodejs-client/src/client/validation.test.js new file mode 100644 index 0000000000..65bfa471a6 --- /dev/null +++ b/sdks/nodejs-client/src/client/validation.test.js @@ -0,0 +1,91 @@ +import { describe, expect, it } from "vitest"; +import { + ensureNonEmptyString, + ensureOptionalBoolean, + ensureOptionalInt, + ensureOptionalString, + ensureOptionalStringArray, + ensureRating, + ensureStringArray, + validateParams, +} from "./validation"; + +const makeLongString = (length) => "a".repeat(length); + +describe("validation utilities", () => { + it("ensureNonEmptyString throws on empty or whitespace", () => { + expect(() => ensureNonEmptyString("", "name")).toThrow(); + expect(() => ensureNonEmptyString(" ", "name")).toThrow(); + }); + + it("ensureNonEmptyString throws on overly long strings", () => { + expect(() => + ensureNonEmptyString(makeLongString(10001), "name") + ).toThrow(); + }); + + it("ensureOptionalString ignores undefined and validates when set", () => { + expect(() => ensureOptionalString(undefined, "opt")).not.toThrow(); + expect(() => ensureOptionalString("", "opt")).toThrow(); + }); + + it("ensureOptionalString throws on overly long strings", () => { + expect(() => ensureOptionalString(makeLongString(10001), "opt")).toThrow(); + }); + + it("ensureOptionalInt validates integer", () => { + expect(() => ensureOptionalInt(undefined, "limit")).not.toThrow(); + expect(() => ensureOptionalInt(1.2, "limit")).toThrow(); + }); + + it("ensureOptionalBoolean validates boolean", () => { + expect(() => ensureOptionalBoolean(undefined, "flag")).not.toThrow(); + expect(() => ensureOptionalBoolean("yes", "flag")).toThrow(); + }); + + it("ensureStringArray enforces size and content", () => { + expect(() => ensureStringArray([], "items")).toThrow(); + expect(() => ensureStringArray([""], "items")).toThrow(); + expect(() => + ensureStringArray(Array.from({ length: 1001 }, () => "a"), "items") + ).toThrow(); + expect(() => ensureStringArray(["ok"], "items")).not.toThrow(); + }); + + it("ensureOptionalStringArray ignores undefined", () => { + expect(() => ensureOptionalStringArray(undefined, "tags")).not.toThrow(); + }); + + it("ensureOptionalStringArray validates when set", () => { + expect(() => ensureOptionalStringArray(["valid"], "tags")).not.toThrow(); + expect(() => ensureOptionalStringArray([], "tags")).toThrow(); + expect(() => ensureOptionalStringArray([""], "tags")).toThrow(); + }); + + it("ensureRating validates allowed values", () => { + expect(() => ensureRating(undefined)).not.toThrow(); + expect(() => ensureRating("like")).not.toThrow(); + expect(() => ensureRating("bad")).toThrow(); + }); + + it("validateParams enforces generic rules", () => { + expect(() => validateParams({ user: 123 })).toThrow(); + expect(() => validateParams({ rating: "bad" })).toThrow(); + expect(() => validateParams({ page: 1.1 })).toThrow(); + expect(() => validateParams({ files: "bad" })).toThrow(); + // Empty strings are allowed for optional params (e.g., keyword: "" means no filter) + expect(() => validateParams({ keyword: "" })).not.toThrow(); + expect(() => validateParams({ name: makeLongString(10001) })).toThrow(); + expect(() => + validateParams({ items: Array.from({ length: 1001 }, () => "a") }) + ).toThrow(); + expect(() => + validateParams({ + data: Object.fromEntries( + Array.from({ length: 101 }, (_, i) => [String(i), i]) + ), + }) + ).toThrow(); + expect(() => validateParams({ user: "u", page: 1 })).not.toThrow(); + }); +}); diff --git a/sdks/nodejs-client/src/client/validation.ts b/sdks/nodejs-client/src/client/validation.ts new file mode 100644 index 0000000000..6aeec36bdc --- /dev/null +++ b/sdks/nodejs-client/src/client/validation.ts @@ -0,0 +1,136 @@ +import { ValidationError } from "../errors/dify-error"; + +const MAX_STRING_LENGTH = 10000; +const MAX_LIST_LENGTH = 1000; +const MAX_DICT_LENGTH = 100; + +export function ensureNonEmptyString( + value: unknown, + name: string +): asserts value is string { + if (typeof value !== "string" || value.trim().length === 0) { + throw new ValidationError(`${name} must be a non-empty string`); + } + if (value.length > MAX_STRING_LENGTH) { + throw new ValidationError( + `${name} exceeds maximum length of ${MAX_STRING_LENGTH} characters` + ); + } +} + +/** + * Validates optional string fields that must be non-empty when provided. + * Use this for fields like `name` that are optional but should not be empty strings. + * + * For filter parameters that accept empty strings (e.g., `keyword: ""`), + * use `validateParams` which allows empty strings for optional params. + */ +export function ensureOptionalString(value: unknown, name: string): void { + if (value === undefined || value === null) { + return; + } + if (typeof value !== "string" || value.trim().length === 0) { + throw new ValidationError(`${name} must be a non-empty string when set`); + } + if (value.length > MAX_STRING_LENGTH) { + throw new ValidationError( + `${name} exceeds maximum length of ${MAX_STRING_LENGTH} characters` + ); + } +} + +export function ensureOptionalInt(value: unknown, name: string): void { + if (value === undefined || value === null) { + return; + } + if (!Number.isInteger(value)) { + throw new ValidationError(`${name} must be an integer when set`); + } +} + +export function ensureOptionalBoolean(value: unknown, name: string): void { + if (value === undefined || value === null) { + return; + } + if (typeof value !== "boolean") { + throw new ValidationError(`${name} must be a boolean when set`); + } +} + +export function ensureStringArray(value: unknown, name: string): void { + if (!Array.isArray(value) || value.length === 0) { + throw new ValidationError(`${name} must be a non-empty string array`); + } + if (value.length > MAX_LIST_LENGTH) { + throw new ValidationError( + `${name} exceeds maximum size of ${MAX_LIST_LENGTH} items` + ); + } + value.forEach((item) => { + if (typeof item !== "string" || item.trim().length === 0) { + throw new ValidationError(`${name} must contain non-empty strings`); + } + }); +} + +export function ensureOptionalStringArray(value: unknown, name: string): void { + if (value === undefined || value === null) { + return; + } + ensureStringArray(value, name); +} + +export function ensureRating(value: unknown): void { + if (value === undefined || value === null) { + return; + } + if (value !== "like" && value !== "dislike") { + throw new ValidationError("rating must be either 'like' or 'dislike'"); + } +} + +export function validateParams(params: Record<string, unknown>): void { + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + + // Only check max length for strings; empty strings are allowed for optional params + // Required fields are validated at method level via ensureNonEmptyString + if (typeof value === "string") { + if (value.length > MAX_STRING_LENGTH) { + throw new ValidationError( + `Parameter '${key}' exceeds maximum length of ${MAX_STRING_LENGTH} characters` + ); + } + } else if (Array.isArray(value)) { + if (value.length > MAX_LIST_LENGTH) { + throw new ValidationError( + `Parameter '${key}' exceeds maximum size of ${MAX_LIST_LENGTH} items` + ); + } + } else if (typeof value === "object") { + if (Object.keys(value as Record<string, unknown>).length > MAX_DICT_LENGTH) { + throw new ValidationError( + `Parameter '${key}' exceeds maximum size of ${MAX_DICT_LENGTH} items` + ); + } + } + + if (key === "user" && typeof value !== "string") { + throw new ValidationError(`Parameter '${key}' must be a string`); + } + if ( + (key === "page" || key === "limit" || key === "page_size") && + !Number.isInteger(value) + ) { + throw new ValidationError(`Parameter '${key}' must be an integer`); + } + if (key === "files" && !Array.isArray(value) && typeof value !== "object") { + throw new ValidationError(`Parameter '${key}' must be a list or dict`); + } + if (key === "rating" && value !== "like" && value !== "dislike") { + throw new ValidationError(`Parameter '${key}' must be 'like' or 'dislike'`); + } + }); +} diff --git a/sdks/nodejs-client/src/client/workflow.test.js b/sdks/nodejs-client/src/client/workflow.test.js new file mode 100644 index 0000000000..79c419b55a --- /dev/null +++ b/sdks/nodejs-client/src/client/workflow.test.js @@ -0,0 +1,119 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { WorkflowClient } from "./workflow"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("WorkflowClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("runs workflows with blocking and streaming modes", async () => { + const { client, request, requestStream } = createHttpClientWithSpies(); + const workflow = new WorkflowClient(client); + + await workflow.run({ inputs: { input: "x" }, user: "user" }); + await workflow.run({ input: "x" }, "user", true); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/run", + data: { + inputs: { input: "x" }, + user: "user", + }, + }); + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/run", + data: { + inputs: { input: "x" }, + user: "user", + response_mode: "streaming", + }, + }); + }); + + it("runs workflow by id", async () => { + const { client, request, requestStream } = createHttpClientWithSpies(); + const workflow = new WorkflowClient(client); + + await workflow.runById("wf", { + inputs: { input: "x" }, + user: "user", + response_mode: "blocking", + }); + await workflow.runById("wf", { + inputs: { input: "x" }, + user: "user", + response_mode: "streaming", + }); + + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/wf/run", + data: { + inputs: { input: "x" }, + user: "user", + response_mode: "blocking", + }, + }); + expect(requestStream).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/wf/run", + data: { + inputs: { input: "x" }, + user: "user", + response_mode: "streaming", + }, + }); + }); + + it("gets run details and stops workflow", async () => { + const { client, request } = createHttpClientWithSpies(); + const workflow = new WorkflowClient(client); + + await workflow.getRun("run"); + await workflow.stop("task", "user"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/workflows/run/run", + }); + expect(request).toHaveBeenCalledWith({ + method: "POST", + path: "/workflows/tasks/task/stop", + data: { user: "user" }, + }); + }); + + it("fetches workflow logs", async () => { + const { client, request } = createHttpClientWithSpies(); + const workflow = new WorkflowClient(client); + + // Use createdByEndUserSessionId to filter by user session (backend API parameter) + await workflow.getLogs({ + keyword: "k", + status: "succeeded", + startTime: "2024-01-01", + endTime: "2024-01-02", + createdByEndUserSessionId: "session-123", + page: 1, + limit: 20, + }); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/workflows/logs", + query: { + keyword: "k", + status: "succeeded", + created_at__before: "2024-01-02", + created_at__after: "2024-01-01", + created_by_end_user_session_id: "session-123", + created_by_account: undefined, + page: 1, + limit: 20, + }, + }); + }); +}); diff --git a/sdks/nodejs-client/src/client/workflow.ts b/sdks/nodejs-client/src/client/workflow.ts new file mode 100644 index 0000000000..ae4d5861fa --- /dev/null +++ b/sdks/nodejs-client/src/client/workflow.ts @@ -0,0 +1,165 @@ +import { DifyClient } from "./base"; +import type { WorkflowRunRequest, WorkflowRunResponse } from "../types/workflow"; +import type { DifyResponse, DifyStream, QueryParams } from "../types/common"; +import { + ensureNonEmptyString, + ensureOptionalInt, + ensureOptionalString, +} from "./validation"; + +export class WorkflowClient extends DifyClient { + run( + request: WorkflowRunRequest + ): Promise<DifyResponse<WorkflowRunResponse> | DifyStream<WorkflowRunResponse>>; + run( + inputs: Record<string, unknown>, + user: string, + stream?: boolean + ): Promise<DifyResponse<WorkflowRunResponse> | DifyStream<WorkflowRunResponse>>; + run( + inputOrRequest: WorkflowRunRequest | Record<string, unknown>, + user?: string, + stream = false + ): Promise<DifyResponse<WorkflowRunResponse> | DifyStream<WorkflowRunResponse>> { + let payload: WorkflowRunRequest; + let shouldStream = stream; + + if (user === undefined && "user" in (inputOrRequest as WorkflowRunRequest)) { + payload = inputOrRequest as WorkflowRunRequest; + shouldStream = payload.response_mode === "streaming"; + } else { + ensureNonEmptyString(user, "user"); + payload = { + inputs: inputOrRequest as Record<string, unknown>, + user, + response_mode: stream ? "streaming" : "blocking", + }; + } + + ensureNonEmptyString(payload.user, "user"); + + if (shouldStream) { + return this.http.requestStream<WorkflowRunResponse>({ + method: "POST", + path: "/workflows/run", + data: payload, + }); + } + + return this.http.request<WorkflowRunResponse>({ + method: "POST", + path: "/workflows/run", + data: payload, + }); + } + + runById( + workflowId: string, + request: WorkflowRunRequest + ): Promise<DifyResponse<WorkflowRunResponse> | DifyStream<WorkflowRunResponse>> { + ensureNonEmptyString(workflowId, "workflowId"); + ensureNonEmptyString(request.user, "user"); + if (request.response_mode === "streaming") { + return this.http.requestStream<WorkflowRunResponse>({ + method: "POST", + path: `/workflows/${workflowId}/run`, + data: request, + }); + } + return this.http.request<WorkflowRunResponse>({ + method: "POST", + path: `/workflows/${workflowId}/run`, + data: request, + }); + } + + getRun(workflowRunId: string): Promise<DifyResponse<WorkflowRunResponse>> { + ensureNonEmptyString(workflowRunId, "workflowRunId"); + return this.http.request({ + method: "GET", + path: `/workflows/run/${workflowRunId}`, + }); + } + + stop( + taskId: string, + user: string + ): Promise<DifyResponse<WorkflowRunResponse>> { + ensureNonEmptyString(taskId, "taskId"); + ensureNonEmptyString(user, "user"); + return this.http.request<WorkflowRunResponse>({ + method: "POST", + path: `/workflows/tasks/${taskId}/stop`, + data: { user }, + }); + } + + /** + * Get workflow execution logs with filtering options. + * + * Note: The backend API filters by `createdByEndUserSessionId` (end user session ID) + * or `createdByAccount` (account ID), not by a generic `user` parameter. + */ + getLogs(options?: { + keyword?: string; + status?: string; + createdAtBefore?: string; + createdAtAfter?: string; + createdByEndUserSessionId?: string; + createdByAccount?: string; + page?: number; + limit?: number; + startTime?: string; + endTime?: string; + }): Promise<DifyResponse<Record<string, unknown>>> { + if (options?.keyword) { + ensureOptionalString(options.keyword, "keyword"); + } + if (options?.status) { + ensureOptionalString(options.status, "status"); + } + if (options?.createdAtBefore) { + ensureOptionalString(options.createdAtBefore, "createdAtBefore"); + } + if (options?.createdAtAfter) { + ensureOptionalString(options.createdAtAfter, "createdAtAfter"); + } + if (options?.createdByEndUserSessionId) { + ensureOptionalString( + options.createdByEndUserSessionId, + "createdByEndUserSessionId" + ); + } + if (options?.createdByAccount) { + ensureOptionalString(options.createdByAccount, "createdByAccount"); + } + if (options?.startTime) { + ensureOptionalString(options.startTime, "startTime"); + } + if (options?.endTime) { + ensureOptionalString(options.endTime, "endTime"); + } + ensureOptionalInt(options?.page, "page"); + ensureOptionalInt(options?.limit, "limit"); + + const createdAtAfter = options?.createdAtAfter ?? options?.startTime; + const createdAtBefore = options?.createdAtBefore ?? options?.endTime; + + const query: QueryParams = { + keyword: options?.keyword, + status: options?.status, + created_at__before: createdAtBefore, + created_at__after: createdAtAfter, + created_by_end_user_session_id: options?.createdByEndUserSessionId, + created_by_account: options?.createdByAccount, + page: options?.page, + limit: options?.limit, + }; + + return this.http.request({ + method: "GET", + path: "/workflows/logs", + query, + }); + } +} diff --git a/sdks/nodejs-client/src/client/workspace.test.js b/sdks/nodejs-client/src/client/workspace.test.js new file mode 100644 index 0000000000..f8fb6e375a --- /dev/null +++ b/sdks/nodejs-client/src/client/workspace.test.js @@ -0,0 +1,21 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { WorkspaceClient } from "./workspace"; +import { createHttpClientWithSpies } from "../../tests/test-utils"; + +describe("WorkspaceClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it("gets models by type", async () => { + const { client, request } = createHttpClientWithSpies(); + const workspace = new WorkspaceClient(client); + + await workspace.getModelsByType("llm"); + + expect(request).toHaveBeenCalledWith({ + method: "GET", + path: "/workspaces/current/models/model-types/llm", + }); + }); +}); diff --git a/sdks/nodejs-client/src/client/workspace.ts b/sdks/nodejs-client/src/client/workspace.ts new file mode 100644 index 0000000000..daee540c99 --- /dev/null +++ b/sdks/nodejs-client/src/client/workspace.ts @@ -0,0 +1,16 @@ +import { DifyClient } from "./base"; +import type { WorkspaceModelType, WorkspaceModelsResponse } from "../types/workspace"; +import type { DifyResponse } from "../types/common"; +import { ensureNonEmptyString } from "./validation"; + +export class WorkspaceClient extends DifyClient { + async getModelsByType( + modelType: WorkspaceModelType + ): Promise<DifyResponse<WorkspaceModelsResponse>> { + ensureNonEmptyString(modelType, "modelType"); + return this.http.request({ + method: "GET", + path: `/workspaces/current/models/model-types/${modelType}`, + }); + } +} diff --git a/sdks/nodejs-client/src/errors/dify-error.test.js b/sdks/nodejs-client/src/errors/dify-error.test.js new file mode 100644 index 0000000000..d151eca391 --- /dev/null +++ b/sdks/nodejs-client/src/errors/dify-error.test.js @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { + APIError, + AuthenticationError, + DifyError, + FileUploadError, + NetworkError, + RateLimitError, + TimeoutError, + ValidationError, +} from "./dify-error"; + +describe("Dify errors", () => { + it("sets base error fields", () => { + const err = new DifyError("base", { + statusCode: 400, + responseBody: { message: "bad" }, + requestId: "req", + retryAfter: 1, + }); + expect(err.name).toBe("DifyError"); + expect(err.statusCode).toBe(400); + expect(err.responseBody).toEqual({ message: "bad" }); + expect(err.requestId).toBe("req"); + expect(err.retryAfter).toBe(1); + }); + + it("creates specific error types", () => { + expect(new APIError("api").name).toBe("APIError"); + expect(new AuthenticationError("auth").name).toBe("AuthenticationError"); + expect(new RateLimitError("rate").name).toBe("RateLimitError"); + expect(new ValidationError("val").name).toBe("ValidationError"); + expect(new NetworkError("net").name).toBe("NetworkError"); + expect(new TimeoutError("timeout").name).toBe("TimeoutError"); + expect(new FileUploadError("upload").name).toBe("FileUploadError"); + }); +}); diff --git a/sdks/nodejs-client/src/errors/dify-error.ts b/sdks/nodejs-client/src/errors/dify-error.ts new file mode 100644 index 0000000000..e393e1b132 --- /dev/null +++ b/sdks/nodejs-client/src/errors/dify-error.ts @@ -0,0 +1,75 @@ +export type DifyErrorOptions = { + statusCode?: number; + responseBody?: unknown; + requestId?: string; + retryAfter?: number; + cause?: unknown; +}; + +export class DifyError extends Error { + statusCode?: number; + responseBody?: unknown; + requestId?: string; + retryAfter?: number; + + constructor(message: string, options: DifyErrorOptions = {}) { + super(message); + this.name = "DifyError"; + this.statusCode = options.statusCode; + this.responseBody = options.responseBody; + this.requestId = options.requestId; + this.retryAfter = options.retryAfter; + if (options.cause) { + (this as { cause?: unknown }).cause = options.cause; + } + } +} + +export class APIError extends DifyError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "APIError"; + } +} + +export class AuthenticationError extends APIError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "AuthenticationError"; + } +} + +export class RateLimitError extends APIError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "RateLimitError"; + } +} + +export class ValidationError extends APIError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "ValidationError"; + } +} + +export class NetworkError extends DifyError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "NetworkError"; + } +} + +export class TimeoutError extends DifyError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "TimeoutError"; + } +} + +export class FileUploadError extends DifyError { + constructor(message: string, options: DifyErrorOptions = {}) { + super(message, options); + this.name = "FileUploadError"; + } +} diff --git a/sdks/nodejs-client/src/http/client.test.js b/sdks/nodejs-client/src/http/client.test.js new file mode 100644 index 0000000000..05892547ed --- /dev/null +++ b/sdks/nodejs-client/src/http/client.test.js @@ -0,0 +1,304 @@ +import axios from "axios"; +import { Readable } from "node:stream"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + APIError, + AuthenticationError, + FileUploadError, + NetworkError, + RateLimitError, + TimeoutError, + ValidationError, +} from "../errors/dify-error"; +import { HttpClient } from "./client"; + +describe("HttpClient", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + it("builds requests with auth headers and JSON content type", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: { ok: true }, + headers: { "x-request-id": "req" }, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + + const client = new HttpClient({ apiKey: "test" }); + const response = await client.request({ + method: "POST", + path: "/chat-messages", + data: { user: "u" }, + }); + + expect(response.requestId).toBe("req"); + const config = mockRequest.mock.calls[0][0]; + expect(config.headers.Authorization).toBe("Bearer test"); + expect(config.headers["Content-Type"]).toBe("application/json"); + expect(config.responseType).toBe("json"); + }); + + it("serializes array query params", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: "ok", + headers: {}, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + + const client = new HttpClient({ apiKey: "test" }); + await client.requestRaw({ + method: "GET", + path: "/datasets", + query: { tag_ids: ["a", "b"], limit: 2 }, + }); + + const config = mockRequest.mock.calls[0][0]; + const queryString = config.paramsSerializer.serialize({ + tag_ids: ["a", "b"], + limit: 2, + }); + expect(queryString).toBe("tag_ids=a&tag_ids=b&limit=2"); + }); + + it("returns SSE stream helpers", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: Readable.from(["data: {\"text\":\"hi\"}\n\n"]), + headers: { "x-request-id": "req" }, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + + const client = new HttpClient({ apiKey: "test" }); + const stream = await client.requestStream({ + method: "POST", + path: "/chat-messages", + data: { user: "u" }, + }); + + expect(stream.status).toBe(200); + expect(stream.requestId).toBe("req"); + await expect(stream.toText()).resolves.toBe("hi"); + }); + + it("returns binary stream helpers", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: Readable.from(["chunk"]), + headers: { "x-request-id": "req" }, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + + const client = new HttpClient({ apiKey: "test" }); + const stream = await client.requestBinaryStream({ + method: "POST", + path: "/text-to-audio", + data: { user: "u", text: "hi" }, + }); + + expect(stream.status).toBe(200); + expect(stream.requestId).toBe("req"); + }); + + it("respects form-data headers", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: "ok", + headers: {}, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + + const client = new HttpClient({ apiKey: "test" }); + const form = { + append: () => {}, + getHeaders: () => ({ "content-type": "multipart/form-data; boundary=abc" }), + }; + + await client.requestRaw({ + method: "POST", + path: "/files/upload", + data: form, + }); + + const config = mockRequest.mock.calls[0][0]; + expect(config.headers["content-type"]).toBe( + "multipart/form-data; boundary=abc" + ); + expect(config.headers["Content-Type"]).toBeUndefined(); + }); + + it("maps 401 and 429 errors", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", maxRetries: 0 }); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + response: { + status: 401, + data: { message: "unauthorized" }, + headers: {}, + }, + }); + await expect( + client.requestRaw({ method: "GET", path: "/meta" }) + ).rejects.toBeInstanceOf(AuthenticationError); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + response: { + status: 429, + data: { message: "rate" }, + headers: { "retry-after": "2" }, + }, + }); + const error = await client + .requestRaw({ method: "GET", path: "/meta" }) + .catch((err) => err); + expect(error).toBeInstanceOf(RateLimitError); + expect(error.retryAfter).toBe(2); + }); + + it("maps validation and upload errors", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", maxRetries: 0 }); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + response: { + status: 422, + data: { message: "invalid" }, + headers: {}, + }, + }); + await expect( + client.requestRaw({ method: "POST", path: "/chat-messages", data: { user: "u" } }) + ).rejects.toBeInstanceOf(ValidationError); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + config: { url: "/files/upload" }, + response: { + status: 400, + data: { message: "bad upload" }, + headers: {}, + }, + }); + await expect( + client.requestRaw({ method: "POST", path: "/files/upload", data: { user: "u" } }) + ).rejects.toBeInstanceOf(FileUploadError); + }); + + it("maps timeout and network errors", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", maxRetries: 0 }); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + code: "ECONNABORTED", + message: "timeout", + }); + await expect( + client.requestRaw({ method: "GET", path: "/meta" }) + ).rejects.toBeInstanceOf(TimeoutError); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + message: "network", + }); + await expect( + client.requestRaw({ method: "GET", path: "/meta" }) + ).rejects.toBeInstanceOf(NetworkError); + }); + + it("retries on timeout errors", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", maxRetries: 1, retryDelay: 0 }); + + mockRequest + .mockRejectedValueOnce({ + isAxiosError: true, + code: "ECONNABORTED", + message: "timeout", + }) + .mockResolvedValueOnce({ status: 200, data: "ok", headers: {} }); + + await client.requestRaw({ method: "GET", path: "/meta" }); + expect(mockRequest).toHaveBeenCalledTimes(2); + }); + + it("validates query parameters before request", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test" }); + + await expect( + client.requestRaw({ method: "GET", path: "/meta", query: { user: 1 } }) + ).rejects.toBeInstanceOf(ValidationError); + expect(mockRequest).not.toHaveBeenCalled(); + }); + + it("returns APIError for other http failures", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", maxRetries: 0 }); + + mockRequest.mockRejectedValueOnce({ + isAxiosError: true, + response: { status: 500, data: { message: "server" }, headers: {} }, + }); + + await expect( + client.requestRaw({ method: "GET", path: "/meta" }) + ).rejects.toBeInstanceOf(APIError); + }); + + it("logs requests and responses when enableLogging is true", async () => { + const mockRequest = vi.fn().mockResolvedValue({ + status: 200, + data: { ok: true }, + headers: {}, + }); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => {}); + + const client = new HttpClient({ apiKey: "test", enableLogging: true }); + await client.requestRaw({ method: "GET", path: "/meta" }); + + expect(consoleInfo).toHaveBeenCalledWith( + expect.stringContaining("dify-client-node response 200 GET") + ); + consoleInfo.mockRestore(); + }); + + it("logs retry attempts when enableLogging is true", async () => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => {}); + + const client = new HttpClient({ + apiKey: "test", + maxRetries: 1, + retryDelay: 0, + enableLogging: true, + }); + + mockRequest + .mockRejectedValueOnce({ + isAxiosError: true, + code: "ECONNABORTED", + message: "timeout", + }) + .mockResolvedValueOnce({ status: 200, data: "ok", headers: {} }); + + await client.requestRaw({ method: "GET", path: "/meta" }); + + expect(consoleInfo).toHaveBeenCalledWith( + expect.stringContaining("dify-client-node retry") + ); + consoleInfo.mockRestore(); + }); +}); diff --git a/sdks/nodejs-client/src/http/client.ts b/sdks/nodejs-client/src/http/client.ts new file mode 100644 index 0000000000..44b63c9903 --- /dev/null +++ b/sdks/nodejs-client/src/http/client.ts @@ -0,0 +1,368 @@ +import axios from "axios"; +import type { + AxiosError, + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, +} from "axios"; +import type { Readable } from "node:stream"; +import { + DEFAULT_BASE_URL, + DEFAULT_MAX_RETRIES, + DEFAULT_RETRY_DELAY_SECONDS, + DEFAULT_TIMEOUT_SECONDS, +} from "../types/common"; +import type { + DifyClientConfig, + DifyResponse, + Headers, + QueryParams, + RequestMethod, +} from "../types/common"; +import type { DifyError } from "../errors/dify-error"; +import { + APIError, + AuthenticationError, + FileUploadError, + NetworkError, + RateLimitError, + TimeoutError, + ValidationError, +} from "../errors/dify-error"; +import { getFormDataHeaders, isFormData } from "./form-data"; +import { createBinaryStream, createSseStream } from "./sse"; +import { getRetryDelayMs, shouldRetry, sleep } from "./retry"; +import { validateParams } from "../client/validation"; + +const DEFAULT_USER_AGENT = "dify-client-node"; + +export type RequestOptions = { + method: RequestMethod; + path: string; + query?: QueryParams; + data?: unknown; + headers?: Headers; + responseType?: AxiosRequestConfig["responseType"]; +}; + +export type HttpClientSettings = Required< + Omit<DifyClientConfig, "apiKey"> +> & { + apiKey: string; +}; + +const normalizeSettings = (config: DifyClientConfig): HttpClientSettings => ({ + apiKey: config.apiKey, + baseUrl: config.baseUrl ?? DEFAULT_BASE_URL, + timeout: config.timeout ?? DEFAULT_TIMEOUT_SECONDS, + maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES, + retryDelay: config.retryDelay ?? DEFAULT_RETRY_DELAY_SECONDS, + enableLogging: config.enableLogging ?? false, +}); + +const normalizeHeaders = (headers: AxiosResponse["headers"]): Headers => { + const result: Headers = {}; + if (!headers) { + return result; + } + Object.entries(headers).forEach(([key, value]) => { + if (Array.isArray(value)) { + result[key.toLowerCase()] = value.join(", "); + } else if (typeof value === "string") { + result[key.toLowerCase()] = value; + } else if (typeof value === "number") { + result[key.toLowerCase()] = value.toString(); + } + }); + return result; +}; + +const resolveRequestId = (headers: Headers): string | undefined => + headers["x-request-id"] ?? headers["x-requestid"]; + +const buildRequestUrl = (baseUrl: string, path: string): string => { + const trimmed = baseUrl.replace(/\/+$/, ""); + return `${trimmed}${path}`; +}; + +const buildQueryString = (params?: QueryParams): string => { + if (!params) { + return ""; + } + const searchParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((item) => { + searchParams.append(key, String(item)); + }); + return; + } + searchParams.append(key, String(value)); + }); + return searchParams.toString(); +}; + +const parseRetryAfterSeconds = (headerValue?: string): number | undefined => { + if (!headerValue) { + return undefined; + } + const asNumber = Number.parseInt(headerValue, 10); + if (!Number.isNaN(asNumber)) { + return asNumber; + } + const asDate = Date.parse(headerValue); + if (!Number.isNaN(asDate)) { + const diff = asDate - Date.now(); + return diff > 0 ? Math.ceil(diff / 1000) : 0; + } + return undefined; +}; + +const isReadableStream = (value: unknown): value is Readable => { + if (!value || typeof value !== "object") { + return false; + } + return typeof (value as { pipe?: unknown }).pipe === "function"; +}; + +const isUploadLikeRequest = (config?: AxiosRequestConfig): boolean => { + const url = (config?.url ?? "").toLowerCase(); + if (!url) { + return false; + } + return ( + url.includes("upload") || + url.includes("/files/") || + url.includes("audio-to-text") || + url.includes("create_by_file") || + url.includes("update_by_file") + ); +}; + +const resolveErrorMessage = (status: number, responseBody: unknown): string => { + if (typeof responseBody === "string" && responseBody.trim().length > 0) { + return responseBody; + } + if ( + responseBody && + typeof responseBody === "object" && + "message" in responseBody + ) { + const message = (responseBody as Record<string, unknown>).message; + if (typeof message === "string" && message.trim().length > 0) { + return message; + } + } + return `Request failed with status code ${status}`; +}; + +const mapAxiosError = (error: unknown): DifyError => { + if (axios.isAxiosError(error)) { + const axiosError = error as AxiosError; + if (axiosError.response) { + const status = axiosError.response.status; + const headers = normalizeHeaders(axiosError.response.headers); + const requestId = resolveRequestId(headers); + const responseBody = axiosError.response.data; + const message = resolveErrorMessage(status, responseBody); + + if (status === 401) { + return new AuthenticationError(message, { + statusCode: status, + responseBody, + requestId, + }); + } + if (status === 429) { + const retryAfter = parseRetryAfterSeconds(headers["retry-after"]); + return new RateLimitError(message, { + statusCode: status, + responseBody, + requestId, + retryAfter, + }); + } + if (status === 422) { + return new ValidationError(message, { + statusCode: status, + responseBody, + requestId, + }); + } + if (status === 400) { + if (isUploadLikeRequest(axiosError.config)) { + return new FileUploadError(message, { + statusCode: status, + responseBody, + requestId, + }); + } + } + return new APIError(message, { + statusCode: status, + responseBody, + requestId, + }); + } + if (axiosError.code === "ECONNABORTED") { + return new TimeoutError("Request timed out", { cause: axiosError }); + } + return new NetworkError(axiosError.message, { cause: axiosError }); + } + if (error instanceof Error) { + return new NetworkError(error.message, { cause: error }); + } + return new NetworkError("Unexpected network error", { cause: error }); +}; + +export class HttpClient { + private axios: AxiosInstance; + private settings: HttpClientSettings; + + constructor(config: DifyClientConfig) { + this.settings = normalizeSettings(config); + this.axios = axios.create({ + baseURL: this.settings.baseUrl, + timeout: this.settings.timeout * 1000, + }); + } + + updateApiKey(apiKey: string): void { + this.settings.apiKey = apiKey; + } + + getSettings(): HttpClientSettings { + return { ...this.settings }; + } + + async request<T>(options: RequestOptions): Promise<DifyResponse<T>> { + const response = await this.requestRaw(options); + const headers = normalizeHeaders(response.headers); + return { + data: response.data as T, + status: response.status, + headers, + requestId: resolveRequestId(headers), + }; + } + + async requestStream<T>(options: RequestOptions) { + const response = await this.requestRaw({ + ...options, + responseType: "stream", + }); + const headers = normalizeHeaders(response.headers); + return createSseStream<T>(response.data as Readable, { + status: response.status, + headers, + requestId: resolveRequestId(headers), + }); + } + + async requestBinaryStream(options: RequestOptions) { + const response = await this.requestRaw({ + ...options, + responseType: "stream", + }); + const headers = normalizeHeaders(response.headers); + return createBinaryStream(response.data as Readable, { + status: response.status, + headers, + requestId: resolveRequestId(headers), + }); + } + + async requestRaw(options: RequestOptions): Promise<AxiosResponse> { + const { method, path, query, data, headers, responseType } = options; + const { apiKey, enableLogging, maxRetries, retryDelay, timeout } = + this.settings; + + if (query) { + validateParams(query as Record<string, unknown>); + } + if ( + data && + typeof data === "object" && + !Array.isArray(data) && + !isFormData(data) && + !isReadableStream(data) + ) { + validateParams(data as Record<string, unknown>); + } + + const requestHeaders: Headers = { + Authorization: `Bearer ${apiKey}`, + ...headers, + }; + if ( + typeof process !== "undefined" && + !!process.versions?.node && + !requestHeaders["User-Agent"] && + !requestHeaders["user-agent"] + ) { + requestHeaders["User-Agent"] = DEFAULT_USER_AGENT; + } + + if (isFormData(data)) { + Object.assign(requestHeaders, getFormDataHeaders(data)); + } else if (data && method !== "GET") { + requestHeaders["Content-Type"] = "application/json"; + } + + const url = buildRequestUrl(this.settings.baseUrl, path); + + if (enableLogging) { + console.info(`dify-client-node request ${method} ${url}`); + } + + const axiosConfig: AxiosRequestConfig = { + method, + url: path, + params: query, + paramsSerializer: { + serialize: (params) => buildQueryString(params as QueryParams), + }, + headers: requestHeaders, + responseType: responseType ?? "json", + timeout: timeout * 1000, + }; + + if (method !== "GET" && data !== undefined) { + axiosConfig.data = data; + } + + let attempt = 0; + // `attempt` is a zero-based retry counter + // Total attempts = 1 (initial) + maxRetries + // e.g., maxRetries=3 means: attempt 0 (initial), then retries at 1, 2, 3 + while (true) { + try { + const response = await this.axios.request(axiosConfig); + if (enableLogging) { + console.info( + `dify-client-node response ${response.status} ${method} ${url}` + ); + } + return response; + } catch (error) { + const mapped = mapAxiosError(error); + if (!shouldRetry(mapped, attempt, maxRetries)) { + throw mapped; + } + const retryAfterSeconds = + mapped instanceof RateLimitError ? mapped.retryAfter : undefined; + const delay = getRetryDelayMs(attempt + 1, retryDelay, retryAfterSeconds); + if (enableLogging) { + console.info( + `dify-client-node retry ${attempt + 1} in ${delay}ms for ${method} ${url}` + ); + } + attempt += 1; + await sleep(delay); + } + } + } +} diff --git a/sdks/nodejs-client/src/http/form-data.test.js b/sdks/nodejs-client/src/http/form-data.test.js new file mode 100644 index 0000000000..2938e41435 --- /dev/null +++ b/sdks/nodejs-client/src/http/form-data.test.js @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import { getFormDataHeaders, isFormData } from "./form-data"; + +describe("form-data helpers", () => { + it("detects form-data like objects", () => { + const formLike = { + append: () => {}, + getHeaders: () => ({ "content-type": "multipart/form-data" }), + }; + expect(isFormData(formLike)).toBe(true); + expect(isFormData({})).toBe(false); + }); + + it("returns headers from form-data", () => { + const formLike = { + append: () => {}, + getHeaders: () => ({ "content-type": "multipart/form-data" }), + }; + expect(getFormDataHeaders(formLike)).toEqual({ + "content-type": "multipart/form-data", + }); + }); +}); diff --git a/sdks/nodejs-client/src/http/form-data.ts b/sdks/nodejs-client/src/http/form-data.ts new file mode 100644 index 0000000000..2efa23e54e --- /dev/null +++ b/sdks/nodejs-client/src/http/form-data.ts @@ -0,0 +1,31 @@ +import type { Headers } from "../types/common"; + +export type FormDataLike = { + append: (...args: unknown[]) => void; + getHeaders?: () => Headers; + constructor?: { name?: string }; +}; + +export const isFormData = (value: unknown): value is FormDataLike => { + if (!value || typeof value !== "object") { + return false; + } + if (typeof FormData !== "undefined" && value instanceof FormData) { + return true; + } + const candidate = value as FormDataLike; + if (typeof candidate.append !== "function") { + return false; + } + if (typeof candidate.getHeaders === "function") { + return true; + } + return candidate.constructor?.name === "FormData"; +}; + +export const getFormDataHeaders = (form: FormDataLike): Headers => { + if (typeof form.getHeaders === "function") { + return form.getHeaders(); + } + return {}; +}; diff --git a/sdks/nodejs-client/src/http/retry.test.js b/sdks/nodejs-client/src/http/retry.test.js new file mode 100644 index 0000000000..fc017f631b --- /dev/null +++ b/sdks/nodejs-client/src/http/retry.test.js @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import { getRetryDelayMs, shouldRetry } from "./retry"; +import { NetworkError, RateLimitError, TimeoutError } from "../errors/dify-error"; + +const withMockedRandom = (value, fn) => { + const original = Math.random; + Math.random = () => value; + try { + fn(); + } finally { + Math.random = original; + } +}; + +describe("retry helpers", () => { + it("getRetryDelayMs honors retry-after header", () => { + expect(getRetryDelayMs(1, 1, 3)).toBe(3000); + }); + + it("getRetryDelayMs uses exponential backoff with jitter", () => { + withMockedRandom(0, () => { + expect(getRetryDelayMs(1, 1)).toBe(1000); + expect(getRetryDelayMs(2, 1)).toBe(2000); + expect(getRetryDelayMs(3, 1)).toBe(4000); + }); + }); + + it("shouldRetry respects max retries", () => { + expect(shouldRetry(new TimeoutError("timeout"), 3, 3)).toBe(false); + }); + + it("shouldRetry retries on network, timeout, and rate limit", () => { + expect(shouldRetry(new TimeoutError("timeout"), 0, 3)).toBe(true); + expect(shouldRetry(new NetworkError("network"), 0, 3)).toBe(true); + expect(shouldRetry(new RateLimitError("limit"), 0, 3)).toBe(true); + expect(shouldRetry(new Error("other"), 0, 3)).toBe(false); + }); +}); diff --git a/sdks/nodejs-client/src/http/retry.ts b/sdks/nodejs-client/src/http/retry.ts new file mode 100644 index 0000000000..3776b78d5f --- /dev/null +++ b/sdks/nodejs-client/src/http/retry.ts @@ -0,0 +1,40 @@ +import { RateLimitError, NetworkError, TimeoutError } from "../errors/dify-error"; + +export const sleep = (ms: number): Promise<void> => + new Promise((resolve) => { + setTimeout(resolve, ms); + }); + +export const getRetryDelayMs = ( + attempt: number, + retryDelaySeconds: number, + retryAfterSeconds?: number +): number => { + if (retryAfterSeconds && retryAfterSeconds > 0) { + return retryAfterSeconds * 1000; + } + const base = retryDelaySeconds * 1000; + const exponential = base * Math.pow(2, Math.max(0, attempt - 1)); + const jitter = Math.random() * base; + return exponential + jitter; +}; + +export const shouldRetry = ( + error: unknown, + attempt: number, + maxRetries: number +): boolean => { + if (attempt >= maxRetries) { + return false; + } + if (error instanceof TimeoutError) { + return true; + } + if (error instanceof NetworkError) { + return true; + } + if (error instanceof RateLimitError) { + return true; + } + return false; +}; diff --git a/sdks/nodejs-client/src/http/sse.test.js b/sdks/nodejs-client/src/http/sse.test.js new file mode 100644 index 0000000000..fff85fd29b --- /dev/null +++ b/sdks/nodejs-client/src/http/sse.test.js @@ -0,0 +1,76 @@ +import { Readable } from "node:stream"; +import { describe, expect, it } from "vitest"; +import { createBinaryStream, createSseStream, parseSseStream } from "./sse"; + +describe("sse parsing", () => { + it("parses event and data lines", async () => { + const stream = Readable.from([ + "event: message\n", + "data: {\"answer\":\"hi\"}\n", + "\n", + ]); + const events = []; + for await (const event of parseSseStream(stream)) { + events.push(event); + } + expect(events).toHaveLength(1); + expect(events[0].event).toBe("message"); + expect(events[0].data).toEqual({ answer: "hi" }); + }); + + it("handles multi-line data payloads", async () => { + const stream = Readable.from(["data: line1\n", "data: line2\n", "\n"]); + const events = []; + for await (const event of parseSseStream(stream)) { + events.push(event); + } + expect(events[0].raw).toBe("line1\nline2"); + expect(events[0].data).toBe("line1\nline2"); + }); + + it("createSseStream exposes toText", async () => { + const stream = Readable.from([ + "data: {\"answer\":\"hello\"}\n\n", + "data: {\"delta\":\" world\"}\n\n", + ]); + const sseStream = createSseStream(stream, { + status: 200, + headers: {}, + requestId: "req", + }); + const text = await sseStream.toText(); + expect(text).toBe("hello world"); + }); + + it("toText extracts text from string data", async () => { + const stream = Readable.from(["data: plain text\n\n"]); + const sseStream = createSseStream(stream, { status: 200, headers: {} }); + const text = await sseStream.toText(); + expect(text).toBe("plain text"); + }); + + it("toText extracts text field from object", async () => { + const stream = Readable.from(['data: {"text":"hello"}\n\n']); + const sseStream = createSseStream(stream, { status: 200, headers: {} }); + const text = await sseStream.toText(); + expect(text).toBe("hello"); + }); + + it("toText returns empty for invalid data", async () => { + const stream = Readable.from(["data: null\n\n", "data: 123\n\n"]); + const sseStream = createSseStream(stream, { status: 200, headers: {} }); + const text = await sseStream.toText(); + expect(text).toBe(""); + }); + + it("createBinaryStream exposes metadata", () => { + const stream = Readable.from(["chunk"]); + const binary = createBinaryStream(stream, { + status: 200, + headers: { "content-type": "audio/mpeg" }, + requestId: "req", + }); + expect(binary.status).toBe(200); + expect(binary.headers["content-type"]).toBe("audio/mpeg"); + }); +}); diff --git a/sdks/nodejs-client/src/http/sse.ts b/sdks/nodejs-client/src/http/sse.ts new file mode 100644 index 0000000000..ed5a17fe39 --- /dev/null +++ b/sdks/nodejs-client/src/http/sse.ts @@ -0,0 +1,133 @@ +import type { Readable } from "node:stream"; +import { StringDecoder } from "node:string_decoder"; +import type { BinaryStream, DifyStream, Headers, StreamEvent } from "../types/common"; + +const readLines = async function* (stream: Readable): AsyncIterable<string> { + const decoder = new StringDecoder("utf8"); + let buffered = ""; + for await (const chunk of stream) { + buffered += decoder.write(chunk as Buffer); + let index = buffered.indexOf("\n"); + while (index >= 0) { + let line = buffered.slice(0, index); + buffered = buffered.slice(index + 1); + if (line.endsWith("\r")) { + line = line.slice(0, -1); + } + yield line; + index = buffered.indexOf("\n"); + } + } + buffered += decoder.end(); + if (buffered) { + yield buffered; + } +}; + +const parseMaybeJson = (value: string): unknown => { + if (!value) { + return null; + } + try { + return JSON.parse(value); + } catch { + return value; + } +}; + +export const parseSseStream = async function* <T>( + stream: Readable +): AsyncIterable<StreamEvent<T>> { + let eventName: string | undefined; + const dataLines: string[] = []; + + const emitEvent = function* (): Iterable<StreamEvent<T>> { + if (!eventName && dataLines.length === 0) { + return; + } + const raw = dataLines.join("\n"); + const parsed = parseMaybeJson(raw) as T | string | null; + yield { + event: eventName, + data: parsed, + raw, + }; + eventName = undefined; + dataLines.length = 0; + }; + + for await (const line of readLines(stream)) { + if (!line) { + yield* emitEvent(); + continue; + } + if (line.startsWith(":")) { + continue; + } + if (line.startsWith("event:")) { + eventName = line.slice("event:".length).trim(); + continue; + } + if (line.startsWith("data:")) { + dataLines.push(line.slice("data:".length).trimStart()); + continue; + } + } + + yield* emitEvent(); +}; + +const extractTextFromEvent = (data: unknown): string => { + if (typeof data === "string") { + return data; + } + if (!data || typeof data !== "object") { + return ""; + } + const record = data as Record<string, unknown>; + if (typeof record.answer === "string") { + return record.answer; + } + if (typeof record.text === "string") { + return record.text; + } + if (typeof record.delta === "string") { + return record.delta; + } + return ""; +}; + +export const createSseStream = <T>( + stream: Readable, + meta: { status: number; headers: Headers; requestId?: string } +): DifyStream<T> => { + const iterator = parseSseStream<T>(stream)[Symbol.asyncIterator](); + const iterable = { + [Symbol.asyncIterator]: () => iterator, + data: stream, + status: meta.status, + headers: meta.headers, + requestId: meta.requestId, + toReadable: () => stream, + toText: async () => { + let text = ""; + for await (const event of iterable) { + text += extractTextFromEvent(event.data); + } + return text; + }, + } satisfies DifyStream<T>; + + return iterable; +}; + +export const createBinaryStream = ( + stream: Readable, + meta: { status: number; headers: Headers; requestId?: string } +): BinaryStream => ({ + data: stream, + status: meta.status, + headers: meta.headers, + requestId: meta.requestId, + toReadable: () => stream, +}); diff --git a/sdks/nodejs-client/src/index.test.js b/sdks/nodejs-client/src/index.test.js new file mode 100644 index 0000000000..289f4d9b1b --- /dev/null +++ b/sdks/nodejs-client/src/index.test.js @@ -0,0 +1,227 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { ChatClient, DifyClient, WorkflowClient, BASE_URL, routes } from "./index"; +import axios from "axios"; + +const mockRequest = vi.fn(); + +const setupAxiosMock = () => { + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); +}; + +beforeEach(() => { + vi.restoreAllMocks(); + mockRequest.mockReset(); + setupAxiosMock(); +}); + +describe("Client", () => { + it("should create a client", () => { + new DifyClient("test"); + + expect(axios.create).toHaveBeenCalledWith({ + baseURL: BASE_URL, + timeout: 60000, + }); + }); + + it("should update the api key", () => { + const difyClient = new DifyClient("test"); + difyClient.updateApiKey("test2"); + + expect(difyClient.getHttpClient().getSettings().apiKey).toBe("test2"); + }); +}); + +describe("Send Requests", () => { + it("should make a successful request to the application parameter", async () => { + const difyClient = new DifyClient("test"); + const method = "GET"; + const endpoint = routes.application.url(); + mockRequest.mockResolvedValue({ + status: 200, + data: "response", + headers: {}, + }); + + await difyClient.sendRequest(method, endpoint); + + const requestConfig = mockRequest.mock.calls[0][0]; + expect(requestConfig).toMatchObject({ + method, + url: endpoint, + params: undefined, + responseType: "json", + timeout: 60000, + }); + expect(requestConfig.headers.Authorization).toBe("Bearer test"); + }); + + it("uses the getMeta route configuration", async () => { + const difyClient = new DifyClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await difyClient.getMeta("end-user"); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: routes.getMeta.method, + url: routes.getMeta.url(), + params: { user: "end-user" }, + headers: expect.objectContaining({ + Authorization: "Bearer test", + }), + responseType: "json", + timeout: 60000, + })); + }); +}); + +describe("File uploads", () => { + const OriginalFormData = globalThis.FormData; + + beforeAll(() => { + globalThis.FormData = class FormDataMock { + append() {} + + getHeaders() { + return { + "content-type": "multipart/form-data; boundary=test", + }; + } + }; + }); + + afterAll(() => { + globalThis.FormData = OriginalFormData; + }); + + it("does not override multipart boundary headers for FormData", async () => { + const difyClient = new DifyClient("test"); + const form = new globalThis.FormData(); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await difyClient.fileUpload(form, "end-user"); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: routes.fileUpload.method, + url: routes.fileUpload.url(), + params: undefined, + headers: expect.objectContaining({ + Authorization: "Bearer test", + "content-type": "multipart/form-data; boundary=test", + }), + responseType: "json", + timeout: 60000, + data: form, + })); + }); +}); + +describe("Workflow client", () => { + it("uses tasks stop path for workflow stop", async () => { + const workflowClient = new WorkflowClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "stopped", headers: {} }); + + await workflowClient.stop("task-1", "end-user"); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: routes.stopWorkflow.method, + url: routes.stopWorkflow.url("task-1"), + params: undefined, + headers: expect.objectContaining({ + Authorization: "Bearer test", + "Content-Type": "application/json", + }), + responseType: "json", + timeout: 60000, + data: { user: "end-user" }, + })); + }); + + it("maps workflow log filters to service api params", async () => { + const workflowClient = new WorkflowClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await workflowClient.getLogs({ + createdAtAfter: "2024-01-01T00:00:00Z", + createdAtBefore: "2024-01-02T00:00:00Z", + createdByEndUserSessionId: "sess-1", + createdByAccount: "acc-1", + page: 2, + limit: 10, + }); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: "GET", + url: "/workflows/logs", + params: { + created_at__after: "2024-01-01T00:00:00Z", + created_at__before: "2024-01-02T00:00:00Z", + created_by_end_user_session_id: "sess-1", + created_by_account: "acc-1", + page: 2, + limit: 10, + }, + headers: expect.objectContaining({ + Authorization: "Bearer test", + }), + responseType: "json", + timeout: 60000, + })); + }); +}); + +describe("Chat client", () => { + it("places user in query for suggested messages", async () => { + const chatClient = new ChatClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await chatClient.getSuggested("msg-1", "end-user"); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: routes.getSuggested.method, + url: routes.getSuggested.url("msg-1"), + params: { user: "end-user" }, + headers: expect.objectContaining({ + Authorization: "Bearer test", + }), + responseType: "json", + timeout: 60000, + })); + }); + + it("uses last_id when listing conversations", async () => { + const chatClient = new ChatClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await chatClient.getConversations("end-user", "last-1", 10); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: routes.getConversations.method, + url: routes.getConversations.url(), + params: { user: "end-user", last_id: "last-1", limit: 10 }, + headers: expect.objectContaining({ + Authorization: "Bearer test", + }), + responseType: "json", + timeout: 60000, + })); + }); + + it("lists app feedbacks without user params", async () => { + const chatClient = new ChatClient("test"); + mockRequest.mockResolvedValue({ status: 200, data: "ok", headers: {} }); + + await chatClient.getAppFeedbacks(1, 20); + + expect(mockRequest).toHaveBeenCalledWith(expect.objectContaining({ + method: "GET", + url: "/app/feedbacks", + params: { page: 1, limit: 20 }, + headers: expect.objectContaining({ + Authorization: "Bearer test", + }), + responseType: "json", + timeout: 60000, + })); + }); +}); diff --git a/sdks/nodejs-client/src/index.ts b/sdks/nodejs-client/src/index.ts new file mode 100644 index 0000000000..3ba3757baa --- /dev/null +++ b/sdks/nodejs-client/src/index.ts @@ -0,0 +1,103 @@ +import { DEFAULT_BASE_URL } from "./types/common"; + +export const BASE_URL = DEFAULT_BASE_URL; + +export const routes = { + feedback: { + method: "POST", + url: (messageId: string) => `/messages/${messageId}/feedbacks`, + }, + application: { + method: "GET", + url: () => "/parameters", + }, + fileUpload: { + method: "POST", + url: () => "/files/upload", + }, + filePreview: { + method: "GET", + url: (fileId: string) => `/files/${fileId}/preview`, + }, + textToAudio: { + method: "POST", + url: () => "/text-to-audio", + }, + audioToText: { + method: "POST", + url: () => "/audio-to-text", + }, + getMeta: { + method: "GET", + url: () => "/meta", + }, + getInfo: { + method: "GET", + url: () => "/info", + }, + getSite: { + method: "GET", + url: () => "/site", + }, + createCompletionMessage: { + method: "POST", + url: () => "/completion-messages", + }, + stopCompletionMessage: { + method: "POST", + url: (taskId: string) => `/completion-messages/${taskId}/stop`, + }, + createChatMessage: { + method: "POST", + url: () => "/chat-messages", + }, + getSuggested: { + method: "GET", + url: (messageId: string) => `/messages/${messageId}/suggested`, + }, + stopChatMessage: { + method: "POST", + url: (taskId: string) => `/chat-messages/${taskId}/stop`, + }, + getConversations: { + method: "GET", + url: () => "/conversations", + }, + getConversationMessages: { + method: "GET", + url: () => "/messages", + }, + renameConversation: { + method: "POST", + url: (conversationId: string) => `/conversations/${conversationId}/name`, + }, + deleteConversation: { + method: "DELETE", + url: (conversationId: string) => `/conversations/${conversationId}`, + }, + runWorkflow: { + method: "POST", + url: () => "/workflows/run", + }, + stopWorkflow: { + method: "POST", + url: (taskId: string) => `/workflows/tasks/${taskId}/stop`, + }, +}; + +export { DifyClient } from "./client/base"; +export { ChatClient } from "./client/chat"; +export { CompletionClient } from "./client/completion"; +export { WorkflowClient } from "./client/workflow"; +export { KnowledgeBaseClient } from "./client/knowledge-base"; +export { WorkspaceClient } from "./client/workspace"; + +export * from "./errors/dify-error"; +export * from "./types/common"; +export * from "./types/annotation"; +export * from "./types/chat"; +export * from "./types/completion"; +export * from "./types/knowledge-base"; +export * from "./types/workflow"; +export * from "./types/workspace"; +export { HttpClient } from "./http/client"; diff --git a/sdks/nodejs-client/src/types/annotation.ts b/sdks/nodejs-client/src/types/annotation.ts new file mode 100644 index 0000000000..dcbd644dab --- /dev/null +++ b/sdks/nodejs-client/src/types/annotation.ts @@ -0,0 +1,18 @@ +export type AnnotationCreateRequest = { + question: string; + answer: string; +}; + +export type AnnotationReplyActionRequest = { + score_threshold: number; + embedding_provider_name: string; + embedding_model_name: string; +}; + +export type AnnotationListOptions = { + page?: number; + limit?: number; + keyword?: string; +}; + +export type AnnotationResponse = Record<string, unknown>; diff --git a/sdks/nodejs-client/src/types/chat.ts b/sdks/nodejs-client/src/types/chat.ts new file mode 100644 index 0000000000..5b627f6cf6 --- /dev/null +++ b/sdks/nodejs-client/src/types/chat.ts @@ -0,0 +1,17 @@ +import type { StreamEvent } from "./common"; + +export type ChatMessageRequest = { + inputs?: Record<string, unknown>; + query: string; + user: string; + response_mode?: "blocking" | "streaming"; + files?: Array<Record<string, unknown>> | null; + conversation_id?: string; + auto_generate_name?: boolean; + workflow_id?: string; + retriever_from?: "app" | "dataset"; +}; + +export type ChatMessageResponse = Record<string, unknown>; + +export type ChatStreamEvent = StreamEvent<Record<string, unknown>>; diff --git a/sdks/nodejs-client/src/types/common.ts b/sdks/nodejs-client/src/types/common.ts new file mode 100644 index 0000000000..00b0fcc756 --- /dev/null +++ b/sdks/nodejs-client/src/types/common.ts @@ -0,0 +1,71 @@ +export const DEFAULT_BASE_URL = "https://api.dify.ai/v1"; +export const DEFAULT_TIMEOUT_SECONDS = 60; +export const DEFAULT_MAX_RETRIES = 3; +export const DEFAULT_RETRY_DELAY_SECONDS = 1; + +export type RequestMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE"; + +export type QueryParamValue = + | string + | number + | boolean + | Array<string | number | boolean> + | undefined; + +export type QueryParams = Record<string, QueryParamValue>; + +export type Headers = Record<string, string>; + +export type DifyClientConfig = { + apiKey: string; + baseUrl?: string; + timeout?: number; + maxRetries?: number; + retryDelay?: number; + enableLogging?: boolean; +}; + +export type DifyResponse<T> = { + data: T; + status: number; + headers: Headers; + requestId?: string; +}; + +export type MessageFeedbackRequest = { + messageId: string; + user: string; + rating?: "like" | "dislike" | null; + content?: string | null; +}; + +export type TextToAudioRequest = { + user: string; + text?: string; + message_id?: string; + streaming?: boolean; + voice?: string; +}; + +export type StreamEvent<T = unknown> = { + event?: string; + data: T | string | null; + raw: string; +}; + +export type DifyStream<T = unknown> = AsyncIterable<StreamEvent<T>> & { + data: NodeJS.ReadableStream; + status: number; + headers: Headers; + requestId?: string; + toText(): Promise<string>; + toReadable(): NodeJS.ReadableStream; +}; + +export type BinaryStream = { + data: NodeJS.ReadableStream; + status: number; + headers: Headers; + requestId?: string; + toReadable(): NodeJS.ReadableStream; +}; diff --git a/sdks/nodejs-client/src/types/completion.ts b/sdks/nodejs-client/src/types/completion.ts new file mode 100644 index 0000000000..4074137c5d --- /dev/null +++ b/sdks/nodejs-client/src/types/completion.ts @@ -0,0 +1,13 @@ +import type { StreamEvent } from "./common"; + +export type CompletionRequest = { + inputs?: Record<string, unknown>; + response_mode?: "blocking" | "streaming"; + user: string; + files?: Array<Record<string, unknown>> | null; + retriever_from?: "app" | "dataset"; +}; + +export type CompletionResponse = Record<string, unknown>; + +export type CompletionStreamEvent = StreamEvent<Record<string, unknown>>; diff --git a/sdks/nodejs-client/src/types/knowledge-base.ts b/sdks/nodejs-client/src/types/knowledge-base.ts new file mode 100644 index 0000000000..a4ddef50ea --- /dev/null +++ b/sdks/nodejs-client/src/types/knowledge-base.ts @@ -0,0 +1,184 @@ +export type DatasetListOptions = { + page?: number; + limit?: number; + keyword?: string | null; + tagIds?: string[]; + includeAll?: boolean; +}; + +export type DatasetCreateRequest = { + name: string; + description?: string; + indexing_technique?: "high_quality" | "economy"; + permission?: string | null; + external_knowledge_api_id?: string | null; + provider?: string; + external_knowledge_id?: string | null; + retrieval_model?: Record<string, unknown> | null; + embedding_model?: string | null; + embedding_model_provider?: string | null; +}; + +export type DatasetUpdateRequest = { + name?: string; + description?: string | null; + indexing_technique?: "high_quality" | "economy" | null; + permission?: string | null; + embedding_model?: string | null; + embedding_model_provider?: string | null; + retrieval_model?: Record<string, unknown> | null; + partial_member_list?: Array<Record<string, string>> | null; + external_retrieval_model?: Record<string, unknown> | null; + external_knowledge_id?: string | null; + external_knowledge_api_id?: string | null; +}; + +export type DocumentStatusAction = "enable" | "disable" | "archive" | "un_archive"; + +export type DatasetTagCreateRequest = { + name: string; +}; + +export type DatasetTagUpdateRequest = { + tag_id: string; + name: string; +}; + +export type DatasetTagDeleteRequest = { + tag_id: string; +}; + +export type DatasetTagBindingRequest = { + tag_ids: string[]; + target_id: string; +}; + +export type DatasetTagUnbindingRequest = { + tag_id: string; + target_id: string; +}; + +export type DocumentTextCreateRequest = { + name: string; + text: string; + process_rule?: Record<string, unknown> | null; + original_document_id?: string | null; + doc_form?: string; + doc_language?: string; + indexing_technique?: string | null; + retrieval_model?: Record<string, unknown> | null; + embedding_model?: string | null; + embedding_model_provider?: string | null; +}; + +export type DocumentTextUpdateRequest = { + name?: string | null; + text?: string | null; + process_rule?: Record<string, unknown> | null; + doc_form?: string; + doc_language?: string; + retrieval_model?: Record<string, unknown> | null; +}; + +export type DocumentListOptions = { + page?: number; + limit?: number; + keyword?: string | null; + status?: string | null; +}; + +export type DocumentGetOptions = { + metadata?: "all" | "only" | "without"; +}; + +export type SegmentCreateRequest = { + segments: Array<Record<string, unknown>>; +}; + +export type SegmentUpdateRequest = { + segment: { + content?: string | null; + answer?: string | null; + keywords?: string[] | null; + regenerate_child_chunks?: boolean; + enabled?: boolean | null; + attachment_ids?: string[] | null; + }; +}; + +export type SegmentListOptions = { + page?: number; + limit?: number; + status?: string[]; + keyword?: string | null; +}; + +export type ChildChunkCreateRequest = { + content: string; +}; + +export type ChildChunkUpdateRequest = { + content: string; +}; + +export type ChildChunkListOptions = { + page?: number; + limit?: number; + keyword?: string | null; +}; + +export type MetadataCreateRequest = { + type: "string" | "number" | "time"; + name: string; +}; + +export type MetadataUpdateRequest = { + name: string; + value?: string | number | null; +}; + +export type DocumentMetadataDetail = { + id: string; + name: string; + value?: string | number | null; +}; + +export type DocumentMetadataOperation = { + document_id: string; + metadata_list: DocumentMetadataDetail[]; + partial_update?: boolean; +}; + +export type MetadataOperationRequest = { + operation_data: DocumentMetadataOperation[]; +}; + +export type HitTestingRequest = { + query?: string | null; + retrieval_model?: Record<string, unknown> | null; + external_retrieval_model?: Record<string, unknown> | null; + attachment_ids?: string[] | null; +}; + +export type DatasourcePluginListOptions = { + isPublished?: boolean; +}; + +export type DatasourceNodeRunRequest = { + inputs: Record<string, unknown>; + datasource_type: string; + credential_id?: string | null; + is_published: boolean; +}; + +export type PipelineRunRequest = { + inputs: Record<string, unknown>; + datasource_type: string; + datasource_info_list: Array<Record<string, unknown>>; + start_node_id: string; + is_published: boolean; + response_mode: "streaming" | "blocking"; +}; + +export type KnowledgeBaseResponse = Record<string, unknown>; +export type PipelineStreamEvent = Record<string, unknown>; diff --git a/sdks/nodejs-client/src/types/workflow.ts b/sdks/nodejs-client/src/types/workflow.ts new file mode 100644 index 0000000000..2b507c7352 --- /dev/null +++ b/sdks/nodejs-client/src/types/workflow.ts @@ -0,0 +1,12 @@ +import type { StreamEvent } from "./common"; + +export type WorkflowRunRequest = { + inputs?: Record<string, unknown>; + user: string; + response_mode?: "blocking" | "streaming"; + files?: Array<Record<string, unknown>> | null; +}; + +export type WorkflowRunResponse = Record<string, unknown>; + +export type WorkflowStreamEvent = StreamEvent<Record<string, unknown>>; diff --git a/sdks/nodejs-client/src/types/workspace.ts b/sdks/nodejs-client/src/types/workspace.ts new file mode 100644 index 0000000000..0ab6743063 --- /dev/null +++ b/sdks/nodejs-client/src/types/workspace.ts @@ -0,0 +1,2 @@ +export type WorkspaceModelType = string; +export type WorkspaceModelsResponse = Record<string, unknown>; diff --git a/sdks/nodejs-client/tests/test-utils.js b/sdks/nodejs-client/tests/test-utils.js new file mode 100644 index 0000000000..0d42514e9a --- /dev/null +++ b/sdks/nodejs-client/tests/test-utils.js @@ -0,0 +1,30 @@ +import axios from "axios"; +import { vi } from "vitest"; +import { HttpClient } from "../src/http/client"; + +export const createHttpClient = (configOverrides = {}) => { + const mockRequest = vi.fn(); + vi.spyOn(axios, "create").mockReturnValue({ request: mockRequest }); + const client = new HttpClient({ apiKey: "test", ...configOverrides }); + return { client, mockRequest }; +}; + +export const createHttpClientWithSpies = (configOverrides = {}) => { + const { client, mockRequest } = createHttpClient(configOverrides); + const request = vi + .spyOn(client, "request") + .mockResolvedValue({ data: "ok", status: 200, headers: {} }); + const requestStream = vi + .spyOn(client, "requestStream") + .mockResolvedValue({ data: null }); + const requestBinaryStream = vi + .spyOn(client, "requestBinaryStream") + .mockResolvedValue({ data: null }); + return { + client, + mockRequest, + request, + requestStream, + requestBinaryStream, + }; +}; diff --git a/sdks/nodejs-client/tsconfig.json b/sdks/nodejs-client/tsconfig.json new file mode 100644 index 0000000000..d2da9a2a59 --- /dev/null +++ b/sdks/nodejs-client/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "rootDir": "src", + "outDir": "dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/sdks/nodejs-client/tsup.config.ts b/sdks/nodejs-client/tsup.config.ts new file mode 100644 index 0000000000..522382c2a5 --- /dev/null +++ b/sdks/nodejs-client/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + clean: true, + sourcemap: true, + splitting: false, + treeshake: true, + outDir: "dist", +}); diff --git a/sdks/nodejs-client/vitest.config.ts b/sdks/nodejs-client/vitest.config.ts new file mode 100644 index 0000000000..5a0a8637a2 --- /dev/null +++ b/sdks/nodejs-client/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["**/*.test.js"], + coverage: { + provider: "v8", + reporter: ["text", "text-summary"], + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.*", "src/**/*.spec.*"], + }, + }, +}); From 111a39b54932a32c3981c16e8acd0e8c6304f95a Mon Sep 17 00:00:00 2001 From: wangxiaolei <fatelei@gmail.com> Date: Wed, 24 Dec 2025 09:40:32 +0800 Subject: [PATCH 52/71] fix: fix firecrawl url concat (#30008) --- .../rag/extractor/firecrawl/firecrawl_app.py | 20 +++++++---- api/services/auth/firecrawl/firecrawl.py | 20 ++++++----- .../rag/extractor/firecrawl/test_firecrawl.py | 34 +++++++++++++++++++ .../services/auth/test_firecrawl_auth.py | 34 +++++++++++-------- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/api/core/rag/extractor/firecrawl/firecrawl_app.py b/api/core/rag/extractor/firecrawl/firecrawl_app.py index 789ac8557d..5d6223db06 100644 --- a/api/core/rag/extractor/firecrawl/firecrawl_app.py +++ b/api/core/rag/extractor/firecrawl/firecrawl_app.py @@ -25,7 +25,7 @@ class FirecrawlApp: } if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v2/scrape", json_data, headers) + response = self._post_request(self._build_url("v2/scrape"), json_data, headers) if response.status_code == 200: response_data = response.json() data = response_data["data"] @@ -42,7 +42,7 @@ class FirecrawlApp: json_data = {"url": url} if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v2/crawl", json_data, headers) + response = self._post_request(self._build_url("v2/crawl"), json_data, headers) if response.status_code == 200: # There's also another two fields in the response: "success" (bool) and "url" (str) job_id = response.json().get("id") @@ -58,7 +58,7 @@ class FirecrawlApp: if params: # Pass through provided params, including optional "sitemap": "only" | "include" | "skip" json_data.update(params) - response = self._post_request(f"{self.base_url}/v2/map", json_data, headers) + response = self._post_request(self._build_url("v2/map"), json_data, headers) if response.status_code == 200: return cast(dict[str, Any], response.json()) elif response.status_code in {402, 409, 500, 429, 408}: @@ -69,7 +69,7 @@ class FirecrawlApp: def check_crawl_status(self, job_id) -> dict[str, Any]: headers = self._prepare_headers() - response = self._get_request(f"{self.base_url}/v2/crawl/{job_id}", headers) + response = self._get_request(self._build_url(f"v2/crawl/{job_id}"), headers) if response.status_code == 200: crawl_status_response = response.json() if crawl_status_response.get("status") == "completed": @@ -120,6 +120,10 @@ class FirecrawlApp: def _prepare_headers(self) -> dict[str, Any]: return {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} + def _build_url(self, path: str) -> str: + # ensure exactly one slash between base and path, regardless of user-provided base_url + return f"{self.base_url.rstrip('/')}/{path.lstrip('/')}" + def _post_request(self, url, data, headers, retries=3, backoff_factor=0.5) -> httpx.Response: for attempt in range(retries): response = httpx.post(url, headers=headers, json=data) @@ -139,7 +143,11 @@ class FirecrawlApp: return response def _handle_error(self, response, action): - error_message = response.json().get("error", "Unknown error occurred") + try: + payload = response.json() + error_message = payload.get("error") or payload.get("message") or response.text or "Unknown error occurred" + except json.JSONDecodeError: + error_message = response.text or "Unknown error occurred" raise Exception(f"Failed to {action}. Status code: {response.status_code}. Error: {error_message}") # type: ignore[return] def search(self, query: str, params: dict[str, Any] | None = None) -> dict[str, Any]: @@ -160,7 +168,7 @@ class FirecrawlApp: } if params: json_data.update(params) - response = self._post_request(f"{self.base_url}/v2/search", json_data, headers) + response = self._post_request(self._build_url("v2/search"), json_data, headers) if response.status_code == 200: response_data = response.json() if not response_data.get("success"): diff --git a/api/services/auth/firecrawl/firecrawl.py b/api/services/auth/firecrawl/firecrawl.py index d455475bfc..b002706931 100644 --- a/api/services/auth/firecrawl/firecrawl.py +++ b/api/services/auth/firecrawl/firecrawl.py @@ -26,7 +26,7 @@ class FirecrawlAuth(ApiKeyAuthBase): "limit": 1, "scrapeOptions": {"onlyMainContent": True}, } - response = self._post_request(f"{self.base_url}/v1/crawl", options, headers) + response = self._post_request(self._build_url("v1/crawl"), options, headers) if response.status_code == 200: return True else: @@ -35,15 +35,17 @@ class FirecrawlAuth(ApiKeyAuthBase): def _prepare_headers(self): return {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} + def _build_url(self, path: str) -> str: + # ensure exactly one slash between base and path, regardless of user-provided base_url + return f"{self.base_url.rstrip('/')}/{path.lstrip('/')}" + def _post_request(self, url, data, headers): return httpx.post(url, headers=headers, json=data) def _handle_error(self, response): - if response.status_code in {402, 409, 500}: - error_message = response.json().get("error", "Unknown error occurred") - raise Exception(f"Failed to authorize. Status code: {response.status_code}. Error: {error_message}") - else: - if response.text: - error_message = json.loads(response.text).get("error", "Unknown error occurred") - raise Exception(f"Failed to authorize. Status code: {response.status_code}. Error: {error_message}") - raise Exception(f"Unexpected error occurred while trying to authorize. Status code: {response.status_code}") + try: + payload = response.json() + except json.JSONDecodeError: + payload = {} + error_message = payload.get("error") or payload.get("message") or (response.text or "Unknown error occurred") + raise Exception(f"Failed to authorize. Status code: {response.status_code}. Error: {error_message}") diff --git a/api/tests/unit_tests/core/rag/extractor/firecrawl/test_firecrawl.py b/api/tests/unit_tests/core/rag/extractor/firecrawl/test_firecrawl.py index b4ee1b91b4..4ee04ddebc 100644 --- a/api/tests/unit_tests/core/rag/extractor/firecrawl/test_firecrawl.py +++ b/api/tests/unit_tests/core/rag/extractor/firecrawl/test_firecrawl.py @@ -1,5 +1,7 @@ import os +from unittest.mock import MagicMock +import pytest from pytest_mock import MockerFixture from core.rag.extractor.firecrawl.firecrawl_app import FirecrawlApp @@ -25,3 +27,35 @@ def test_firecrawl_web_extractor_crawl_mode(mocker: MockerFixture): assert job_id is not None assert isinstance(job_id, str) + + +def test_build_url_normalizes_slashes_for_crawl(mocker: MockerFixture): + api_key = "fc-" + base_urls = ["https://custom.firecrawl.dev", "https://custom.firecrawl.dev/"] + for base in base_urls: + app = FirecrawlApp(api_key=api_key, base_url=base) + mock_post = mocker.patch("httpx.post") + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.json.return_value = {"id": "job123"} + mock_post.return_value = mock_resp + app.crawl_url("https://example.com", params=None) + called_url = mock_post.call_args[0][0] + assert called_url == "https://custom.firecrawl.dev/v2/crawl" + + +def test_error_handler_handles_non_json_error_bodies(mocker: MockerFixture): + api_key = "fc-" + app = FirecrawlApp(api_key=api_key, base_url="https://custom.firecrawl.dev/") + mock_post = mocker.patch("httpx.post") + mock_resp = MagicMock() + mock_resp.status_code = 404 + mock_resp.text = "Not Found" + mock_resp.json.side_effect = Exception("Not JSON") + mock_post.return_value = mock_resp + + with pytest.raises(Exception) as excinfo: + app.scrape_url("https://example.com") + + # Should not raise a JSONDecodeError; current behavior reports status code only + assert str(excinfo.value) == "Failed to scrape URL. Status code: 404" diff --git a/api/tests/unit_tests/services/auth/test_firecrawl_auth.py b/api/tests/unit_tests/services/auth/test_firecrawl_auth.py index b5ee55706d..ab50d6a92c 100644 --- a/api/tests/unit_tests/services/auth/test_firecrawl_auth.py +++ b/api/tests/unit_tests/services/auth/test_firecrawl_auth.py @@ -1,3 +1,4 @@ +import json from unittest.mock import MagicMock, patch import httpx @@ -110,9 +111,11 @@ class TestFirecrawlAuth: @pytest.mark.parametrize( ("status_code", "response_text", "has_json_error", "expected_error_contains"), [ - (403, '{"error": "Forbidden"}', True, "Failed to authorize. Status code: 403. Error: Forbidden"), - (404, "", True, "Unexpected error occurred while trying to authorize. Status code: 404"), - (401, "Not JSON", True, "Expecting value"), # JSON decode error + (403, '{"error": "Forbidden"}', False, "Failed to authorize. Status code: 403. Error: Forbidden"), + # empty body falls back to generic message + (404, "", True, "Failed to authorize. Status code: 404. Error: Unknown error occurred"), + # non-JSON body is surfaced directly + (401, "Not JSON", True, "Failed to authorize. Status code: 401. Error: Not JSON"), ], ) @patch("services.auth.firecrawl.firecrawl.httpx.post") @@ -124,12 +127,14 @@ class TestFirecrawlAuth: mock_response.status_code = status_code mock_response.text = response_text if has_json_error: - mock_response.json.side_effect = Exception("Not JSON") + mock_response.json.side_effect = json.JSONDecodeError("Not JSON", "", 0) + else: + mock_response.json.return_value = {"error": "Forbidden"} mock_post.return_value = mock_response with pytest.raises(Exception) as exc_info: auth_instance.validate_credentials() - assert expected_error_contains in str(exc_info.value) + assert str(exc_info.value) == expected_error_contains @pytest.mark.parametrize( ("exception_type", "exception_message"), @@ -164,20 +169,21 @@ class TestFirecrawlAuth: @patch("services.auth.firecrawl.firecrawl.httpx.post") def test_should_use_custom_base_url_in_validation(self, mock_post): - """Test that custom base URL is used in validation""" + """Test that custom base URL is used in validation and normalized""" mock_response = MagicMock() mock_response.status_code = 200 mock_post.return_value = mock_response - credentials = { - "auth_type": "bearer", - "config": {"api_key": "test_api_key_123", "base_url": "https://custom.firecrawl.dev"}, - } - auth = FirecrawlAuth(credentials) - result = auth.validate_credentials() + for base in ("https://custom.firecrawl.dev", "https://custom.firecrawl.dev/"): + credentials = { + "auth_type": "bearer", + "config": {"api_key": "test_api_key_123", "base_url": base}, + } + auth = FirecrawlAuth(credentials) + result = auth.validate_credentials() - assert result is True - assert mock_post.call_args[0][0] == "https://custom.firecrawl.dev/v1/crawl" + assert result is True + assert mock_post.call_args[0][0] == "https://custom.firecrawl.dev/v1/crawl" @patch("services.auth.firecrawl.firecrawl.httpx.post") def test_should_handle_timeout_with_retry_suggestion(self, mock_post, auth_instance): From 0a448a13c8b3b062063a5704b2737729e37af62b Mon Sep 17 00:00:00 2001 From: Asuka Minato <i@asukaminato.eu.org> Date: Wed, 24 Dec 2025 10:41:42 +0900 Subject: [PATCH 53/71] refactor: split changes for api/controllers/console/extension.py (#29888) --- api/controllers/console/extension.py | 75 +++--- .../controllers/console/test_extension.py | 236 ++++++++++++++++++ 2 files changed, 271 insertions(+), 40 deletions(-) create mode 100644 api/tests/unit_tests/controllers/console/test_extension.py diff --git a/api/controllers/console/extension.py b/api/controllers/console/extension.py index 08f29b4655..efa46c9779 100644 --- a/api/controllers/console/extension.py +++ b/api/controllers/console/extension.py @@ -1,14 +1,32 @@ -from flask_restx import Resource, fields, marshal_with, reqparse +from flask import request +from flask_restx import Resource, fields, marshal_with +from pydantic import BaseModel, Field from constants import HIDDEN_VALUE -from controllers.console import console_ns -from controllers.console.wraps import account_initialization_required, setup_required from fields.api_based_extension_fields import api_based_extension_fields from libs.login import current_account_with_tenant, login_required from models.api_based_extension import APIBasedExtension from services.api_based_extension_service import APIBasedExtensionService from services.code_based_extension_service import CodeBasedExtensionService +from ..common.schema import register_schema_models +from . import console_ns +from .wraps import account_initialization_required, setup_required + + +class CodeBasedExtensionQuery(BaseModel): + module: str + + +class APIBasedExtensionPayload(BaseModel): + name: str = Field(description="Extension name") + api_endpoint: str = Field(description="API endpoint URL") + api_key: str = Field(description="API key for authentication") + + +register_schema_models(console_ns, APIBasedExtensionPayload) + + api_based_extension_model = console_ns.model("ApiBasedExtensionModel", api_based_extension_fields) api_based_extension_list_model = fields.List(fields.Nested(api_based_extension_model)) @@ -18,11 +36,7 @@ api_based_extension_list_model = fields.List(fields.Nested(api_based_extension_m class CodeBasedExtensionAPI(Resource): @console_ns.doc("get_code_based_extension") @console_ns.doc(description="Get code-based extension data by module name") - @console_ns.expect( - console_ns.parser().add_argument( - "module", type=str, required=True, location="args", help="Extension module name" - ) - ) + @console_ns.doc(params={"module": "Extension module name"}) @console_ns.response( 200, "Success", @@ -35,10 +49,9 @@ class CodeBasedExtensionAPI(Resource): @login_required @account_initialization_required def get(self): - parser = reqparse.RequestParser().add_argument("module", type=str, required=True, location="args") - args = parser.parse_args() + query = CodeBasedExtensionQuery.model_validate(request.args.to_dict(flat=True)) # type: ignore - return {"module": args["module"], "data": CodeBasedExtensionService.get_code_based_extension(args["module"])} + return {"module": query.module, "data": CodeBasedExtensionService.get_code_based_extension(query.module)} @console_ns.route("/api-based-extension") @@ -56,30 +69,21 @@ class APIBasedExtensionAPI(Resource): @console_ns.doc("create_api_based_extension") @console_ns.doc(description="Create a new API-based extension") - @console_ns.expect( - console_ns.model( - "CreateAPIBasedExtensionRequest", - { - "name": fields.String(required=True, description="Extension name"), - "api_endpoint": fields.String(required=True, description="API endpoint URL"), - "api_key": fields.String(required=True, description="API key for authentication"), - }, - ) - ) + @console_ns.expect(console_ns.models[APIBasedExtensionPayload.__name__]) @console_ns.response(201, "Extension created successfully", api_based_extension_model) @setup_required @login_required @account_initialization_required @marshal_with(api_based_extension_model) def post(self): - args = console_ns.payload + payload = APIBasedExtensionPayload.model_validate(console_ns.payload or {}) _, current_tenant_id = current_account_with_tenant() extension_data = APIBasedExtension( tenant_id=current_tenant_id, - name=args["name"], - api_endpoint=args["api_endpoint"], - api_key=args["api_key"], + name=payload.name, + api_endpoint=payload.api_endpoint, + api_key=payload.api_key, ) return APIBasedExtensionService.save(extension_data) @@ -104,16 +108,7 @@ class APIBasedExtensionDetailAPI(Resource): @console_ns.doc("update_api_based_extension") @console_ns.doc(description="Update API-based extension") @console_ns.doc(params={"id": "Extension ID"}) - @console_ns.expect( - console_ns.model( - "UpdateAPIBasedExtensionRequest", - { - "name": fields.String(required=True, description="Extension name"), - "api_endpoint": fields.String(required=True, description="API endpoint URL"), - "api_key": fields.String(required=True, description="API key for authentication"), - }, - ) - ) + @console_ns.expect(console_ns.models[APIBasedExtensionPayload.__name__]) @console_ns.response(200, "Extension updated successfully", api_based_extension_model) @setup_required @login_required @@ -125,13 +120,13 @@ class APIBasedExtensionDetailAPI(Resource): extension_data_from_db = APIBasedExtensionService.get_with_tenant_id(current_tenant_id, api_based_extension_id) - args = console_ns.payload + payload = APIBasedExtensionPayload.model_validate(console_ns.payload or {}) - extension_data_from_db.name = args["name"] - extension_data_from_db.api_endpoint = args["api_endpoint"] + extension_data_from_db.name = payload.name + extension_data_from_db.api_endpoint = payload.api_endpoint - if args["api_key"] != HIDDEN_VALUE: - extension_data_from_db.api_key = args["api_key"] + if payload.api_key != HIDDEN_VALUE: + extension_data_from_db.api_key = payload.api_key return APIBasedExtensionService.save(extension_data_from_db) diff --git a/api/tests/unit_tests/controllers/console/test_extension.py b/api/tests/unit_tests/controllers/console/test_extension.py new file mode 100644 index 0000000000..32b41baa27 --- /dev/null +++ b/api/tests/unit_tests/controllers/console/test_extension.py @@ -0,0 +1,236 @@ +from __future__ import annotations + +import builtins +import uuid +from datetime import UTC, datetime +from unittest.mock import MagicMock + +import pytest +from flask import Flask +from flask.views import MethodView as FlaskMethodView + +_NEEDS_METHOD_VIEW_CLEANUP = False +if not hasattr(builtins, "MethodView"): + builtins.MethodView = FlaskMethodView + _NEEDS_METHOD_VIEW_CLEANUP = True + +from constants import HIDDEN_VALUE +from controllers.console.extension import ( + APIBasedExtensionAPI, + APIBasedExtensionDetailAPI, + CodeBasedExtensionAPI, +) + +if _NEEDS_METHOD_VIEW_CLEANUP: + delattr(builtins, "MethodView") +from models.account import AccountStatus +from models.api_based_extension import APIBasedExtension + + +def _make_extension( + *, + name: str = "Sample Extension", + api_endpoint: str = "https://example.com/api", + api_key: str = "super-secret-key", +) -> APIBasedExtension: + extension = APIBasedExtension( + tenant_id="tenant-123", + name=name, + api_endpoint=api_endpoint, + api_key=api_key, + ) + extension.id = f"{uuid.uuid4()}" + extension.created_at = datetime.now(tz=UTC) + return extension + + +@pytest.fixture(autouse=True) +def _mock_console_guards(monkeypatch: pytest.MonkeyPatch) -> MagicMock: + """Bypass console decorators so handlers can run in isolation.""" + + import controllers.console.extension as extension_module + from controllers.console import wraps as wraps_module + + account = MagicMock() + account.status = AccountStatus.ACTIVE + account.current_tenant_id = "tenant-123" + account.id = "account-123" + account.is_authenticated = True + + monkeypatch.setattr(wraps_module.dify_config, "EDITION", "CLOUD") + monkeypatch.setattr("libs.login.dify_config.LOGIN_DISABLED", True) + monkeypatch.delenv("INIT_PASSWORD", raising=False) + monkeypatch.setattr(extension_module, "current_account_with_tenant", lambda: (account, "tenant-123")) + monkeypatch.setattr(wraps_module, "current_account_with_tenant", lambda: (account, "tenant-123")) + + # The login_required decorator consults the shared LocalProxy in libs.login. + monkeypatch.setattr("libs.login.current_user", account) + monkeypatch.setattr("libs.login.check_csrf_token", lambda *_, **__: None) + + return account + + +@pytest.fixture(autouse=True) +def _restx_mask_defaults(app: Flask): + app.config.setdefault("RESTX_MASK_HEADER", "X-Fields") + app.config.setdefault("RESTX_MASK_SWAGGER", False) + + +def test_code_based_extension_get_returns_service_data(app: Flask, monkeypatch: pytest.MonkeyPatch): + service_result = {"entrypoint": "main:agent"} + service_mock = MagicMock(return_value=service_result) + monkeypatch.setattr( + "controllers.console.extension.CodeBasedExtensionService.get_code_based_extension", + service_mock, + ) + + with app.test_request_context( + "/console/api/code-based-extension", + method="GET", + query_string={"module": "workflow.tools"}, + ): + response = CodeBasedExtensionAPI().get() + + assert response == {"module": "workflow.tools", "data": service_result} + service_mock.assert_called_once_with("workflow.tools") + + +def test_api_based_extension_get_returns_tenant_extensions(app: Flask, monkeypatch: pytest.MonkeyPatch): + extension = _make_extension(name="Weather API", api_key="abcdefghi123") + service_mock = MagicMock(return_value=[extension]) + monkeypatch.setattr( + "controllers.console.extension.APIBasedExtensionService.get_all_by_tenant_id", + service_mock, + ) + + with app.test_request_context("/console/api/api-based-extension", method="GET"): + response = APIBasedExtensionAPI().get() + + assert response[0]["id"] == extension.id + assert response[0]["name"] == "Weather API" + assert response[0]["api_endpoint"] == extension.api_endpoint + assert response[0]["api_key"].startswith(extension.api_key[:3]) + service_mock.assert_called_once_with("tenant-123") + + +def test_api_based_extension_post_creates_extension(app: Flask, monkeypatch: pytest.MonkeyPatch): + saved_extension = _make_extension(name="Docs API", api_key="saved-secret") + save_mock = MagicMock(return_value=saved_extension) + monkeypatch.setattr("controllers.console.extension.APIBasedExtensionService.save", save_mock) + + payload = { + "name": "Docs API", + "api_endpoint": "https://docs.example.com/hook", + "api_key": "plain-secret", + } + + with app.test_request_context("/console/api/api-based-extension", method="POST", json=payload): + response = APIBasedExtensionAPI().post() + + args, _ = save_mock.call_args + created_extension: APIBasedExtension = args[0] + assert created_extension.tenant_id == "tenant-123" + assert created_extension.name == payload["name"] + assert created_extension.api_endpoint == payload["api_endpoint"] + assert created_extension.api_key == payload["api_key"] + assert response["name"] == saved_extension.name + save_mock.assert_called_once() + + +def test_api_based_extension_detail_get_fetches_extension(app: Flask, monkeypatch: pytest.MonkeyPatch): + extension = _make_extension(name="Docs API", api_key="abcdefg12345") + service_mock = MagicMock(return_value=extension) + monkeypatch.setattr( + "controllers.console.extension.APIBasedExtensionService.get_with_tenant_id", + service_mock, + ) + + extension_id = uuid.uuid4() + with app.test_request_context(f"/console/api/api-based-extension/{extension_id}", method="GET"): + response = APIBasedExtensionDetailAPI().get(extension_id) + + assert response["id"] == extension.id + assert response["name"] == extension.name + service_mock.assert_called_once_with("tenant-123", str(extension_id)) + + +def test_api_based_extension_detail_post_keeps_hidden_api_key(app: Flask, monkeypatch: pytest.MonkeyPatch): + existing_extension = _make_extension(name="Docs API", api_key="keep-me") + get_mock = MagicMock(return_value=existing_extension) + save_mock = MagicMock(return_value=existing_extension) + monkeypatch.setattr( + "controllers.console.extension.APIBasedExtensionService.get_with_tenant_id", + get_mock, + ) + monkeypatch.setattr("controllers.console.extension.APIBasedExtensionService.save", save_mock) + + payload = { + "name": "Docs API Updated", + "api_endpoint": "https://docs.example.com/v2", + "api_key": HIDDEN_VALUE, + } + + extension_id = uuid.uuid4() + with app.test_request_context( + f"/console/api/api-based-extension/{extension_id}", + method="POST", + json=payload, + ): + response = APIBasedExtensionDetailAPI().post(extension_id) + + assert existing_extension.name == payload["name"] + assert existing_extension.api_endpoint == payload["api_endpoint"] + assert existing_extension.api_key == "keep-me" + save_mock.assert_called_once_with(existing_extension) + assert response["name"] == payload["name"] + + +def test_api_based_extension_detail_post_updates_api_key_when_provided(app: Flask, monkeypatch: pytest.MonkeyPatch): + existing_extension = _make_extension(name="Docs API", api_key="old-secret") + get_mock = MagicMock(return_value=existing_extension) + save_mock = MagicMock(return_value=existing_extension) + monkeypatch.setattr( + "controllers.console.extension.APIBasedExtensionService.get_with_tenant_id", + get_mock, + ) + monkeypatch.setattr("controllers.console.extension.APIBasedExtensionService.save", save_mock) + + payload = { + "name": "Docs API Updated", + "api_endpoint": "https://docs.example.com/v2", + "api_key": "new-secret", + } + + extension_id = uuid.uuid4() + with app.test_request_context( + f"/console/api/api-based-extension/{extension_id}", + method="POST", + json=payload, + ): + response = APIBasedExtensionDetailAPI().post(extension_id) + + assert existing_extension.api_key == "new-secret" + save_mock.assert_called_once_with(existing_extension) + assert response["name"] == payload["name"] + + +def test_api_based_extension_detail_delete_removes_extension(app: Flask, monkeypatch: pytest.MonkeyPatch): + existing_extension = _make_extension() + get_mock = MagicMock(return_value=existing_extension) + delete_mock = MagicMock() + monkeypatch.setattr( + "controllers.console.extension.APIBasedExtensionService.get_with_tenant_id", + get_mock, + ) + monkeypatch.setattr("controllers.console.extension.APIBasedExtensionService.delete", delete_mock) + + extension_id = uuid.uuid4() + with app.test_request_context( + f"/console/api/api-based-extension/{extension_id}", + method="DELETE", + ): + response, status = APIBasedExtensionDetailAPI().delete(extension_id) + + delete_mock.assert_called_once_with(existing_extension) + assert response == {"result": "success"} + assert status == 204 From 037b8ae9e29001961e7716305f55d3642e62ff53 Mon Sep 17 00:00:00 2001 From: Asuka Minato <i@asukaminato.eu.org> Date: Wed, 24 Dec 2025 10:41:51 +0900 Subject: [PATCH 54/71] refactor: split changes for api/controllers/web/forgot_password.py (#29858) --- api/controllers/web/forgot_password.py | 89 ++++---- .../controllers/web/test_forgot_password.py | 195 ++++++++++++++++++ 2 files changed, 246 insertions(+), 38 deletions(-) create mode 100644 api/tests/unit_tests/controllers/web/test_forgot_password.py diff --git a/api/controllers/web/forgot_password.py b/api/controllers/web/forgot_password.py index b9e391e049..690b76655f 100644 --- a/api/controllers/web/forgot_password.py +++ b/api/controllers/web/forgot_password.py @@ -2,10 +2,12 @@ import base64 import secrets from flask import request -from flask_restx import Resource, reqparse +from flask_restx import Resource +from pydantic import BaseModel, Field, field_validator from sqlalchemy import select from sqlalchemy.orm import Session +from controllers.common.schema import register_schema_models from controllers.console.auth.error import ( AuthenticationFailedError, EmailCodeError, @@ -18,14 +20,40 @@ from controllers.console.error import EmailSendIpLimitError from controllers.console.wraps import email_password_login_enabled, only_edition_enterprise, setup_required from controllers.web import web_ns from extensions.ext_database import db -from libs.helper import email, extract_remote_ip +from libs.helper import EmailStr, extract_remote_ip from libs.password import hash_password, valid_password from models import Account from services.account_service import AccountService +class ForgotPasswordSendPayload(BaseModel): + email: EmailStr + language: str | None = None + + +class ForgotPasswordCheckPayload(BaseModel): + email: EmailStr + code: str + token: str = Field(min_length=1) + + +class ForgotPasswordResetPayload(BaseModel): + token: str = Field(min_length=1) + new_password: str + password_confirm: str + + @field_validator("new_password", "password_confirm") + @classmethod + def validate_password(cls, value: str) -> str: + return valid_password(value) + + +register_schema_models(web_ns, ForgotPasswordSendPayload, ForgotPasswordCheckPayload, ForgotPasswordResetPayload) + + @web_ns.route("/forgot-password") class ForgotPasswordSendEmailApi(Resource): + @web_ns.expect(web_ns.models[ForgotPasswordSendPayload.__name__]) @only_edition_enterprise @setup_required @email_password_login_enabled @@ -40,35 +68,31 @@ class ForgotPasswordSendEmailApi(Resource): } ) def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("email", type=email, required=True, location="json") - .add_argument("language", type=str, required=False, location="json") - ) - args = parser.parse_args() + payload = ForgotPasswordSendPayload.model_validate(web_ns.payload or {}) ip_address = extract_remote_ip(request) if AccountService.is_email_send_ip_limit(ip_address): raise EmailSendIpLimitError() - if args["language"] is not None and args["language"] == "zh-Hans": + if payload.language == "zh-Hans": language = "zh-Hans" else: language = "en-US" with Session(db.engine) as session: - account = session.execute(select(Account).filter_by(email=args["email"])).scalar_one_or_none() + account = session.execute(select(Account).filter_by(email=payload.email)).scalar_one_or_none() token = None if account is None: raise AuthenticationFailedError() else: - token = AccountService.send_reset_password_email(account=account, email=args["email"], language=language) + token = AccountService.send_reset_password_email(account=account, email=payload.email, language=language) return {"result": "success", "data": token} @web_ns.route("/forgot-password/validity") class ForgotPasswordCheckApi(Resource): + @web_ns.expect(web_ns.models[ForgotPasswordCheckPayload.__name__]) @only_edition_enterprise @setup_required @email_password_login_enabled @@ -78,45 +102,40 @@ class ForgotPasswordCheckApi(Resource): responses={200: "Token is valid", 400: "Bad request - invalid token format", 401: "Invalid or expired token"} ) def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("email", type=str, required=True, location="json") - .add_argument("code", type=str, required=True, location="json") - .add_argument("token", type=str, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + payload = ForgotPasswordCheckPayload.model_validate(web_ns.payload or {}) - user_email = args["email"] + user_email = payload.email - is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(args["email"]) + is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(payload.email) if is_forgot_password_error_rate_limit: raise EmailPasswordResetLimitError() - token_data = AccountService.get_reset_password_data(args["token"]) + token_data = AccountService.get_reset_password_data(payload.token) if token_data is None: raise InvalidTokenError() if user_email != token_data.get("email"): raise InvalidEmailError() - if args["code"] != token_data.get("code"): - AccountService.add_forgot_password_error_rate_limit(args["email"]) + if payload.code != token_data.get("code"): + AccountService.add_forgot_password_error_rate_limit(payload.email) raise EmailCodeError() # Verified, revoke the first token - AccountService.revoke_reset_password_token(args["token"]) + AccountService.revoke_reset_password_token(payload.token) # Refresh token data by generating a new token _, new_token = AccountService.generate_reset_password_token( - user_email, code=args["code"], additional_data={"phase": "reset"} + user_email, code=payload.code, additional_data={"phase": "reset"} ) - AccountService.reset_forgot_password_error_rate_limit(args["email"]) + AccountService.reset_forgot_password_error_rate_limit(payload.email) return {"is_valid": True, "email": token_data.get("email"), "token": new_token} @web_ns.route("/forgot-password/resets") class ForgotPasswordResetApi(Resource): + @web_ns.expect(web_ns.models[ForgotPasswordResetPayload.__name__]) @only_edition_enterprise @setup_required @email_password_login_enabled @@ -131,20 +150,14 @@ class ForgotPasswordResetApi(Resource): } ) def post(self): - parser = ( - reqparse.RequestParser() - .add_argument("token", type=str, required=True, nullable=False, location="json") - .add_argument("new_password", type=valid_password, required=True, nullable=False, location="json") - .add_argument("password_confirm", type=valid_password, required=True, nullable=False, location="json") - ) - args = parser.parse_args() + payload = ForgotPasswordResetPayload.model_validate(web_ns.payload or {}) # Validate passwords match - if args["new_password"] != args["password_confirm"]: + if payload.new_password != payload.password_confirm: raise PasswordMismatchError() # Validate token and get reset data - reset_data = AccountService.get_reset_password_data(args["token"]) + reset_data = AccountService.get_reset_password_data(payload.token) if not reset_data: raise InvalidTokenError() # Must use token in reset phase @@ -152,11 +165,11 @@ class ForgotPasswordResetApi(Resource): raise InvalidTokenError() # Revoke token to prevent reuse - AccountService.revoke_reset_password_token(args["token"]) + AccountService.revoke_reset_password_token(payload.token) # Generate secure salt and hash password salt = secrets.token_bytes(16) - password_hashed = hash_password(args["new_password"], salt) + password_hashed = hash_password(payload.new_password, salt) email = reset_data.get("email", "") @@ -170,7 +183,7 @@ class ForgotPasswordResetApi(Resource): return {"result": "success"} - def _update_existing_account(self, account, password_hashed, salt, session): + def _update_existing_account(self, account: Account, password_hashed, salt, session): # Update existing account credentials account.password = base64.b64encode(password_hashed).decode() account.password_salt = base64.b64encode(salt).decode() diff --git a/api/tests/unit_tests/controllers/web/test_forgot_password.py b/api/tests/unit_tests/controllers/web/test_forgot_password.py new file mode 100644 index 0000000000..d7c0d24f14 --- /dev/null +++ b/api/tests/unit_tests/controllers/web/test_forgot_password.py @@ -0,0 +1,195 @@ +"""Unit tests for controllers.web.forgot_password endpoints.""" + +from __future__ import annotations + +import base64 +import builtins +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +import pytest +from flask import Flask +from flask.views import MethodView + +# Ensure flask_restx.api finds MethodView during import. +if not hasattr(builtins, "MethodView"): + builtins.MethodView = MethodView # type: ignore[attr-defined] + + +def _load_controller_module(): + """Import controllers.web.forgot_password using a stub package.""" + + import importlib + import importlib.util + import sys + from types import ModuleType + + parent_module_name = "controllers.web" + module_name = f"{parent_module_name}.forgot_password" + + if parent_module_name not in sys.modules: + from flask_restx import Namespace + + stub = ModuleType(parent_module_name) + stub.__file__ = "controllers/web/__init__.py" + stub.__path__ = ["controllers/web"] + stub.__package__ = "controllers" + stub.__spec__ = importlib.util.spec_from_loader(parent_module_name, loader=None, is_package=True) + stub.web_ns = Namespace("web", description="Web API", path="/") + sys.modules[parent_module_name] = stub + + return importlib.import_module(module_name) + + +forgot_password_module = _load_controller_module() +ForgotPasswordCheckApi = forgot_password_module.ForgotPasswordCheckApi +ForgotPasswordResetApi = forgot_password_module.ForgotPasswordResetApi +ForgotPasswordSendEmailApi = forgot_password_module.ForgotPasswordSendEmailApi + + +@pytest.fixture +def app() -> Flask: + """Configure a minimal Flask app for request contexts.""" + + app = Flask(__name__) + app.config["TESTING"] = True + return app + + +@pytest.fixture(autouse=True) +def _enable_web_endpoint_guards(): + """Stub enterprise and feature toggles used by route decorators.""" + + features = SimpleNamespace(enable_email_password_login=True) + with ( + patch("controllers.console.wraps.dify_config.ENTERPRISE_ENABLED", True), + patch("controllers.console.wraps.dify_config.EDITION", "CLOUD"), + patch("controllers.console.wraps.FeatureService.get_system_features", return_value=features), + ): + yield + + +@pytest.fixture(autouse=True) +def _mock_controller_db(): + """Replace controller-level db reference with a simple stub.""" + + fake_db = SimpleNamespace(engine=MagicMock(name="engine")) + fake_wraps_db = SimpleNamespace( + session=MagicMock(query=MagicMock(return_value=MagicMock(first=MagicMock(return_value=True)))) + ) + with ( + patch("controllers.web.forgot_password.db", fake_db), + patch("controllers.console.wraps.db", fake_wraps_db), + ): + yield fake_db + + +@patch("controllers.web.forgot_password.AccountService.send_reset_password_email", return_value="reset-token") +@patch("controllers.web.forgot_password.Session") +@patch("controllers.web.forgot_password.AccountService.is_email_send_ip_limit", return_value=False) +@patch("controllers.web.forgot_password.extract_remote_ip", return_value="203.0.113.10") +def test_send_reset_email_success( + mock_extract_ip: MagicMock, + mock_is_ip_limit: MagicMock, + mock_session: MagicMock, + mock_send_email: MagicMock, + app: Flask, +): + """POST /forgot-password returns token when email exists and limits allow.""" + + mock_account = MagicMock() + session_ctx = MagicMock() + mock_session.return_value.__enter__.return_value = session_ctx + session_ctx.execute.return_value.scalar_one_or_none.return_value = mock_account + + with app.test_request_context( + "/forgot-password", + method="POST", + json={"email": "user@example.com"}, + ): + response = ForgotPasswordSendEmailApi().post() + + assert response == {"result": "success", "data": "reset-token"} + mock_extract_ip.assert_called_once() + mock_is_ip_limit.assert_called_once_with("203.0.113.10") + mock_send_email.assert_called_once_with(account=mock_account, email="user@example.com", language="en-US") + + +@patch("controllers.web.forgot_password.AccountService.reset_forgot_password_error_rate_limit") +@patch("controllers.web.forgot_password.AccountService.generate_reset_password_token", return_value=({}, "new-token")) +@patch("controllers.web.forgot_password.AccountService.revoke_reset_password_token") +@patch("controllers.web.forgot_password.AccountService.get_reset_password_data") +@patch("controllers.web.forgot_password.AccountService.is_forgot_password_error_rate_limit", return_value=False) +def test_check_token_success( + mock_is_rate_limited: MagicMock, + mock_get_data: MagicMock, + mock_revoke: MagicMock, + mock_generate: MagicMock, + mock_reset_limit: MagicMock, + app: Flask, +): + """POST /forgot-password/validity validates the code and refreshes token.""" + + mock_get_data.return_value = {"email": "user@example.com", "code": "123456"} + + with app.test_request_context( + "/forgot-password/validity", + method="POST", + json={"email": "user@example.com", "code": "123456", "token": "old-token"}, + ): + response = ForgotPasswordCheckApi().post() + + assert response == {"is_valid": True, "email": "user@example.com", "token": "new-token"} + mock_is_rate_limited.assert_called_once_with("user@example.com") + mock_get_data.assert_called_once_with("old-token") + mock_revoke.assert_called_once_with("old-token") + mock_generate.assert_called_once_with( + "user@example.com", + code="123456", + additional_data={"phase": "reset"}, + ) + mock_reset_limit.assert_called_once_with("user@example.com") + + +@patch("controllers.web.forgot_password.hash_password", return_value=b"hashed-value") +@patch("controllers.web.forgot_password.secrets.token_bytes", return_value=b"0123456789abcdef") +@patch("controllers.web.forgot_password.Session") +@patch("controllers.web.forgot_password.AccountService.revoke_reset_password_token") +@patch("controllers.web.forgot_password.AccountService.get_reset_password_data") +def test_reset_password_success( + mock_get_data: MagicMock, + mock_revoke_token: MagicMock, + mock_session: MagicMock, + mock_token_bytes: MagicMock, + mock_hash_password: MagicMock, + app: Flask, +): + """POST /forgot-password/resets updates the stored password when token is valid.""" + + mock_get_data.return_value = {"email": "user@example.com", "phase": "reset"} + account = MagicMock() + session_ctx = MagicMock() + mock_session.return_value.__enter__.return_value = session_ctx + session_ctx.execute.return_value.scalar_one_or_none.return_value = account + + with app.test_request_context( + "/forgot-password/resets", + method="POST", + json={ + "token": "reset-token", + "new_password": "StrongPass123!", + "password_confirm": "StrongPass123!", + }, + ): + response = ForgotPasswordResetApi().post() + + assert response == {"result": "success"} + mock_get_data.assert_called_once_with("reset-token") + mock_revoke_token.assert_called_once_with("reset-token") + mock_token_bytes.assert_called_once_with(16) + mock_hash_password.assert_called_once_with("StrongPass123!", b"0123456789abcdef") + expected_password = base64.b64encode(b"hashed-value").decode() + assert account.password == expected_password + expected_salt = base64.b64encode(b"0123456789abcdef").decode() + assert account.password_salt == expected_salt + session_ctx.commit.assert_called_once() From 95330162a4e9d70f1ddbb9c877628da8cb04b250 Mon Sep 17 00:00:00 2001 From: Yuya Sato <sato.yuya1211@gmail.com> Date: Wed, 24 Dec 2025 10:53:10 +0900 Subject: [PATCH 55/71] feat(docker): add environment variables synchronization tool (#29845) Co-authored-by: Claude Sonnet 4 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .gitignore | 1 + docker/README.md | 45 ++++ docker/dify-env-sync.sh | 465 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) create mode 100755 docker/dify-env-sync.sh diff --git a/.gitignore b/.gitignore index 4e9a0eaa23..17a2bd5b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -195,6 +195,7 @@ docker/nginx/ssl/* !docker/nginx/ssl/.gitkeep docker/middleware.env docker/docker-compose.override.yaml +docker/env-backup/* sdks/python-client/build sdks/python-client/dist diff --git a/docker/README.md b/docker/README.md index 375570f106..4c40317f37 100644 --- a/docker/README.md +++ b/docker/README.md @@ -23,6 +23,10 @@ Welcome to the new `docker` directory for deploying Dify using Docker Compose. T - Navigate to the `docker` directory. - Copy the `.env.example` file to a new file named `.env` by running `cp .env.example .env`. - Customize the `.env` file as needed. Refer to the `.env.example` file for detailed configuration options. + - **Optional (Recommended for upgrades)**: + You may use the environment synchronization tool to help keep your `.env` file aligned with the latest `.env.example` updates, while preserving your custom settings. + This is especially useful when upgrading Dify or managing a large, customized `.env` file. + See the [Environment Variables Synchronization](#environment-variables-synchronization) section below. 1. **Running the Services**: - Execute `docker compose up` from the `docker` directory to start the services. - To specify a vector database, set the `VECTOR_STORE` variable in your `.env` file to your desired vector database service, such as `milvus`, `weaviate`, or `opensearch`. @@ -111,6 +115,47 @@ The `.env.example` file provided in the Docker setup is extensive and covers a w - Each service like `nginx`, `redis`, `db`, and vector databases have specific environment variables that are directly referenced in the `docker-compose.yaml`. +### Environment Variables Synchronization + +When upgrading Dify or pulling the latest changes, new environment variables may be introduced in `.env.example`. + +To help keep your existing `.env` file up to date **without losing your custom values**, an optional environment variables synchronization tool is provided. + +> This tool performs a **one-way synchronization** from `.env.example` to `.env`. +> Existing values in `.env` are never overwritten automatically. + +#### `dify-env-sync.sh` (Optional) + +This script compares your current `.env` file with the latest `.env.example` template and helps safely apply new or updated environment variables. + +**What it does** + +- Creates a backup of the current `.env` file before making any changes +- Synchronizes newly added environment variables from `.env.example` +- Preserves all existing custom values in `.env` +- Displays differences and variables removed from `.env.example` for review + +**Backup behavior** + +Before synchronization, the current `.env` file is saved to the `env-backup/` directory with a timestamped filename +(e.g. `env-backup/.env.backup_20231218_143022`). + +**When to use** + +- After upgrading Dify to a newer version +- When `.env.example` has been updated with new environment variables +- When managing a large or heavily customized `.env` file + +**Usage** + +```bash +# Grant execution permission (first time only) +chmod +x dify-env-sync.sh + +# Run the synchronization +./dify-env-sync.sh +``` + ### Additional Information - **Continuous Improvement Phase**: We are actively seeking feedback from the community to refine and enhance the deployment process. As more users adopt this new method, we will continue to make improvements based on your experiences and suggestions. diff --git a/docker/dify-env-sync.sh b/docker/dify-env-sync.sh new file mode 100755 index 0000000000..edeeae3abc --- /dev/null +++ b/docker/dify-env-sync.sh @@ -0,0 +1,465 @@ +#!/bin/bash + +# ================================================================ +# Dify Environment Variables Synchronization Script +# +# Features: +# - Synchronize latest settings from .env.example to .env +# - Preserve custom settings in existing .env +# - Add new environment variables +# - Detect removed environment variables +# - Create backup files +# ================================================================ + +set -eo pipefail # Exit on error and pipe failures (safer for complex variable handling) + +# Error handling function +# Arguments: +# $1 - Line number where error occurred +# $2 - Error code +handle_error() { + local line_no=$1 + local error_code=$2 + echo -e "\033[0;31m[ERROR]\033[0m Script error: line $line_no with error code $error_code" >&2 + echo -e "\033[0;31m[ERROR]\033[0m Debug info: current working directory $(pwd)" >&2 + exit $error_code +} + +# Set error trap +trap 'handle_error ${LINENO} $?' ERR + +# Color settings for output +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Logging functions +# Print informational message in blue +# Arguments: $1 - Message to print +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +# Print success message in green +# Arguments: $1 - Message to print +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +# Print warning message in yellow +# Arguments: $1 - Message to print +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" >&2 +} + +# Print error message in red to stderr +# Arguments: $1 - Message to print +log_error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Check for required files and create .env if missing +# Verifies that .env.example exists and creates .env from template if needed +check_files() { + log_info "Checking required files..." + + if [[ ! -f ".env.example" ]]; then + log_error ".env.example file not found" + exit 1 + fi + + if [[ ! -f ".env" ]]; then + log_warning ".env file does not exist. Creating from .env.example." + cp ".env.example" ".env" + log_success ".env file created" + fi + + log_success "Required files verified" +} + +# Create timestamped backup of .env file +# Creates env-backup directory if needed and backs up current .env file +create_backup() { + local timestamp=$(date +"%Y%m%d_%H%M%S") + local backup_dir="env-backup" + + # Create backup directory if it doesn't exist + if [[ ! -d "$backup_dir" ]]; then + mkdir -p "$backup_dir" + log_info "Created backup directory: $backup_dir" + fi + + if [[ -f ".env" ]]; then + local backup_file="${backup_dir}/.env.backup_${timestamp}" + cp ".env" "$backup_file" + log_success "Backed up existing .env to $backup_file" + fi +} + +# Detect differences between .env and .env.example (optimized for large files) +detect_differences() { + log_info "Detecting differences between .env and .env.example..." + + # Create secure temporary directory + local temp_dir=$(mktemp -d) + local temp_diff="$temp_dir/env_diff" + + # Store diff file path as global variable + declare -g DIFF_FILE="$temp_diff" + declare -g TEMP_DIR="$temp_dir" + + # Initialize difference file + > "$temp_diff" + + # Use awk for efficient comparison (much faster for large files) + local diff_count=$(awk -F= ' + BEGIN { OFS="\x01" } + FNR==NR { + if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + key = $1 + value = substr($0, index($0,"=")+1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) + env_values[key] = value + } + next + } + { + if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + key = $1 + example_value = substr($0, index($0,"=")+1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", example_value) + + if (key in env_values && env_values[key] != example_value) { + print key, env_values[key], example_value > "'$temp_diff'" + diff_count++ + } + } + } + END { print diff_count } + ' .env .env.example) + + if [[ $diff_count -gt 0 ]]; then + log_success "Detected differences in $diff_count environment variables" + # Show detailed differences + show_differences_detail + else + log_info "No differences detected" + fi +} + +# Parse environment variable line +# Extracts key-value pairs from .env file format lines +# Arguments: +# $1 - Line to parse +# Returns: +# 0 - Success, outputs "key|value" format +# 1 - Skip (empty line, comment, or invalid format) +parse_env_line() { + local line="$1" + local key="" + local value="" + + # Skip empty lines or comment lines + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && return 1 + + # Split by = + if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + + # Remove leading and trailing whitespace + key=$(echo "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//') + + if [[ -n "$key" ]]; then + echo "$key|$value" + return 0 + fi + fi + + return 1 +} + +# Show detailed differences +show_differences_detail() { + log_info "" + log_info "=== Environment Variable Differences ===" + + # Read differences from the already created diff file + if [[ ! -s "$DIFF_FILE" ]]; then + log_info "No differences to display" + return + fi + + # Display differences + local count=1 + while IFS=$'\x01' read -r key env_value example_value; do + echo "" + echo -e "${YELLOW}[$count] $key${NC}" + echo -e " ${GREEN}.env (current)${NC} : ${env_value}" + echo -e " ${BLUE}.env.example (recommended)${NC}: ${example_value}" + + # Analyze value changes + analyze_value_change "$env_value" "$example_value" + ((count++)) + done < "$DIFF_FILE" + + echo "" + log_info "=== Difference Analysis Complete ===" + log_info "Note: Consider changing to the recommended values above." + log_info "Current implementation preserves .env values." + echo "" +} + +# Analyze value changes +analyze_value_change() { + local current_value="$1" + local recommended_value="$2" + + # Analyze value characteristics + local analysis="" + + # Empty value check + if [[ -z "$current_value" && -n "$recommended_value" ]]; then + analysis=" ${RED}→ Setting from empty to recommended value${NC}" + elif [[ -n "$current_value" && -z "$recommended_value" ]]; then + analysis=" ${RED}→ Recommended value changed to empty${NC}" + # Numeric check - using arithmetic evaluation for robust comparison + elif [[ "$current_value" =~ ^[0-9]+$ && "$recommended_value" =~ ^[0-9]+$ ]]; then + # Use arithmetic evaluation to handle leading zeros correctly + if (( 10#$current_value < 10#$recommended_value )); then + analysis=" ${BLUE}→ Numeric increase (${current_value} < ${recommended_value})${NC}" + elif (( 10#$current_value > 10#$recommended_value )); then + analysis=" ${YELLOW}→ Numeric decrease (${current_value} > ${recommended_value})${NC}" + fi + # Boolean check + elif [[ "$current_value" =~ ^(true|false)$ && "$recommended_value" =~ ^(true|false)$ ]]; then + if [[ "$current_value" != "$recommended_value" ]]; then + analysis=" ${BLUE}→ Boolean value change (${current_value} → ${recommended_value})${NC}" + fi + # URL/endpoint check + elif [[ "$current_value" =~ ^https?:// || "$recommended_value" =~ ^https?:// ]]; then + analysis=" ${BLUE}→ URL/endpoint change${NC}" + # File path check + elif [[ "$current_value" =~ ^/ || "$recommended_value" =~ ^/ ]]; then + analysis=" ${BLUE}→ File path change${NC}" + else + # Length comparison + local current_len=${#current_value} + local recommended_len=${#recommended_value} + if [[ $current_len -ne $recommended_len ]]; then + analysis=" ${YELLOW}→ String length change (${current_len} → ${recommended_len} characters)${NC}" + fi + fi + + if [[ -n "$analysis" ]]; then + echo -e "$analysis" + fi +} + +# Synchronize .env file with .env.example while preserving custom values +# Creates a new .env file based on .env.example structure, preserving existing custom values +# Global variables used: DIFF_FILE, TEMP_DIR +sync_env_file() { + log_info "Starting partial synchronization of .env file..." + + local new_env_file=".env.new" + local preserved_count=0 + local updated_count=0 + + # Pre-process diff file for efficient lookup + local lookup_file="" + if [[ -f "$DIFF_FILE" && -s "$DIFF_FILE" ]]; then + lookup_file="${DIFF_FILE}.lookup" + # Create sorted lookup file for fast search + sort "$DIFF_FILE" > "$lookup_file" + log_info "Created lookup file for $(wc -l < "$DIFF_FILE") preserved values" + fi + + # Use AWK for efficient processing (much faster than bash loop for large files) + log_info "Processing $(wc -l < .env.example) lines with AWK..." + + local preserved_keys_file="${TEMP_DIR}/preserved_keys" + local awk_preserved_count_file="${TEMP_DIR}/awk_preserved_count" + local awk_updated_count_file="${TEMP_DIR}/awk_updated_count" + + awk -F'=' -v lookup_file="$lookup_file" -v preserved_file="$preserved_keys_file" \ + -v preserved_count_file="$awk_preserved_count_file" -v updated_count_file="$awk_updated_count_file" ' + BEGIN { + preserved_count = 0 + updated_count = 0 + + # Load preserved values if lookup file exists + if (lookup_file != "") { + while ((getline line < lookup_file) > 0) { + split(line, parts, "\x01") + key = parts[1] + value = parts[2] + preserved_values[key] = value + } + close(lookup_file) + } + } + + # Process each line + { + # Check if this is an environment variable line + if (/^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) { + # Extract key + key = $1 + gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) + + # Check if key should be preserved + if (key in preserved_values) { + print key "=" preserved_values[key] + print key > preserved_file + preserved_count++ + } else { + print $0 + updated_count++ + } + } else { + # Not an env var line, preserve as-is + print $0 + } + } + + END { + print preserved_count > preserved_count_file + print updated_count > updated_count_file + } + ' .env.example > "$new_env_file" + + # Read counters and preserved keys + if [[ -f "$awk_preserved_count_file" ]]; then + preserved_count=$(cat "$awk_preserved_count_file") + fi + if [[ -f "$awk_updated_count_file" ]]; then + updated_count=$(cat "$awk_updated_count_file") + fi + + # Show what was preserved + if [[ -f "$preserved_keys_file" ]]; then + while read -r key; do + [[ -n "$key" ]] && log_info " Preserved: $key (.env value)" + done < "$preserved_keys_file" + fi + + # Clean up lookup file + [[ -n "$lookup_file" ]] && rm -f "$lookup_file" + + # Replace the original .env file + if mv "$new_env_file" ".env"; then + log_success "Successfully created new .env file" + else + log_error "Failed to replace .env file" + rm -f "$new_env_file" + return 1 + fi + + # Clean up difference file and temporary directory + if [[ -n "${TEMP_DIR:-}" ]]; then + rm -rf "${TEMP_DIR}" + unset TEMP_DIR + fi + if [[ -n "${DIFF_FILE:-}" ]]; then + unset DIFF_FILE + fi + + log_success "Partial synchronization of .env file completed" + log_info " Preserved .env values: $preserved_count" + log_info " Updated to .env.example values: $updated_count" +} + +# Detect removed environment variables +detect_removed_variables() { + log_info "Detecting removed environment variables..." + + if [[ ! -f ".env" ]]; then + return + fi + + # Use temporary files for efficient lookup + local temp_dir="${TEMP_DIR:-$(mktemp -d)}" + local temp_example_keys="$temp_dir/example_keys" + local temp_current_keys="$temp_dir/current_keys" + local cleanup_temp_dir="" + + # Set flag if we created a new temp directory + if [[ -z "${TEMP_DIR:-}" ]]; then + cleanup_temp_dir="$temp_dir" + fi + + # Get keys from .env.example and .env, sorted for comm + awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env.example | sort > "$temp_example_keys" + awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env | sort > "$temp_current_keys" + + # Get keys from existing .env and check for removals + local removed_vars=() + while IFS= read -r var; do + removed_vars+=("$var") + done < <(comm -13 "$temp_example_keys" "$temp_current_keys") + + # Clean up temporary files if we created a new temp directory + if [[ -n "$cleanup_temp_dir" ]]; then + rm -rf "$cleanup_temp_dir" + fi + + if [[ ${#removed_vars[@]} -gt 0 ]]; then + log_warning "The following environment variables have been removed from .env.example:" + for var in "${removed_vars[@]}"; do + log_warning " - $var" + done + log_warning "Consider manually removing these variables from .env" + else + log_success "No removed environment variables found" + fi +} + +# Show statistics +show_statistics() { + log_info "Synchronization statistics:" + + local total_example=$(grep -c "^[^#]*=" .env.example 2>/dev/null || echo "0") + local total_env=$(grep -c "^[^#]*=" .env 2>/dev/null || echo "0") + + log_info " .env.example environment variables: $total_example" + log_info " .env environment variables: $total_env" +} + +# Main execution function +# Orchestrates the complete synchronization process in the correct order +main() { + log_info "=== Dify Environment Variables Synchronization Script ===" + log_info "Execution started: $(date)" + + # Check prerequisites + check_files + + # Create backup + create_backup + + # Detect differences + detect_differences + + # Detect removed variables (before sync) + detect_removed_variables + + # Synchronize environment file + sync_env_file + + # Show statistics + show_statistics + + log_success "=== Synchronization process completed successfully ===" + log_info "Execution finished: $(date)" +} + +# Execute main function only when script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file From a5309bee256f8d9edc2d19f966d1549ff9193b8f Mon Sep 17 00:00:00 2001 From: Rhys <nghuutho74@gmail.com> Date: Wed, 24 Dec 2025 10:21:51 +0700 Subject: [PATCH 56/71] fix: handle missing `credential_id` (#30051) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../console/datasets/datasets_document.py | 2 +- api/core/indexing_runner.py | 2 +- api/core/rag/extractor/notion_extractor.py | 14 +++++++++++--- .../core/datasource/test_notion_provider.py | 8 ++++---- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index 6145da31a5..e94768f985 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -572,7 +572,7 @@ class DocumentBatchIndexingEstimateApi(DocumentResource): datasource_type=DatasourceType.NOTION, notion_info=NotionInfo.model_validate( { - "credential_id": data_source_info["credential_id"], + "credential_id": data_source_info.get("credential_id"), "notion_workspace_id": data_source_info["notion_workspace_id"], "notion_obj_id": data_source_info["notion_page_id"], "notion_page_type": data_source_info["type"], diff --git a/api/core/indexing_runner.py b/api/core/indexing_runner.py index 59de4f403d..f1b50f360b 100644 --- a/api/core/indexing_runner.py +++ b/api/core/indexing_runner.py @@ -396,7 +396,7 @@ class IndexingRunner: datasource_type=DatasourceType.NOTION, notion_info=NotionInfo.model_validate( { - "credential_id": data_source_info["credential_id"], + "credential_id": data_source_info.get("credential_id"), "notion_workspace_id": data_source_info["notion_workspace_id"], "notion_obj_id": data_source_info["notion_page_id"], "notion_page_type": data_source_info["type"], diff --git a/api/core/rag/extractor/notion_extractor.py b/api/core/rag/extractor/notion_extractor.py index e87ab38349..372af8fd94 100644 --- a/api/core/rag/extractor/notion_extractor.py +++ b/api/core/rag/extractor/notion_extractor.py @@ -48,13 +48,21 @@ class NotionExtractor(BaseExtractor): if notion_access_token: self._notion_access_token = notion_access_token else: - self._notion_access_token = self._get_access_token(tenant_id, self._credential_id) - if not self._notion_access_token: + try: + self._notion_access_token = self._get_access_token(tenant_id, self._credential_id) + except Exception as e: + logger.warning( + ( + "Failed to get Notion access token from datasource credentials: %s, " + "falling back to environment variable NOTION_INTEGRATION_TOKEN" + ), + e, + ) integration_token = dify_config.NOTION_INTEGRATION_TOKEN if integration_token is None: raise ValueError( "Must specify `integration_token` or set environment variable `NOTION_INTEGRATION_TOKEN`." - ) + ) from e self._notion_access_token = integration_token diff --git a/api/tests/unit_tests/core/datasource/test_notion_provider.py b/api/tests/unit_tests/core/datasource/test_notion_provider.py index 9e7255bc3f..e4bd7d3bdf 100644 --- a/api/tests/unit_tests/core/datasource/test_notion_provider.py +++ b/api/tests/unit_tests/core/datasource/test_notion_provider.py @@ -96,7 +96,7 @@ class TestNotionExtractorAuthentication: def test_init_with_integration_token_fallback(self, mock_get_token, mock_config, mock_document_model): """Test NotionExtractor falls back to integration token when credential not found.""" # Arrange - mock_get_token.return_value = None + mock_get_token.side_effect = Exception("No credential id found") mock_config.NOTION_INTEGRATION_TOKEN = "integration-token-fallback" # Act @@ -105,7 +105,7 @@ class TestNotionExtractorAuthentication: notion_obj_id="page-456", notion_page_type="page", tenant_id="tenant-789", - credential_id="cred-123", + credential_id=None, document_model=mock_document_model, ) @@ -117,7 +117,7 @@ class TestNotionExtractorAuthentication: def test_init_missing_credentials_raises_error(self, mock_get_token, mock_config, mock_document_model): """Test NotionExtractor raises error when no credentials available.""" # Arrange - mock_get_token.return_value = None + mock_get_token.side_effect = Exception("No credential id found") mock_config.NOTION_INTEGRATION_TOKEN = None # Act & Assert @@ -127,7 +127,7 @@ class TestNotionExtractorAuthentication: notion_obj_id="page-456", notion_page_type="page", tenant_id="tenant-789", - credential_id="cred-123", + credential_id=None, document_model=mock_document_model, ) assert "Must specify `integration_token`" in str(exc_info.value) From f439e081b557fade41dbe4f36830c681be21c2bf Mon Sep 17 00:00:00 2001 From: Novice <novice12185727@gmail.com> Date: Wed, 24 Dec 2025 11:28:52 +0800 Subject: [PATCH 57/71] fix: loop streaming by clearing stale subgraph variables (#30059) --- api/core/workflow/enums.py | 1 + api/core/workflow/nodes/loop/entities.py | 6 ++++++ api/core/workflow/nodes/loop/loop_node.py | 22 ++++++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/api/core/workflow/enums.py b/api/core/workflow/enums.py index cf12d5ec1f..c08b62a253 100644 --- a/api/core/workflow/enums.py +++ b/api/core/workflow/enums.py @@ -247,6 +247,7 @@ class WorkflowNodeExecutionMetadataKey(StrEnum): ERROR_STRATEGY = "error_strategy" # node in continue on error mode return the field LOOP_VARIABLE_MAP = "loop_variable_map" # single loop variable output DATASOURCE_INFO = "datasource_info" + COMPLETED_REASON = "completed_reason" # completed reason for loop node class WorkflowNodeExecutionStatus(StrEnum): diff --git a/api/core/workflow/nodes/loop/entities.py b/api/core/workflow/nodes/loop/entities.py index 4fcad888e4..92a8702fc3 100644 --- a/api/core/workflow/nodes/loop/entities.py +++ b/api/core/workflow/nodes/loop/entities.py @@ -1,3 +1,4 @@ +from enum import StrEnum from typing import Annotated, Any, Literal from pydantic import AfterValidator, BaseModel, Field, field_validator @@ -96,3 +97,8 @@ class LoopState(BaseLoopState): Get current output. """ return self.current_output + + +class LoopCompletedReason(StrEnum): + LOOP_BREAK = "loop_break" + LOOP_COMPLETED = "loop_completed" diff --git a/api/core/workflow/nodes/loop/loop_node.py b/api/core/workflow/nodes/loop/loop_node.py index 1c26bbc2d0..1f9fc8a115 100644 --- a/api/core/workflow/nodes/loop/loop_node.py +++ b/api/core/workflow/nodes/loop/loop_node.py @@ -29,7 +29,7 @@ from core.workflow.node_events import ( ) from core.workflow.nodes.base import LLMUsageTrackingMixin from core.workflow.nodes.base.node import Node -from core.workflow.nodes.loop.entities import LoopNodeData, LoopVariableData +from core.workflow.nodes.loop.entities import LoopCompletedReason, LoopNodeData, LoopVariableData from core.workflow.utils.condition.processor import ConditionProcessor from factories.variable_factory import TypeMismatchError, build_segment_with_type, segment_to_variable from libs.datetime_utils import naive_utc_now @@ -96,6 +96,7 @@ class LoopNode(LLMUsageTrackingMixin, Node[LoopNodeData]): loop_duration_map: dict[str, float] = {} single_loop_variable_map: dict[str, dict[str, Any]] = {} # single loop variable output loop_usage = LLMUsage.empty_usage() + loop_node_ids = self._extract_loop_node_ids_from_config(self.graph_config, self._node_id) # Start Loop event yield LoopStartedEvent( @@ -118,6 +119,8 @@ class LoopNode(LLMUsageTrackingMixin, Node[LoopNodeData]): loop_count = 0 for i in range(loop_count): + # Clear stale variables from previous loop iterations to avoid streaming old values + self._clear_loop_subgraph_variables(loop_node_ids) graph_engine = self._create_graph_engine(start_at=start_at, root_node_id=root_node_id) loop_start_time = naive_utc_now() @@ -177,7 +180,11 @@ class LoopNode(LLMUsageTrackingMixin, Node[LoopNodeData]): WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS: loop_usage.total_tokens, WorkflowNodeExecutionMetadataKey.TOTAL_PRICE: loop_usage.total_price, WorkflowNodeExecutionMetadataKey.CURRENCY: loop_usage.currency, - "completed_reason": "loop_break" if reach_break_condition else "loop_completed", + WorkflowNodeExecutionMetadataKey.COMPLETED_REASON: ( + LoopCompletedReason.LOOP_BREAK + if reach_break_condition + else LoopCompletedReason.LOOP_COMPLETED.value + ), WorkflowNodeExecutionMetadataKey.LOOP_DURATION_MAP: loop_duration_map, WorkflowNodeExecutionMetadataKey.LOOP_VARIABLE_MAP: single_loop_variable_map, }, @@ -274,6 +281,17 @@ class LoopNode(LLMUsageTrackingMixin, Node[LoopNodeData]): if WorkflowNodeExecutionMetadataKey.LOOP_ID not in current_metadata: event.node_run_result.metadata = {**current_metadata, **loop_metadata} + def _clear_loop_subgraph_variables(self, loop_node_ids: set[str]) -> None: + """ + Remove variables produced by loop sub-graph nodes from previous iterations. + + Keeping stale variables causes a freshly created response coordinator in the + next iteration to fall back to outdated values when no stream chunks exist. + """ + variable_pool = self.graph_runtime_state.variable_pool + for node_id in loop_node_ids: + variable_pool.remove([node_id]) + @classmethod def _extract_variable_selector_to_variable_mapping( cls, From dcde854c5e5dbad1347424b8a959825d1300e63b Mon Sep 17 00:00:00 2001 From: Joel <iamjoel007@gmail.com> Date: Wed, 24 Dec 2025 14:45:33 +0800 Subject: [PATCH 58/71] chore: some tests (#30078) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../app/log-annotation/index.spec.tsx | 176 ++++++++++++ web/app/components/base/input/index.tsx | 6 +- .../billing/priority-label/index.spec.tsx | 125 ++++++++ .../explore/app-list/index.spec.tsx | 271 ++++++++++++++++++ web/app/components/explore/category.spec.tsx | 79 +++++ web/app/components/explore/index.spec.tsx | 140 +++++++++ .../explore/item-operation/index.spec.tsx | 109 +++++++ .../explore/item-operation/index.tsx | 6 +- .../sidebar/app-nav-item/index.spec.tsx | 99 +++++++ .../components/explore/sidebar/index.spec.tsx | 164 +++++++++++ 10 files changed, 1173 insertions(+), 2 deletions(-) create mode 100644 web/app/components/app/log-annotation/index.spec.tsx create mode 100644 web/app/components/billing/priority-label/index.spec.tsx create mode 100644 web/app/components/explore/app-list/index.spec.tsx create mode 100644 web/app/components/explore/category.spec.tsx create mode 100644 web/app/components/explore/index.spec.tsx create mode 100644 web/app/components/explore/item-operation/index.spec.tsx create mode 100644 web/app/components/explore/sidebar/app-nav-item/index.spec.tsx create mode 100644 web/app/components/explore/sidebar/index.spec.tsx diff --git a/web/app/components/app/log-annotation/index.spec.tsx b/web/app/components/app/log-annotation/index.spec.tsx new file mode 100644 index 0000000000..064092f20e --- /dev/null +++ b/web/app/components/app/log-annotation/index.spec.tsx @@ -0,0 +1,176 @@ +import type { App, AppIconType } from '@/types/app' +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useStore as useAppStore } from '@/app/components/app/store' +import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' +import { AppModeEnum } from '@/types/app' +import LogAnnotation from './index' + +const mockRouterPush = vi.fn() +vi.mock('next/navigation', () => ({ + useRouter: () => ({ + push: mockRouterPush, + }), +})) + +vi.mock('@/app/components/app/annotation', () => ({ + __esModule: true, + default: ({ appDetail }: { appDetail: App }) => ( + <div data-testid="annotation" data-app-id={appDetail.id} /> + ), +})) + +vi.mock('@/app/components/app/log', () => ({ + __esModule: true, + default: ({ appDetail }: { appDetail: App }) => ( + <div data-testid="log" data-app-id={appDetail.id} /> + ), +})) + +vi.mock('@/app/components/app/workflow-log', () => ({ + __esModule: true, + default: ({ appDetail }: { appDetail: App }) => ( + <div data-testid="workflow-log" data-app-id={appDetail.id} /> + ), +})) + +const createMockApp = (overrides: Partial<App> = {}): App => ({ + id: 'app-123', + name: 'Test App', + description: 'Test app description', + author_name: 'Test Author', + icon_type: 'emoji' as AppIconType, + icon: ':icon:', + icon_background: '#FFEAD5', + icon_url: null, + use_icon_as_answer_icon: false, + mode: AppModeEnum.CHAT, + enable_site: true, + enable_api: true, + api_rpm: 60, + api_rph: 3600, + is_demo: false, + model_config: {} as App['model_config'], + app_model_config: {} as App['app_model_config'], + created_at: Date.now(), + updated_at: Date.now(), + site: { + access_token: 'token', + app_base_url: 'https://example.com', + } as App['site'], + api_base_url: 'https://api.example.com', + tags: [], + access_mode: 'public_access' as App['access_mode'], + ...overrides, +}) + +describe('LogAnnotation', () => { + beforeEach(() => { + vi.clearAllMocks() + useAppStore.setState({ appDetail: createMockApp() }) + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render loading state when app detail is missing', () => { + // Arrange + useAppStore.setState({ appDetail: undefined }) + + // Act + render(<LogAnnotation pageType={PageType.log} />) + + // Assert + expect(screen.getByRole('status')).toBeInTheDocument() + }) + + it('should render log and annotation tabs for non-completion apps', () => { + // Arrange + useAppStore.setState({ appDetail: createMockApp({ mode: AppModeEnum.CHAT }) }) + + // Act + render(<LogAnnotation pageType={PageType.log} />) + + // Assert + expect(screen.getByText('appLog.title')).toBeInTheDocument() + expect(screen.getByText('appAnnotation.title')).toBeInTheDocument() + }) + + it('should render only log tab for completion apps', () => { + // Arrange + useAppStore.setState({ appDetail: createMockApp({ mode: AppModeEnum.COMPLETION }) }) + + // Act + render(<LogAnnotation pageType={PageType.log} />) + + // Assert + expect(screen.getByText('appLog.title')).toBeInTheDocument() + expect(screen.queryByText('appAnnotation.title')).not.toBeInTheDocument() + }) + + it('should hide tabs and render workflow log in workflow mode', () => { + // Arrange + useAppStore.setState({ appDetail: createMockApp({ mode: AppModeEnum.WORKFLOW }) }) + + // Act + render(<LogAnnotation pageType={PageType.log} />) + + // Assert + expect(screen.queryByText('appLog.title')).not.toBeInTheDocument() + expect(screen.getByTestId('workflow-log')).toBeInTheDocument() + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should render log content when page type is log', () => { + // Arrange + useAppStore.setState({ appDetail: createMockApp({ mode: AppModeEnum.CHAT }) }) + + // Act + render(<LogAnnotation pageType={PageType.log} />) + + // Assert + expect(screen.getByTestId('log')).toBeInTheDocument() + expect(screen.queryByTestId('annotation')).not.toBeInTheDocument() + }) + + it('should render annotation content when page type is annotation', () => { + // Arrange + useAppStore.setState({ appDetail: createMockApp({ mode: AppModeEnum.CHAT }) }) + + // Act + render(<LogAnnotation pageType={PageType.annotation} />) + + // Assert + expect(screen.getByTestId('annotation')).toBeInTheDocument() + expect(screen.queryByTestId('log')).not.toBeInTheDocument() + }) + }) + + // User interaction behavior + describe('User Interactions', () => { + it('should navigate to annotations when switching from log tab', async () => { + // Arrange + const user = userEvent.setup() + + // Act + render(<LogAnnotation pageType={PageType.log} />) + await user.click(screen.getByText('appAnnotation.title')) + + // Assert + expect(mockRouterPush).toHaveBeenCalledWith('/app/app-123/annotations') + }) + + it('should navigate to logs when switching from annotation tab', async () => { + // Arrange + const user = userEvent.setup() + + // Act + render(<LogAnnotation pageType={PageType.annotation} />) + await user.click(screen.getByText('appLog.title')) + + // Assert + expect(mockRouterPush).toHaveBeenCalledWith('/app/app-123/logs') + }) + }) +}) diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index 98529a26bc..6c6a0c6a75 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -110,7 +110,11 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ {...props} /> {showClearIcon && value && !disabled && !destructive && ( - <div className={cn('group absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer p-[1px]')} onClick={onClear}> + <div + className={cn('group absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer p-[1px]')} + onClick={onClear} + data-testid="input-clear" + > <RiCloseCircleFill className="h-3.5 w-3.5 cursor-pointer text-text-quaternary group-hover:text-text-tertiary" /> </div> )} diff --git a/web/app/components/billing/priority-label/index.spec.tsx b/web/app/components/billing/priority-label/index.spec.tsx new file mode 100644 index 0000000000..0d176d1611 --- /dev/null +++ b/web/app/components/billing/priority-label/index.spec.tsx @@ -0,0 +1,125 @@ +import type { Mock } from 'vitest' +import { fireEvent, render, screen } from '@testing-library/react' +import { createMockPlan } from '@/__mocks__/provider-context' +import { useProviderContext } from '@/context/provider-context' +import { Plan } from '../type' +import PriorityLabel from './index' + +vi.mock('@/context/provider-context', () => ({ + useProviderContext: vi.fn(), +})) + +const useProviderContextMock = useProviderContext as Mock + +const setupPlan = (planType: Plan) => { + useProviderContextMock.mockReturnValue(createMockPlan(planType)) +} + +describe('PriorityLabel', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering: basic label output for sandbox plan. + describe('Rendering', () => { + it('should render the standard priority label when plan is sandbox', () => { + // Arrange + setupPlan(Plan.sandbox) + + // Act + render(<PriorityLabel />) + + // Assert + expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument() + }) + }) + + // Props: custom class name applied to the label container. + describe('Props', () => { + it('should apply custom className to the label container', () => { + // Arrange + setupPlan(Plan.sandbox) + + // Act + render(<PriorityLabel className="custom-class" />) + + // Assert + const label = screen.getByText('billing.plansCommon.priority.standard').closest('div') + expect(label).toHaveClass('custom-class') + }) + }) + + // Plan types: label text and icon visibility for different plans. + describe('Plan Types', () => { + it('should render priority label and icon when plan is professional', () => { + // Arrange + setupPlan(Plan.professional) + + // Act + const { container } = render(<PriorityLabel />) + + // Assert + expect(screen.getByText('billing.plansCommon.priority.priority')).toBeInTheDocument() + expect(container.querySelector('svg')).toBeInTheDocument() + }) + + it('should render top priority label and icon when plan is team', () => { + // Arrange + setupPlan(Plan.team) + + // Act + const { container } = render(<PriorityLabel />) + + // Assert + expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument() + expect(container.querySelector('svg')).toBeInTheDocument() + }) + + it('should render standard label without icon when plan is sandbox', () => { + // Arrange + setupPlan(Plan.sandbox) + + // Act + const { container } = render(<PriorityLabel />) + + // Assert + expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument() + expect(container.querySelector('svg')).not.toBeInTheDocument() + }) + }) + + // Edge cases: tooltip content varies by priority level. + describe('Edge Cases', () => { + it('should show the tip text when priority is not top priority', async () => { + // Arrange + setupPlan(Plan.sandbox) + + // Act + render(<PriorityLabel />) + const label = screen.getByText('billing.plansCommon.priority.standard').closest('div') + fireEvent.mouseEnter(label as HTMLElement) + + // Assert + expect(await screen.findByText( + 'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.standard', + )).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.documentProcessingPriorityTip')).toBeInTheDocument() + }) + + it('should hide the tip text when priority is top priority', async () => { + // Arrange + setupPlan(Plan.enterprise) + + // Act + render(<PriorityLabel />) + const label = screen.getByText('billing.plansCommon.priority.top-priority').closest('div') + fireEvent.mouseEnter(label as HTMLElement) + + // Assert + expect(await screen.findByText( + 'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.top-priority', + )).toBeInTheDocument() + expect(screen.queryByText('billing.plansCommon.documentProcessingPriorityTip')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/explore/app-list/index.spec.tsx b/web/app/components/explore/app-list/index.spec.tsx new file mode 100644 index 0000000000..e73fcdf0ad --- /dev/null +++ b/web/app/components/explore/app-list/index.spec.tsx @@ -0,0 +1,271 @@ +import type { Mock } from 'vitest' +import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' +import type { App } from '@/models/explore' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import ExploreContext from '@/context/explore-context' +import { fetchAppDetail } from '@/service/explore' +import { AppModeEnum } from '@/types/app' +import AppList from './index' + +const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en"}' +let mockTabValue = allCategoriesEn +const mockSetTab = vi.fn() +let mockSWRData: { categories: string[], allList: App[] } = { categories: [], allList: [] } +const mockHandleImportDSL = vi.fn() +const mockHandleImportDSLConfirm = vi.fn() + +vi.mock('@/hooks/use-tab-searchparams', () => ({ + useTabSearchParams: () => [mockTabValue, mockSetTab], +})) + +vi.mock('ahooks', async () => { + const actual = await vi.importActual<typeof import('ahooks')>('ahooks') + const React = await vi.importActual<typeof import('react')>('react') + return { + ...actual, + useDebounceFn: (fn: (...args: unknown[]) => void) => { + const fnRef = React.useRef(fn) + fnRef.current = fn + return { + run: () => setTimeout(() => fnRef.current(), 0), + } + }, + } +}) + +vi.mock('swr', () => ({ + __esModule: true, + default: () => ({ data: mockSWRData }), +})) + +vi.mock('@/service/explore', () => ({ + fetchAppDetail: vi.fn(), + fetchAppList: vi.fn(), +})) + +vi.mock('@/hooks/use-import-dsl', () => ({ + useImportDSL: () => ({ + handleImportDSL: mockHandleImportDSL, + handleImportDSLConfirm: mockHandleImportDSLConfirm, + versions: ['v1'], + isFetching: false, + }), +})) + +vi.mock('@/app/components/explore/create-app-modal', () => ({ + __esModule: true, + default: (props: CreateAppModalProps) => { + if (!props.show) + return null + return ( + <div data-testid="create-app-modal"> + <button + data-testid="confirm-create" + onClick={() => props.onConfirm({ + name: 'New App', + icon_type: 'emoji', + icon: '🤖', + icon_background: '#fff', + description: 'desc', + })} + > + confirm + </button> + <button data-testid="hide-create" onClick={props.onHide}>hide</button> + </div> + ) + }, +})) + +vi.mock('@/app/components/app/create-from-dsl-modal/dsl-confirm-modal', () => ({ + __esModule: true, + default: ({ onConfirm, onCancel }: { onConfirm: () => void, onCancel: () => void }) => ( + <div data-testid="dsl-confirm-modal"> + <button data-testid="dsl-confirm" onClick={onConfirm}>confirm</button> + <button data-testid="dsl-cancel" onClick={onCancel}>cancel</button> + </div> + ), +})) + +const createApp = (overrides: Partial<App> = {}): App => ({ + app: { + id: overrides.app?.id ?? 'app-basic-id', + mode: overrides.app?.mode ?? AppModeEnum.CHAT, + icon_type: overrides.app?.icon_type ?? 'emoji', + icon: overrides.app?.icon ?? '😀', + icon_background: overrides.app?.icon_background ?? '#fff', + icon_url: overrides.app?.icon_url ?? '', + name: overrides.app?.name ?? 'Alpha', + description: overrides.app?.description ?? 'Alpha description', + use_icon_as_answer_icon: overrides.app?.use_icon_as_answer_icon ?? false, + }, + app_id: overrides.app_id ?? 'app-1', + description: overrides.description ?? 'Alpha description', + copyright: overrides.copyright ?? '', + privacy_policy: overrides.privacy_policy ?? null, + custom_disclaimer: overrides.custom_disclaimer ?? null, + category: overrides.category ?? 'Writing', + position: overrides.position ?? 1, + is_listed: overrides.is_listed ?? true, + install_count: overrides.install_count ?? 0, + installed: overrides.installed ?? false, + editable: overrides.editable ?? false, + is_agent: overrides.is_agent ?? false, +}) + +const renderWithContext = (hasEditPermission = false, onSuccess?: () => void) => { + return render( + <ExploreContext.Provider + value={{ + controlUpdateInstalledApps: 0, + setControlUpdateInstalledApps: vi.fn(), + hasEditPermission, + installedApps: [], + setInstalledApps: vi.fn(), + isFetchingInstalledApps: false, + setIsFetchingInstalledApps: vi.fn(), + }} + > + <AppList onSuccess={onSuccess} /> + </ExploreContext.Provider>, + ) +} + +describe('AppList', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTabValue = allCategoriesEn + mockSWRData = { categories: [], allList: [] } + }) + + // Rendering: show loading when categories are not ready. + describe('Rendering', () => { + it('should render loading when categories are empty', () => { + // Arrange + mockSWRData = { categories: [], allList: [] } + + // Act + renderWithContext() + + // Assert + expect(screen.getByRole('status')).toBeInTheDocument() + }) + + it('should render app cards when data is available', () => { + // Arrange + mockSWRData = { + categories: ['Writing', 'Translate'], + allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })], + } + + // Act + renderWithContext() + + // Assert + expect(screen.getByText('Alpha')).toBeInTheDocument() + expect(screen.getByText('Beta')).toBeInTheDocument() + }) + }) + + // Props: category selection filters the list. + describe('Props', () => { + it('should filter apps by selected category', () => { + // Arrange + mockTabValue = 'Writing' + mockSWRData = { + categories: ['Writing', 'Translate'], + allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })], + } + + // Act + renderWithContext() + + // Assert + expect(screen.getByText('Alpha')).toBeInTheDocument() + expect(screen.queryByText('Beta')).not.toBeInTheDocument() + }) + }) + + // User interactions: search and create flow. + describe('User Interactions', () => { + it('should filter apps by search keywords', async () => { + // Arrange + mockSWRData = { + categories: ['Writing'], + allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })], + } + renderWithContext() + + // Act + const input = screen.getByPlaceholderText('common.operation.search') + fireEvent.change(input, { target: { value: 'gam' } }) + + // Assert + await waitFor(() => { + expect(screen.queryByText('Alpha')).not.toBeInTheDocument() + expect(screen.getByText('Gamma')).toBeInTheDocument() + }) + }) + + it('should handle create flow and confirm DSL when pending', async () => { + // Arrange + const onSuccess = vi.fn() + mockSWRData = { + categories: ['Writing'], + allList: [createApp()], + }; + (fetchAppDetail as unknown as Mock).mockResolvedValue({ export_data: 'yaml-content' }) + mockHandleImportDSL.mockImplementation(async (_payload: unknown, options: { onSuccess?: () => void, onPending?: () => void }) => { + options.onPending?.() + }) + mockHandleImportDSLConfirm.mockImplementation(async (options: { onSuccess?: () => void }) => { + options.onSuccess?.() + }) + + // Act + renderWithContext(true, onSuccess) + fireEvent.click(screen.getByText('explore.appCard.addToWorkspace')) + fireEvent.click(await screen.findByTestId('confirm-create')) + + // Assert + await waitFor(() => { + expect(fetchAppDetail).toHaveBeenCalledWith('app-basic-id') + }) + expect(mockHandleImportDSL).toHaveBeenCalledTimes(1) + expect(await screen.findByTestId('dsl-confirm-modal')).toBeInTheDocument() + + fireEvent.click(screen.getByTestId('dsl-confirm')) + await waitFor(() => { + expect(mockHandleImportDSLConfirm).toHaveBeenCalledTimes(1) + expect(onSuccess).toHaveBeenCalledTimes(1) + }) + }) + }) + + // Edge cases: handle clearing search keywords. + describe('Edge Cases', () => { + it('should reset search results when clear icon is clicked', async () => { + // Arrange + mockSWRData = { + categories: ['Writing'], + allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })], + } + renderWithContext() + + // Act + const input = screen.getByPlaceholderText('common.operation.search') + fireEvent.change(input, { target: { value: 'gam' } }) + await waitFor(() => { + expect(screen.queryByText('Alpha')).not.toBeInTheDocument() + }) + + fireEvent.click(screen.getByTestId('input-clear')) + + // Assert + await waitFor(() => { + expect(screen.getByText('Alpha')).toBeInTheDocument() + expect(screen.getByText('Gamma')).toBeInTheDocument() + }) + }) + }) +}) diff --git a/web/app/components/explore/category.spec.tsx b/web/app/components/explore/category.spec.tsx new file mode 100644 index 0000000000..a84b17c844 --- /dev/null +++ b/web/app/components/explore/category.spec.tsx @@ -0,0 +1,79 @@ +import type { AppCategory } from '@/models/explore' +import { fireEvent, render, screen } from '@testing-library/react' +import Category from './category' + +describe('Category', () => { + const allCategoriesEn = 'Recommended' + + const renderComponent = (overrides: Partial<React.ComponentProps<typeof Category>> = {}) => { + const props: React.ComponentProps<typeof Category> = { + list: ['Writing', 'Recommended'] as AppCategory[], + value: allCategoriesEn, + onChange: vi.fn(), + allCategoriesEn, + ...overrides, + } + return { + props, + ...render(<Category {...props} />), + } + } + + // Rendering: basic categories and all-categories button. + describe('Rendering', () => { + it('should render all categories item and translated categories', () => { + // Arrange + renderComponent() + + // Assert + expect(screen.getByText('explore.apps.allCategories')).toBeInTheDocument() + expect(screen.getByText('explore.category.Writing')).toBeInTheDocument() + }) + + it('should not render allCategoriesEn again inside the category list', () => { + // Arrange + renderComponent() + + // Assert + const recommendedItems = screen.getAllByText('explore.apps.allCategories') + expect(recommendedItems).toHaveLength(1) + }) + }) + + // Props: clicking items triggers onChange. + describe('Props', () => { + it('should call onChange with category value when category item is clicked', () => { + // Arrange + const { props } = renderComponent() + + // Act + fireEvent.click(screen.getByText('explore.category.Writing')) + + // Assert + expect(props.onChange).toHaveBeenCalledWith('Writing') + }) + + it('should call onChange with allCategoriesEn when all categories is clicked', () => { + // Arrange + const { props } = renderComponent({ value: 'Writing' }) + + // Act + fireEvent.click(screen.getByText('explore.apps.allCategories')) + + // Assert + expect(props.onChange).toHaveBeenCalledWith(allCategoriesEn) + }) + }) + + // Edge cases: handle values not in the list. + describe('Edge Cases', () => { + it('should treat unknown value as all categories selection', () => { + // Arrange + renderComponent({ value: 'Unknown' }) + + // Assert + const allCategoriesItem = screen.getByText('explore.apps.allCategories') + expect(allCategoriesItem.className).toContain('bg-components-main-nav-nav-button-bg-active') + }) + }) +}) diff --git a/web/app/components/explore/index.spec.tsx b/web/app/components/explore/index.spec.tsx new file mode 100644 index 0000000000..8f361ad471 --- /dev/null +++ b/web/app/components/explore/index.spec.tsx @@ -0,0 +1,140 @@ +import type { Mock } from 'vitest' +import { render, screen, waitFor } from '@testing-library/react' +import { useContext } from 'use-context-selector' +import { useAppContext } from '@/context/app-context' +import ExploreContext from '@/context/explore-context' +import { MediaType } from '@/hooks/use-breakpoints' +import useDocumentTitle from '@/hooks/use-document-title' +import { useMembers } from '@/service/use-common' +import Explore from './index' + +const mockReplace = vi.fn() +const mockPush = vi.fn() +const mockInstalledAppsData = { installed_apps: [] as const } + +vi.mock('next/navigation', () => ({ + useRouter: () => ({ + replace: mockReplace, + push: mockPush, + }), + useSelectedLayoutSegments: () => ['apps'], +})) + +vi.mock('@/hooks/use-breakpoints', () => ({ + __esModule: true, + default: () => MediaType.pc, + MediaType: { + mobile: 'mobile', + tablet: 'tablet', + pc: 'pc', + }, +})) + +vi.mock('@/service/use-explore', () => ({ + useGetInstalledApps: () => ({ + isFetching: false, + data: mockInstalledAppsData, + refetch: vi.fn(), + }), + useUninstallApp: () => ({ + mutateAsync: vi.fn(), + }), + useUpdateAppPinStatus: () => ({ + mutateAsync: vi.fn(), + }), +})) + +vi.mock('@/context/app-context', () => ({ + useAppContext: vi.fn(), +})) + +vi.mock('@/service/use-common', () => ({ + useMembers: vi.fn(), +})) + +vi.mock('@/hooks/use-document-title', () => ({ + __esModule: true, + default: vi.fn(), +})) + +const ContextReader = () => { + const { hasEditPermission } = useContext(ExploreContext) + return <div>{hasEditPermission ? 'edit-yes' : 'edit-no'}</div> +} + +describe('Explore', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering: provides ExploreContext and children. + describe('Rendering', () => { + it('should render children and provide edit permission from members role', async () => { + // Arrange + ; (useAppContext as Mock).mockReturnValue({ + userProfile: { id: 'user-1' }, + isCurrentWorkspaceDatasetOperator: false, + }); + (useMembers as Mock).mockReturnValue({ + data: { + accounts: [{ id: 'user-1', role: 'admin' }], + }, + }) + + // Act + render(( + <Explore> + <ContextReader /> + </Explore> + )) + + // Assert + await waitFor(() => { + expect(screen.getByText('edit-yes')).toBeInTheDocument() + }) + }) + }) + + // Effects: set document title and redirect dataset operators. + describe('Effects', () => { + it('should set document title on render', () => { + // Arrange + ; (useAppContext as Mock).mockReturnValue({ + userProfile: { id: 'user-1' }, + isCurrentWorkspaceDatasetOperator: false, + }); + (useMembers as Mock).mockReturnValue({ data: { accounts: [] } }) + + // Act + render(( + <Explore> + <div>child</div> + </Explore> + )) + + // Assert + expect(useDocumentTitle).toHaveBeenCalledWith('common.menus.explore') + }) + + it('should redirect dataset operators to /datasets', async () => { + // Arrange + ; (useAppContext as Mock).mockReturnValue({ + userProfile: { id: 'user-1' }, + isCurrentWorkspaceDatasetOperator: true, + }); + (useMembers as Mock).mockReturnValue({ data: { accounts: [] } }) + + // Act + render(( + <Explore> + <div>child</div> + </Explore> + )) + + // Assert + await waitFor(() => { + expect(mockReplace).toHaveBeenCalledWith('/datasets') + }) + }) + }) +}) diff --git a/web/app/components/explore/item-operation/index.spec.tsx b/web/app/components/explore/item-operation/index.spec.tsx new file mode 100644 index 0000000000..9084e5564e --- /dev/null +++ b/web/app/components/explore/item-operation/index.spec.tsx @@ -0,0 +1,109 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import ItemOperation from './index' + +describe('ItemOperation', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + const renderComponent = (overrides: Partial<React.ComponentProps<typeof ItemOperation>> = {}) => { + const props: React.ComponentProps<typeof ItemOperation> = { + isPinned: false, + isShowDelete: true, + togglePin: vi.fn(), + onDelete: vi.fn(), + ...overrides, + } + return { + props, + ...render(<ItemOperation {...props} />), + } + } + + // Rendering: menu items show after opening. + describe('Rendering', () => { + it('should render pin and delete actions when menu is open', async () => { + // Arrange + renderComponent() + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + + // Assert + expect(await screen.findByText('explore.sidebar.action.pin')).toBeInTheDocument() + expect(screen.getByText('explore.sidebar.action.delete')).toBeInTheDocument() + }) + }) + + // Props: render optional rename action and pinned label text. + describe('Props', () => { + it('should render rename action when isShowRenameConversation is true', async () => { + // Arrange + renderComponent({ isShowRenameConversation: true }) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + + // Assert + expect(await screen.findByText('explore.sidebar.action.rename')).toBeInTheDocument() + }) + + it('should render unpin label when isPinned is true', async () => { + // Arrange + renderComponent({ isPinned: true }) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + + // Assert + expect(await screen.findByText('explore.sidebar.action.unpin')).toBeInTheDocument() + }) + }) + + // User interactions: clicking action items triggers callbacks. + describe('User Interactions', () => { + it('should call togglePin when clicking pin action', async () => { + // Arrange + const { props } = renderComponent() + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + fireEvent.click(await screen.findByText('explore.sidebar.action.pin')) + + // Assert + expect(props.togglePin).toHaveBeenCalledTimes(1) + }) + + it('should call onDelete when clicking delete action', async () => { + // Arrange + const { props } = renderComponent() + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + fireEvent.click(await screen.findByText('explore.sidebar.action.delete')) + + // Assert + expect(props.onDelete).toHaveBeenCalledTimes(1) + }) + }) + + // Edge cases: menu closes after mouse leave when no hovering state remains. + describe('Edge Cases', () => { + it('should close the menu when mouse leaves the panel and item is not hovering', async () => { + // Arrange + renderComponent() + fireEvent.click(screen.getByTestId('item-operation-trigger')) + const pinText = await screen.findByText('explore.sidebar.action.pin') + const menu = pinText.closest('div')?.parentElement as HTMLElement + + // Act + fireEvent.mouseEnter(menu) + fireEvent.mouseLeave(menu) + + // Assert + await waitFor(() => { + expect(screen.queryByText('explore.sidebar.action.pin')).not.toBeInTheDocument() + }) + }) + }) +}) diff --git a/web/app/components/explore/item-operation/index.tsx b/web/app/components/explore/item-operation/index.tsx index 3703c0d4c0..abbfdd09bd 100644 --- a/web/app/components/explore/item-operation/index.tsx +++ b/web/app/components/explore/item-operation/index.tsx @@ -53,7 +53,11 @@ const ItemOperation: FC<IItemOperationProps> = ({ <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} > - <div className={cn(className, s.btn, 'h-6 w-6 rounded-md border-none py-1', (isItemHovering || open) && `${s.open} !bg-components-actionbar-bg !shadow-none`)}></div> + <div + className={cn(className, s.btn, 'h-6 w-6 rounded-md border-none py-1', (isItemHovering || open) && `${s.open} !bg-components-actionbar-bg !shadow-none`)} + data-testid="item-operation-trigger" + > + </div> </PortalToFollowElemTrigger> <PortalToFollowElemContent className="z-50" diff --git a/web/app/components/explore/sidebar/app-nav-item/index.spec.tsx b/web/app/components/explore/sidebar/app-nav-item/index.spec.tsx new file mode 100644 index 0000000000..542ecf33c2 --- /dev/null +++ b/web/app/components/explore/sidebar/app-nav-item/index.spec.tsx @@ -0,0 +1,99 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import AppNavItem from './index' + +const mockPush = vi.fn() + +vi.mock('next/navigation', () => ({ + useRouter: () => ({ + push: mockPush, + }), +})) + +vi.mock('ahooks', async () => { + const actual = await vi.importActual<typeof import('ahooks')>('ahooks') + return { + ...actual, + useHover: () => false, + } +}) + +const baseProps = { + isMobile: false, + name: 'My App', + id: 'app-123', + icon_type: 'emoji' as const, + icon: '🤖', + icon_background: '#fff', + icon_url: '', + isSelected: false, + isPinned: false, + togglePin: vi.fn(), + uninstallable: false, + onDelete: vi.fn(), +} + +describe('AppNavItem', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering: display app name for desktop and hide for mobile. + describe('Rendering', () => { + it('should render name and item operation on desktop', () => { + // Arrange + render(<AppNavItem {...baseProps} />) + + // Assert + expect(screen.getByText('My App')).toBeInTheDocument() + expect(screen.getByTestId('item-operation-trigger')).toBeInTheDocument() + }) + + it('should hide name on mobile', () => { + // Arrange + render(<AppNavItem {...baseProps} isMobile />) + + // Assert + expect(screen.queryByText('My App')).not.toBeInTheDocument() + }) + }) + + // User interactions: navigation and delete flow. + describe('User Interactions', () => { + it('should navigate to installed app when item is clicked', () => { + // Arrange + render(<AppNavItem {...baseProps} />) + + // Act + fireEvent.click(screen.getByText('My App')) + + // Assert + expect(mockPush).toHaveBeenCalledWith('/explore/installed/app-123') + }) + + it('should call onDelete with app id when delete action is clicked', async () => { + // Arrange + render(<AppNavItem {...baseProps} />) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + fireEvent.click(await screen.findByText('explore.sidebar.action.delete')) + + // Assert + expect(baseProps.onDelete).toHaveBeenCalledWith('app-123') + }) + }) + + // Edge cases: hide delete when uninstallable or selected. + describe('Edge Cases', () => { + it('should not render delete action when app is uninstallable', () => { + // Arrange + render(<AppNavItem {...baseProps} uninstallable />) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + + // Assert + expect(screen.queryByText('explore.sidebar.action.delete')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/explore/sidebar/index.spec.tsx b/web/app/components/explore/sidebar/index.spec.tsx new file mode 100644 index 0000000000..0cbd05aa08 --- /dev/null +++ b/web/app/components/explore/sidebar/index.spec.tsx @@ -0,0 +1,164 @@ +import type { InstalledApp } from '@/models/explore' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import Toast from '@/app/components/base/toast' +import ExploreContext from '@/context/explore-context' +import { MediaType } from '@/hooks/use-breakpoints' +import { AppModeEnum } from '@/types/app' +import SideBar from './index' + +const mockSegments = ['apps'] +const mockPush = vi.fn() +const mockRefetch = vi.fn() +const mockUninstall = vi.fn() +const mockUpdatePinStatus = vi.fn() +let mockIsFetching = false +let mockInstalledApps: InstalledApp[] = [] + +vi.mock('next/navigation', () => ({ + useSelectedLayoutSegments: () => mockSegments, + useRouter: () => ({ + push: mockPush, + }), +})) + +vi.mock('@/hooks/use-breakpoints', () => ({ + __esModule: true, + default: () => MediaType.pc, + MediaType: { + mobile: 'mobile', + tablet: 'tablet', + pc: 'pc', + }, +})) + +vi.mock('@/service/use-explore', () => ({ + useGetInstalledApps: () => ({ + isFetching: mockIsFetching, + data: { installed_apps: mockInstalledApps }, + refetch: mockRefetch, + }), + useUninstallApp: () => ({ + mutateAsync: mockUninstall, + }), + useUpdateAppPinStatus: () => ({ + mutateAsync: mockUpdatePinStatus, + }), +})) + +const createInstalledApp = (overrides: Partial<InstalledApp> = {}): InstalledApp => ({ + id: overrides.id ?? 'app-123', + uninstallable: overrides.uninstallable ?? false, + is_pinned: overrides.is_pinned ?? false, + app: { + id: overrides.app?.id ?? 'app-basic-id', + mode: overrides.app?.mode ?? AppModeEnum.CHAT, + icon_type: overrides.app?.icon_type ?? 'emoji', + icon: overrides.app?.icon ?? '🤖', + icon_background: overrides.app?.icon_background ?? '#fff', + icon_url: overrides.app?.icon_url ?? '', + name: overrides.app?.name ?? 'My App', + description: overrides.app?.description ?? 'desc', + use_icon_as_answer_icon: overrides.app?.use_icon_as_answer_icon ?? false, + }, +}) + +const renderWithContext = (installedApps: InstalledApp[] = []) => { + return render( + <ExploreContext.Provider + value={{ + controlUpdateInstalledApps: 0, + setControlUpdateInstalledApps: vi.fn(), + hasEditPermission: true, + installedApps, + setInstalledApps: vi.fn(), + isFetchingInstalledApps: false, + setIsFetchingInstalledApps: vi.fn(), + }} + > + <SideBar controlUpdateInstalledApps={0} /> + </ExploreContext.Provider>, + ) +} + +describe('SideBar', () => { + beforeEach(() => { + vi.clearAllMocks() + mockIsFetching = false + mockInstalledApps = [] + vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() })) + }) + + // Rendering: show discovery and workspace section. + describe('Rendering', () => { + it('should render workspace items when installed apps exist', () => { + // Arrange + mockInstalledApps = [createInstalledApp()] + + // Act + renderWithContext(mockInstalledApps) + + // Assert + expect(screen.getByText('explore.sidebar.discovery')).toBeInTheDocument() + expect(screen.getByText('explore.sidebar.workspace')).toBeInTheDocument() + expect(screen.getByText('My App')).toBeInTheDocument() + }) + }) + + // Effects: refresh and sync installed apps state. + describe('Effects', () => { + it('should refetch installed apps on mount', () => { + // Arrange + mockInstalledApps = [createInstalledApp()] + + // Act + renderWithContext(mockInstalledApps) + + // Assert + expect(mockRefetch).toHaveBeenCalledTimes(1) + }) + }) + + // User interactions: delete and pin flows. + describe('User Interactions', () => { + it('should uninstall app and show toast when delete is confirmed', async () => { + // Arrange + mockInstalledApps = [createInstalledApp()] + mockUninstall.mockResolvedValue(undefined) + renderWithContext(mockInstalledApps) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + fireEvent.click(await screen.findByText('explore.sidebar.action.delete')) + fireEvent.click(await screen.findByText('common.operation.confirm')) + + // Assert + await waitFor(() => { + expect(mockUninstall).toHaveBeenCalledWith('app-123') + expect(Toast.notify).toHaveBeenCalledWith(expect.objectContaining({ + type: 'success', + message: 'common.api.remove', + })) + }) + }) + + it('should update pin status and show toast when pin is clicked', async () => { + // Arrange + mockInstalledApps = [createInstalledApp({ is_pinned: false })] + mockUpdatePinStatus.mockResolvedValue(undefined) + renderWithContext(mockInstalledApps) + + // Act + fireEvent.click(screen.getByTestId('item-operation-trigger')) + fireEvent.click(await screen.findByText('explore.sidebar.action.pin')) + + // Assert + await waitFor(() => { + expect(mockUpdatePinStatus).toHaveBeenCalledWith({ appId: 'app-123', isPinned: true }) + expect(Toast.notify).toHaveBeenCalledWith(expect.objectContaining({ + type: 'success', + message: 'common.api.success', + })) + }) + }) + }) +}) From b2b7e82e281512a7249aabc754dbe4d65f825ad9 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 15:25:28 +0800 Subject: [PATCH 59/71] refactor(web): migrate log service to TanStack Query (#30065) --- .claude/skills/frontend-testing/SKILL.md | 6 +- .../assets/component-test.template.tsx | 2 +- .../frontend-testing/references/checklist.md | 14 +- .../frontend-testing/references/mocking.md | 25 +- .../frontend-testing/references/workflow.md | 12 +- .github/workflows/web-tests.yml | 2 +- .../components/app/annotation/filter.spec.tsx | 350 +++++++++++-- web/app/components/app/annotation/filter.tsx | 8 +- web/app/components/app/log/filter.tsx | 7 +- web/app/components/app/log/index.tsx | 23 +- web/app/components/app/log/list.tsx | 10 +- .../app/workflow-log/index.spec.tsx | 494 ++++++++++-------- web/app/components/app/workflow-log/index.tsx | 9 +- web/app/components/base/skeleton/index.tsx | 3 +- .../model-parameter-modal/model-display.tsx | 30 +- web/models/log.ts | 15 - web/package.json | 1 + web/service/log.ts | 54 +- web/service/use-log.ts | 89 ++++ web/testing/testing.md | 4 +- 20 files changed, 741 insertions(+), 417 deletions(-) create mode 100644 web/service/use-log.ts diff --git a/.claude/skills/frontend-testing/SKILL.md b/.claude/skills/frontend-testing/SKILL.md index 7475513ba0..65602c92eb 100644 --- a/.claude/skills/frontend-testing/SKILL.md +++ b/.claude/skills/frontend-testing/SKILL.md @@ -49,10 +49,10 @@ pnpm test pnpm test:watch # Run specific file -pnpm test -- path/to/file.spec.tsx +pnpm test path/to/file.spec.tsx # Generate coverage report -pnpm test -- --coverage +pnpm test:coverage # Analyze component complexity pnpm analyze-component <path> @@ -155,7 +155,7 @@ describe('ComponentName', () => { For each file: ┌────────────────────────────────────────┐ │ 1. Write test │ - │ 2. Run: pnpm test -- <file>.spec.tsx │ + │ 2. Run: pnpm test <file>.spec.tsx │ │ 3. PASS? → Mark complete, next file │ │ FAIL? → Fix first, then continue │ └────────────────────────────────────────┘ diff --git a/.claude/skills/frontend-testing/assets/component-test.template.tsx b/.claude/skills/frontend-testing/assets/component-test.template.tsx index 92dd797c83..c39baff916 100644 --- a/.claude/skills/frontend-testing/assets/component-test.template.tsx +++ b/.claude/skills/frontend-testing/assets/component-test.template.tsx @@ -198,7 +198,7 @@ describe('ComponentName', () => { }) // -------------------------------------------------------------------------- - // Async Operations (if component fetches data - useSWR, useQuery, fetch) + // Async Operations (if component fetches data - useQuery, fetch) // -------------------------------------------------------------------------- // WHY: Async operations have 3 states users experience: loading, success, error describe('Async Operations', () => { diff --git a/.claude/skills/frontend-testing/references/checklist.md b/.claude/skills/frontend-testing/references/checklist.md index aad80b120e..1ff2b27bbb 100644 --- a/.claude/skills/frontend-testing/references/checklist.md +++ b/.claude/skills/frontend-testing/references/checklist.md @@ -114,15 +114,15 @@ For the current file being tested: **Run these checks after EACH test file, not just at the end:** -- [ ] Run `pnpm test -- path/to/file.spec.tsx` - **MUST PASS before next file** +- [ ] Run `pnpm test path/to/file.spec.tsx` - **MUST PASS before next file** - [ ] Fix any failures immediately - [ ] Mark file as complete in todo list - [ ] Only then proceed to next file ### After All Files Complete -- [ ] Run full directory test: `pnpm test -- path/to/directory/` -- [ ] Check coverage report: `pnpm test -- --coverage` +- [ ] Run full directory test: `pnpm test path/to/directory/` +- [ ] Check coverage report: `pnpm test:coverage` - [ ] Run `pnpm lint:fix` on all test files - [ ] Run `pnpm type-check:tsgo` @@ -186,16 +186,16 @@ Always test these scenarios: ```bash # Run specific test -pnpm test -- path/to/file.spec.tsx +pnpm test path/to/file.spec.tsx # Run with coverage -pnpm test -- --coverage path/to/file.spec.tsx +pnpm test:coverage path/to/file.spec.tsx # Watch mode -pnpm test:watch -- path/to/file.spec.tsx +pnpm test:watch path/to/file.spec.tsx # Update snapshots (use sparingly) -pnpm test -- -u path/to/file.spec.tsx +pnpm test -u path/to/file.spec.tsx # Analyze component pnpm analyze-component path/to/component.tsx diff --git a/.claude/skills/frontend-testing/references/mocking.md b/.claude/skills/frontend-testing/references/mocking.md index 51920ebc64..23889c8d3d 100644 --- a/.claude/skills/frontend-testing/references/mocking.md +++ b/.claude/skills/frontend-testing/references/mocking.md @@ -242,32 +242,9 @@ describe('Component with Context', () => { }) ``` -### 7. SWR / React Query +### 7. React Query ```typescript -// SWR -vi.mock('swr', () => ({ - __esModule: true, - default: vi.fn(), -})) - -import useSWR from 'swr' -const mockedUseSWR = vi.mocked(useSWR) - -describe('Component with SWR', () => { - it('should show loading state', () => { - mockedUseSWR.mockReturnValue({ - data: undefined, - error: undefined, - isLoading: true, - }) - - render(<Component />) - expect(screen.getByText(/loading/i)).toBeInTheDocument() - }) -}) - -// React Query import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const createTestQueryClient = () => new QueryClient({ diff --git a/.claude/skills/frontend-testing/references/workflow.md b/.claude/skills/frontend-testing/references/workflow.md index b0f2994bde..009c3e013b 100644 --- a/.claude/skills/frontend-testing/references/workflow.md +++ b/.claude/skills/frontend-testing/references/workflow.md @@ -35,7 +35,7 @@ When testing a **single component, hook, or utility**: 2. Run `pnpm analyze-component <path>` (if available) 3. Check complexity score and features detected 4. Write the test file -5. Run test: `pnpm test -- <file>.spec.tsx` +5. Run test: `pnpm test <file>.spec.tsx` 6. Fix any failures 7. Verify coverage meets goals (100% function, >95% branch) ``` @@ -80,7 +80,7 @@ Process files in this recommended order: ``` ┌─────────────────────────────────────────────┐ │ 1. Write test file │ -│ 2. Run: pnpm test -- <file>.spec.tsx │ +│ 2. Run: pnpm test <file>.spec.tsx │ │ 3. If FAIL → Fix immediately, re-run │ │ 4. If PASS → Mark complete in todo list │ │ 5. ONLY THEN proceed to next file │ @@ -95,10 +95,10 @@ After all individual tests pass: ```bash # Run all tests in the directory together -pnpm test -- path/to/directory/ +pnpm test path/to/directory/ # Check coverage -pnpm test -- --coverage path/to/directory/ +pnpm test:coverage path/to/directory/ ``` ## Component Complexity Guidelines @@ -201,9 +201,9 @@ Run pnpm test ← Multiple failures, hard to debug ``` # GOOD: Incremental with verification Write component-a.spec.tsx -Run pnpm test -- component-a.spec.tsx ✅ +Run pnpm test component-a.spec.tsx ✅ Write component-b.spec.tsx -Run pnpm test -- component-b.spec.tsx ✅ +Run pnpm test component-b.spec.tsx ✅ ...continue... ``` diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index 8eba0f084b..adf52a1362 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -42,7 +42,7 @@ jobs: run: pnpm run check:i18n-types - name: Run tests - run: pnpm test --coverage + run: pnpm test:coverage - name: Coverage Summary if: always() diff --git a/web/app/components/app/annotation/filter.spec.tsx b/web/app/components/app/annotation/filter.spec.tsx index 9b733a8c10..7bb39bd444 100644 --- a/web/app/components/app/annotation/filter.spec.tsx +++ b/web/app/components/app/annotation/filter.spec.tsx @@ -1,72 +1,332 @@ +import type { UseQueryResult } from '@tanstack/react-query' import type { Mock } from 'vitest' import type { QueryParam } from './filter' +import type { AnnotationsCountResponse } from '@/models/log' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' -import useSWR from 'swr' +import * as useLogModule from '@/service/use-log' import Filter from './filter' -vi.mock('swr', () => ({ - __esModule: true, - default: vi.fn(), -})) +vi.mock('@/service/use-log') -vi.mock('@/service/log', () => ({ - fetchAnnotationsCount: vi.fn(), -})) +const mockUseAnnotationsCount = useLogModule.useAnnotationsCount as Mock -const mockUseSWR = useSWR as unknown as Mock +// ============================================================================ +// Test Utilities +// ============================================================================ + +const createQueryClient = () => new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}) + +const renderWithQueryClient = (ui: React.ReactElement) => { + const queryClient = createQueryClient() + return render( + <QueryClientProvider client={queryClient}> + {ui} + </QueryClientProvider>, + ) +} + +// ============================================================================ +// Mock Return Value Factory +// ============================================================================ + +type MockQueryResult<T> = Pick<UseQueryResult<T>, 'data' | 'isLoading' | 'error' | 'refetch'> + +const createMockQueryResult = <T,>( + overrides: Partial<MockQueryResult<T>> = {}, +): MockQueryResult<T> => ({ + data: undefined, + isLoading: false, + error: null, + refetch: vi.fn(), + ...overrides, +}) + +// ============================================================================ +// Tests +// ============================================================================ describe('Filter', () => { const appId = 'app-1' const childContent = 'child-content' + const defaultQueryParams: QueryParam = { keyword: '' } beforeEach(() => { vi.clearAllMocks() }) - it('should render nothing until annotation count is fetched', () => { - mockUseSWR.mockReturnValue({ data: undefined }) + // -------------------------------------------------------------------------- + // Rendering Tests (REQUIRED) + // -------------------------------------------------------------------------- + describe('Rendering', () => { + it('should render nothing when data is loading', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ isLoading: true }), + ) - const { container } = render( - <Filter - appId={appId} - queryParams={{ keyword: '' }} - setQueryParams={vi.fn()} - > - <div>{childContent}</div> - </Filter>, - ) + // Act + const { container } = renderWithQueryClient( + <Filter + appId={appId} + queryParams={defaultQueryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) - expect(container.firstChild).toBeNull() - expect(mockUseSWR).toHaveBeenCalledWith( - { url: `/apps/${appId}/annotations/count` }, - expect.any(Function), - ) + // Assert + expect(container.firstChild).toBeNull() + }) + + it('should render nothing when data is undefined', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ data: undefined, isLoading: false }), + ) + + // Act + const { container } = renderWithQueryClient( + <Filter + appId={appId} + queryParams={defaultQueryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert + expect(container.firstChild).toBeNull() + }) + + it('should render filter and children when data is available', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 20 }, + isLoading: false, + }), + ) + + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={defaultQueryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() + expect(screen.getByText(childContent)).toBeInTheDocument() + }) }) - it('should propagate keyword changes and clearing behavior', () => { - mockUseSWR.mockReturnValue({ data: { total: 20 } }) - const queryParams: QueryParam = { keyword: 'prefill' } - const setQueryParams = vi.fn() + // -------------------------------------------------------------------------- + // Props Tests (REQUIRED) + // -------------------------------------------------------------------------- + describe('Props', () => { + it('should call useAnnotationsCount with appId', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 10 }, + isLoading: false, + }), + ) - const { container } = render( - <Filter - appId={appId} - queryParams={queryParams} - setQueryParams={setQueryParams} - > - <div>{childContent}</div> - </Filter>, - ) + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={defaultQueryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) - const input = screen.getByPlaceholderText('common.operation.search') as HTMLInputElement - fireEvent.change(input, { target: { value: 'updated' } }) - expect(setQueryParams).toHaveBeenCalledWith({ ...queryParams, keyword: 'updated' }) + // Assert + expect(mockUseAnnotationsCount).toHaveBeenCalledWith(appId) + }) - const clearButton = input.parentElement?.querySelector('div.cursor-pointer') as HTMLElement - fireEvent.click(clearButton) - expect(setQueryParams).toHaveBeenCalledWith({ ...queryParams, keyword: '' }) + it('should display keyword value in input', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 10 }, + isLoading: false, + }), + ) + const queryParams: QueryParam = { keyword: 'test-keyword' } - expect(container).toHaveTextContent(childContent) + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={queryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert + expect(screen.getByPlaceholderText('common.operation.search')).toHaveValue('test-keyword') + }) + }) + + // -------------------------------------------------------------------------- + // User Interactions + // -------------------------------------------------------------------------- + describe('User Interactions', () => { + it('should call setQueryParams when typing in search input', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 20 }, + isLoading: false, + }), + ) + const queryParams: QueryParam = { keyword: '' } + const setQueryParams = vi.fn() + + renderWithQueryClient( + <Filter + appId={appId} + queryParams={queryParams} + setQueryParams={setQueryParams} + > + <div>{childContent}</div> + </Filter>, + ) + + // Act + const input = screen.getByPlaceholderText('common.operation.search') + fireEvent.change(input, { target: { value: 'updated' } }) + + // Assert + expect(setQueryParams).toHaveBeenCalledWith({ ...queryParams, keyword: 'updated' }) + }) + + it('should call setQueryParams with empty keyword when clearing input', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 20 }, + isLoading: false, + }), + ) + const queryParams: QueryParam = { keyword: 'prefill' } + const setQueryParams = vi.fn() + + renderWithQueryClient( + <Filter + appId={appId} + queryParams={queryParams} + setQueryParams={setQueryParams} + > + <div>{childContent}</div> + </Filter>, + ) + + // Act + const input = screen.getByPlaceholderText('common.operation.search') + const clearButton = input.parentElement?.querySelector('div.cursor-pointer') + if (clearButton) + fireEvent.click(clearButton) + + // Assert + expect(setQueryParams).toHaveBeenCalledWith({ ...queryParams, keyword: '' }) + }) + }) + + // -------------------------------------------------------------------------- + // Edge Cases (REQUIRED) + // -------------------------------------------------------------------------- + describe('Edge Cases', () => { + it('should handle empty keyword in queryParams', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 5 }, + isLoading: false, + }), + ) + + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={{ keyword: '' }} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert + expect(screen.getByPlaceholderText('common.operation.search')).toHaveValue('') + }) + + it('should handle undefined keyword in queryParams', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 5 }, + isLoading: false, + }), + ) + + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={{ keyword: undefined }} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() + }) + + it('should handle zero count', () => { + // Arrange + mockUseAnnotationsCount.mockReturnValue( + createMockQueryResult<AnnotationsCountResponse>({ + data: { count: 0 }, + isLoading: false, + }), + ) + + // Act + renderWithQueryClient( + <Filter + appId={appId} + queryParams={defaultQueryParams} + setQueryParams={vi.fn()} + > + <div>{childContent}</div> + </Filter>, + ) + + // Assert - should still render when count is 0 + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() + }) }) }) diff --git a/web/app/components/app/annotation/filter.tsx b/web/app/components/app/annotation/filter.tsx index 76f33d2f1b..b64a033793 100644 --- a/web/app/components/app/annotation/filter.tsx +++ b/web/app/components/app/annotation/filter.tsx @@ -2,9 +2,8 @@ import type { FC } from 'react' import * as React from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import Input from '@/app/components/base/input' -import { fetchAnnotationsCount } from '@/service/log' +import { useAnnotationsCount } from '@/service/use-log' export type QueryParam = { keyword?: string @@ -23,10 +22,9 @@ const Filter: FC<IFilterProps> = ({ setQueryParams, children, }) => { - // TODO: change fetch list api - const { data } = useSWR({ url: `/apps/${appId}/annotations/count` }, fetchAnnotationsCount) + const { data, isLoading } = useAnnotationsCount(appId) const { t } = useTranslation() - if (!data) + if (isLoading || !data) return null return ( <div className="mb-2 flex flex-row flex-wrap items-center justify-between gap-2"> diff --git a/web/app/components/app/log/filter.tsx b/web/app/components/app/log/filter.tsx index 8984ff3494..4a0103449f 100644 --- a/web/app/components/app/log/filter.tsx +++ b/web/app/components/app/log/filter.tsx @@ -6,11 +6,10 @@ import dayjs from 'dayjs' import quarterOfYear from 'dayjs/plugin/quarterOfYear' import * as React from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import Chip from '@/app/components/base/chip' import Input from '@/app/components/base/input' import Sort from '@/app/components/base/sort' -import { fetchAnnotationsCount } from '@/service/log' +import { useAnnotationsCount } from '@/service/use-log' dayjs.extend(quarterOfYear) @@ -36,9 +35,9 @@ type IFilterProps = { } const Filter: FC<IFilterProps> = ({ isChatMode, appId, queryParams, setQueryParams }: IFilterProps) => { - const { data } = useSWR({ url: `/apps/${appId}/annotations/count` }, fetchAnnotationsCount) + const { data, isLoading } = useAnnotationsCount(appId) const { t } = useTranslation() - if (!data) + if (isLoading || !data) return null return ( <div className="mb-2 flex flex-row flex-wrap items-center gap-2"> diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx index 183826464f..0e6e4bba2d 100644 --- a/web/app/components/app/log/index.tsx +++ b/web/app/components/app/log/index.tsx @@ -8,11 +8,10 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import Loading from '@/app/components/base/loading' import Pagination from '@/app/components/base/pagination' import { APP_PAGE_LIMIT } from '@/config' -import { fetchChatConversations, fetchCompletionConversations } from '@/service/log' +import { useChatConversations, useCompletionConversations } from '@/service/use-log' import { AppModeEnum } from '@/types/app' import EmptyElement from './empty-element' import Filter, { TIME_PERIOD_MAPPING } from './filter' @@ -88,19 +87,15 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => { } // When the details are obtained, proceed to the next request - const { data: chatConversations, mutate: mutateChatList } = useSWR(() => isChatMode - ? { - url: `/apps/${appDetail.id}/chat-conversations`, - params: query, - } - : null, fetchChatConversations) + const { data: chatConversations, refetch: mutateChatList } = useChatConversations({ + appId: isChatMode ? appDetail.id : '', + params: query, + }) - const { data: completionConversations, mutate: mutateCompletionList } = useSWR(() => !isChatMode - ? { - url: `/apps/${appDetail.id}/completion-conversations`, - params: query, - } - : null, fetchCompletionConversations) + const { data: completionConversations, refetch: mutateCompletionList } = useCompletionConversations({ + appId: !isChatMode ? appDetail.id : '', + params: query, + }) const total = isChatMode ? chatConversations?.total : completionConversations?.total diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 06cd20b323..2b13a09b3a 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -17,7 +17,6 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import { createContext, useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' import ModelInfo from '@/app/components/app/log/model-info' @@ -38,7 +37,8 @@ import { WorkflowContextProvider } from '@/app/components/workflow/context' import { useAppContext } from '@/context/app-context' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useTimestamp from '@/hooks/use-timestamp' -import { fetchChatConversationDetail, fetchChatMessages, fetchCompletionConversationDetail, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log' +import { fetchChatMessages, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log' +import { useChatConversationDetail, useCompletionConversationDetail } from '@/service/use-log' import { AppModeEnum } from '@/types/app' import { cn } from '@/utils/classnames' import PromptLogModal from '../../base/prompt-log-modal' @@ -825,8 +825,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { */ const CompletionConversationDetailComp: FC<{ appId?: string, conversationId?: string }> = ({ appId, conversationId }) => { // Text Generator App Session Details Including Message List - const detailParams = ({ url: `/apps/${appId}/completion-conversations/${conversationId}` }) - const { data: conversationDetail, mutate: conversationDetailMutate } = useSWR(() => (appId && conversationId) ? detailParams : null, fetchCompletionConversationDetail) + const { data: conversationDetail, refetch: conversationDetailMutate } = useCompletionConversationDetail(appId, conversationId) const { notify } = useContext(ToastContext) const { t } = useTranslation() @@ -875,8 +874,7 @@ const CompletionConversationDetailComp: FC<{ appId?: string, conversationId?: st * Chat App Conversation Detail Component */ const ChatConversationDetailComp: FC<{ appId?: string, conversationId?: string }> = ({ appId, conversationId }) => { - const detailParams = { url: `/apps/${appId}/chat-conversations/${conversationId}` } - const { data: conversationDetail } = useSWR(() => (appId && conversationId) ? detailParams : null, fetchChatConversationDetail) + const { data: conversationDetail } = useChatConversationDetail(appId, conversationId) const { notify } = useContext(ToastContext) const { t } = useTranslation() diff --git a/web/app/components/app/workflow-log/index.spec.tsx b/web/app/components/app/workflow-log/index.spec.tsx index 8d87a6426b..d689758b30 100644 --- a/web/app/components/app/workflow-log/index.spec.tsx +++ b/web/app/components/app/workflow-log/index.spec.tsx @@ -1,9 +1,9 @@ -import type { MockedFunction } from 'vitest' +import type { UseQueryResult } from '@tanstack/react-query' /** * Logs Container Component Tests * * Tests the main Logs container component which: - * - Fetches workflow logs via useSWR + * - Fetches workflow logs via TanStack Query * - Manages query parameters (status, period, keyword) * - Handles pagination * - Renders Filter, List, and Empty states @@ -15,14 +15,16 @@ import type { MockedFunction } from 'vitest' * - trigger-by-display.spec.tsx */ +import type { MockedFunction } from 'vitest' import type { ILogsProps } from './index' import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } from '@/models/log' import type { App, AppIconType, AppModeEnum } from '@/types/app' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import useSWR from 'swr' import { APP_PAGE_LIMIT } from '@/config' import { WorkflowRunTriggeredFrom } from '@/models/log' +import * as useLogModule from '@/service/use-log' import { TIME_PERIOD_MAPPING } from './filter' import Logs from './index' @@ -30,7 +32,7 @@ import Logs from './index' // Mocks // ============================================================================ -vi.mock('swr') +vi.mock('@/service/use-log') vi.mock('ahooks', () => ({ useDebounce: <T,>(value: T) => value, @@ -72,10 +74,6 @@ vi.mock('@/app/components/base/amplitude/utils', () => ({ trackEvent: (...args: unknown[]) => mockTrackEvent(...args), })) -vi.mock('@/service/log', () => ({ - fetchWorkflowLogs: vi.fn(), -})) - vi.mock('@/hooks/use-theme', () => ({ __esModule: true, default: () => { @@ -89,38 +87,76 @@ vi.mock('@/context/app-context', () => ({ }), })) -// Mock useTimestamp -vi.mock('@/hooks/use-timestamp', () => ({ - __esModule: true, - default: () => ({ - formatTime: (timestamp: number, _format: string) => `formatted-${timestamp}`, - }), -})) - -// Mock useBreakpoints -vi.mock('@/hooks/use-breakpoints', () => ({ - __esModule: true, - default: () => 'pc', - MediaType: { - mobile: 'mobile', - pc: 'pc', - }, -})) - -// Mock BlockIcon -vi.mock('@/app/components/workflow/block-icon', () => ({ - __esModule: true, - default: () => <div data-testid="block-icon">BlockIcon</div>, -})) - // Mock WorkflowContextProvider vi.mock('@/app/components/workflow/context', () => ({ WorkflowContextProvider: ({ children }: { children: React.ReactNode }) => ( - <div data-testid="workflow-context-provider">{children}</div> + <>{children}</> ), })) -const mockedUseSWR = useSWR as unknown as MockedFunction<typeof useSWR> +const mockedUseWorkflowLogs = useLogModule.useWorkflowLogs as MockedFunction<typeof useLogModule.useWorkflowLogs> + +// ============================================================================ +// Test Utilities +// ============================================================================ + +const createQueryClient = () => new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}) + +const renderWithQueryClient = (ui: React.ReactElement) => { + const queryClient = createQueryClient() + return render( + <QueryClientProvider client={queryClient}> + {ui} + </QueryClientProvider>, + ) +} + +// ============================================================================ +// Mock Return Value Factory +// ============================================================================ + +const createMockQueryResult = <T,>( + overrides: { data?: T, isLoading?: boolean, error?: Error | null } = {}, +): UseQueryResult<T, Error> => { + const isLoading = overrides.isLoading ?? false + const error = overrides.error ?? null + const data = overrides.data + + return { + data, + isLoading, + error, + refetch: vi.fn(), + isError: !!error, + isPending: isLoading, + isSuccess: !isLoading && !error && data !== undefined, + isFetching: isLoading, + isRefetching: false, + isLoadingError: false, + isRefetchError: false, + isInitialLoading: isLoading, + isPaused: false, + isEnabled: true, + status: isLoading ? 'pending' : error ? 'error' : 'success', + fetchStatus: isLoading ? 'fetching' : 'idle', + dataUpdatedAt: Date.now(), + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isFetched: !isLoading, + isFetchedAfterMount: !isLoading, + isPlaceholderData: false, + isStale: false, + promise: Promise.resolve(data as T), + } as UseQueryResult<T, Error> +} // ============================================================================ // Test Data Factories @@ -195,6 +231,20 @@ const createMockLogsResponse = ( page: 1, }) +// ============================================================================ +// Type-safe Mock Helper +// ============================================================================ + +type WorkflowLogsParams = { + appId: string + params?: Record<string, string | number | boolean | undefined> +} + +const getMockCallParams = (): WorkflowLogsParams | undefined => { + const lastCall = mockedUseWorkflowLogs.mock.calls.at(-1) + return lastCall?.[0] +} + // ============================================================================ // Tests // ============================================================================ @@ -213,45 +263,48 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Rendering', () => { it('should render without crashing', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByText('appLog.workflowTitle')).toBeInTheDocument() }) it('should render title and subtitle', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByText('appLog.workflowTitle')).toBeInTheDocument() expect(screen.getByText('appLog.workflowSubtitle')).toBeInTheDocument() }) it('should render Filter component', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() }) }) @@ -261,30 +314,33 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Loading State', () => { it('should show loading spinner when data is undefined', () => { - mockedUseSWR.mockReturnValue({ - data: undefined, - mutate: vi.fn(), - isValidating: true, - isLoading: true, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: undefined, + isLoading: true, + }), + ) - const { container } = render(<Logs {...defaultProps} />) + // Act + const { container } = renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(container.querySelector('.spin-animation')).toBeInTheDocument() }) it('should not show loading spinner when data is available', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([createMockWorkflowLog()], 1), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([createMockWorkflowLog()], 1), + }), + ) - const { container } = render(<Logs {...defaultProps} />) + // Act + const { container } = renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(container.querySelector('.spin-animation')).not.toBeInTheDocument() }) }) @@ -294,16 +350,17 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Empty State', () => { it('should render empty element when total is 0', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByText('appLog.table.empty.element.title')).toBeInTheDocument() expect(screen.queryByRole('table')).not.toBeInTheDocument() }) @@ -313,20 +370,21 @@ describe('Logs Container', () => { // Data Fetching Tests // -------------------------------------------------------------------------- describe('Data Fetching', () => { - it('should call useSWR with correct URL and default params', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + it('should call useWorkflowLogs with correct appId and default params', () => { + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) - const keyArg = mockedUseSWR.mock.calls.at(-1)?.[0] as { url: string, params: Record<string, unknown> } - expect(keyArg).toMatchObject({ - url: `/apps/${defaultProps.appDetail.id}/workflow-app-logs`, + // Assert + const callArg = getMockCallParams() + expect(callArg).toMatchObject({ + appId: defaultProps.appDetail.id, params: expect.objectContaining({ page: 1, detail: true, @@ -336,34 +394,36 @@ describe('Logs Container', () => { }) it('should include date filters for non-allTime periods', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) - const keyArg = mockedUseSWR.mock.calls.at(-1)?.[0] as { params?: Record<string, unknown> } - expect(keyArg?.params).toHaveProperty('created_at__after') - expect(keyArg?.params).toHaveProperty('created_at__before') + // Assert + const callArg = getMockCallParams() + expect(callArg?.params).toHaveProperty('created_at__after') + expect(callArg?.params).toHaveProperty('created_at__before') }) it('should not include status param when status is all', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) - const keyArg = mockedUseSWR.mock.calls.at(-1)?.[0] as { params?: Record<string, unknown> } - expect(keyArg?.params).not.toHaveProperty('status') + // Assert + const callArg = getMockCallParams() + expect(callArg?.params).not.toHaveProperty('status') }) }) @@ -372,24 +432,23 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Filter Integration', () => { it('should update query when selecting status filter', async () => { + // Arrange const user = userEvent.setup() - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + renderWithQueryClient(<Logs {...defaultProps} />) - // Click status filter + // Act await user.click(screen.getByText('All')) await user.click(await screen.findByText('Success')) - // Check that useSWR was called with updated params + // Assert await waitFor(() => { - const lastCall = mockedUseSWR.mock.calls.at(-1)?.[0] as { params?: Record<string, unknown> } + const lastCall = getMockCallParams() expect(lastCall?.params).toMatchObject({ status: 'succeeded', }) @@ -397,46 +456,46 @@ describe('Logs Container', () => { }) it('should update query when selecting period filter', async () => { + // Arrange const user = userEvent.setup() - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + renderWithQueryClient(<Logs {...defaultProps} />) - // Click period filter + // Act await user.click(screen.getByText('appLog.filter.period.last7days')) await user.click(await screen.findByText('appLog.filter.period.allTime')) - // When period is allTime (9), date filters should be removed + // Assert await waitFor(() => { - const lastCall = mockedUseSWR.mock.calls.at(-1)?.[0] as { params?: Record<string, unknown> } + const lastCall = getMockCallParams() expect(lastCall?.params).not.toHaveProperty('created_at__after') expect(lastCall?.params).not.toHaveProperty('created_at__before') }) }) it('should update query when typing keyword', async () => { + // Arrange const user = userEvent.setup() - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) - render(<Logs {...defaultProps} />) + renderWithQueryClient(<Logs {...defaultProps} />) + // Act const searchInput = screen.getByPlaceholderText('common.operation.search') await user.type(searchInput, 'test-keyword') + // Assert await waitFor(() => { - const lastCall = mockedUseSWR.mock.calls.at(-1)?.[0] as { params?: Record<string, unknown> } + const lastCall = getMockCallParams() expect(lastCall?.params).toMatchObject({ keyword: 'test-keyword', }) @@ -449,36 +508,35 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Pagination', () => { it('should not render pagination when total is less than limit', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([createMockWorkflowLog()], 1), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([createMockWorkflowLog()], 1), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) - // Pagination component should not be rendered + // Assert expect(screen.queryByRole('navigation')).not.toBeInTheDocument() }) it('should render pagination when total exceeds limit', () => { + // Arrange const logs = Array.from({ length: APP_PAGE_LIMIT }, (_, i) => createMockWorkflowLog({ id: `log-${i}` })) - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse(logs, APP_PAGE_LIMIT + 10), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse(logs, APP_PAGE_LIMIT + 10), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) - // Should show pagination - checking for any pagination-related element - // The Pagination component renders page controls + // Assert expect(screen.getByRole('table')).toBeInTheDocument() }) }) @@ -488,37 +546,39 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('List Rendering', () => { it('should render List component when data is available', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([createMockWorkflowLog()], 1), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([createMockWorkflowLog()], 1), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByRole('table')).toBeInTheDocument() }) it('should display log data in table', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([ - createMockWorkflowLog({ - workflow_run: createMockWorkflowRun({ - status: 'succeeded', - total_tokens: 500, + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([ + createMockWorkflowLog({ + workflow_run: createMockWorkflowRun({ + status: 'succeeded', + total_tokens: 500, + }), }), - }), - ], 1), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + ], 1), + }), + ) - render(<Logs {...defaultProps} />) + // Act + renderWithQueryClient(<Logs {...defaultProps} />) + // Assert expect(screen.getByText('Success')).toBeInTheDocument() expect(screen.getByText('500')).toBeInTheDocument() }) @@ -541,52 +601,54 @@ describe('Logs Container', () => { // -------------------------------------------------------------------------- describe('Edge Cases', () => { it('should handle different app modes', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([createMockWorkflowLog()], 1), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([createMockWorkflowLog()], 1), + }), + ) const chatApp = createMockApp({ mode: 'advanced-chat' as AppModeEnum }) - render(<Logs appDetail={chatApp} />) + // Act + renderWithQueryClient(<Logs appDetail={chatApp} />) - // Should render without trigger column + // Assert expect(screen.queryByText('appLog.table.header.triggered_from')).not.toBeInTheDocument() }) - it('should handle error state from useSWR', () => { - mockedUseSWR.mockReturnValue({ - data: undefined, - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: new Error('Failed to fetch'), - }) + it('should handle error state from useWorkflowLogs', () => { + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: undefined, + error: new Error('Failed to fetch'), + }), + ) - const { container } = render(<Logs {...defaultProps} />) + // Act + const { container } = renderWithQueryClient(<Logs {...defaultProps} />) - // Should show loading state when data is undefined + // Assert - should show loading state when data is undefined expect(container.querySelector('.spin-animation')).toBeInTheDocument() }) it('should handle app with different ID', () => { - mockedUseSWR.mockReturnValue({ - data: createMockLogsResponse([], 0), - mutate: vi.fn(), - isValidating: false, - isLoading: false, - error: undefined, - }) + // Arrange + mockedUseWorkflowLogs.mockReturnValue( + createMockQueryResult<WorkflowLogsResponse>({ + data: createMockLogsResponse([], 0), + }), + ) const customApp = createMockApp({ id: 'custom-app-123' }) - render(<Logs appDetail={customApp} />) + // Act + renderWithQueryClient(<Logs appDetail={customApp} />) - const keyArg = mockedUseSWR.mock.calls.at(-1)?.[0] as { url: string } - expect(keyArg?.url).toBe('/apps/custom-app-123/workflow-app-logs') + // Assert + const callArg = getMockCallParams() + expect(callArg?.appId).toBe('custom-app-123') }) }) }) diff --git a/web/app/components/app/workflow-log/index.tsx b/web/app/components/app/workflow-log/index.tsx index 1390f2d435..12438d6d17 100644 --- a/web/app/components/app/workflow-log/index.tsx +++ b/web/app/components/app/workflow-log/index.tsx @@ -9,13 +9,12 @@ import { omit } from 'lodash-es' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import EmptyElement from '@/app/components/app/log/empty-element' import Loading from '@/app/components/base/loading' import Pagination from '@/app/components/base/pagination' import { APP_PAGE_LIMIT } from '@/config' import { useAppContext } from '@/context/app-context' -import { fetchWorkflowLogs } from '@/service/log' +import { useWorkflowLogs } from '@/service/use-log' import Filter, { TIME_PERIOD_MAPPING } from './filter' import List from './list' @@ -55,10 +54,10 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => { ...omit(debouncedQueryParams, ['period', 'status']), } - const { data: workflowLogs, mutate } = useSWR({ - url: `/apps/${appDetail.id}/workflow-app-logs`, + const { data: workflowLogs, refetch: mutate } = useWorkflowLogs({ + appId: appDetail.id, params: query, - }, fetchWorkflowLogs) + }) const total = workflowLogs?.total return ( diff --git a/web/app/components/base/skeleton/index.tsx b/web/app/components/base/skeleton/index.tsx index 9cd7e3f09c..cbb5a3d7c3 100644 --- a/web/app/components/base/skeleton/index.tsx +++ b/web/app/components/base/skeleton/index.tsx @@ -36,7 +36,8 @@ export const SkeletonPoint: FC<SkeletonProps> = (props) => { <div className={cn('text-xs font-medium text-text-quaternary', className)} {...rest}>·</div> ) } -/** Usage +/** + * Usage * <SkeletonContainer> * <SkeletonRow> * <SkeletonRectangle className="w-96" /> diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx index e9e8ec7525..d8a4fabac0 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/model-display.tsx @@ -6,20 +6,22 @@ type ModelDisplayProps = { } const ModelDisplay = ({ currentModel, modelId }: ModelDisplayProps) => { - return currentModel ? ( - <ModelName - className="flex grow items-center gap-1 px-1 py-[3px]" - modelItem={currentModel} - showMode - showFeatures - /> - ) : ( - <div className="flex grow items-center gap-1 truncate px-1 py-[3px] opacity-50"> - <div className="system-sm-regular overflow-hidden text-ellipsis text-components-input-text-filled"> - {modelId} - </div> - </div> - ) + return currentModel + ? ( + <ModelName + className="flex grow items-center gap-1 px-1 py-[3px]" + modelItem={currentModel} + showMode + showFeatures + /> + ) + : ( + <div className="flex grow items-center gap-1 truncate px-1 py-[3px] opacity-50"> + <div className="system-sm-regular overflow-hidden text-ellipsis text-components-input-text-filled"> + {modelId} + </div> + </div> + ) } export default ModelDisplay diff --git a/web/models/log.ts b/web/models/log.ts index 8c022ee6b2..d15d6d6688 100644 --- a/web/models/log.ts +++ b/web/models/log.ts @@ -6,21 +6,6 @@ import type { } from '@/app/components/workflow/types' import type { VisionFile } from '@/types/app' -// Log type contains key:string conversation_id:string created_at:string question:string answer:string -export type Conversation = { - id: string - key: string - conversationId: string - question: string - answer: string - userRate: number - adminRate: number -} - -export type ConversationListResponse = { - logs: Conversation[] -} - export const CompletionParams = ['temperature', 'top_p', 'presence_penalty', 'max_token', 'stop', 'frequency_penalty'] as const export type CompletionParamType = typeof CompletionParams[number] diff --git a/web/package.json b/web/package.json index ce2e59e022..baecd2c5c7 100644 --- a/web/package.json +++ b/web/package.json @@ -38,6 +38,7 @@ "gen:i18n-types": "node ./i18n-config/generate-i18n-types.js", "check:i18n-types": "node ./i18n-config/check-i18n-sync.js", "test": "vitest run", + "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", "analyze-component": "node testing/analyze-component.js", "storybook": "storybook dev -p 6006", diff --git a/web/service/log.ts b/web/service/log.ts index aa0be7ac3b..a540cea22c 100644 --- a/web/service/log.ts +++ b/web/service/log.ts @@ -1,80 +1,38 @@ -import type { Fetcher } from 'swr' import type { AgentLogDetailRequest, AgentLogDetailResponse, - AnnotationsCountResponse, - ChatConversationFullDetailResponse, - ChatConversationsRequest, - ChatConversationsResponse, ChatMessagesRequest, ChatMessagesResponse, - CompletionConversationFullDetailResponse, - CompletionConversationsRequest, - CompletionConversationsResponse, - ConversationListResponse, LogMessageAnnotationsRequest, LogMessageAnnotationsResponse, LogMessageFeedbacksRequest, LogMessageFeedbacksResponse, - WorkflowLogsResponse, WorkflowRunDetailResponse, } from '@/models/log' import type { NodeTracingListResponse } from '@/types/workflow' import { get, post } from './base' -export const fetchConversationList: Fetcher<ConversationListResponse, { name: string, appId: string, params?: Record<string, any> }> = ({ appId, params }) => { - return get<ConversationListResponse>(`/console/api/apps/${appId}/messages`, params) -} - -// (Text Generation Application) Session List -export const fetchCompletionConversations: Fetcher<CompletionConversationsResponse, { url: string, params?: CompletionConversationsRequest }> = ({ url, params }) => { - return get<CompletionConversationsResponse>(url, { params }) -} - -// (Text Generation Application) Session Detail -export const fetchCompletionConversationDetail: Fetcher<CompletionConversationFullDetailResponse, { url: string }> = ({ url }) => { - return get<CompletionConversationFullDetailResponse>(url, {}) -} - -// (Chat Application) Session List -export const fetchChatConversations: Fetcher<ChatConversationsResponse, { url: string, params?: ChatConversationsRequest }> = ({ url, params }) => { - return get<ChatConversationsResponse>(url, { params }) -} - -// (Chat Application) Session Detail -export const fetchChatConversationDetail: Fetcher<ChatConversationFullDetailResponse, { url: string }> = ({ url }) => { - return get<ChatConversationFullDetailResponse>(url, {}) -} - // (Chat Application) Message list in one session -export const fetchChatMessages: Fetcher<ChatMessagesResponse, { url: string, params: ChatMessagesRequest }> = ({ url, params }) => { +export const fetchChatMessages = ({ url, params }: { url: string, params: ChatMessagesRequest }): Promise<ChatMessagesResponse> => { return get<ChatMessagesResponse>(url, { params }) } -export const updateLogMessageFeedbacks: Fetcher<LogMessageFeedbacksResponse, { url: string, body: LogMessageFeedbacksRequest }> = ({ url, body }) => { +export const updateLogMessageFeedbacks = ({ url, body }: { url: string, body: LogMessageFeedbacksRequest }): Promise<LogMessageFeedbacksResponse> => { return post<LogMessageFeedbacksResponse>(url, { body }) } -export const updateLogMessageAnnotations: Fetcher<LogMessageAnnotationsResponse, { url: string, body: LogMessageAnnotationsRequest }> = ({ url, body }) => { +export const updateLogMessageAnnotations = ({ url, body }: { url: string, body: LogMessageAnnotationsRequest }): Promise<LogMessageAnnotationsResponse> => { return post<LogMessageAnnotationsResponse>(url, { body }) } -export const fetchAnnotationsCount: Fetcher<AnnotationsCountResponse, { url: string }> = ({ url }) => { - return get<AnnotationsCountResponse>(url) -} - -export const fetchWorkflowLogs: Fetcher<WorkflowLogsResponse, { url: string, params: Record<string, any> }> = ({ url, params }) => { - return get<WorkflowLogsResponse>(url, { params }) -} - -export const fetchRunDetail = (url: string) => { +export const fetchRunDetail = (url: string): Promise<WorkflowRunDetailResponse> => { return get<WorkflowRunDetailResponse>(url) } -export const fetchTracingList: Fetcher<NodeTracingListResponse, { url: string }> = ({ url }) => { +export const fetchTracingList = ({ url }: { url: string }): Promise<NodeTracingListResponse> => { return get<NodeTracingListResponse>(url) } -export const fetchAgentLogDetail = ({ appID, params }: { appID: string, params: AgentLogDetailRequest }) => { +export const fetchAgentLogDetail = ({ appID, params }: { appID: string, params: AgentLogDetailRequest }): Promise<AgentLogDetailResponse> => { return get<AgentLogDetailResponse>(`/apps/${appID}/agent/logs`, { params }) } diff --git a/web/service/use-log.ts b/web/service/use-log.ts new file mode 100644 index 0000000000..b120adda2f --- /dev/null +++ b/web/service/use-log.ts @@ -0,0 +1,89 @@ +import type { + AnnotationsCountResponse, + ChatConversationFullDetailResponse, + ChatConversationsRequest, + ChatConversationsResponse, + CompletionConversationFullDetailResponse, + CompletionConversationsRequest, + CompletionConversationsResponse, + WorkflowLogsResponse, +} from '@/models/log' +import { useQuery } from '@tanstack/react-query' +import { get } from './base' + +const NAME_SPACE = 'log' + +// ============ Annotations Count ============ + +export const useAnnotationsCount = (appId: string) => { + return useQuery<AnnotationsCountResponse>({ + queryKey: [NAME_SPACE, 'annotations-count', appId], + queryFn: () => get<AnnotationsCountResponse>(`/apps/${appId}/annotations/count`), + enabled: !!appId, + }) +} + +// ============ Chat Conversations ============ + +type ChatConversationsParams = { + appId: string + params?: Partial<ChatConversationsRequest> +} + +export const useChatConversations = ({ appId, params }: ChatConversationsParams) => { + return useQuery<ChatConversationsResponse>({ + queryKey: [NAME_SPACE, 'chat-conversations', appId, params], + queryFn: () => get<ChatConversationsResponse>(`/apps/${appId}/chat-conversations`, { params }), + enabled: !!appId, + }) +} + +// ============ Completion Conversations ============ + +type CompletionConversationsParams = { + appId: string + params?: Partial<CompletionConversationsRequest> +} + +export const useCompletionConversations = ({ appId, params }: CompletionConversationsParams) => { + return useQuery<CompletionConversationsResponse>({ + queryKey: [NAME_SPACE, 'completion-conversations', appId, params], + queryFn: () => get<CompletionConversationsResponse>(`/apps/${appId}/completion-conversations`, { params }), + enabled: !!appId, + }) +} + +// ============ Chat Conversation Detail ============ + +export const useChatConversationDetail = (appId?: string, conversationId?: string) => { + return useQuery<ChatConversationFullDetailResponse>({ + queryKey: [NAME_SPACE, 'chat-conversation-detail', appId, conversationId], + queryFn: () => get<ChatConversationFullDetailResponse>(`/apps/${appId}/chat-conversations/${conversationId}`), + enabled: !!appId && !!conversationId, + }) +} + +// ============ Completion Conversation Detail ============ + +export const useCompletionConversationDetail = (appId?: string, conversationId?: string) => { + return useQuery<CompletionConversationFullDetailResponse>({ + queryKey: [NAME_SPACE, 'completion-conversation-detail', appId, conversationId], + queryFn: () => get<CompletionConversationFullDetailResponse>(`/apps/${appId}/completion-conversations/${conversationId}`), + enabled: !!appId && !!conversationId, + }) +} + +// ============ Workflow Logs ============ + +type WorkflowLogsParams = { + appId: string + params?: Record<string, string | number | boolean | undefined> +} + +export const useWorkflowLogs = ({ appId, params }: WorkflowLogsParams) => { + return useQuery<WorkflowLogsResponse>({ + queryKey: [NAME_SPACE, 'workflow-logs', appId, params], + queryFn: () => get<WorkflowLogsResponse>(`/apps/${appId}/workflow-app-logs`, { params }), + enabled: !!appId, + }) +} diff --git a/web/testing/testing.md b/web/testing/testing.md index a2c8399d45..08fc716cf3 100644 --- a/web/testing/testing.md +++ b/web/testing/testing.md @@ -21,10 +21,10 @@ pnpm test pnpm test:watch # Generate coverage report -pnpm test -- --coverage +pnpm test:coverage # Run specific file -pnpm test -- path/to/file.spec.tsx +pnpm test path/to/file.spec.tsx ``` ## Project Test Setup From 0f41924db48596058edc1a2ebbe49eac23433c9e Mon Sep 17 00:00:00 2001 From: Joel <iamjoel007@gmail.com> Date: Wed, 24 Dec 2025 16:17:59 +0800 Subject: [PATCH 60/71] chore: some tests (#30084) Co-authored-by: yyh <yuanyouhuilyz@gmail.com> --- .../base/feature-panel/index.spec.tsx | 71 +++++ .../base/feature-panel/index.tsx | 4 +- .../apps-full-in-dialog/index.spec.tsx | 274 ++++++++++++++++++ .../billing/pricing/assets/index.spec.tsx | 64 ++++ .../billing/pricing/footer.spec.tsx | 68 +++++ .../billing/pricing/header.spec.tsx | 72 +++++ .../components/billing/pricing/index.spec.tsx | 119 ++++++++ .../pricing/plan-switcher/index.spec.tsx | 109 +++++++ .../plan-range-switcher.spec.tsx | 81 ++++++ .../pricing/plan-switcher/tab.spec.tsx | 95 ++++++ .../components/billing/progress-bar/index.tsx | 1 + 11 files changed, 956 insertions(+), 2 deletions(-) create mode 100644 web/app/components/app/configuration/base/feature-panel/index.spec.tsx create mode 100644 web/app/components/billing/apps-full-in-dialog/index.spec.tsx create mode 100644 web/app/components/billing/pricing/assets/index.spec.tsx create mode 100644 web/app/components/billing/pricing/footer.spec.tsx create mode 100644 web/app/components/billing/pricing/header.spec.tsx create mode 100644 web/app/components/billing/pricing/index.spec.tsx create mode 100644 web/app/components/billing/pricing/plan-switcher/index.spec.tsx create mode 100644 web/app/components/billing/pricing/plan-switcher/plan-range-switcher.spec.tsx create mode 100644 web/app/components/billing/pricing/plan-switcher/tab.spec.tsx diff --git a/web/app/components/app/configuration/base/feature-panel/index.spec.tsx b/web/app/components/app/configuration/base/feature-panel/index.spec.tsx new file mode 100644 index 0000000000..7e1b661399 --- /dev/null +++ b/web/app/components/app/configuration/base/feature-panel/index.spec.tsx @@ -0,0 +1,71 @@ +import { render, screen } from '@testing-library/react' +import FeaturePanel from './index' + +describe('FeaturePanel', () => { + // Rendering behavior for standard layout. + describe('Rendering', () => { + it('should render the title and children when provided', () => { + // Arrange + render( + <FeaturePanel title="Panel Title"> + <div>Panel Body</div> + </FeaturePanel>, + ) + + // Assert + expect(screen.getByText('Panel Title')).toBeInTheDocument() + expect(screen.getByText('Panel Body')).toBeInTheDocument() + }) + }) + + // Prop-driven presentation details like icons, actions, and spacing. + describe('Props', () => { + it('should render header icon and right slot and apply header border', () => { + // Arrange + render( + <FeaturePanel + title="Feature" + headerIcon={<span>Icon</span>} + headerRight={<button type="button">Action</button>} + hasHeaderBottomBorder={true} + />, + ) + + // Assert + expect(screen.getByText('Icon')).toBeInTheDocument() + expect(screen.getByText('Action')).toBeInTheDocument() + const header = screen.getByTestId('feature-panel-header') + expect(header).toHaveClass('border-b') + }) + + it('should apply custom className and remove padding when noBodySpacing is true', () => { + // Arrange + const { container } = render( + <FeaturePanel title="Spacing" className="custom-panel" noBodySpacing={true}> + <div>Body</div> + </FeaturePanel>, + ) + + // Assert + const root = container.firstElementChild as HTMLElement + expect(root).toHaveClass('custom-panel') + expect(root).toHaveClass('pb-0') + const body = screen.getByTestId('feature-panel-body') + expect(body).not.toHaveClass('mt-1') + expect(body).not.toHaveClass('px-3') + }) + }) + + // Edge cases when optional content is missing. + describe('Edge Cases', () => { + it('should not render the body wrapper when children is undefined', () => { + // Arrange + render(<FeaturePanel title="No Body" />) + + // Assert + expect(screen.queryByText('No Body')).toBeInTheDocument() + expect(screen.queryByText('Panel Body')).not.toBeInTheDocument() + expect(screen.queryByTestId('feature-panel-body')).not.toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/app/configuration/base/feature-panel/index.tsx b/web/app/components/app/configuration/base/feature-panel/index.tsx index 20c4a8dc17..06ae2ab10a 100644 --- a/web/app/components/app/configuration/base/feature-panel/index.tsx +++ b/web/app/components/app/configuration/base/feature-panel/index.tsx @@ -25,7 +25,7 @@ const FeaturePanel: FC<IFeaturePanelProps> = ({ return ( <div className={cn('rounded-xl border-l-[0.5px] border-t-[0.5px] border-effects-highlight bg-background-section-burn pb-3', noBodySpacing && 'pb-0', className)}> {/* Header */} - <div className={cn('px-3 pt-2', hasHeaderBottomBorder && 'border-b border-divider-subtle')}> + <div className={cn('px-3 pt-2', hasHeaderBottomBorder && 'border-b border-divider-subtle')} data-testid="feature-panel-header"> <div className="flex h-8 items-center justify-between"> <div className="flex shrink-0 items-center space-x-1"> {headerIcon && <div className="flex h-6 w-6 items-center justify-center">{headerIcon}</div>} @@ -38,7 +38,7 @@ const FeaturePanel: FC<IFeaturePanelProps> = ({ </div> {/* Body */} {children && ( - <div className={cn(!noBodySpacing && 'mt-1 px-3')}> + <div className={cn(!noBodySpacing && 'mt-1 px-3')} data-testid="feature-panel-body"> {children} </div> )} diff --git a/web/app/components/billing/apps-full-in-dialog/index.spec.tsx b/web/app/components/billing/apps-full-in-dialog/index.spec.tsx new file mode 100644 index 0000000000..a11b582b0f --- /dev/null +++ b/web/app/components/billing/apps-full-in-dialog/index.spec.tsx @@ -0,0 +1,274 @@ +import type { Mock } from 'vitest' +import type { UsagePlanInfo } from '@/app/components/billing/type' +import type { AppContextValue } from '@/context/app-context' +import type { ProviderContextState } from '@/context/provider-context' +import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' +import { render, screen } from '@testing-library/react' +import { Plan } from '@/app/components/billing/type' +import { mailToSupport } from '@/app/components/header/utils/util' +import { useAppContext } from '@/context/app-context' +import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' +import AppsFull from './index' + +vi.mock('@/context/app-context', () => ({ + useAppContext: vi.fn(), +})) + +vi.mock('@/context/provider-context', async (importOriginal) => { + const actual = await importOriginal<typeof import('@/context/provider-context')>() + return { + ...actual, + useProviderContext: vi.fn(), + } +}) + +vi.mock('@/context/modal-context', () => ({ + useModalContext: () => ({ + setShowPricingModal: vi.fn(), + }), +})) + +vi.mock('@/app/components/header/utils/util', () => ({ + mailToSupport: vi.fn(), +})) + +const buildUsage = (overrides: Partial<UsagePlanInfo> = {}): UsagePlanInfo => ({ + buildApps: 0, + teamMembers: 0, + annotatedResponse: 0, + documentsUploadQuota: 0, + apiRateLimit: 0, + triggerEvents: 0, + vectorSpace: 0, + ...overrides, +}) + +const buildProviderContext = (overrides: Partial<ProviderContextState> = {}): ProviderContextState => ({ + ...baseProviderContextValue, + plan: { + ...baseProviderContextValue.plan, + type: Plan.sandbox, + usage: buildUsage({ buildApps: 2 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + ...overrides, +}) + +const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextValue => { + const userProfile: UserProfileResponse = { + id: 'user-id', + name: 'Test User', + email: 'user@example.com', + avatar: '', + avatar_url: '', + is_password_set: false, + } + const currentWorkspace: ICurrentWorkspace = { + id: 'workspace-id', + name: 'Workspace', + plan: '', + status: '', + created_at: 0, + role: 'normal', + providers: [], + } + const langGeniusVersionInfo: LangGeniusVersionResponse = { + current_env: '', + current_version: '1.0.0', + latest_version: '', + release_date: '', + release_notes: '', + version: '', + can_auto_update: false, + } + const base: Omit<AppContextValue, 'useSelector'> = { + userProfile, + currentWorkspace, + isCurrentWorkspaceManager: false, + isCurrentWorkspaceOwner: false, + isCurrentWorkspaceEditor: false, + isCurrentWorkspaceDatasetOperator: false, + mutateUserProfile: vi.fn(), + mutateCurrentWorkspace: vi.fn(), + langGeniusVersionInfo, + isLoadingCurrentWorkspace: false, + } + const useSelector: AppContextValue['useSelector'] = selector => selector({ ...base, useSelector }) + return { + ...base, + useSelector, + ...overrides, + } +} + +describe('AppsFull', () => { + beforeEach(() => { + vi.clearAllMocks() + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext()) + ;(useAppContext as Mock).mockReturnValue(buildAppContext()) + ;(mailToSupport as Mock).mockReturnValue('mailto:support@example.com') + }) + + // Rendering behavior for non-team plans. + describe('Rendering', () => { + it('should render the sandbox messaging and upgrade button', () => { + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByText('billing.apps.fullTip1')).toBeInTheDocument() + expect(screen.getByText('billing.apps.fullTip1des')).toBeInTheDocument() + expect(screen.getByText('billing.upgradeBtn.encourageShort')).toBeInTheDocument() + expect(screen.getByText('2/10')).toBeInTheDocument() + }) + }) + + // Prop-driven behavior for team plans and contact CTA. + describe('Props', () => { + it('should render team messaging and contact button for non-sandbox plans', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.team, + usage: buildUsage({ buildApps: 8 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByText('billing.apps.fullTip2')).toBeInTheDocument() + expect(screen.getByText('billing.apps.fullTip2des')).toBeInTheDocument() + expect(screen.queryByText('billing.upgradeBtn.encourageShort')).not.toBeInTheDocument() + expect(screen.getByRole('link', { name: 'billing.apps.contactUs' })).toHaveAttribute('href', 'mailto:support@example.com') + expect(mailToSupport).toHaveBeenCalledWith('user@example.com', Plan.team, '1.0.0') + }) + + it('should render upgrade button for professional plans', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.professional, + usage: buildUsage({ buildApps: 4 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByText('billing.apps.fullTip1')).toBeInTheDocument() + expect(screen.getByText('billing.upgradeBtn.encourageShort')).toBeInTheDocument() + expect(screen.queryByText('billing.apps.contactUs')).not.toBeInTheDocument() + }) + + it('should render contact button for enterprise plans', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.enterprise, + usage: buildUsage({ buildApps: 9 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByText('billing.apps.fullTip1')).toBeInTheDocument() + expect(screen.queryByText('billing.upgradeBtn.encourageShort')).not.toBeInTheDocument() + expect(screen.getByRole('link', { name: 'billing.apps.contactUs' })).toHaveAttribute('href', 'mailto:support@example.com') + expect(mailToSupport).toHaveBeenCalledWith('user@example.com', Plan.enterprise, '1.0.0') + }) + }) + + // Edge cases for progress color thresholds. + describe('Edge Cases', () => { + it('should use the success color when usage is below 50%', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.sandbox, + usage: buildUsage({ buildApps: 2 }), + total: buildUsage({ buildApps: 5 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByTestId('billing-progress-bar')).toHaveClass('bg-components-progress-bar-progress-solid') + }) + + it('should use the warning color when usage is between 50% and 80%', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.sandbox, + usage: buildUsage({ buildApps: 6 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByTestId('billing-progress-bar')).toHaveClass('bg-components-progress-warning-progress') + }) + + it('should use the error color when usage is 80% or higher', () => { + // Arrange + ;(useProviderContext as Mock).mockReturnValue(buildProviderContext({ + plan: { + ...baseProviderContextValue.plan, + type: Plan.sandbox, + usage: buildUsage({ buildApps: 8 }), + total: buildUsage({ buildApps: 10 }), + reset: { + apiRateLimit: null, + triggerEvents: null, + }, + }, + })) + + // Act + render(<AppsFull loc="billing_dialog" />) + + // Assert + expect(screen.getByTestId('billing-progress-bar')).toHaveClass('bg-components-progress-error-progress') + }) + }) +}) diff --git a/web/app/components/billing/pricing/assets/index.spec.tsx b/web/app/components/billing/pricing/assets/index.spec.tsx new file mode 100644 index 0000000000..7980f9a182 --- /dev/null +++ b/web/app/components/billing/pricing/assets/index.spec.tsx @@ -0,0 +1,64 @@ +import { render } from '@testing-library/react' +import { + Cloud, + Community, + Enterprise, + EnterpriseNoise, + NoiseBottom, + NoiseTop, + Premium, + PremiumNoise, + Professional, + Sandbox, + SelfHosted, + Team, +} from './index' + +describe('Pricing Assets', () => { + // Rendering: each asset should render an svg. + describe('Rendering', () => { + it('should render static assets without crashing', () => { + // Arrange + const assets = [ + <Community key="community" />, + <Enterprise key="enterprise" />, + <EnterpriseNoise key="enterprise-noise" />, + <NoiseBottom key="noise-bottom" />, + <NoiseTop key="noise-top" />, + <Premium key="premium" />, + <PremiumNoise key="premium-noise" />, + <Professional key="professional" />, + <Sandbox key="sandbox" />, + <Team key="team" />, + ] + + // Act / Assert + assets.forEach((asset) => { + const { container, unmount } = render(asset) + expect(container.querySelector('svg')).toBeInTheDocument() + unmount() + }) + }) + }) + + // Props: active state should change fill color for selectable assets. + describe('Props', () => { + it('should render active state for Cloud', () => { + // Arrange + const { container } = render(<Cloud isActive />) + + // Assert + const rects = Array.from(container.querySelectorAll('rect')) + expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-saas-dify-blue-accessible)')).toBe(true) + }) + + it('should render inactive state for SelfHosted', () => { + // Arrange + const { container } = render(<SelfHosted isActive={false} />) + + // Assert + const rects = Array.from(container.querySelectorAll('rect')) + expect(rects.some(rect => rect.getAttribute('fill') === 'var(--color-text-primary)')).toBe(true) + }) + }) +}) diff --git a/web/app/components/billing/pricing/footer.spec.tsx b/web/app/components/billing/pricing/footer.spec.tsx new file mode 100644 index 0000000000..f8e7965f5e --- /dev/null +++ b/web/app/components/billing/pricing/footer.spec.tsx @@ -0,0 +1,68 @@ +import { render, screen } from '@testing-library/react' +import * as React from 'react' +import { CategoryEnum } from '.' +import Footer from './footer' + +let mockTranslations: Record<string, string> = {} + +vi.mock('next/link', () => ({ + default: ({ children, href, className, target }: { children: React.ReactNode, href: string, className?: string, target?: string }) => ( + <a href={href} className={className} target={target} data-testid="pricing-link"> + {children} + </a> + ), +})) + +vi.mock('react-i18next', async (importOriginal) => { + const actual = await importOriginal<typeof import('react-i18next')>() + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => mockTranslations[key] ?? key, + }), + } +}) + +describe('Footer', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTranslations = {} + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render tax tips and comparison link when in cloud category', () => { + // Arrange + render(<Footer pricingPageURL="https://dify.ai/pricing#plans-and-features" currentCategory={CategoryEnum.CLOUD} />) + + // Assert + expect(screen.getByText('billing.plansCommon.taxTip')).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.taxTipSecond')).toBeInTheDocument() + expect(screen.getByTestId('pricing-link')).toHaveAttribute('href', 'https://dify.ai/pricing#plans-and-features') + expect(screen.getByText('billing.plansCommon.comparePlanAndFeatures')).toBeInTheDocument() + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should hide tax tips when category is self-hosted', () => { + // Arrange + render(<Footer pricingPageURL="https://dify.ai/pricing#plans-and-features" currentCategory={CategoryEnum.SELF} />) + + // Assert + expect(screen.queryByText('billing.plansCommon.taxTip')).not.toBeInTheDocument() + expect(screen.queryByText('billing.plansCommon.taxTipSecond')).not.toBeInTheDocument() + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should render link even when pricing URL is empty', () => { + // Arrange + render(<Footer pricingPageURL="" currentCategory={CategoryEnum.CLOUD} />) + + // Assert + expect(screen.getByTestId('pricing-link')).toHaveAttribute('href', '') + }) + }) +}) diff --git a/web/app/components/billing/pricing/header.spec.tsx b/web/app/components/billing/pricing/header.spec.tsx new file mode 100644 index 0000000000..0395e5dd48 --- /dev/null +++ b/web/app/components/billing/pricing/header.spec.tsx @@ -0,0 +1,72 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import Header from './header' + +let mockTranslations: Record<string, string> = {} + +vi.mock('react-i18next', async (importOriginal) => { + const actual = await importOriginal<typeof import('react-i18next')>() + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => mockTranslations[key] ?? key, + }), + } +}) + +describe('Header', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTranslations = {} + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render title and description translations', () => { + // Arrange + const handleClose = vi.fn() + + // Act + render(<Header onClose={handleClose} />) + + // Assert + expect(screen.getByText('billing.plansCommon.title.plans')).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.title.description')).toBeInTheDocument() + expect(screen.getByRole('button')).toBeInTheDocument() + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should invoke onClose when close button is clicked', () => { + // Arrange + const handleClose = vi.fn() + render(<Header onClose={handleClose} />) + + // Act + fireEvent.click(screen.getByRole('button')) + + // Assert + expect(handleClose).toHaveBeenCalledTimes(1) + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should render structure when translations are empty strings', () => { + // Arrange + mockTranslations = { + 'billing.plansCommon.title.plans': '', + 'billing.plansCommon.title.description': '', + } + + // Act + const { container } = render(<Header onClose={vi.fn()} />) + + // Assert + expect(container.querySelector('span')).toBeInTheDocument() + expect(container.querySelector('p')).toBeInTheDocument() + expect(screen.getByRole('button')).toBeInTheDocument() + }) + }) +}) diff --git a/web/app/components/billing/pricing/index.spec.tsx b/web/app/components/billing/pricing/index.spec.tsx new file mode 100644 index 0000000000..141c2d9c96 --- /dev/null +++ b/web/app/components/billing/pricing/index.spec.tsx @@ -0,0 +1,119 @@ +import type { Mock } from 'vitest' +import type { UsagePlanInfo } from '../type' +import { fireEvent, render, screen } from '@testing-library/react' +import { useKeyPress } from 'ahooks' +import * as React from 'react' +import { useAppContext } from '@/context/app-context' +import { useGetPricingPageLanguage } from '@/context/i18n' +import { useProviderContext } from '@/context/provider-context' +import { Plan } from '../type' +import Pricing from './index' + +let mockTranslations: Record<string, string> = {} +let mockLanguage: string | null = 'en' + +vi.mock('next/link', () => ({ + default: ({ children, href, className, target }: { children: React.ReactNode, href: string, className?: string, target?: string }) => ( + <a href={href} className={className} target={target} data-testid="pricing-link"> + {children} + </a> + ), +})) + +vi.mock('ahooks', () => ({ + useKeyPress: vi.fn(), +})) + +vi.mock('@/context/app-context', () => ({ + useAppContext: vi.fn(), +})) + +vi.mock('@/context/provider-context', () => ({ + useProviderContext: vi.fn(), +})) + +vi.mock('@/context/i18n', () => ({ + useGetPricingPageLanguage: vi.fn(), +})) + +vi.mock('react-i18next', async (importOriginal) => { + const actual = await importOriginal<typeof import('react-i18next')>() + return { + ...actual, + useTranslation: () => ({ + t: (key: string, options?: { returnObjects?: boolean }) => { + if (options?.returnObjects) + return mockTranslations[key] ?? [] + return mockTranslations[key] ?? key + }, + }), + Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>, + } +}) + +const buildUsage = (): UsagePlanInfo => ({ + buildApps: 0, + teamMembers: 0, + annotatedResponse: 0, + documentsUploadQuota: 0, + apiRateLimit: 0, + triggerEvents: 0, + vectorSpace: 0, +}) + +describe('Pricing', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTranslations = {} + mockLanguage = 'en' + ;(useAppContext as Mock).mockReturnValue({ isCurrentWorkspaceManager: true }) + ;(useProviderContext as Mock).mockReturnValue({ + plan: { + type: Plan.sandbox, + usage: buildUsage(), + total: buildUsage(), + }, + }) + ;(useGetPricingPageLanguage as Mock).mockImplementation(() => mockLanguage) + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render pricing header and localized footer link', () => { + // Arrange + render(<Pricing onCancel={vi.fn()} />) + + // Assert + expect(screen.getByText('billing.plansCommon.title.plans')).toBeInTheDocument() + expect(screen.getByTestId('pricing-link')).toHaveAttribute('href', 'https://dify.ai/en/pricing#plans-and-features') + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should register esc key handler and allow switching categories', () => { + // Arrange + const handleCancel = vi.fn() + render(<Pricing onCancel={handleCancel} />) + + // Act + fireEvent.click(screen.getByText('billing.plansCommon.self')) + + // Assert + expect(useKeyPress).toHaveBeenCalledWith(['esc'], handleCancel) + expect(screen.queryByRole('switch')).not.toBeInTheDocument() + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should fall back to default pricing URL when language is empty', () => { + // Arrange + mockLanguage = '' + render(<Pricing onCancel={vi.fn()} />) + + // Assert + expect(screen.getByTestId('pricing-link')).toHaveAttribute('href', 'https://dify.ai/pricing#plans-and-features') + }) + }) +}) diff --git a/web/app/components/billing/pricing/plan-switcher/index.spec.tsx b/web/app/components/billing/pricing/plan-switcher/index.spec.tsx new file mode 100644 index 0000000000..641d359bfd --- /dev/null +++ b/web/app/components/billing/pricing/plan-switcher/index.spec.tsx @@ -0,0 +1,109 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import { CategoryEnum } from '../index' +import PlanSwitcher from './index' +import { PlanRange } from './plan-range-switcher' + +let mockTranslations: Record<string, string> = {} + +vi.mock('react-i18next', async (importOriginal) => { + const actual = await importOriginal<typeof import('react-i18next')>() + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => mockTranslations[key] ?? key, + }), + } +}) + +describe('PlanSwitcher', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTranslations = {} + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render category tabs and plan range switcher for cloud', () => { + // Arrange + render( + <PlanSwitcher + currentCategory={CategoryEnum.CLOUD} + currentPlanRange={PlanRange.monthly} + onChangeCategory={vi.fn()} + onChangePlanRange={vi.fn()} + />, + ) + + // Assert + expect(screen.getByText('billing.plansCommon.cloud')).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.self')).toBeInTheDocument() + expect(screen.getByRole('switch')).toBeInTheDocument() + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should call onChangeCategory when selecting a tab', () => { + // Arrange + const handleChangeCategory = vi.fn() + render( + <PlanSwitcher + currentCategory={CategoryEnum.CLOUD} + currentPlanRange={PlanRange.monthly} + onChangeCategory={handleChangeCategory} + onChangePlanRange={vi.fn()} + />, + ) + + // Act + fireEvent.click(screen.getByText('billing.plansCommon.self')) + + // Assert + expect(handleChangeCategory).toHaveBeenCalledTimes(1) + expect(handleChangeCategory).toHaveBeenCalledWith(CategoryEnum.SELF) + }) + + it('should hide plan range switcher when category is self-hosted', () => { + // Arrange + render( + <PlanSwitcher + currentCategory={CategoryEnum.SELF} + currentPlanRange={PlanRange.yearly} + onChangeCategory={vi.fn()} + onChangePlanRange={vi.fn()} + />, + ) + + // Assert + expect(screen.queryByRole('switch')).not.toBeInTheDocument() + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should render tabs when translation strings are empty', () => { + // Arrange + mockTranslations = { + 'billing.plansCommon.cloud': '', + 'billing.plansCommon.self': '', + } + + // Act + const { container } = render( + <PlanSwitcher + currentCategory={CategoryEnum.SELF} + currentPlanRange={PlanRange.monthly} + onChangeCategory={vi.fn()} + onChangePlanRange={vi.fn()} + />, + ) + + // Assert + const labels = container.querySelectorAll('span') + expect(labels).toHaveLength(2) + expect(labels[0]?.textContent).toBe('') + expect(labels[1]?.textContent).toBe('') + }) + }) +}) diff --git a/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.spec.tsx b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.spec.tsx new file mode 100644 index 0000000000..0b4c00603c --- /dev/null +++ b/web/app/components/billing/pricing/plan-switcher/plan-range-switcher.spec.tsx @@ -0,0 +1,81 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import PlanRangeSwitcher, { PlanRange } from './plan-range-switcher' + +let mockTranslations: Record<string, string> = {} + +vi.mock('react-i18next', async (importOriginal) => { + const actual = await importOriginal<typeof import('react-i18next')>() + return { + ...actual, + useTranslation: () => ({ + t: (key: string) => mockTranslations[key] ?? key, + }), + } +}) + +describe('PlanRangeSwitcher', () => { + beforeEach(() => { + vi.clearAllMocks() + mockTranslations = {} + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render the annual billing label', () => { + // Arrange + render(<PlanRangeSwitcher value={PlanRange.monthly} onChange={vi.fn()} />) + + // Assert + expect(screen.getByText('billing.plansCommon.annualBilling')).toBeInTheDocument() + expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false') + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should switch to yearly when toggled from monthly', () => { + // Arrange + const handleChange = vi.fn() + render(<PlanRangeSwitcher value={PlanRange.monthly} onChange={handleChange} />) + + // Act + fireEvent.click(screen.getByRole('switch')) + + // Assert + expect(handleChange).toHaveBeenCalledTimes(1) + expect(handleChange).toHaveBeenCalledWith(PlanRange.yearly) + }) + + it('should switch to monthly when toggled from yearly', () => { + // Arrange + const handleChange = vi.fn() + render(<PlanRangeSwitcher value={PlanRange.yearly} onChange={handleChange} />) + + // Act + fireEvent.click(screen.getByRole('switch')) + + // Assert + expect(handleChange).toHaveBeenCalledTimes(1) + expect(handleChange).toHaveBeenCalledWith(PlanRange.monthly) + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should render when the translation string is empty', () => { + // Arrange + mockTranslations = { + 'billing.plansCommon.annualBilling': '', + } + + // Act + const { container } = render(<PlanRangeSwitcher value={PlanRange.monthly} onChange={vi.fn()} />) + + // Assert + const label = container.querySelector('span') + expect(label).toBeInTheDocument() + expect(label?.textContent).toBe('') + }) + }) +}) diff --git a/web/app/components/billing/pricing/plan-switcher/tab.spec.tsx b/web/app/components/billing/pricing/plan-switcher/tab.spec.tsx new file mode 100644 index 0000000000..5c335e0dd1 --- /dev/null +++ b/web/app/components/billing/pricing/plan-switcher/tab.spec.tsx @@ -0,0 +1,95 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import * as React from 'react' +import Tab from './tab' + +const Icon = ({ isActive }: { isActive: boolean }) => ( + <svg data-testid="tab-icon" data-active={isActive ? 'true' : 'false'} /> +) + +describe('PlanSwitcherTab', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + // Rendering behavior + describe('Rendering', () => { + it('should render label and icon', () => { + // Arrange + render( + <Tab + Icon={Icon} + value="cloud" + label="Cloud" + isActive={false} + onClick={vi.fn()} + />, + ) + + // Assert + expect(screen.getByText('Cloud')).toBeInTheDocument() + expect(screen.getByTestId('tab-icon')).toHaveAttribute('data-active', 'false') + }) + }) + + // Prop-driven behavior + describe('Props', () => { + it('should call onClick with the provided value', () => { + // Arrange + const handleClick = vi.fn() + render( + <Tab + Icon={Icon} + value="self" + label="Self" + isActive={false} + onClick={handleClick} + />, + ) + + // Act + fireEvent.click(screen.getByText('Self')) + + // Assert + expect(handleClick).toHaveBeenCalledTimes(1) + expect(handleClick).toHaveBeenCalledWith('self') + }) + + it('should apply active text class when isActive is true', () => { + // Arrange + render( + <Tab + Icon={Icon} + value="cloud" + label="Cloud" + isActive + onClick={vi.fn()} + />, + ) + + // Assert + expect(screen.getByText('Cloud')).toHaveClass('text-saas-dify-blue-accessible') + expect(screen.getByTestId('tab-icon')).toHaveAttribute('data-active', 'true') + }) + }) + + // Edge case rendering behavior + describe('Edge Cases', () => { + it('should render when label is empty', () => { + // Arrange + const { container } = render( + <Tab + Icon={Icon} + value="cloud" + label="" + isActive={false} + onClick={vi.fn()} + />, + ) + + // Assert + const label = container.querySelector('span') + expect(label).toBeInTheDocument() + expect(label?.textContent).toBe('') + }) + }) +}) diff --git a/web/app/components/billing/progress-bar/index.tsx b/web/app/components/billing/progress-bar/index.tsx index 383b516b61..c41fc53310 100644 --- a/web/app/components/billing/progress-bar/index.tsx +++ b/web/app/components/billing/progress-bar/index.tsx @@ -12,6 +12,7 @@ const ProgressBar = ({ return ( <div className="overflow-hidden rounded-[6px] bg-components-progress-bar-bg"> <div + data-testid="billing-progress-bar" className={cn('h-1 rounded-[6px]', color)} style={{ width: `${Math.min(percent, 100)}%`, From 1e3823e6056e9d8cfcef9e10d13010fcd19099ef Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:31:16 +0800 Subject: [PATCH 61/71] chore: fix type check for i18n (#30058) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> --- .../translate-i18n-base-on-english.yml | 12 +- .github/workflows/web-tests.yml | 3 - .../[appId]/overview/card-view.tsx | 2 +- .../overview/long-time-range-picker.tsx | 2 +- .../time-range-picker/range-selector.tsx | 2 +- .../[datasetId]/settings/page.tsx | 6 +- .../app-sidebar/dataset-info/index.tsx | 2 +- .../app-sidebar/dataset-sidebar-dropdown.tsx | 2 +- .../app/annotation/header-opts/index.spec.tsx | 7 +- .../components/app/app-publisher/index.tsx | 2 +- .../config-var/config-modal/index.tsx | 4 +- .../app/configuration/config-var/index.tsx | 2 +- .../config-var/select-type-item/index.tsx | 2 +- .../config/automatic/get-automatic-res.tsx | 4 +- .../dataset-config/settings-modal/index.tsx | 4 +- web/app/components/app/log/filter.tsx | 2 +- .../components/app/workflow-log/filter.tsx | 2 +- web/app/components/base/block-input/index.tsx | 2 +- .../base/chat/embedded-chatbot/hooks.tsx | 3 +- .../base/date-and-time-picker/hooks.ts | 4 +- .../base/encrypted-bottom/index.tsx | 4 +- .../text-to-speech/param-config-content.tsx | 4 +- .../components/base/file-uploader/hooks.ts | 4 +- .../field/input-type-select/hooks.tsx | 2 +- .../components/base/image-uploader/hooks.ts | 4 +- web/app/components/base/tag-input/index.tsx | 2 +- .../pricing/plans/cloud-plan-item/index.tsx | 4 +- .../plans/self-hosted-plan-item/button.tsx | 2 +- .../plans/self-hosted-plan-item/index.tsx | 8 +- .../self-hosted-plan-item/list/index.tsx | 4 +- .../billing/priority-label/index.tsx | 4 +- .../components/billing/upgrade-btn/index.tsx | 6 +- .../custom/custom-web-app-brand/index.tsx | 2 +- .../common/image-uploader/hooks/use-upload.ts | 4 +- .../common/retrieval-method-info/index.tsx | 4 +- .../list/template-card/content.tsx | 2 +- .../create/embedding-process/index.tsx | 2 +- .../datasets/create/file-uploader/index.tsx | 2 +- .../datasets/create/top-bar/index.tsx | 2 +- .../data-source/local-file/index.tsx | 2 +- .../embedding-process/rule-detail.tsx | 2 +- .../detail/batch-modal/csv-uploader.tsx | 2 +- .../documents/detail/embedding/index.tsx | 2 +- .../components/query-input/index.tsx | 2 +- .../datasets/list/dataset-card/index.tsx | 8 +- web/app/components/explore/category.tsx | 2 +- .../goto-anything/actions/commands/theme.tsx | 6 +- .../goto-anything/command-selector.tsx | 4 +- web/app/components/goto-anything/index.tsx | 4 +- .../account-setting/language-page/index.tsx | 3 +- .../invite-modal/role-selector.tsx | 2 +- .../members-page/operation/index.tsx | 4 +- .../presets-parameter.tsx | 2 +- .../plugins/base/deprecation-notice.tsx | 4 +- web/app/components/plugins/card/index.tsx | 7 +- web/app/components/plugins/hooks.ts | 6 +- .../plugins/marketplace/description/index.tsx | 5 +- .../components/plugins/marketplace/index.tsx | 3 +- .../plugins/marketplace/list/card-wrapper.tsx | 3 +- .../plugins/marketplace/list/index.tsx | 3 +- .../marketplace/list/list-with-collection.tsx | 3 +- .../plugins/marketplace/list/list-wrapper.tsx | 3 +- .../subscription-list/create/common-modal.tsx | 2 +- .../subscription-list/create/index.tsx | 2 +- web/app/components/plugins/types.ts | 6 +- .../hooks/use-available-nodes-meta-data.ts | 4 +- .../hooks/use-pipeline-template.ts | 2 +- .../get-schema.tsx | 2 +- .../edit-custom-collection-modal/index.tsx | 2 +- .../edit-custom-collection-modal/test-api.tsx | 2 +- web/app/components/tools/provider/empty.tsx | 6 +- .../hooks/use-available-nodes-meta-data.ts | 4 +- .../hooks/use-workflow-template.ts | 6 +- .../workflow/block-selector/blocks.tsx | 2 +- .../block-selector/featured-tools.tsx | 5 +- .../block-selector/featured-triggers.tsx | 5 +- .../workflow/block-selector/hooks.ts | 4 +- .../workflow/block-selector/start-blocks.tsx | 8 +- .../workflow/hooks/use-checklist.ts | 6 +- .../components/variable/output-var-list.tsx | 2 +- .../_base/components/variable/var-list.tsx | 2 +- .../components/operation-selector.tsx | 6 +- .../workflow/nodes/assigner/node.tsx | 2 +- .../components/condition-files-list-value.tsx | 8 +- .../condition-list/condition-item.tsx | 4 +- .../condition-list/condition-operator.tsx | 4 +- .../if-else/components/condition-value.tsx | 4 +- .../nodes/iteration/use-interactions.ts | 2 +- .../components/dataset-item.tsx | 2 +- .../condition-list/condition-operator.tsx | 4 +- .../components/filter-condition.tsx | 4 +- .../llm/components/config-prompt-item.tsx | 2 +- .../components/condition-files-list-value.tsx | 8 +- .../condition-list/condition-item.tsx | 4 +- .../condition-list/condition-operator.tsx | 4 +- .../nodes/loop/components/condition-value.tsx | 4 +- .../loop/components/loop-variables/item.tsx | 2 +- .../components/extract-parameter/update.tsx | 2 +- .../nodes/start/components/var-list.tsx | 2 +- .../workflow/nodes/start/use-config.ts | 2 +- .../components/mode-switcher.tsx | 4 +- .../nodes/trigger-webhook/use-config.ts | 4 +- .../components/node-variable-item.tsx | 2 +- .../components/var-group-item.tsx | 2 +- .../components/variable-modal.tsx | 2 +- .../panel/env-panel/variable-modal.tsx | 2 +- .../workflow/run/loop-result-panel.tsx | 2 +- .../forgot-password/ForgotPasswordForm.tsx | 2 +- web/app/install/installForm.tsx | 4 +- web/app/signin/invite-settings/page.tsx | 3 +- web/hooks/use-format-time-from-now.ts | 26 +-- web/hooks/use-knowledge.ts | 4 +- web/hooks/use-metadata.ts | 12 +- web/i18n-config/README.md | 30 +--- web/i18n-config/auto-gen-i18n.js | 6 +- web/i18n-config/check-i18n-sync.js | 134 --------------- web/i18n-config/check-i18n.js | 6 +- web/i18n-config/generate-i18n-types.js | 149 ---------------- web/i18n-config/i18next-config.ts | 128 +++++++------- web/i18n-config/index.ts | 5 +- web/i18n-config/language.ts | 66 ++++---- web/i18n-config/languages.json | 158 ----------------- web/i18n-config/languages.ts | 160 ++++++++++++++++++ web/i18n-config/server.ts | 11 +- web/i18n/en-US/app-api.ts | 1 + web/i18n/en-US/app-debug.ts | 3 + web/i18n/en-US/app.ts | 6 + web/i18n/en-US/common.ts | 6 + web/i18n/en-US/dataset.ts | 1 + web/i18n/en-US/login.ts | 1 + web/i18n/en-US/plugin-trigger.ts | 1 + web/i18n/en-US/tools.ts | 1 + web/i18n/en-US/workflow.ts | 3 + web/i18n/ja-JP/app-api.ts | 1 + web/i18n/ja-JP/app-debug.ts | 3 + web/i18n/ja-JP/app.ts | 6 + web/i18n/ja-JP/common.ts | 6 + web/i18n/ja-JP/dataset.ts | 1 + web/i18n/ja-JP/login.ts | 1 + web/i18n/ja-JP/plugin-trigger.ts | 1 + web/i18n/ja-JP/tools.ts | 1 + web/i18n/ja-JP/workflow.ts | 3 + web/i18n/zh-Hans/app-api.ts | 1 + web/i18n/zh-Hans/app-debug.ts | 3 + web/i18n/zh-Hans/app.ts | 6 + web/i18n/zh-Hans/common.ts | 6 + web/i18n/zh-Hans/dataset.ts | 1 + web/i18n/zh-Hans/login.ts | 1 + web/i18n/zh-Hans/plugin-trigger.ts | 1 + web/i18n/zh-Hans/tools.ts | 1 + web/i18n/zh-Hans/workflow.ts | 3 + web/package.json | 8 +- web/pnpm-lock.yaml | 106 +----------- web/types/i18n.d.ts | 84 +-------- web/utils/format.ts | 27 +-- 155 files changed, 560 insertions(+), 1015 deletions(-) delete mode 100644 web/i18n-config/check-i18n-sync.js delete mode 100644 web/i18n-config/generate-i18n-types.js delete mode 100644 web/i18n-config/languages.json create mode 100644 web/i18n-config/languages.ts diff --git a/.github/workflows/translate-i18n-base-on-english.yml b/.github/workflows/translate-i18n-base-on-english.yml index 8bb82d5d44..87e24a4f90 100644 --- a/.github/workflows/translate-i18n-base-on-english.yml +++ b/.github/workflows/translate-i18n-base-on-english.yml @@ -1,4 +1,4 @@ -name: Check i18n Files and Create PR +name: Translate i18n Files Based on English on: push: @@ -67,25 +67,19 @@ jobs: working-directory: ./web run: pnpm run auto-gen-i18n ${{ env.FILE_ARGS }} - - name: Generate i18n type definitions - if: env.FILES_CHANGED == 'true' - working-directory: ./web - run: pnpm run gen:i18n-types - - name: Create Pull Request if: env.FILES_CHANGED == 'true' uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore(i18n): update translations based on en-US changes' - title: 'chore(i18n): translate i18n files and update type definitions' + title: 'chore(i18n): translate i18n files based on en-US changes' body: | - This PR was automatically created to update i18n files and TypeScript type definitions based on changes in en-US locale. + This PR was automatically created to update i18n translation files based on changes in en-US locale. **Triggered by:** ${{ github.sha }} **Changes included:** - Updated translation files for all locales - - Regenerated TypeScript type definitions for type safety branch: chore/automated-i18n-updates-${{ github.sha }} delete-branch: true diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index adf52a1362..1a8925e38d 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -38,9 +38,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Check i18n types synchronization - run: pnpm run check:i18n-types - - name: Run tests run: pnpm test:coverage diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx index e9877f1715..34bb0f82f8 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/card-view.tsx @@ -104,7 +104,7 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => { notify({ type, - message: t(`common.actionMsg.${message}`), + message: t(`common.actionMsg.${message}` as any) as string, }) } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx index 557b723259..55ed906a45 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/long-time-range-picker.tsx @@ -53,7 +53,7 @@ const LongTimeRangePicker: FC<Props> = ({ return ( <SimpleSelect - items={Object.entries(periodMapping).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} + items={Object.entries(periodMapping).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}` as any) as string }))} className="mt-0 !w-40" notClearable={true} onSelect={handleSelect} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx index be7181c759..88cb79ce0d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx @@ -66,7 +66,7 @@ const RangeSelector: FC<Props> = ({ }, []) return ( <SimpleSelect - items={ranges.map(v => ({ ...v, name: t(`appLog.filter.period.${v.name}`) }))} + items={ranges.map(v => ({ ...v, name: t(`appLog.filter.period.${v.name}` as any) as string }))} className="mt-0 !w-40" notClearable={true} onSelect={handleSelectRange} diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx index 9dfeaef528..aa64df3449 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx @@ -1,15 +1,15 @@ import * as React from 'react' import Form from '@/app/components/datasets/settings/form' -import { getLocaleOnServer, useTranslation as translate } from '@/i18n-config/server' +import { getLocaleOnServer, getTranslation } from '@/i18n-config/server' const Settings = async () => { const locale = await getLocaleOnServer() - const { t } = await translate(locale, 'dataset-settings') + const { t } = await getTranslation(locale, 'dataset-settings') return ( <div className="h-full overflow-y-auto"> <div className="flex flex-col gap-y-0.5 px-6 pb-2 pt-3"> - <div className="system-xl-semibold text-text-primary">{t('title')}</div> + <div className="system-xl-semibold text-text-primary">{t('title') as any}</div> <div className="system-sm-regular text-text-tertiary">{t('desc')}</div> </div> <Form /> diff --git a/web/app/components/app-sidebar/dataset-info/index.tsx b/web/app/components/app-sidebar/dataset-info/index.tsx index ce409ff13a..39a1115062 100644 --- a/web/app/components/app-sidebar/dataset-info/index.tsx +++ b/web/app/components/app-sidebar/dataset-info/index.tsx @@ -73,7 +73,7 @@ const DatasetInfo: FC<DatasetInfoProps> = ({ {isExternalProvider && t('dataset.externalTag')} {!isExternalProvider && isPipelinePublished && dataset.doc_form && dataset.indexing_technique && ( <div className="flex items-center gap-x-2"> - <span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</span> + <span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}` as any) as string}</span> <span>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</span> </div> )} diff --git a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx index d8e26826ca..0e55a8af65 100644 --- a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx @@ -116,7 +116,7 @@ const DatasetSidebarDropdown = ({ {isExternalProvider && t('dataset.externalTag')} {!isExternalProvider && dataset.doc_form && dataset.indexing_technique && ( <div className="flex items-center gap-x-2"> - <span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)}</span> + <span>{t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}` as any) as string}</span> <span>{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)}</span> </div> )} diff --git a/web/app/components/app/annotation/header-opts/index.spec.tsx b/web/app/components/app/annotation/header-opts/index.spec.tsx index c742f8effc..c52507fb22 100644 --- a/web/app/components/app/annotation/header-opts/index.spec.tsx +++ b/web/app/components/app/annotation/header-opts/index.spec.tsx @@ -1,5 +1,6 @@ import type { ComponentProps } from 'react' import type { AnnotationItemBasic } from '../type' +import type { Locale } from '@/i18n-config' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' @@ -166,7 +167,7 @@ type HeaderOptionsProps = ComponentProps<typeof HeaderOptions> const renderComponent = ( props: Partial<HeaderOptionsProps> = {}, - locale: string = LanguagesSupported[0] as string, + locale: Locale = LanguagesSupported[0], ) => { const defaultProps: HeaderOptionsProps = { appId: 'test-app-id', @@ -353,7 +354,7 @@ describe('HeaderOptions', () => { }) const revokeSpy = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(vi.fn()) - renderComponent({}, LanguagesSupported[1] as string) + renderComponent({}, LanguagesSupported[1]) await expandExportMenu(user) @@ -441,7 +442,7 @@ describe('HeaderOptions', () => { view.rerender( <I18NContext.Provider value={{ - locale: LanguagesSupported[0] as string, + locale: LanguagesSupported[0], i18n: {}, setLocaleOnClient: vi.fn(), }} diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 83449dbe96..4b8a70f0ce 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -84,7 +84,7 @@ const AccessModeDisplay: React.FC<{ mode?: AccessMode }> = ({ mode }) => { <> <Icon className="h-4 w-4 shrink-0 text-text-secondary" /> <div className="grow truncate"> - <span className="system-sm-medium text-text-secondary">{t(`app.accessControlDialog.accessItems.${label}`)}</span> + <span className="system-sm-medium text-text-secondary">{t(`app.accessControlDialog.accessItems.${label}` as any) as string}</span> </div> </> ) diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index 41f37b5895..b1d8e8cd19 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -96,7 +96,7 @@ const ConfigModal: FC<IConfigModalProps> = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('appDebug.variableConfig.varName') }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: t('appDebug.variableConfig.varName') }) as string, }) return false } @@ -216,7 +216,7 @@ const ConfigModal: FC<IConfigModalProps> = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }) as string, }) return } diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 7a2a86393a..bf528f8ca6 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -98,7 +98,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar if (errorMsgKey) { Toast.notify({ type: 'error', - message: t(errorMsgKey, { key: t(typeName) }), + message: t(errorMsgKey as any, { key: t(typeName as any) as string }) as string, }) return false } diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.tsx index ccb958977c..a72a74a5e9 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.tsx @@ -23,7 +23,7 @@ const SelectTypeItem: FC<ISelectTypeItemProps> = ({ onClick, }) => { const { t } = useTranslation() - const typeName = t(`appDebug.variableConfig.${i18nFileTypeMap[type] || type}`) + const typeName = t(`appDebug.variableConfig.${i18nFileTypeMap[type] || type}` as any) as string return ( <div diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index 46fcaee52b..9f29952197 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -141,7 +141,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({ const [editorKey, setEditorKey] = useState(`${flowId}-0`) const handleChooseTemplate = useCallback((key: string) => { return () => { - const template = t(`appDebug.generate.template.${key}.instruction`) + const template = t(`appDebug.generate.template.${key}.instruction` as any) as string setInstruction(template) setEditorKey(`${flowId}-${Date.now()}`) } @@ -322,7 +322,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({ <TryLabel key={item.key} Icon={item.icon} - text={t(`appDebug.generate.template.${item.key}.name`)} + text={t(`appDebug.generate.template.${item.key}.name` as any) as string} onClick={handleChooseTemplate(item.key)} /> ))} diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 1454ab0c62..243aafdbdd 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -295,7 +295,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ isExternal rowClass={rowClass} labelClass={labelClass} - t={t} + t={t as any} topK={topK} scoreThreshold={scoreThreshold} scoreThresholdEnabled={scoreThresholdEnabled} @@ -308,7 +308,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ isExternal={false} rowClass={rowClass} labelClass={labelClass} - t={t} + t={t as any} indexMethod={indexMethod} retrievalConfig={retrievalConfig} showMultiModalTip={showMultiModalTip} diff --git a/web/app/components/app/log/filter.tsx b/web/app/components/app/log/filter.tsx index 4a0103449f..26c21e6cf6 100644 --- a/web/app/components/app/log/filter.tsx +++ b/web/app/components/app/log/filter.tsx @@ -50,7 +50,7 @@ const Filter: FC<IFilterProps> = ({ isChatMode, appId, queryParams, setQueryPara setQueryParams({ ...queryParams, period: item.value }) }} onClear={() => setQueryParams({ ...queryParams, period: '9' })} - items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} + items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}` as any) as string }))} /> <Chip className="min-w-[150px]" diff --git a/web/app/components/app/workflow-log/filter.tsx b/web/app/components/app/workflow-log/filter.tsx index 9e3b213deb..55b8e08175 100644 --- a/web/app/components/app/workflow-log/filter.tsx +++ b/web/app/components/app/workflow-log/filter.tsx @@ -55,7 +55,7 @@ const Filter: FC<IFilterProps> = ({ queryParams, setQueryParams }: IFilterProps) setQueryParams({ ...queryParams, period: item.value }) }} onClear={() => setQueryParams({ ...queryParams, period: '9' })} - items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))} + items={Object.entries(TIME_PERIOD_MAPPING).map(([k, v]) => ({ value: k, name: t(`appLog.filter.period.${v.name}` as any) as string }))} /> <Input wrapperClassName="w-[200px]" diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index 1b80b21059..6ab6639a0b 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -94,7 +94,7 @@ const BlockInput: FC<IBlockInputProps> = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }) as string, }) return } diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 24df08f8a8..3c7fd576a3 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -3,6 +3,7 @@ import type { ChatItem, Feedback, } from '../types' +import type { Locale } from '@/i18n-config' import type { // AppData, ConversationItem, @@ -93,7 +94,7 @@ export const useEmbeddedChatbot = () => { if (localeParam) { // If locale parameter exists in URL, use it instead of default - await changeLanguage(localeParam) + await changeLanguage(localeParam as Locale) } else if (localeFromSysVar) { // If locale is set as a system variable, use that diff --git a/web/app/components/base/date-and-time-picker/hooks.ts b/web/app/components/base/date-and-time-picker/hooks.ts index f79f28053f..ba66873cc0 100644 --- a/web/app/components/base/date-and-time-picker/hooks.ts +++ b/web/app/components/base/date-and-time-picker/hooks.ts @@ -6,7 +6,7 @@ const YEAR_RANGE = 100 export const useDaysOfWeek = () => { const { t } = useTranslation() - const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => t(`time.daysInWeek.${day}`)) + const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => t(`time.daysInWeek.${day}` as any) as string) return daysOfWeek } @@ -26,7 +26,7 @@ export const useMonths = () => { 'October', 'November', 'December', - ].map(month => t(`time.months.${month}`)) + ].map(month => t(`time.months.${month}` as any) as string) return months } diff --git a/web/app/components/base/encrypted-bottom/index.tsx b/web/app/components/base/encrypted-bottom/index.tsx index ff75d53db6..75b3b8151d 100644 --- a/web/app/components/base/encrypted-bottom/index.tsx +++ b/web/app/components/base/encrypted-bottom/index.tsx @@ -16,7 +16,7 @@ export const EncryptedBottom = (props: Props) => { return ( <div className={cn('system-xs-regular flex items-center justify-center rounded-b-2xl border-t-[0.5px] border-divider-subtle bg-background-soft px-2 py-3 text-text-tertiary', className)}> <RiLock2Fill className="mx-1 h-3 w-3 text-text-quaternary" /> - {t(frontTextKey || 'common.provider.encrypted.front')} + {t((frontTextKey || 'common.provider.encrypted.front') as any) as string} <Link className="mx-1 text-text-accent" target="_blank" @@ -25,7 +25,7 @@ export const EncryptedBottom = (props: Props) => { > PKCS1_OAEP </Link> - {t(backTextKey || 'common.provider.encrypted.back')} + {t((backTextKey || 'common.provider.encrypted.back') as any) as string} </div> ) } diff --git a/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx b/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx index fc1052e172..ca407a69ce 100644 --- a/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx +++ b/web/app/components/base/features/new-feature-panel/text-to-speech/param-config-content.tsx @@ -97,7 +97,7 @@ const VoiceParamConfig = ({ className="h-full w-full cursor-pointer rounded-lg border-0 bg-components-input-bg-normal py-1.5 pl-3 pr-10 focus-visible:bg-state-base-hover focus-visible:outline-none group-hover:bg-state-base-hover sm:text-sm sm:leading-6" > <span className={cn('block truncate text-left text-text-secondary', !languageItem?.name && 'text-text-tertiary')}> - {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} + {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}` as any) as string : localLanguagePlaceholder} </span> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <ChevronDownIcon @@ -128,7 +128,7 @@ const VoiceParamConfig = ({ <span className={cn('block', selected && 'font-normal')} > - {t(`common.voice.language.${(item.value).toString().replace('-', '')}`)} + {t(`common.voice.language.${(item.value).toString().replace('-', '')}` as any) as string} </span> {(selected || item.value === text2speech?.language) && ( <span diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts index 2e09f0aa62..e62addee2d 100644 --- a/web/app/components/base/file-uploader/hooks.ts +++ b/web/app/components/base/file-uploader/hooks.ts @@ -174,7 +174,7 @@ export const useFile = (fileConfig: FileUpload, noNeedToCheckEnable = true) => { handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 }) }, onErrorCallback: (error?: any) => { - const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t) + const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t as any) notify({ type: 'error', message: errorMessage }) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, @@ -287,7 +287,7 @@ export const useFile = (fileConfig: FileUpload, noNeedToCheckEnable = true) => { handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 }) }, onErrorCallback: (error?: any) => { - const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t) + const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t as any) notify({ type: 'error', message: errorMessage }) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, diff --git a/web/app/components/base/form/components/field/input-type-select/hooks.tsx b/web/app/components/base/form/components/field/input-type-select/hooks.tsx index 67621fef67..eb7da8d9d0 100644 --- a/web/app/components/base/form/components/field/input-type-select/hooks.tsx +++ b/web/app/components/base/form/components/field/input-type-select/hooks.tsx @@ -44,7 +44,7 @@ export const useInputTypeOptions = (supportFile: boolean) => { return options.map((value) => { return { value, - label: t(`appDebug.variableConfig.${i18nFileTypeMap[value] || value}`), + label: t(`appDebug.variableConfig.${i18nFileTypeMap[value] || value}` as any), Icon: INPUT_TYPE_ICON[value], type: DATA_TYPE[value], } diff --git a/web/app/components/base/image-uploader/hooks.ts b/web/app/components/base/image-uploader/hooks.ts index 065a808d33..f098c378eb 100644 --- a/web/app/components/base/image-uploader/hooks.ts +++ b/web/app/components/base/image-uploader/hooks.ts @@ -82,7 +82,7 @@ export const useImageFiles = () => { setFiles(newFiles) }, onErrorCallback: (error?: any) => { - const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t) + const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t as any) notify({ type: 'error', message: errorMessage }) const newFiles = [...files.slice(0, index), { ...currentImageFile, progress: -1 }, ...files.slice(index + 1)] filesRef.current = newFiles @@ -160,7 +160,7 @@ export const useLocalFileUploader = ({ limit, disabled = false, onUpload }: useL onUpload({ ...imageFile, fileId: res.id, progress: 100 }) }, onErrorCallback: (error?: any) => { - const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t) + const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t as any) notify({ type: 'error', message: errorMessage }) onUpload({ ...imageFile, progress: -1 }) }, diff --git a/web/app/components/base/tag-input/index.tsx b/web/app/components/base/tag-input/index.tsx index 3241543565..6fe0016a1b 100644 --- a/web/app/components/base/tag-input/index.tsx +++ b/web/app/components/base/tag-input/index.tsx @@ -127,7 +127,7 @@ const TagInput: FC<TagInputProps> = ({ setValue(e.target.value) }} onKeyDown={handleKeyDown} - placeholder={t(placeholder || (isSpecialMode ? 'common.model.params.stop_sequencesPlaceholder' : 'datasetDocuments.segment.addKeyWord'))} + placeholder={t((placeholder || (isSpecialMode ? 'common.model.params.stop_sequencesPlaceholder' : 'datasetDocuments.segment.addKeyWord')) as any)} /> </div> ) diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx index 57c85cf297..345c915c2b 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx @@ -106,7 +106,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({ {ICON_MAP[plan]} <div className="flex min-h-[104px] flex-col gap-y-2"> <div className="flex items-center gap-x-2.5"> - <div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`)}</div> + <div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name` as any) as string}</div> { isMostPopularPlan && ( <div className="flex items-center justify-center bg-saas-dify-blue-static px-1.5 py-1"> @@ -117,7 +117,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({ ) } </div> - <div className="system-sm-regular text-text-secondary">{t(`${i18nPrefix}.description`)}</div> + <div className="system-sm-regular text-text-secondary">{t(`${i18nPrefix}.description` as any) as string}</div> </div> </div> {/* Price */} diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx index 544141a6a5..73c7f31cb5 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/button.tsx @@ -42,7 +42,7 @@ const Button = ({ onClick={handleGetPayUrl} > <div className="flex grow items-center gap-x-2"> - <span>{t(`${i18nPrefix}.btnText`)}</span> + <span>{t(`${i18nPrefix}.btnText` as any) as string}</span> {isPremiumPlan && ( <span className="pb-px pt-[7px]"> <AwsMarketplace className="h-6" /> diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx index b89d0c6941..a1880af523 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/index.tsx @@ -85,16 +85,16 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({ <div className=" flex flex-col gap-y-6 px-1 pt-10"> {STYLE_MAP[plan].icon} <div className="flex min-h-[104px] flex-col gap-y-2"> - <div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`)}</div> - <div className="system-md-regular line-clamp-2 text-text-secondary">{t(`${i18nPrefix}.description`)}</div> + <div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name` as any) as string}</div> + <div className="system-md-regular line-clamp-2 text-text-secondary">{t(`${i18nPrefix}.description` as any) as string}</div> </div> </div> {/* Price */} <div className="flex items-end gap-x-2 px-1 pb-8 pt-4"> - <div className="title-4xl-semi-bold shrink-0 text-text-primary">{t(`${i18nPrefix}.price`)}</div> + <div className="title-4xl-semi-bold shrink-0 text-text-primary">{t(`${i18nPrefix}.price` as any) as string}</div> {!isFreePlan && ( <span className="system-md-regular pb-0.5 text-text-tertiary"> - {t(`${i18nPrefix}.priceTip`)} + {t(`${i18nPrefix}.priceTip` as any) as string} </span> )} </div> diff --git a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx index 4ed307d36e..e7828decb9 100644 --- a/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx +++ b/web/app/components/billing/pricing/plans/self-hosted-plan-item/list/index.tsx @@ -12,13 +12,13 @@ const List = ({ }: ListProps) => { const { t } = useTranslation() const i18nPrefix = `billing.plans.${plan}` - const features = t(`${i18nPrefix}.features`, { returnObjects: true }) as string[] + const features = t(`${i18nPrefix}.features` as any, { returnObjects: true }) as unknown as string[] return ( <div className="flex flex-col gap-y-[10px] p-6"> <div className="system-md-semibold text-text-secondary"> <Trans - i18nKey={t(`${i18nPrefix}.includesTitle`)} + i18nKey={t(`${i18nPrefix}.includesTitle` as any) as string} components={{ highlight: <span className="text-text-warning"></span> }} /> </div> diff --git a/web/app/components/billing/priority-label/index.tsx b/web/app/components/billing/priority-label/index.tsx index e5a6857078..b66fdc7ea9 100644 --- a/web/app/components/billing/priority-label/index.tsx +++ b/web/app/components/billing/priority-label/index.tsx @@ -31,7 +31,7 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => { return ( <Tooltip popupContent={( <div> - <div className="mb-1 text-xs font-semibold text-text-primary">{`${t('billing.plansCommon.documentProcessingPriority')}: ${t(`billing.plansCommon.priority.${priority}`)}`}</div> + <div className="mb-1 text-xs font-semibold text-text-primary">{`${t('billing.plansCommon.documentProcessingPriority')}: ${t(`billing.plansCommon.priority.${priority}` as any) as string}`}</div> { priority !== DocumentProcessingPriority.topPriority && ( <div className="text-xs text-text-secondary">{t('billing.plansCommon.documentProcessingPriorityTip')}</div> @@ -51,7 +51,7 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => { <RiAedFill className="mr-0.5 size-3" /> ) } - <span>{t(`billing.plansCommon.priority.${priority}`)}</span> + <span>{t(`billing.plansCommon.priority.${priority}` as any) as string}</span> </div> </Tooltip> ) diff --git a/web/app/components/billing/upgrade-btn/index.tsx b/web/app/components/billing/upgrade-btn/index.tsx index 0f23022b35..2a43090d84 100644 --- a/web/app/components/billing/upgrade-btn/index.tsx +++ b/web/app/components/billing/upgrade-btn/index.tsx @@ -46,8 +46,8 @@ const UpgradeBtn: FC<Props> = ({ } } - const defaultBadgeLabel = t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}`) - const label = labelKey ? t(labelKey) : defaultBadgeLabel + const defaultBadgeLabel = t(`billing.upgradeBtn.${isShort ? 'encourageShort' : 'encourage'}` as any) as string + const label = labelKey ? t(labelKey as any) as string : defaultBadgeLabel if (isPlain) { return ( @@ -56,7 +56,7 @@ const UpgradeBtn: FC<Props> = ({ style={style} onClick={onClick} > - {labelKey ? label : t('billing.upgradeBtn.plain')} + {labelKey ? label : t('billing.upgradeBtn.plain' as any) as string} </Button> ) } diff --git a/web/app/components/custom/custom-web-app-brand/index.tsx b/web/app/components/custom/custom-web-app-brand/index.tsx index 3c6557dc63..6a86441bd4 100644 --- a/web/app/components/custom/custom-web-app-brand/index.tsx +++ b/web/app/components/custom/custom-web-app-brand/index.tsx @@ -68,7 +68,7 @@ const CustomWebAppBrand = () => { setFileId(res.id) }, onErrorCallback: (error?: any) => { - const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t) + const errorMessage = getImageUploadErrorMessage(error, t('common.imageUploader.uploadFromComputerUploadError'), t as any) notify({ type: 'error', message: errorMessage }) setUploadProgress(-1) }, diff --git a/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts b/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts index 7a0868b14c..44bde33a96 100644 --- a/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts +++ b/web/app/components/datasets/common/image-uploader/hooks/use-upload.ts @@ -145,7 +145,7 @@ export const useUpload = () => { handleUpdateFile({ ...uploadingFile, uploadedId: res.id, progress: 100 }) }, onErrorCallback: (error?: any) => { - const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t) + const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t as any) Toast.notify({ type: 'error', message: errorMessage }) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, @@ -187,7 +187,7 @@ export const useUpload = () => { }) }, onErrorCallback: (error?: any) => { - const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t) + const errorMessage = getFileUploadErrorMessage(error, t('common.fileUploader.uploadFromComputerUploadError'), t as any) Toast.notify({ type: 'error', message: errorMessage }) handleUpdateFile({ ...uploadingFile, progress: -1 }) }, diff --git a/web/app/components/datasets/common/retrieval-method-info/index.tsx b/web/app/components/datasets/common/retrieval-method-info/index.tsx index df8a93f666..3ab33e0278 100644 --- a/web/app/components/datasets/common/retrieval-method-info/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-info/index.tsx @@ -33,8 +33,8 @@ const EconomicalRetrievalMethodConfig: FC<Props> = ({ <div className="space-y-2"> <RadioCard icon={icon} - title={t(`dataset.retrieval.${type}.title`)} - description={t(`dataset.retrieval.${type}.description`)} + title={t(`dataset.retrieval.${type}.title` as any) as string} + description={t(`dataset.retrieval.${type}.description` as any) as string} noRadio chosenConfigWrapClassName="!pb-3" chosenConfig={( diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx index 8452d20d2d..348ba88b82 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/content.tsx @@ -44,7 +44,7 @@ const Content = ({ {name} </div> <div className="system-2xs-medium-uppercase text-text-tertiary"> - {t(`dataset.chunkingMode.${DOC_FORM_TEXT[chunkStructure]}`)} + {t(`dataset.chunkingMode.${DOC_FORM_TEXT[chunkStructure]}` as any) as string} </div> </div> </div> diff --git a/web/app/components/datasets/create/embedding-process/index.tsx b/web/app/components/datasets/create/embedding-process/index.tsx index 541eb62b60..d2050268aa 100644 --- a/web/app/components/datasets/create/embedding-process/index.tsx +++ b/web/app/components/datasets/create/embedding-process/index.tsx @@ -141,7 +141,7 @@ const RuleDetail: FC<{ <FieldInfo label={t('datasetSettings.form.retrievalSetting.title')} // displayedValue={t(`datasetSettings.form.retrievalSetting.${retrievalMethod}`) as string} - displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title`) as string} + displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title` as any) as string} valueIcon={( <Image className="size-4" diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index 97d1625c10..08ce25a534 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -132,7 +132,7 @@ const FileUploader = ({ return Promise.resolve({ ...completeFile }) }) .catch((e) => { - const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t) + const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t as any) notify({ type: 'error', message: errorMessage }) onFileUpdate(fileItem, -2, fileListRef.current) return Promise.resolve({ ...fileItem }) diff --git a/web/app/components/datasets/create/top-bar/index.tsx b/web/app/components/datasets/create/top-bar/index.tsx index 3f30d9a8da..632ad3ab73 100644 --- a/web/app/components/datasets/create/top-bar/index.tsx +++ b/web/app/components/datasets/create/top-bar/index.tsx @@ -39,7 +39,7 @@ export const TopBar: FC<TopBarProps> = (props) => { <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"> <Stepper steps={Array.from({ length: 3 }, (_, i) => ({ - name: t(STEP_T_MAP[i + 1]), + name: t(STEP_T_MAP[i + 1] as any) as string, }))} {...rest} /> diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx index 31570ef4cf..c36155e104 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/local-file/index.tsx @@ -155,7 +155,7 @@ const LocalFile = ({ return Promise.resolve({ ...completeFile }) }) .catch((e) => { - const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t) + const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t as any) notify({ type: 'error', message: errorMessage }) updateFile(fileItem, -2, fileListRef.current) return Promise.resolve({ ...fileItem }) diff --git a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx index a16e284bcf..55590636a6 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/processing/embedding-process/rule-detail.tsx @@ -63,7 +63,7 @@ const RuleDetail = ({ /> <FieldInfo label={t('datasetSettings.form.retrievalSetting.title')} - displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title`) as string} + displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title` as any) as string} valueIcon={( <Image className="size-4" diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx index 3e55da0a90..c6ab97a8da 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx @@ -75,7 +75,7 @@ const CSVUploader: FC<Props> = ({ return Promise.resolve({ ...completeFile }) }) .catch((e) => { - const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t) + const errorMessage = getFileUploadErrorMessage(e, t('datasetCreation.stepOne.uploader.failed'), t as any) notify({ type: 'error', message: errorMessage }) const errorFile = { ...fileItem, diff --git a/web/app/components/datasets/documents/detail/embedding/index.tsx b/web/app/components/datasets/documents/detail/embedding/index.tsx index db83d89c40..2978ec5681 100644 --- a/web/app/components/datasets/documents/detail/embedding/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/index.tsx @@ -133,7 +133,7 @@ const RuleDetail: FC<IRuleDetailProps> = React.memo(({ /> <FieldInfo label={t('datasetSettings.form.retrievalSetting.title')} - displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title`) as string} + displayedValue={t(`dataset.retrieval.${indexingType === IndexingType.ECONOMICAL ? 'keyword_search' : retrievalMethod}.title` as any) as string} valueIcon={( <Image className="size-4" diff --git a/web/app/components/datasets/hit-testing/components/query-input/index.tsx b/web/app/components/datasets/hit-testing/components/query-input/index.tsx index 959e7f3425..92ad6d3b4a 100644 --- a/web/app/components/datasets/hit-testing/components/query-input/index.tsx +++ b/web/app/components/datasets/hit-testing/components/query-input/index.tsx @@ -228,7 +228,7 @@ const QueryInput = ({ className="flex h-7 cursor-pointer items-center space-x-0.5 rounded-lg border-[0.5px] border-components-button-secondary-bg bg-components-button-secondary-bg px-1.5 shadow-xs backdrop-blur-[5px] hover:bg-components-button-secondary-bg-hover" > {icon} - <div className="text-xs font-medium uppercase text-text-secondary">{t(`dataset.retrieval.${retrievalMethod}.title`)}</div> + <div className="text-xs font-medium uppercase text-text-secondary">{t(`dataset.retrieval.${retrievalMethod}.title` as any) as string}</div> <RiEqualizer2Line className="size-4 text-components-menu-item-text"></RiEqualizer2Line> </div> )} diff --git a/web/app/components/datasets/list/dataset-card/index.tsx b/web/app/components/datasets/list/dataset-card/index.tsx index 8087b80fda..e72349db3a 100644 --- a/web/app/components/datasets/list/dataset-card/index.tsx +++ b/web/app/components/datasets/list/dataset-card/index.tsx @@ -217,17 +217,17 @@ const DatasetCard = ({ {dataset.doc_form && ( <span className="min-w-0 max-w-full truncate" - title={t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} + title={t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}` as any) as string} > - {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} + {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}` as any) as string} </span> )} {dataset.indexing_technique && ( <span className="min-w-0 max-w-full truncate" - title={formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} + title={formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method) as any} > - {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} + {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method) as any} </span> )} {dataset.is_multimodal && ( diff --git a/web/app/components/explore/category.tsx b/web/app/components/explore/category.tsx index eba883d849..593ed8d938 100644 --- a/web/app/components/explore/category.tsx +++ b/web/app/components/explore/category.tsx @@ -50,7 +50,7 @@ const Category: FC<ICategoryProps> = ({ className={itemClassName(name === value)} onClick={() => onChange(name)} > - {(categoryI18n as any)[name] ? t(`explore.category.${name}`) : name} + {(categoryI18n as any)[name] ? t(`explore.category.${name}` as any) as string : name} </div> ))} </div> diff --git a/web/app/components/goto-anything/actions/commands/theme.tsx b/web/app/components/goto-anything/actions/commands/theme.tsx index dc8ca46bc0..34df84e33b 100644 --- a/web/app/components/goto-anything/actions/commands/theme.tsx +++ b/web/app/components/goto-anything/actions/commands/theme.tsx @@ -36,13 +36,13 @@ const buildThemeCommands = (query: string, locale?: string): CommandSearchResult const q = query.toLowerCase() const list = THEME_ITEMS.filter(item => !q - || i18n.t(item.titleKey, { lng: locale }).toLowerCase().includes(q) + || i18n.t(item.titleKey as any, { lng: locale }).toLowerCase().includes(q) || item.id.includes(q), ) return list.map(item => ({ id: item.id, - title: i18n.t(item.titleKey, { lng: locale }), - description: i18n.t(item.descKey, { lng: locale }), + title: i18n.t(item.titleKey as any, { lng: locale }), + description: i18n.t(item.descKey as any, { lng: locale }), type: 'command' as const, icon: ( <div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg"> diff --git a/web/app/components/goto-anything/command-selector.tsx b/web/app/components/goto-anything/command-selector.tsx index 0d89669ec8..f62a7a3829 100644 --- a/web/app/components/goto-anything/command-selector.tsx +++ b/web/app/components/goto-anything/command-selector.tsx @@ -117,7 +117,7 @@ const CommandSelector: FC<Props> = ({ actions, onCommandSelect, searchFilter, co '/community': 'app.gotoAnything.actions.communityDesc', '/zen': 'app.gotoAnything.actions.zenDesc', } - return t(slashKeyMap[item.key] || item.description) + return t((slashKeyMap[item.key] || item.description) as any) })() ) : ( @@ -128,7 +128,7 @@ const CommandSelector: FC<Props> = ({ actions, onCommandSelect, searchFilter, co '@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc', '@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc', } - return t(keyMap[item.key]) + return t(keyMap[item.key] as any) as string })() )} </span> diff --git a/web/app/components/goto-anything/index.tsx b/web/app/components/goto-anything/index.tsx index 1c61cb9516..76c2e26ebd 100644 --- a/web/app/components/goto-anything/index.tsx +++ b/web/app/components/goto-anything/index.tsx @@ -243,7 +243,7 @@ const GotoAnything: FC<Props> = ({ knowledge: 'app.gotoAnything.emptyState.noKnowledgeBasesFound', node: 'app.gotoAnything.emptyState.noWorkflowNodesFound', } - return t(keyMap[commandType] || 'app.gotoAnything.noResults') + return t((keyMap[commandType] || 'app.gotoAnything.noResults') as any) })() : t('app.gotoAnything.noResults')} </div> @@ -410,7 +410,7 @@ const GotoAnything: FC<Props> = ({ 'workflow-node': 'app.gotoAnything.groups.workflowNodes', 'command': 'app.gotoAnything.groups.commands', } - return t(typeMap[type] || `${type}s`) + return t((typeMap[type] || `${type}s`) as any) })()} className="p-2 capitalize text-text-secondary" > diff --git a/web/app/components/header/account-setting/language-page/index.tsx b/web/app/components/header/account-setting/language-page/index.tsx index 00db4dbbaf..0568cb1ec9 100644 --- a/web/app/components/header/account-setting/language-page/index.tsx +++ b/web/app/components/header/account-setting/language-page/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { Item } from '@/app/components/base/select' +import type { Locale } from '@/i18n-config' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' @@ -32,7 +33,7 @@ export default function LanguagePage() { await updateUserProfile({ url, body: { [bodyKey]: item.value } }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) - setLocaleOnClient(item.value.toString()) + setLocaleOnClient(item.value.toString() as Locale) } catch (e) { notify({ type: 'error', message: (e as Error).message }) diff --git a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx index 7d8169e4c4..07f89df24d 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/role-selector.tsx @@ -36,7 +36,7 @@ const RoleSelector = ({ value, onChange }: RoleSelectorProps) => { className="block" > <div className={cn('flex cursor-pointer items-center rounded-lg bg-components-input-bg-normal px-3 py-2 hover:bg-state-base-hover', open && 'bg-state-base-hover')}> - <div className="mr-2 grow text-sm leading-5 text-text-primary">{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}`) })}</div> + <div className="mr-2 grow text-sm leading-5 text-text-primary">{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}` as any) })}</div> <RiArrowDownSLine className="h-4 w-4 shrink-0 text-text-secondary" /> </div> </PortalToFollowElemTrigger> diff --git a/web/app/components/header/account-setting/members-page/operation/index.tsx b/web/app/components/header/account-setting/members-page/operation/index.tsx index 2b3c9e350a..da61746685 100644 --- a/web/app/components/header/account-setting/members-page/operation/index.tsx +++ b/web/app/components/header/account-setting/members-page/operation/index.tsx @@ -106,8 +106,8 @@ const Operation = ({ : <div className="mr-1 mt-[2px] h-4 w-4 text-text-accent" /> } <div> - <div className="system-sm-semibold whitespace-nowrap text-text-secondary">{t(`common.members.${toHump(role)}`)}</div> - <div className="system-xs-regular whitespace-nowrap text-text-tertiary">{t(`common.members.${toHump(role)}Tip`)}</div> + <div className="system-sm-semibold whitespace-nowrap text-text-secondary">{t(`common.members.${toHump(role)}` as any)}</div> + <div className="system-xs-regular whitespace-nowrap text-text-tertiary">{t(`common.members.${toHump(role)}Tip` as any)}</div> </div> </div> </MenuItem> diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx index d53467028c..9e8637ce49 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/presets-parameter.tsx @@ -44,7 +44,7 @@ const PresetsParameter: FC<PresetsParameterProps> = ({ text: ( <div className="flex h-full items-center"> {getToneIcon(tone.id)} - {t(`common.model.tone.${tone.name}`) as string} + {t(`common.model.tone.${tone.name}` as any) as string} </div> ), } diff --git a/web/app/components/plugins/base/deprecation-notice.tsx b/web/app/components/plugins/base/deprecation-notice.tsx index 8832c77961..76d64a45f4 100644 --- a/web/app/components/plugins/base/deprecation-notice.tsx +++ b/web/app/components/plugins/base/deprecation-notice.tsx @@ -82,7 +82,7 @@ const DeprecationNotice: FC<DeprecationNoticeProps> = ({ ), }} values={{ - deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`), + deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}` as any) as string, alternativePluginId, }} /> @@ -91,7 +91,7 @@ const DeprecationNotice: FC<DeprecationNoticeProps> = ({ { hasValidDeprecatedReason && !alternativePluginId && ( <span> - {t(`${i18nPrefix}.onlyReason`, { deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`) })} + {t(`${i18nPrefix}.onlyReason` as any, { deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}` as any) as string }) as string} </span> ) } diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index 1cb15bf70b..f063a8d572 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -1,11 +1,14 @@ 'use client' import type { Plugin } from '../types' +import type { Locale } from '@/i18n-config' import { RiAlertFill } from '@remixicon/react' import * as React from 'react' import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' -import { renderI18nObject } from '@/i18n-config' +import { + renderI18nObject, +} from '@/i18n-config' import { getLanguage } from '@/i18n-config/language' import { Theme } from '@/types/app' import { cn } from '@/utils/classnames' @@ -30,7 +33,7 @@ export type Props = { footer?: React.ReactNode isLoading?: boolean loadingFileName?: string - locale?: string + locale?: Locale limitedInstall?: boolean } diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts index 8303a4cc46..423ce1023c 100644 --- a/web/app/components/plugins/hooks.ts +++ b/web/app/components/plugins/hooks.ts @@ -20,7 +20,7 @@ export const useTags = (translateFromOut?: TFunction) => { return tagKeys.map((tag) => { return { name: tag, - label: t(`pluginTags.tags.${tag}`), + label: t(`pluginTags.tags.${tag}` as any) as string, } }) }, [t]) @@ -66,14 +66,14 @@ export const useCategories = (translateFromOut?: TFunction, isSingle?: boolean) } return { name: category, - label: isSingle ? t(`plugin.categorySingle.${category}`) : t(`plugin.category.${category}s`), + label: isSingle ? t(`plugin.categorySingle.${category}` as any) as string : t(`plugin.category.${category}s` as any) as string, } }) }, [t, isSingle]) const categoriesMap = useMemo(() => { return categories.reduce((acc, category) => { - acc[category.name] = category + acc[category.name] = category as any return acc }, {} as Record<string, Category>) }, [categories]) diff --git a/web/app/components/plugins/marketplace/description/index.tsx b/web/app/components/plugins/marketplace/description/index.tsx index 5b1ac6bb09..66a6368c08 100644 --- a/web/app/components/plugins/marketplace/description/index.tsx +++ b/web/app/components/plugins/marketplace/description/index.tsx @@ -1,10 +1,11 @@ +import type { Locale } from '@/i18n-config' import { getLocaleOnServer, - useTranslation as translate, + getTranslation as translate, } from '@/i18n-config/server' type DescriptionProps = { - locale?: string + locale?: Locale } const Description = async ({ locale: localeFromProps, diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 47acb840e4..ff9a4d60bc 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -1,5 +1,6 @@ import type { MarketplaceCollection, SearchParams } from './types' import type { Plugin } from '@/app/components/plugins/types' +import type { Locale } from '@/i18n-config' import { TanstackQueryInitializer } from '@/context/query-client' import { MarketplaceContextProvider } from './context' import Description from './description' @@ -8,7 +9,7 @@ import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper' import { getMarketplaceCollectionsAndPlugins } from './utils' type MarketplaceProps = { - locale: string + locale: Locale showInstallButton?: boolean shouldExclude?: boolean searchParams?: SearchParams diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index 159107eb97..ddc505b0d8 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -1,5 +1,6 @@ 'use client' import type { Plugin } from '@/app/components/plugins/types' +import type { Locale } from '@/i18n-config' import { RiArrowRightUpLine } from '@remixicon/react' import { useBoolean } from 'ahooks' import { useTheme } from 'next-themes' @@ -17,7 +18,7 @@ import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '.. type CardWrapperProps = { plugin: Plugin showInstallButton?: boolean - locale?: string + locale?: Locale } const CardWrapperComponent = ({ plugin, diff --git a/web/app/components/plugins/marketplace/list/index.tsx b/web/app/components/plugins/marketplace/list/index.tsx index 95f7cb37a8..54889b232f 100644 --- a/web/app/components/plugins/marketplace/list/index.tsx +++ b/web/app/components/plugins/marketplace/list/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { Plugin } from '../../types' import type { MarketplaceCollection } from '../types' +import type { Locale } from '@/i18n-config' import { cn } from '@/utils/classnames' import Empty from '../empty' import CardWrapper from './card-wrapper' @@ -11,7 +12,7 @@ type ListProps = { marketplaceCollectionPluginsMap: Record<string, Plugin[]> plugins?: Plugin[] showInstallButton?: boolean - locale: string + locale: Locale cardContainerClassName?: string cardRender?: (plugin: Plugin) => React.JSX.Element | null onMoreClick?: () => void diff --git a/web/app/components/plugins/marketplace/list/list-with-collection.tsx b/web/app/components/plugins/marketplace/list/list-with-collection.tsx index c401fbe3b9..2d246efb82 100644 --- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx +++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx @@ -3,6 +3,7 @@ import type { MarketplaceCollection } from '../types' import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types' import type { Plugin } from '@/app/components/plugins/types' +import type { Locale } from '@/i18n-config' import { RiArrowRightSLine } from '@remixicon/react' import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' import { getLanguage } from '@/i18n-config/language' @@ -13,7 +14,7 @@ type ListWithCollectionProps = { marketplaceCollections: MarketplaceCollection[] marketplaceCollectionPluginsMap: Record<string, Plugin[]> showInstallButton?: boolean - locale: string + locale: Locale cardContainerClassName?: string cardRender?: (plugin: Plugin) => React.JSX.Element | null onMoreClick?: (searchParams?: SearchParamsFromCollection) => void diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx index f2fbd085f0..650c8a7447 100644 --- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx @@ -1,6 +1,7 @@ 'use client' import type { Plugin } from '../../types' import type { MarketplaceCollection } from '../types' +import type { Locale } from '@/i18n-config' import { useEffect } from 'react' import Loading from '@/app/components/base/loading' import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' @@ -12,7 +13,7 @@ type ListWrapperProps = { marketplaceCollections: MarketplaceCollection[] marketplaceCollectionPluginsMap: Record<string, Plugin[]> showInstallButton?: boolean - locale: string + locale: Locale } const ListWrapper = ({ marketplaceCollections, diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx index 16a789e67b..31e0bd6a85 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx @@ -330,7 +330,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { return ( <Modal - title={t(`pluginTrigger.modal.${createType === SupportedCreationMethods.APIKEY ? 'apiKey' : createType.toLowerCase()}.title`)} + title={t(`pluginTrigger.modal.${createType === SupportedCreationMethods.APIKEY ? 'apiKey' : createType.toLowerCase()}.title` as any)} confirmButtonText={ currentStep === ApiKeyStep.Verify ? isVerifyingCredentials ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify') diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx index 39a5a99a46..5d5df968d1 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx @@ -208,7 +208,7 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU ) : ( <Tooltip - popupContent={subscriptionCount >= MAX_COUNT ? t('pluginTrigger.subscription.maxCount', { num: MAX_COUNT }) : t(`pluginTrigger.subscription.addType.options.${methodType.toLowerCase()}.description`)} + popupContent={subscriptionCount >= MAX_COUNT ? t('pluginTrigger.subscription.maxCount', { num: MAX_COUNT }) : t(`pluginTrigger.subscription.addType.options.${methodType.toLowerCase()}.description` as any)} disabled={!(supportedMethods?.length === 1 || subscriptionCount >= MAX_COUNT)} > <ActionButton diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index b50fdd2425..818c2a0388 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -260,9 +260,9 @@ export type Plugin = { icon: string icon_dark?: string verified: boolean - label: Record<Locale, string> - brief: Record<Locale, string> - description: Record<Locale, string> + label: Partial<Record<Locale, string>> + brief: Partial<Record<Locale, string>> + description: Partial<Record<Locale, string>> // Repo readme.md content introduction: string repository: string diff --git a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts index 6464534c83..45c36196f5 100644 --- a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts @@ -36,8 +36,8 @@ export const useAvailableNodesMetaData = () => { const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => { const { metaData } = node - const title = t(`workflow.blocks.${metaData.type}`) - const description = t(`workflow.blocksAbout.${metaData.type}`) + const title = t(`workflow.blocks.${metaData.type}` as any) as string + const description = t(`workflow.blocksAbout.${metaData.type}` as any) as string return { ...node, metaData: { diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-template.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-template.ts index 5955ee5c45..fc64e6c8c7 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline-template.ts +++ b/web/app/components/rag-pipeline/hooks/use-pipeline-template.ts @@ -14,7 +14,7 @@ export const usePipelineTemplate = () => { data: { ...knowledgeBaseDefault.defaultValue as KnowledgeBaseNodeType, type: knowledgeBaseDefault.metaData.type, - title: t(`workflow.blocks.${knowledgeBaseDefault.metaData.type}`), + title: t(`workflow.blocks.${knowledgeBaseDefault.metaData.type}` as any) as string, selected: true, }, position: { diff --git a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx index 4ecee282f9..5c85aee32c 100644 --- a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx @@ -111,7 +111,7 @@ const GetSchema: FC<Props> = ({ }} className="system-sm-regular cursor-pointer whitespace-nowrap rounded-lg px-3 py-1.5 leading-5 text-text-secondary hover:bg-components-panel-on-panel-item-bg-hover" > - {t(`tools.createTool.exampleOptions.${item.key}`)} + {t(`tools.createTool.exampleOptions.${item.key}` as any) as string} </div> ))} </div> diff --git a/web/app/components/tools/edit-custom-collection-modal/index.tsx b/web/app/components/tools/edit-custom-collection-modal/index.tsx index 93ef9142d9..474c262010 100644 --- a/web/app/components/tools/edit-custom-collection-modal/index.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/index.tsx @@ -292,7 +292,7 @@ const EditCustomCollectionModal: FC<Props> = ({ <div> <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.authMethod.title')}</div> <div className="flex h-9 cursor-pointer items-center justify-between rounded-lg bg-components-input-bg-normal px-2.5" onClick={() => setCredentialsModalShow(true)}> - <div className="system-xs-regular text-text-primary">{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div> + <div className="system-xs-regular text-text-primary">{t(`tools.createTool.authMethod.types.${credential.auth_type}` as any) as string}</div> <RiSettings2Line className="h-4 w-4 text-text-secondary" /> </div> </div> diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 8aacb7ad07..30ead4425b 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -78,7 +78,7 @@ const TestApi: FC<Props> = ({ <div> <div className="system-sm-medium py-2 text-text-primary">{t('tools.createTool.authMethod.title')}</div> <div className="flex h-9 cursor-pointer items-center justify-between rounded-lg bg-components-input-bg-normal px-2.5" onClick={() => setCredentialsModalShow(true)}> - <div className="system-xs-regular text-text-primary">{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}</div> + <div className="system-xs-regular text-text-primary">{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}` as any) as string}</div> <RiSettings2Line className="h-4 w-4 text-text-secondary" /> </div> </div> diff --git a/web/app/components/tools/provider/empty.tsx b/web/app/components/tools/provider/empty.tsx index 7e916ba62f..e79607751e 100644 --- a/web/app/components/tools/provider/empty.tsx +++ b/web/app/components/tools/provider/empty.tsx @@ -33,17 +33,17 @@ const Empty = ({ const Comp = (hasLink ? Link : 'div') as any const linkProps = hasLink ? { href: getLink(type), target: '_blank' } : {} const renderType = isAgent ? 'agent' : type - const hasTitle = t(`tools.addToolModal.${renderType}.title`) !== `tools.addToolModal.${renderType}.title` + const hasTitle = t(`tools.addToolModal.${renderType}.title` as any) as string !== `tools.addToolModal.${renderType}.title` return ( <div className="flex flex-col items-center justify-center"> <NoToolPlaceholder className={theme === 'dark' ? 'invert' : ''} /> <div className="mb-1 mt-2 text-[13px] font-medium leading-[18px] text-text-primary"> - {hasTitle ? t(`tools.addToolModal.${renderType}.title`) : 'No tools available'} + {hasTitle ? t(`tools.addToolModal.${renderType}.title` as any) as string : 'No tools available'} </div> {(!isAgent && hasTitle) && ( <Comp className={cn('flex items-center text-[13px] leading-[18px] text-text-tertiary', hasLink && 'cursor-pointer hover:text-text-accent')} {...linkProps}> - {t(`tools.addToolModal.${renderType}.tip`)} + {t(`tools.addToolModal.${renderType}.tip` as any) as string} {' '} {hasLink && <RiArrowRightUpLine className="ml-0.5 h-3 w-3" />} </Comp> diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts index 24d862321d..7f08612234 100644 --- a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -42,8 +42,8 @@ export const useAvailableNodesMetaData = () => { const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => { const { metaData } = node - const title = t(`workflow.blocks.${metaData.type}`) - const description = t(`workflow.blocksAbout.${metaData.type}`) + const title = t(`workflow.blocks.${metaData.type}` as any) as string + const description = t(`workflow.blocksAbout.${metaData.type}` as any) as string const helpLinkPath = `guides/workflow/node/${metaData.helpLinkUri}` return { ...node, diff --git a/web/app/components/workflow-app/hooks/use-workflow-template.ts b/web/app/components/workflow-app/hooks/use-workflow-template.ts index b48a68e1eb..46f2c3e0a3 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-template.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-template.ts @@ -18,7 +18,7 @@ export const useWorkflowTemplate = () => { data: { ...startDefault.defaultValue as StartNodeType, type: startDefault.metaData.type, - title: t(`workflow.blocks.${startDefault.metaData.type}`), + title: t(`workflow.blocks.${startDefault.metaData.type}` as any) as string, }, position: START_INITIAL_POSITION, }) @@ -34,7 +34,7 @@ export const useWorkflowTemplate = () => { }, selected: true, type: llmDefault.metaData.type, - title: t(`workflow.blocks.${llmDefault.metaData.type}`), + title: t(`workflow.blocks.${llmDefault.metaData.type}` as any) as string, }, position: { x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET, @@ -48,7 +48,7 @@ export const useWorkflowTemplate = () => { ...answerDefault.defaultValue, answer: `{{#${llmNode.id}.text#}}`, type: answerDefault.metaData.type, - title: t(`workflow.blocks.${answerDefault.metaData.type}`), + title: t(`workflow.blocks.${answerDefault.metaData.type}` as any) as string, }, position: { x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET * 2, diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index e626073a3a..51d08d15df 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -85,7 +85,7 @@ const Blocks = ({ { classification !== '-' && !!filteredList.length && ( <div className="flex h-[22px] items-start px-3 text-xs font-medium text-text-tertiary"> - {t(`workflow.tabs.${classification}`)} + {t(`workflow.tabs.${classification}` as any) as string} </div> ) } diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx index 1e6b976739..343d56bd1a 100644 --- a/web/app/components/workflow/block-selector/featured-tools.tsx +++ b/web/app/components/workflow/block-selector/featured-tools.tsx @@ -2,6 +2,7 @@ import type { ToolWithProvider } from '../types' import type { ToolDefaultValue, ToolValue } from './types' import type { Plugin } from '@/app/components/plugins/types' +import type { Locale } from '@/i18n-config' import { RiMoreLine } from '@remixicon/react' import Link from 'next/link' import { useEffect, useMemo, useState } from 'react' @@ -178,7 +179,7 @@ const FeaturedTools = ({ onInstallSuccess={async () => { await onInstallSuccess?.() }} - t={t} + t={t as any} /> ))} </div> @@ -221,7 +222,7 @@ const FeaturedTools = ({ type FeaturedToolUninstalledItemProps = { plugin: Plugin - language: string + language: Locale onInstallSuccess?: () => Promise<void> | void t: (key: string, options?: Record<string, any>) => string } diff --git a/web/app/components/workflow/block-selector/featured-triggers.tsx b/web/app/components/workflow/block-selector/featured-triggers.tsx index c986a8abc0..66705a9d06 100644 --- a/web/app/components/workflow/block-selector/featured-triggers.tsx +++ b/web/app/components/workflow/block-selector/featured-triggers.tsx @@ -1,6 +1,7 @@ 'use client' import type { TriggerDefaultValue, TriggerWithProvider } from './types' import type { Plugin } from '@/app/components/plugins/types' +import type { Locale } from '@/i18n-config' import { RiMoreLine } from '@remixicon/react' import Link from 'next/link' import { useEffect, useMemo, useState } from 'react' @@ -170,7 +171,7 @@ const FeaturedTriggers = ({ onInstallSuccess={async () => { await onInstallSuccess?.() }} - t={t} + t={t as any} /> ))} </div> @@ -213,7 +214,7 @@ const FeaturedTriggers = ({ type FeaturedTriggerUninstalledItemProps = { plugin: Plugin - language: string + language: Locale onInstallSuccess?: () => Promise<void> | void t: (key: string, options?: Record<string, any>) => string } diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index 075a0b7d38..462d58df9f 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -17,7 +17,7 @@ export const useBlocks = () => { return BLOCKS.map((block) => { return { ...block, - title: t(`workflow.blocks.${block.type}`), + title: t(`workflow.blocks.${block.type}` as any) as string, } }) } @@ -28,7 +28,7 @@ export const useStartBlocks = () => { return START_BLOCKS.map((block) => { return { ...block, - title: t(`workflow.blocks.${block.type}`), + title: t(`workflow.blocks.${block.type}` as any) as string, } }) } diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx index 5c4311b805..b6aaef84f9 100644 --- a/web/app/components/workflow/block-selector/start-blocks.tsx +++ b/web/app/components/workflow/block-selector/start-blocks.tsx @@ -43,7 +43,7 @@ const StartBlocks = ({ if (blockType === BlockEnumValues.TriggerWebhook) return t('workflow.customWebhook') - return t(`workflow.blocks.${blockType}`) + return t(`workflow.blocks.${blockType}` as any) as string } return START_BLOCKS.filter((block) => { @@ -83,10 +83,10 @@ const StartBlocks = ({ <div className="system-md-medium mb-1 text-text-primary"> {block.type === BlockEnumValues.TriggerWebhook ? t('workflow.customWebhook') - : t(`workflow.blocks.${block.type}`)} + : t(`workflow.blocks.${block.type}` as any) as string} </div> <div className="system-xs-regular text-text-secondary"> - {t(`workflow.blocksAbout.${block.type}`)} + {t(`workflow.blocksAbout.${block.type}` as any) as string} </div> {(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && ( <div className="system-xs-regular mb-1 mt-1 text-text-tertiary"> @@ -107,7 +107,7 @@ const StartBlocks = ({ type={block.type} /> <div className="flex w-0 grow items-center justify-between text-sm text-text-secondary"> - <span className="truncate">{t(`workflow.blocks.${block.type}`)}</span> + <span className="truncate">{t(`workflow.blocks.${block.type}` as any) as string}</span> {block.type === BlockEnumValues.Start && ( <span className="system-xs-regular ml-2 shrink-0 text-text-quaternary">{t('workflow.blocks.originalStartNode')}</span> )} diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 6e49bfa0be..7cead40705 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -237,8 +237,8 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { list.push({ id: `${type}-need-added`, type, - title: t(`workflow.blocks.${type}`), - errorMessage: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }), + title: t(`workflow.blocks.${type}` as any) as string, + errorMessage: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}` as any) as string }), canNavigate: false, }) } @@ -409,7 +409,7 @@ export const useChecklistBeforePublish = () => { const type = isRequiredNodesType[i] if (!filteredNodes.find(node => node.data.type === type)) { - notify({ type: 'error', message: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}`) }) }) + notify({ type: 'error', message: t('workflow.common.needAdd', { node: t(`workflow.blocks.${type}` as any) as string }) }) return false } } diff --git a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx index 44df18ddf2..91f89b147e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx @@ -44,7 +44,7 @@ const OutputVarList: FC<Props> = ({ if (!isValid) { setToastHandler(Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }), })) return } diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx index 2d96baaf28..7b548c6afc 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-list.tsx @@ -57,7 +57,7 @@ const VarList: FC<Props> = ({ if (!isValid) { setToastHandle(Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }), })) return } diff --git a/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx b/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx index fd346e632d..0cc905ae06 100644 --- a/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx +++ b/web/app/components/workflow/nodes/assigner/components/operation-selector.tsx @@ -72,7 +72,7 @@ const OperationSelector: FC<OperationSelectorProps> = ({ className={`system-sm-regular overflow-hidden truncate text-ellipsis ${selectedItem ? 'text-components-input-text-filled' : 'text-components-input-text-disabled'}`} > - {selectedItem?.name ? t(`${i18nPrefix}.operations.${selectedItem?.name}`) : t(`${i18nPrefix}.operations.title`)} + {selectedItem?.name ? t(`${i18nPrefix}.operations.${selectedItem?.name}` as any) as string : t(`${i18nPrefix}.operations.title` as any) as string} </span> </div> <RiArrowDownSLine className={`h-4 w-4 text-text-quaternary ${disabled && 'text-components-input-text-placeholder'} ${open && 'text-text-secondary'}`} /> @@ -83,7 +83,7 @@ const OperationSelector: FC<OperationSelectorProps> = ({ <div className="flex w-[140px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg"> <div className="flex flex-col items-start self-stretch p-1"> <div className="flex items-start self-stretch px-3 pb-0.5 pt-1"> - <div className="system-xs-medium-uppercase flex grow text-text-tertiary">{t(`${i18nPrefix}.operations.title`)}</div> + <div className="system-xs-medium-uppercase flex grow text-text-tertiary">{t(`${i18nPrefix}.operations.title` as any) as string}</div> </div> {items.map(item => ( item.value === 'divider' @@ -100,7 +100,7 @@ const OperationSelector: FC<OperationSelectorProps> = ({ }} > <div className="flex min-h-5 grow items-center gap-1 px-1"> - <span className="system-sm-medium flex grow text-text-secondary">{t(`${i18nPrefix}.operations.${item.name}`)}</span> + <span className="system-sm-medium flex grow text-text-secondary">{t(`${i18nPrefix}.operations.${item.name}` as any) as string}</span> </div> {item.value === value && ( <div className="flex items-center justify-center"> diff --git a/web/app/components/workflow/nodes/assigner/node.tsx b/web/app/components/workflow/nodes/assigner/node.tsx index be30104242..3eb9f0d620 100644 --- a/web/app/components/workflow/nodes/assigner/node.tsx +++ b/web/app/components/workflow/nodes/assigner/node.tsx @@ -73,7 +73,7 @@ const NodeComponent: FC<NodeProps<AssignerNodeType>> = ({ nodeType={node?.data.type} nodeTitle={node?.data.title} rightSlot={ - writeMode && <Badge className="!ml-auto shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}`)} /> + writeMode && <Badge className="!ml-auto shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}` as any) as string} /> } /> </div> diff --git a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx index 53df68c337..0bb2245c71 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-files-list-value.tsx @@ -34,7 +34,7 @@ const ConditionValue = ({ const variableSelector = variable_selector as ValueSelector - const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator + const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}` as any) as string : operator const formatValue = useCallback((c: Condition) => { const notHasValue = comparisonOperatorNotRequireValue(c.comparison_operator) if (notHasValue) @@ -59,7 +59,7 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(c.value) ? c.value[0] : c.value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + ? (t(`workflow.nodes.ifElse.optionName.${name.i18nKey}` as any) as string).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -91,9 +91,9 @@ const ConditionValue = ({ sub_variable_condition?.conditions.map((c: Condition, index) => ( <div className="relative flex h-6 items-center space-x-1" key={c.id}> <div className="system-xs-medium text-text-accent">{c.key}</div> - <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> + <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}` as any) as string : c.comparison_operator}</div> {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className="system-xs-regular text-text-secondary">{isSelect(c) ? selectName(c) : formatValue(c)}</div>} - {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} + {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}` as any) as string}</div>)} </div> )) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx index b323e65066..f49b8ef2fc 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx @@ -168,13 +168,13 @@ const ConditionItem = ({ if (isSelect) { if (fileAttr?.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) { return FILE_TYPE_OPTIONS.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } if (fileAttr?.key === 'transfer_method') { return TRANSFER_METHOD.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx index e2753ba6e7..64c6d35d8f 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx @@ -39,7 +39,7 @@ const ConditionOperator = ({ const options = useMemo(() => { return getOperators(varType, file).map((o) => { return { - label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o, + label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}` as any) as string : o, value: o, } }) @@ -65,7 +65,7 @@ const ConditionOperator = ({ { selectedOption ? selectedOption.label - : t(`${i18nPrefix}.select`) + : t(`${i18nPrefix}.select` as any) as string } <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx index 376c3a670f..c0cd78e4c5 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -35,7 +35,7 @@ const ConditionValue = ({ const { t } = useTranslation() const nodes = useNodes() const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.')) - const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator + const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}` as any) as string : operator const notHasValue = comparisonOperatorNotRequireValue(operator) const node: Node<CommonNodeType> | undefined = nodes.find(n => n.id === variableSelector[0]) as Node<CommonNodeType> const isException = isExceptionVariable(variableName, node?.data.type) @@ -63,7 +63,7 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(value) ? value[0] : value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + ? (t(`workflow.nodes.ifElse.optionName.${name.i18nKey}` as any) as string).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` diff --git a/web/app/components/workflow/nodes/iteration/use-interactions.ts b/web/app/components/workflow/nodes/iteration/use-interactions.ts index c6fddff5ad..87a4b8ad5d 100644 --- a/web/app/components/workflow/nodes/iteration/use-interactions.ts +++ b/web/app/components/workflow/nodes/iteration/use-interactions.ts @@ -135,7 +135,7 @@ export const useNodeIterationInteractions = () => { _isBundled: false, _connectedSourceHandleIds: [], _connectedTargetHandleIds: [], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}`)} ${childNodeTypeCount[childNodeType]}` : t(`workflow.blocks.${childNodeType}`), + title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}` as any) as string} ${childNodeTypeCount[childNodeType]}` : t(`workflow.blocks.${childNodeType}` as any) as string, iteration_id: newNodeId, type: childNodeType, }, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx index b3f2701524..5c764cba28 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx @@ -121,7 +121,7 @@ const DatasetItem: FC<Props> = ({ payload.provider === 'external' && ( <Badge className="shrink-0 group-hover/dataset-item:hidden" - text={t('dataset.externalTag') as string} + text={t('dataset.externalTag')} /> ) } diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx index 8f0430b655..d248d96dc0 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-operator.tsx @@ -42,7 +42,7 @@ const ConditionOperator = ({ const options = useMemo(() => { return getOperators(variableType).map((o) => { return { - label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o, + label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}` as any) as string : o, value: o, } }) @@ -68,7 +68,7 @@ const ConditionOperator = ({ { selectedOption ? selectedOption.label - : t(`${i18nPrefix}.select`) + : t(`${i18nPrefix}.select` as any) as string } <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> diff --git a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx index 8dd817a5ad..1ca931d32c 100644 --- a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx @@ -66,13 +66,13 @@ const FilterCondition: FC<Props> = ({ if (isSelect) { if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) { return FILE_TYPE_OPTIONS.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } if (condition.key === 'transfer_method') { return TRANSFER_METHOD.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index 776ad6804c..64d7a24a53 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -121,7 +121,7 @@ const ConfigPromptItem: FC<Props> = ({ <Tooltip popupContent={ - <div className="max-w-[180px]">{t(`${i18nPrefix}.roleDescription.${payload.role}`)}</div> + <div className="max-w-[180px]">{t(`${i18nPrefix}.roleDescription.${payload.role}` as any) as string}</div> } triggerClassName="w-4 h-4" /> diff --git a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx index e13832ed46..dfe16902fd 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-files-list-value.tsx @@ -34,7 +34,7 @@ const ConditionValue = ({ const variableSelector = variable_selector as ValueSelector - const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator + const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}` as any) as string : operator const formatValue = useCallback((c: Condition) => { const notHasValue = comparisonOperatorNotRequireValue(c.comparison_operator) if (notHasValue) @@ -59,7 +59,7 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(c.value) ? c.value[0] : c.value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + ? (t(`workflow.nodes.ifElse.optionName.${name.i18nKey}` as any) as string).replace(/\{\{#([^#]*)#\}\}/g, (a: string, b: string) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` @@ -91,9 +91,9 @@ const ConditionValue = ({ sub_variable_condition?.conditions.map((c: Condition, index) => ( <div className="relative flex h-6 items-center space-x-1" key={c.id}> <div className="system-xs-medium text-text-accent">{c.key}</div> - <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}`) : c.comparison_operator}</div> + <div className="system-xs-medium text-text-primary">{isComparisonOperatorNeedTranslate(c.comparison_operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${c.comparison_operator}` as any) as string : c.comparison_operator}</div> {c.comparison_operator && !isEmptyRelatedOperator(c.comparison_operator) && <div className="system-xs-regular text-text-secondary">{isSelect(c) ? selectName(c) : formatValue(c)}</div>} - {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}`)}</div>)} + {index !== sub_variable_condition.conditions.length - 1 && (<div className="absolute bottom-[-10px] right-1 z-10 text-[10px] font-medium uppercase leading-4 text-text-accent">{t(`${i18nPrefix}.${sub_variable_condition.logical_operator}` as any) as string}</div>)} </div> )) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx index ea3e2ef5be..95e7b58dd0 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-item.tsx @@ -145,13 +145,13 @@ const ConditionItem = ({ if (isSelect) { if (fileAttr?.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) { return FILE_TYPE_OPTIONS.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } if (fileAttr?.key === 'transfer_method') { return TRANSFER_METHOD.map(item => ({ - name: t(`${optionNameI18NPrefix}.${item.i18nKey}`), + name: t(`${optionNameI18NPrefix}.${item.i18nKey}` as any) as string, value: item.value, })) } diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx index a33b2b7727..9943109c2b 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-operator.tsx @@ -39,7 +39,7 @@ const ConditionOperator = ({ const options = useMemo(() => { return getOperators(varType, file).map((o) => { return { - label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o, + label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}` as any) as string : o, value: o, } }) @@ -65,7 +65,7 @@ const ConditionOperator = ({ { selectedOption ? selectedOption.label - : t(`${i18nPrefix}.select`) + : t(`${i18nPrefix}.select` as any) as string } <RiArrowDownSLine className="ml-1 h-3.5 w-3.5" /> </Button> diff --git a/web/app/components/workflow/nodes/loop/components/condition-value.tsx b/web/app/components/workflow/nodes/loop/components/condition-value.tsx index c24a1a18a6..10fa2cef42 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-value.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-value.tsx @@ -27,7 +27,7 @@ const ConditionValue = ({ value, }: ConditionValueProps) => { const { t } = useTranslation() - const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator + const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}` as any) as string : operator const notHasValue = comparisonOperatorNotRequireValue(operator) const formatValue = useMemo(() => { if (notHasValue) @@ -50,7 +50,7 @@ const ConditionValue = ({ if (isSelect) { const name = [...FILE_TYPE_OPTIONS, ...TRANSFER_METHOD].filter(item => item.value === (Array.isArray(value) ? value[0] : value))[0] return name - ? t(`workflow.nodes.ifElse.optionName.${name.i18nKey}`).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { + ? (t(`workflow.nodes.ifElse.optionName.${name.i18nKey}` as any) as string).replace(/\{\{#([^#]*)#\}\}/g, (a, b) => { const arr: string[] = b.split('.') if (isSystemVar(arr)) return `{{${b}}}` diff --git a/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx b/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx index 973e78ae73..949c53ca97 100644 --- a/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx +++ b/web/app/components/workflow/nodes/loop/components/loop-variables/item.tsx @@ -30,7 +30,7 @@ const Item = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: t('workflow.env.modal.name') }) as string, }) return false } diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx index 288e486ea7..61921296d4 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx @@ -56,7 +56,7 @@ const AddExtractParameter: FC<Props> = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }) as string, }) return } diff --git a/web/app/components/workflow/nodes/start/components/var-list.tsx b/web/app/components/workflow/nodes/start/components/var-list.tsx index bda45ca5dd..4d3c6cd871 100644 --- a/web/app/components/workflow/nodes/start/components/var-list.tsx +++ b/web/app/components/workflow/nodes/start/components/var-list.tsx @@ -45,7 +45,7 @@ const VarList: FC<Props> = ({ if (errorMsgKey) { Toast.notify({ type: 'error', - message: t(errorMsgKey, { key: t(typeName) }), + message: t(errorMsgKey as any, { key: t(typeName as any) as string }) as string, }) return false } diff --git a/web/app/components/workflow/nodes/start/use-config.ts b/web/app/components/workflow/nodes/start/use-config.ts index 8eed650f98..e563e710ce 100644 --- a/web/app/components/workflow/nodes/start/use-config.ts +++ b/web/app/components/workflow/nodes/start/use-config.ts @@ -99,7 +99,7 @@ const useConfig = (id: string, payload: StartNodeType) => { if (errorMsgKey) { Toast.notify({ type: 'error', - message: t(errorMsgKey, { key: t(typeName) }), + message: t(errorMsgKey as any, { key: t(typeName as any) as string }) as string, }) return false } diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx index 7c1f4e8f9d..de0ac01cb6 100644 --- a/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx +++ b/web/app/components/workflow/nodes/trigger-schedule/components/mode-switcher.tsx @@ -15,12 +15,12 @@ const ModeSwitcher = ({ mode, onChange }: ModeSwitcherProps) => { const options = [ { Icon: RiCalendarLine, - text: t('workflow.nodes.triggerSchedule.mode.visual'), + text: t('workflow.nodes.triggerSchedule.modeVisual'), value: 'visual' as const, }, { Icon: RiCodeLine, - text: t('workflow.nodes.triggerSchedule.mode.cron'), + text: t('workflow.nodes.triggerSchedule.modeCron'), value: 'cron' as const, }, ] diff --git a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts index dec79b8eaf..03cc72b237 100644 --- a/web/app/components/workflow/nodes/trigger-webhook/use-config.ts +++ b/web/app/components/workflow/nodes/trigger-webhook/use-config.ts @@ -103,9 +103,9 @@ const useConfig = (id: string, payload: WebhookTriggerNodeType) => { if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: t('appDebug.variableConfig.varName'), - }), + }) as string, }) return false } diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx index d722c1d231..d1274ee65f 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx @@ -120,7 +120,7 @@ const NodeVariableItem = ({ {VariableIcon} {VariableName} </div> - {writeMode && <Badge className="shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}`)} />} + {writeMode && <Badge className="shrink-0" text={t(`${i18nPrefix}.operations.${writeMode}` as any) as string} />} </div> ) } diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx index 8fb1cfba61..f8b6298a9b 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx @@ -96,7 +96,7 @@ const VarGroupItem: FC<Props> = ({ if (!isValid) { Toast.notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: errorKey }) as string, }) return } diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx index 33e2e07376..aafeffb54a 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx @@ -127,7 +127,7 @@ const ChatVariableModal = ({ if (!isValid) { notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: t('workflow.env.modal.name') }) as string, }) return false } diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx index e253d6c27c..383e15f20b 100644 --- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx +++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx @@ -37,7 +37,7 @@ const VariableModal = ({ if (!isValid) { notify({ type: 'error', - message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }), + message: t(`appDebug.varKeyError.${errorMessageKey}` as any, { key: t('workflow.env.modal.name') }) as string, }) return false } diff --git a/web/app/components/workflow/run/loop-result-panel.tsx b/web/app/components/workflow/run/loop-result-panel.tsx index 8238be82f3..6e37657057 100644 --- a/web/app/components/workflow/run/loop-result-panel.tsx +++ b/web/app/components/workflow/run/loop-result-panel.tsx @@ -43,7 +43,7 @@ const LoopResultPanel: FC<Props> = ({ <div className={cn(!noWrap && 'shrink-0 ', 'px-4 pt-3')}> <div className="flex h-8 shrink-0 items-center justify-between"> <div className="system-xl-semibold truncate text-text-primary"> - {t(`${i18nPrefix}.testRunLoop`)} + {t(`${i18nPrefix}.testRunLoop` as any) as string} </div> <div className="ml-2 shrink-0 cursor-pointer p-1" onClick={onHide}> <RiCloseLine className="h-4 w-4 text-text-tertiary" /> diff --git a/web/app/forgot-password/ForgotPasswordForm.tsx b/web/app/forgot-password/ForgotPasswordForm.tsx index 2b50c1c452..f06b952b68 100644 --- a/web/app/forgot-password/ForgotPasswordForm.tsx +++ b/web/app/forgot-password/ForgotPasswordForm.tsx @@ -108,7 +108,7 @@ const ForgotPasswordForm = () => { {...register('email')} placeholder={t('login.emailPlaceholder') || ''} /> - {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}`)}</span>} + {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}` as any) as string}</span>} </div> </div> )} diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index 60de8e0501..f0290cca50 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -138,7 +138,7 @@ const InstallForm = () => { placeholder={t('login.emailPlaceholder') || ''} className="system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" /> - {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}`)}</span>} + {errors.email && <span className="text-sm text-red-400">{t(`${errors.email?.message}` as any) as string}</span>} </div> </div> @@ -154,7 +154,7 @@ const InstallForm = () => { className="system-sm-regular w-full appearance-none rounded-md border border-transparent bg-components-input-bg-normal px-3 py-[7px] text-components-input-text-filled caret-primary-600 outline-none placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs" /> </div> - {errors.name && <span className="text-sm text-red-400">{t(`${errors.name.message}`)}</span>} + {errors.name && <span className="text-sm text-red-400">{t(`${errors.name.message}` as any) as string}</span>} </div> <div className="mb-5"> diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index 9abd4366e1..e3bbe420bc 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -1,4 +1,5 @@ 'use client' +import type { Locale } from '@/i18n-config' import { RiAccountCircleLine } from '@remixicon/react' import { noop } from 'lodash-es' import Link from 'next/link' @@ -123,7 +124,7 @@ export default function InviteSettingsPage() { defaultValue={LanguagesSupported[0]} items={languages.filter(item => item.supported)} onSelect={(item) => { - setLanguage(item.value as string) + setLanguage(item.value as Locale) }} /> </div> diff --git a/web/hooks/use-format-time-from-now.ts b/web/hooks/use-format-time-from-now.ts index 09d8db7321..970a64e7d5 100644 --- a/web/hooks/use-format-time-from-now.ts +++ b/web/hooks/use-format-time-from-now.ts @@ -1,8 +1,8 @@ -import type { Locale } from '@/i18n-config' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { useCallback } from 'react' import { useI18N } from '@/context/i18n' +import { localeMap } from '@/i18n-config/language' import 'dayjs/locale/de' import 'dayjs/locale/es' import 'dayjs/locale/fa' @@ -26,30 +26,6 @@ import 'dayjs/locale/zh-tw' dayjs.extend(relativeTime) -const localeMap: Record<Locale, string> = { - 'en-US': 'en', - 'zh-Hans': 'zh-cn', - 'zh-Hant': 'zh-tw', - 'pt-BR': 'pt-br', - 'es-ES': 'es', - 'fr-FR': 'fr', - 'de-DE': 'de', - 'ja-JP': 'ja', - 'ko-KR': 'ko', - 'ru-RU': 'ru', - 'it-IT': 'it', - 'th-TH': 'th', - 'id-ID': 'id', - 'uk-UA': 'uk', - 'vi-VN': 'vi', - 'ro-RO': 'ro', - 'pl-PL': 'pl', - 'hi-IN': 'hi', - 'tr-TR': 'tr', - 'fa-IR': 'fa', - 'sl-SI': 'sl', -} - export const useFormatTimeFromNow = () => { const { locale } = useI18N() const formatTimeFromNow = useCallback((time: number) => { diff --git a/web/hooks/use-knowledge.ts b/web/hooks/use-knowledge.ts index 400d9722de..e3c2cd49d1 100644 --- a/web/hooks/use-knowledge.ts +++ b/web/hooks/use-knowledge.ts @@ -5,14 +5,14 @@ export const useKnowledge = () => { const { t } = useTranslation() const formatIndexingTechnique = useCallback((indexingTechnique: string) => { - return t(`dataset.indexingTechnique.${indexingTechnique}`) + return t(`dataset.indexingTechnique.${indexingTechnique}` as any) as string }, [t]) const formatIndexingMethod = useCallback((indexingMethod: string, isEco?: boolean) => { if (isEco) return t('dataset.indexingMethod.invertedIndex') - return t(`dataset.indexingMethod.${indexingMethod}`) + return t(`dataset.indexingMethod.${indexingMethod}` as any) as string }, [t]) const formatIndexingTechniqueAndMethod = useCallback((indexingTechnique: string, indexingMethod: string) => { diff --git a/web/hooks/use-metadata.ts b/web/hooks/use-metadata.ts index a51e6b150e..6b0946b68d 100644 --- a/web/hooks/use-metadata.ts +++ b/web/hooks/use-metadata.ts @@ -86,7 +86,7 @@ export const useMetadataMap = (): MetadataMap => { }, 'volume/issue/page_numbers': { label: t(`${fieldPrefix}.paper.volumeIssuePage`) }, 'doi': { label: t(`${fieldPrefix}.paper.DOI`) }, - 'topic/keywords': { label: t(`${fieldPrefix}.paper.topicKeywords`) }, + 'topic/keywords': { label: t(`${fieldPrefix}.paper.topicKeywords` as any) as string }, 'abstract': { label: t(`${fieldPrefix}.paper.abstract`), inputType: 'textarea', @@ -160,7 +160,7 @@ export const useMetadataMap = (): MetadataMap => { 'end_date': { label: t(`${fieldPrefix}.IMChat.endDate`) }, 'participants': { label: t(`${fieldPrefix}.IMChat.participants`) }, 'topicKeywords': { - label: t(`${fieldPrefix}.IMChat.topicKeywords`), + label: t(`${fieldPrefix}.IMChat.topicKeywords` as any) as string, inputType: 'textarea', }, 'fileType': { label: t(`${fieldPrefix}.IMChat.fileType`) }, @@ -193,7 +193,7 @@ export const useMetadataMap = (): MetadataMap => { allowEdit: false, subFieldsMap: { 'title': { label: t(`${fieldPrefix}.notion.title`) }, - 'language': { label: t(`${fieldPrefix}.notion.lang`), inputType: 'select' }, + 'language': { label: t(`${fieldPrefix}.notion.lang` as any) as string, inputType: 'select' }, 'author/creator': { label: t(`${fieldPrefix}.notion.author`) }, 'creation_date': { label: t(`${fieldPrefix}.notion.createdTime`) }, 'last_modified_date': { @@ -201,7 +201,7 @@ export const useMetadataMap = (): MetadataMap => { }, 'notion_page_link': { label: t(`${fieldPrefix}.notion.url`) }, 'category/tags': { label: t(`${fieldPrefix}.notion.tag`) }, - 'description': { label: t(`${fieldPrefix}.notion.desc`) }, + 'description': { label: t(`${fieldPrefix}.notion.desc` as any) as string }, }, }, synced_from_github: { @@ -241,7 +241,7 @@ export const useMetadataMap = (): MetadataMap => { }, 'data_source_type': { label: t(`${fieldPrefix}.originInfo.source`), - render: value => t(`datasetDocuments.metadata.source.${value === 'notion_import' ? 'notion' : value}`), + render: value => t(`datasetDocuments.metadata.source.${value === 'notion_import' ? 'notion' : value}` as any) as string, }, }, }, @@ -323,7 +323,7 @@ export const useLanguages = () => { cs: t(`${langPrefix}cs`), th: t(`${langPrefix}th`), id: t(`${langPrefix}id`), - ro: t(`${langPrefix}ro`), + ro: t(`${langPrefix}ro` as any) as string, } } diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md index 0fe8922345..c724d94aa7 100644 --- a/web/i18n-config/README.md +++ b/web/i18n-config/README.md @@ -55,28 +55,9 @@ cp -r en-US id-ID 1. Add type to new language in the `language.ts` file. -```typescript -export type I18nText = { - 'en-US': string - 'zh-Hans': string - 'pt-BR': string - 'es-ES': string - 'fr-FR': string - 'de-DE': string - 'ja-JP': string - 'ko-KR': string - 'ru-RU': string - 'it-IT': string - 'uk-UA': string - 'id-ID': string - 'tr-TR': string - 'fa-IR': string - 'ar-TN': string - 'YOUR_LANGUAGE_CODE': string -} -``` +> Note: `I18nText` type is now automatically derived from `LanguagesSupported`, so you don't need to manually add types. -4. Add the new language to the `language.json` file. +4. Add the new language to the `languages.ts` file. ```typescript export const languages = [ @@ -189,11 +170,10 @@ We have a list of languages that we support in the `language.ts` file. But some ## Utility scripts -- Auto-fill translations: `pnpm run auto-gen-i18n -- --file app common --lang zh-Hans ja-JP [--dry-run]` +- Auto-fill translations: `pnpm run auto-gen-i18n --file app common --lang zh-Hans ja-JP [--dry-run]` - Use space-separated values; repeat `--file` / `--lang` as needed. Defaults to all en-US files and all supported locales except en-US. - Protects placeholders (`{{var}}`, `${var}`, `<tag>`) before translation and restores them after. -- Check missing/extra keys: `pnpm run check-i18n -- --file app billing --lang zh-Hans [--auto-remove]` +- Check missing/extra keys: `pnpm run check-i18n --file app billing --lang zh-Hans [--auto-remove]` - Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys (CI will fail); `--auto-remove` deletes extra keys automatically. -- Generate types: `pnpm run gen:i18n-types`; verify sync: `pnpm run check:i18n-types`. -Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on en-US changes to main; `.github/workflows/web-tests.yml` checks i18n keys and type sync on web changes. +Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on en-US changes to main; `.github/workflows/web-tests.yml` checks i18n keys on web changes. diff --git a/web/i18n-config/auto-gen-i18n.js b/web/i18n-config/auto-gen-i18n.js index 561fa95869..6c8cb05bbd 100644 --- a/web/i18n-config/auto-gen-i18n.js +++ b/web/i18n-config/auto-gen-i18n.js @@ -6,11 +6,11 @@ import vm from 'node:vm' import { translate } from 'bing-translate-api' import { generateCode, loadFile, parseModule } from 'magicast' import { transpile } from 'typescript' +import data from './languages' const require = createRequire(import.meta.url) const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -const data = require('./languages.json') const targetLanguage = 'en-US' const i18nFolder = '../i18n' // Path to i18n folder relative to this script @@ -117,8 +117,8 @@ Options: -h, --help Show help Examples: - pnpm run auto-gen-i18n -- --file app common --lang zh-Hans ja-JP - pnpm run auto-gen-i18n -- --dry-run + pnpm run auto-gen-i18n --file app common --lang zh-Hans ja-JP + pnpm run auto-gen-i18n --dry-run `) } diff --git a/web/i18n-config/check-i18n-sync.js b/web/i18n-config/check-i18n-sync.js deleted file mode 100644 index af00d23875..0000000000 --- a/web/i18n-config/check-i18n-sync.js +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node - -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import lodash from 'lodash' -import ts from 'typescript' - -const { camelCase } = lodash - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -// Import the NAMESPACES array from i18next-config.ts -function getNamespacesFromConfig() { - const configPath = path.join(__dirname, 'i18next-config.ts') - const configContent = fs.readFileSync(configPath, 'utf8') - const sourceFile = ts.createSourceFile(configPath, configContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) - - const namespaces = [] - - const visit = (node) => { - if ( - ts.isVariableDeclaration(node) - && node.name.getText() === 'NAMESPACES' - && node.initializer - && ts.isArrayLiteralExpression(node.initializer) - ) { - node.initializer.elements.forEach((el) => { - if (ts.isStringLiteral(el)) - namespaces.push(el.text) - }) - } - ts.forEachChild(node, visit) - } - - visit(sourceFile) - - if (!namespaces.length) - throw new Error('Could not find NAMESPACES array in i18next-config.ts') - - return namespaces -} - -function getNamespacesFromTypes() { - const typesPath = path.join(__dirname, '../types/i18n.d.ts') - - if (!fs.existsSync(typesPath)) { - return null - } - - const typesContent = fs.readFileSync(typesPath, 'utf8') - - // Extract namespaces from Messages type - const messagesMatch = typesContent.match(/export type Messages = \{([\s\S]*?)\}/) - if (!messagesMatch) { - return null - } - - // Parse the properties - const propertiesStr = messagesMatch[1] - const properties = propertiesStr - .split('\n') - .map(line => line.trim()) - .filter(line => line.includes(':')) - .map(line => line.split(':')[0].trim()) - .filter(prop => prop.length > 0) - - return properties -} - -function main() { - try { - console.log('🔍 Checking i18n types synchronization...') - - // Get namespaces from config - const configNamespaces = getNamespacesFromConfig() - console.log(`📦 Found ${configNamespaces.length} namespaces in config`) - - // Convert to camelCase for comparison - const configCamelCase = configNamespaces.map(ns => camelCase(ns)).sort() - - // Get namespaces from type definitions - const typeNamespaces = getNamespacesFromTypes() - - if (!typeNamespaces) { - console.error('❌ Type definitions file not found or invalid') - console.error(' Run: pnpm run gen:i18n-types') - process.exit(1) - } - - console.log(`🔧 Found ${typeNamespaces.length} namespaces in types`) - - const typeCamelCase = typeNamespaces.sort() - - // Compare arrays - const configSet = new Set(configCamelCase) - const typeSet = new Set(typeCamelCase) - - // Find missing in types - const missingInTypes = configCamelCase.filter(ns => !typeSet.has(ns)) - - // Find extra in types - const extraInTypes = typeCamelCase.filter(ns => !configSet.has(ns)) - - let hasErrors = false - - if (missingInTypes.length > 0) { - hasErrors = true - console.error('❌ Missing in type definitions:') - missingInTypes.forEach(ns => console.error(` - ${ns}`)) - } - - if (extraInTypes.length > 0) { - hasErrors = true - console.error('❌ Extra in type definitions:') - extraInTypes.forEach(ns => console.error(` - ${ns}`)) - } - - if (hasErrors) { - console.error('\n💡 To fix synchronization issues:') - console.error(' Run: pnpm run gen:i18n-types') - process.exit(1) - } - - console.log('✅ i18n types are synchronized') - } - catch (error) { - console.error('❌ Error:', error.message) - process.exit(1) - } -} - -main() diff --git a/web/i18n-config/check-i18n.js b/web/i18n-config/check-i18n.js index d69885e6f0..d70564556c 100644 --- a/web/i18n-config/check-i18n.js +++ b/web/i18n-config/check-i18n.js @@ -4,13 +4,13 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import vm from 'node:vm' import { transpile } from 'typescript' +import data from './languages' const require = createRequire(import.meta.url) const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const targetLanguage = 'en-US' -const data = require('./languages.json') const languages = data.languages.filter(language => language.supported).map(language => language.value) @@ -103,8 +103,8 @@ Options: -h, --help Show help Examples: - pnpm run check-i18n -- --file app billing --lang zh-Hans ja-JP - pnpm run check-i18n -- --auto-remove + pnpm run check-i18n --file app billing --lang zh-Hans ja-JP + pnpm run check-i18n --auto-remove `) } diff --git a/web/i18n-config/generate-i18n-types.js b/web/i18n-config/generate-i18n-types.js deleted file mode 100644 index 0b3c0195af..0000000000 --- a/web/i18n-config/generate-i18n-types.js +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env node - -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import lodash from 'lodash' -import ts from 'typescript' - -const { camelCase } = lodash -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -// Import the NAMESPACES array from i18next-config.ts -function getNamespacesFromConfig() { - const configPath = path.join(__dirname, 'i18next-config.ts') - const configContent = fs.readFileSync(configPath, 'utf8') - const sourceFile = ts.createSourceFile(configPath, configContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS) - - const namespaces = [] - - const visit = (node) => { - if ( - ts.isVariableDeclaration(node) - && node.name.getText() === 'NAMESPACES' - && node.initializer - && ts.isArrayLiteralExpression(node.initializer) - ) { - node.initializer.elements.forEach((el) => { - if (ts.isStringLiteral(el)) - namespaces.push(el.text) - }) - } - ts.forEachChild(node, visit) - } - - visit(sourceFile) - - if (!namespaces.length) - throw new Error('Could not find NAMESPACES array in i18next-config.ts') - - return namespaces -} - -function generateTypeDefinitions(namespaces) { - const header = `// TypeScript type definitions for Dify's i18next configuration -// This file is auto-generated. Do not edit manually. -// To regenerate, run: pnpm run gen:i18n-types -import 'react-i18next' - -// Extract types from translation files using typeof import pattern` - - // Generate individual type definitions - const typeDefinitions = namespaces.map((namespace) => { - const typeName = `${camelCase(namespace).replace(/^\w/, c => c.toUpperCase())}Messages` - return `type ${typeName} = typeof import('../i18n/en-US/${namespace}').default` - }).join('\n') - - // Generate Messages interface - const messagesInterface = ` -// Complete type structure that matches i18next-config.ts camelCase conversion -export type Messages = { -${namespaces.map((namespace) => { - const camelCased = camelCase(namespace) - const typeName = `${camelCase(namespace).replace(/^\w/, c => c.toUpperCase())}Messages` - return ` ${camelCased}: ${typeName};` -}).join('\n')} -}` - - const utilityTypes = ` -// Utility type to flatten nested object keys into dot notation -type FlattenKeys<T> = T extends object - ? { - [K in keyof T]: T[K] extends object - ? \`\${K & string}.\${FlattenKeys<T[K]> & string}\` - : \`\${K & string}\` - }[keyof T] - : never - -export type ValidTranslationKeys = FlattenKeys<Messages>` - - const moduleDeclarations = ` -// Extend react-i18next with Dify's type structure -declare module 'react-i18next' { - interface CustomTypeOptions { - defaultNS: 'translation'; - resources: { - translation: Messages; - }; - } -} - -// Extend i18next for complete type safety -declare module 'i18next' { - interface CustomTypeOptions { - defaultNS: 'translation'; - resources: { - translation: Messages; - }; - } -}` - - return [header, typeDefinitions, messagesInterface, utilityTypes, moduleDeclarations].join('\n\n') -} - -function main() { - const args = process.argv.slice(2) - const checkMode = args.includes('--check') - - try { - console.log('📦 Generating i18n type definitions...') - - // Get namespaces from config - const namespaces = getNamespacesFromConfig() - console.log(`✅ Found ${namespaces.length} namespaces`) - - // Generate type definitions - const typeDefinitions = generateTypeDefinitions(namespaces) - - const outputPath = path.join(__dirname, '../types/i18n.d.ts') - - if (checkMode) { - // Check mode: compare with existing file - if (!fs.existsSync(outputPath)) { - console.error('❌ Type definitions file does not exist') - process.exit(1) - } - - const existingContent = fs.readFileSync(outputPath, 'utf8') - if (existingContent.trim() !== typeDefinitions.trim()) { - console.error('❌ Type definitions are out of sync') - console.error(' Run: pnpm run gen:i18n-types') - process.exit(1) - } - - console.log('✅ Type definitions are in sync') - } - else { - // Generate mode: write file - fs.writeFileSync(outputPath, typeDefinitions) - console.log(`✅ Generated type definitions: ${outputPath}`) - } - } - catch (error) { - console.error('❌ Error:', error.message) - process.exit(1) - } -} - -main() diff --git a/web/i18n-config/i18next-config.ts b/web/i18n-config/i18next-config.ts index b310d380e2..e82f5f2acb 100644 --- a/web/i18n-config/i18next-config.ts +++ b/web/i18n-config/i18next-config.ts @@ -1,10 +1,10 @@ 'use client' +import type { Locale } from '.' import i18n from 'i18next' -import { camelCase } from 'lodash-es' -import { initReactI18next } from 'react-i18next' +import { camelCase, kebabCase } from 'lodash-es' +import { initReactI18next } from 'react-i18next' import app from '../i18n/en-US/app' -// Static imports for en-US (fallback language) import appAnnotation from '../i18n/en-US/app-annotation' import appApi from '../i18n/en-US/app-api' import appDebug from '../i18n/en-US/app-debug' @@ -35,7 +35,56 @@ import time from '../i18n/en-US/time' import tools from '../i18n/en-US/tools' import workflow from '../i18n/en-US/workflow' -const requireSilent = async (lang: string, namespace: string) => { +// @keep-sorted +export const messagesEN = { + app, + appAnnotation, + appApi, + appDebug, + appLog, + appOverview, + billing, + common, + custom, + dataset, + datasetCreation, + datasetDocuments, + datasetHitTesting, + datasetPipeline, + datasetSettings, + education, + explore, + layout, + login, + oauth, + pipeline, + plugin, + pluginTags, + pluginTrigger, + register, + runLog, + share, + time, + tools, + workflow, +} + +// pluginTrigger -> plugin-trigger + +export type KebabCase<S extends string> = S extends `${infer T}${infer U}` + ? T extends Lowercase<T> + ? `${T}${KebabCase<U>}` + : `-${Lowercase<T>}${KebabCase<U>}` + : S + +export type CamelCase<S extends string> = S extends `${infer T}-${infer U}` + ? `${T}${Capitalize<CamelCase<U>>}` + : S + +export type KeyPrefix = keyof typeof messagesEN +export type Namespace = KebabCase<KeyPrefix> + +const requireSilent = async (lang: Locale, namespace: Namespace) => { let res try { res = (await import(`../i18n/${lang}/${namespace}`)).default @@ -47,40 +96,9 @@ const requireSilent = async (lang: string, namespace: string) => { return res } -const NAMESPACES = [ - 'app-annotation', - 'app-api', - 'app-debug', - 'app-log', - 'app-overview', - 'app', - 'billing', - 'common', - 'custom', - 'dataset-creation', - 'dataset-documents', - 'dataset-hit-testing', - 'dataset-pipeline', - 'dataset-settings', - 'dataset', - 'education', - 'explore', - 'layout', - 'login', - 'oauth', - 'pipeline', - 'plugin-tags', - 'plugin-trigger', - 'plugin', - 'register', - 'run-log', - 'share', - 'time', - 'tools', - 'workflow', -] +const NAMESPACES = Object.keys(messagesEN).map(kebabCase) as Namespace[] -export const loadLangResources = async (lang: string) => { +export const loadLangResources = async (lang: Locale) => { const modules = await Promise.all( NAMESPACES.map(ns => requireSilent(lang, ns)), ) @@ -93,41 +111,9 @@ export const loadLangResources = async (lang: string) => { // Load en-US resources first to make sure fallback works const getInitialTranslations = () => { - const en_USResources: Record<string, any> = { - appAnnotation, - appApi, - appDebug, - appLog, - appOverview, - app, - billing, - common, - custom, - datasetCreation, - datasetDocuments, - datasetHitTesting, - datasetPipeline, - datasetSettings, - dataset, - education, - explore, - layout, - login, - oauth, - pipeline, - pluginTags, - pluginTrigger, - plugin, - register, - runLog, - share, - time, - tools, - workflow, - } return { 'en-US': { - translation: en_USResources, + translation: messagesEN, }, } } @@ -140,7 +126,7 @@ if (!i18n.isInitialized) { }) } -export const changeLanguage = async (lng?: string) => { +export const changeLanguage = async (lng?: Locale) => { if (!lng) return if (!i18n.hasResourceBundle(lng, 'translation')) { diff --git a/web/i18n-config/index.ts b/web/i18n-config/index.ts index 8a0f712f2a..bb73ef4b71 100644 --- a/web/i18n-config/index.ts +++ b/web/i18n-config/index.ts @@ -1,5 +1,6 @@ -import Cookies from 'js-cookie' +import type { Locale } from '@/i18n-config/language' +import Cookies from 'js-cookie' import { LOCALE_COOKIE_NAME } from '@/config' import { changeLanguage } from '@/i18n-config/i18next-config' import { LanguagesSupported } from '@/i18n-config/language' @@ -9,7 +10,7 @@ export const i18n = { locales: LanguagesSupported, } as const -export type Locale = typeof i18n['locales'][number] +export { Locale } export const setLocaleOnClient = async (locale: Locale, reloadPage = true) => { Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 }) diff --git a/web/i18n-config/language.ts b/web/i18n-config/language.ts index a1fe6e790f..28afd9eabf 100644 --- a/web/i18n-config/language.ts +++ b/web/i18n-config/language.ts @@ -1,4 +1,4 @@ -import data from './languages.json' +import data from './languages' export type Item = { value: number | string @@ -6,40 +6,20 @@ export type Item = { example: string } -export type I18nText = { - 'en-US': string - 'zh-Hans': string - 'zh-Hant': string - 'pt-BR': string - 'es-ES': string - 'fr-FR': string - 'de-DE': string - 'ja-JP': string - 'ko-KR': string - 'ru-RU': string - 'it-IT': string - 'th-TH': string - 'id-ID': string - 'uk-UA': string - 'vi-VN': string - 'ro-RO': string - 'pl-PL': string - 'hi-IN': string - 'tr-TR': string - 'fa-IR': string - 'sl-SI': string - 'ar-TN': string -} +export type I18nText = Record<typeof LanguagesSupported[number], string> export const languages = data.languages -export const LanguagesSupported = languages.filter(item => item.supported).map(item => item.value) +// for compatibility +export type Locale = 'ja_JP' | 'zh_Hans' | 'en_US' | (typeof languages[number])['value'] -export const getLanguage = (locale: string) => { +export const LanguagesSupported: Locale[] = languages.filter(item => item.supported).map(item => item.value) + +export const getLanguage = (locale: Locale): Locale => { if (['zh-Hans', 'ja-JP'].includes(locale)) - return locale.replace('-', '_') + return locale.replace('-', '_') as Locale - return LanguagesSupported[0].replace('-', '_') + return LanguagesSupported[0].replace('-', '_') as Locale } const DOC_LANGUAGE: Record<string, string> = { @@ -48,6 +28,34 @@ const DOC_LANGUAGE: Record<string, string> = { 'en-US': 'en', } +export const localeMap: Record<Locale, string> = { + 'en-US': 'en', + 'en_US': 'en', + 'zh-Hans': 'zh-cn', + 'zh_Hans': 'zh-cn', + 'zh-Hant': 'zh-tw', + 'pt-BR': 'pt-br', + 'es-ES': 'es', + 'fr-FR': 'fr', + 'de-DE': 'de', + 'ja-JP': 'ja', + 'ja_JP': 'ja', + 'ko-KR': 'ko', + 'ru-RU': 'ru', + 'it-IT': 'it', + 'th-TH': 'th', + 'id-ID': 'id', + 'uk-UA': 'uk', + 'vi-VN': 'vi', + 'ro-RO': 'ro', + 'pl-PL': 'pl', + 'hi-IN': 'hi', + 'tr-TR': 'tr', + 'fa-IR': 'fa', + 'sl-SI': 'sl', + 'ar-TN': 'ar', +} + export const getDocLanguage = (locale: string) => { return DOC_LANGUAGE[locale] || 'en' } diff --git a/web/i18n-config/languages.json b/web/i18n-config/languages.json deleted file mode 100644 index 6e0025b8de..0000000000 --- a/web/i18n-config/languages.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "languages": [ - { - "value": "en-US", - "name": "English (United States)", - "prompt_name": "English", - "example": "Hello, Dify!", - "supported": true - }, - { - "value": "zh-Hans", - "name": "简体中文", - "prompt_name": "Chinese Simplified", - "example": "你好,Dify!", - "supported": true - }, - { - "value": "zh-Hant", - "name": "繁體中文", - "prompt_name": "Chinese Traditional", - "example": "你好,Dify!", - "supported": true - }, - { - "value": "pt-BR", - "name": "Português (Brasil)", - "prompt_name": "Portuguese", - "example": "Olá, Dify!", - "supported": true - }, - { - "value": "es-ES", - "name": "Español (España)", - "prompt_name": "Spanish", - "example": "¡Hola, Dify!", - "supported": true - }, - { - "value": "fr-FR", - "name": "Français (France)", - "prompt_name": "French", - "example": "Bonjour, Dify!", - "supported": true - }, - { - "value": "de-DE", - "name": "Deutsch (Deutschland)", - "prompt_name": "German", - "example": "Hallo, Dify!", - "supported": true - }, - { - "value": "ja-JP", - "name": "日本語 (日本)", - "prompt_name": "Japanese", - "example": "こんにちは、Dify!", - "supported": true - }, - { - "value": "ko-KR", - "name": "한국어 (대한민국)", - "prompt_name": "Korean", - "example": "안녕하세요, Dify!", - "supported": true - }, - { - "value": "ru-RU", - "name": "Русский (Россия)", - "prompt_name": "Russian", - "example": " Привет, Dify!", - "supported": true - }, - { - "value": "it-IT", - "name": "Italiano (Italia)", - "prompt_name": "Italian", - "example": "Ciao, Dify!", - "supported": true - }, - { - "value": "th-TH", - "name": "ไทย (ประเทศไทย)", - "prompt_name": "Thai", - "example": "สวัสดี Dify!", - "supported": true - }, - { - "value": "uk-UA", - "name": "Українська (Україна)", - "prompt_name": "Ukrainian", - "example": "Привет, Dify!", - "supported": true - }, - { - "value": "vi-VN", - "name": "Tiếng Việt (Việt Nam)", - "prompt_name": "Vietnamese", - "example": "Xin chào, Dify!", - "supported": true - }, - { - "value": "ro-RO", - "name": "Română (România)", - "prompt_name": "Romanian", - "example": "Salut, Dify!", - "supported": true - }, - { - "value": "pl-PL", - "name": "Polski (Polish)", - "prompt_name": "Polish", - "example": "Cześć, Dify!", - "supported": true - }, - { - "value": "hi-IN", - "name": "Hindi (India)", - "prompt_name": "Hindi", - "example": "नमस्ते, Dify!", - "supported": true - }, - { - "value": "tr-TR", - "name": "Türkçe", - "prompt_name": "Türkçe", - "example": "Selam!", - "supported": true - }, - { - "value": "fa-IR", - "name": "Farsi (Iran)", - "prompt_name": "Farsi", - "example": "سلام, دیفای!", - "supported": true - }, - { - "value": "sl-SI", - "name": "Slovensko (Slovenija)", - "prompt_name": "Slovensko", - "example": "Zdravo, Dify!", - "supported": true - }, - { - "value": "id-ID", - "name": "Bahasa Indonesia", - "prompt_name": "Indonesian", - "example": "Halo, Dify!", - "supported": true - }, - { - "value": "ar-TN", - "name": "العربية (تونس)", - "prompt_name": "Tunisian Arabic", - "example": "مرحبا، Dify!", - "supported": true - } - ] -} diff --git a/web/i18n-config/languages.ts b/web/i18n-config/languages.ts new file mode 100644 index 0000000000..5077aee1d2 --- /dev/null +++ b/web/i18n-config/languages.ts @@ -0,0 +1,160 @@ +const data = { + languages: [ + { + value: 'en-US', + name: 'English (United States)', + prompt_name: 'English', + example: 'Hello, Dify!', + supported: true, + }, + { + value: 'zh-Hans', + name: '简体中文', + prompt_name: 'Chinese Simplified', + example: '你好,Dify!', + supported: true, + }, + { + value: 'zh-Hant', + name: '繁體中文', + prompt_name: 'Chinese Traditional', + example: '你好,Dify!', + supported: true, + }, + { + value: 'pt-BR', + name: 'Português (Brasil)', + prompt_name: 'Portuguese', + example: 'Olá, Dify!', + supported: true, + }, + { + value: 'es-ES', + name: 'Español (España)', + prompt_name: 'Spanish', + example: '¡Hola, Dify!', + supported: true, + }, + { + value: 'fr-FR', + name: 'Français (France)', + prompt_name: 'French', + example: 'Bonjour, Dify!', + supported: true, + }, + { + value: 'de-DE', + name: 'Deutsch (Deutschland)', + prompt_name: 'German', + example: 'Hallo, Dify!', + supported: true, + }, + { + value: 'ja-JP', + name: '日本語 (日本)', + prompt_name: 'Japanese', + example: 'こんにちは、Dify!', + supported: true, + }, + { + value: 'ko-KR', + name: '한국어 (대한민국)', + prompt_name: 'Korean', + example: '안녕하세요, Dify!', + supported: true, + }, + { + value: 'ru-RU', + name: 'Русский (Россия)', + prompt_name: 'Russian', + example: ' Привет, Dify!', + supported: true, + }, + { + value: 'it-IT', + name: 'Italiano (Italia)', + prompt_name: 'Italian', + example: 'Ciao, Dify!', + supported: true, + }, + { + value: 'th-TH', + name: 'ไทย (ประเทศไทย)', + prompt_name: 'Thai', + example: 'สวัสดี Dify!', + supported: true, + }, + { + value: 'uk-UA', + name: 'Українська (Україна)', + prompt_name: 'Ukrainian', + example: 'Привет, Dify!', + supported: true, + }, + { + value: 'vi-VN', + name: 'Tiếng Việt (Việt Nam)', + prompt_name: 'Vietnamese', + example: 'Xin chào, Dify!', + supported: true, + }, + { + value: 'ro-RO', + name: 'Română (România)', + prompt_name: 'Romanian', + example: 'Salut, Dify!', + supported: true, + }, + { + value: 'pl-PL', + name: 'Polski (Polish)', + prompt_name: 'Polish', + example: 'Cześć, Dify!', + supported: true, + }, + { + value: 'hi-IN', + name: 'Hindi (India)', + prompt_name: 'Hindi', + example: 'नमस्ते, Dify!', + supported: true, + }, + { + value: 'tr-TR', + name: 'Türkçe', + prompt_name: 'Türkçe', + example: 'Selam!', + supported: true, + }, + { + value: 'fa-IR', + name: 'Farsi (Iran)', + prompt_name: 'Farsi', + example: 'سلام, دیفای!', + supported: true, + }, + { + value: 'sl-SI', + name: 'Slovensko (Slovenija)', + prompt_name: 'Slovensko', + example: 'Zdravo, Dify!', + supported: true, + }, + { + value: 'id-ID', + name: 'Bahasa Indonesia', + prompt_name: 'Indonesian', + example: 'Halo, Dify!', + supported: true, + }, + { + value: 'ar-TN', + name: 'العربية (تونس)', + prompt_name: 'Tunisian Arabic', + example: 'مرحبا، Dify!', + supported: true, + }, + ], +} as const + +export default data diff --git a/web/i18n-config/server.ts b/web/i18n-config/server.ts index c4e008cf84..75a5f3943b 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n-config/server.ts @@ -1,19 +1,20 @@ import type { Locale } from '.' +import type { KeyPrefix, Namespace } from './i18next-config' import { match } from '@formatjs/intl-localematcher' import { createInstance } from 'i18next' - import resourcesToBackend from 'i18next-resources-to-backend' +import { camelCase } from 'lodash-es' import Negotiator from 'negotiator' import { cookies, headers } from 'next/headers' import { initReactI18next } from 'react-i18next/initReactI18next' import { i18n } from '.' // https://locize.com/blog/next-13-app-dir-i18n/ -const initI18next = async (lng: Locale, ns: string) => { +const initI18next = async (lng: Locale, ns: Namespace) => { const i18nInstance = createInstance() await i18nInstance .use(initReactI18next) - .use(resourcesToBackend((language: string, namespace: string) => import(`../i18n/${language}/${namespace}.ts`))) + .use(resourcesToBackend((language: Locale, namespace: Namespace) => import(`../i18n/${language}/${namespace}.ts`))) .init({ lng: lng === 'zh-Hans' ? 'zh-Hans' : lng, ns, @@ -22,10 +23,10 @@ const initI18next = async (lng: Locale, ns: string) => { return i18nInstance } -export async function useTranslation(lng: Locale, ns = '', options: Record<string, any> = {}) { +export async function getTranslation(lng: Locale, ns: Namespace) { const i18nextInstance = await initI18next(lng, ns) return { - t: i18nextInstance.getFixedT(lng, ns, options.keyPrefix), + t: i18nextInstance.getFixedT(lng, 'translation', camelCase(ns) as KeyPrefix), i18n: i18nextInstance, } } diff --git a/web/i18n/en-US/app-api.ts b/web/i18n/en-US/app-api.ts index 1fba63c977..17f1a06782 100644 --- a/web/i18n/en-US/app-api.ts +++ b/web/i18n/en-US/app-api.ts @@ -79,6 +79,7 @@ const translation = { pathParams: 'Path Params', query: 'Query', toc: 'Contents', + noContent: 'No content', }, } diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 815c6d9aeb..5604de3dd1 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -32,6 +32,9 @@ const translation = { cancelDisagree: 'Cancel dislike', userAction: 'User ', }, + code: { + instruction: 'Instruction', + }, notSetAPIKey: { title: 'LLM provider key has not been set', trailFinished: 'Trail finished', diff --git a/web/i18n/en-US/app.ts b/web/i18n/en-US/app.ts index 1f41d3601e..45ebd61aec 100644 --- a/web/i18n/en-US/app.ts +++ b/web/i18n/en-US/app.ts @@ -1,4 +1,9 @@ const translation = { + theme: { + switchDark: 'Switch to dark theme', + switchLight: 'Switch to light theme', + }, + appNamePlaceholder: 'Give your app a name', createApp: 'CREATE APP', types: { all: 'All', @@ -298,6 +303,7 @@ const translation = { commandHint: 'Type @ to browse by category', slashHint: 'Type / to see all available commands', actions: { + slashTitle: 'Commands', searchApplications: 'Search Applications', searchApplicationsDesc: 'Search and navigate to your applications', searchPlugins: 'Search Plugins', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 92d24b1351..2e378afeda 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -1,4 +1,6 @@ const translation = { + loading: 'Loading', + error: 'Error', theme: { theme: 'Theme', light: 'light', @@ -71,6 +73,8 @@ const translation = { saveAndRegenerate: 'Save & Regenerate Child Chunks', view: 'View', viewMore: 'VIEW MORE', + back: 'Back', + imageDownloaded: 'Image downloaded', regenerate: 'Regenerate', submit: 'Submit', skip: 'Skip', @@ -252,6 +256,7 @@ const translation = { feedbackPlaceholder: 'Optional', editWorkspaceInfo: 'Edit Workspace Info', workspaceName: 'Workspace Name', + workspaceNamePlaceholder: 'Enter workspace name', workspaceIcon: 'Workspace Icon', changeEmail: { title: 'Change Email', @@ -515,6 +520,7 @@ const translation = { emptyProviderTip: 'Please install a model provider first.', auth: { unAuthorized: 'Unauthorized', + credentialRemoved: 'Credential removed', authRemoved: 'Auth removed', apiKeys: 'API Keys', addApiKey: 'Add API Key', diff --git a/web/i18n/en-US/dataset.ts b/web/i18n/en-US/dataset.ts index 6ffd312fff..ee1997f699 100644 --- a/web/i18n/en-US/dataset.ts +++ b/web/i18n/en-US/dataset.ts @@ -245,6 +245,7 @@ const translation = { button: 'Drag and drop file or folder, or', browse: 'Browse', tip: '{{supportTypes}} (Max {{batchCount}}, {{size}}MB each)', + fileSizeLimitExceeded: 'File size exceeds the {{size}}MB limit', }, } diff --git a/web/i18n/en-US/login.ts b/web/i18n/en-US/login.ts index dd923db217..3b0c8bbba1 100644 --- a/web/i18n/en-US/login.ts +++ b/web/i18n/en-US/login.ts @@ -64,6 +64,7 @@ const translation = { passwordInvalid: 'Password must contain letters and numbers, and the length must be greater than 8', registrationNotAllowed: 'Account not found. Please contact the system admin to register.', invalidEmailOrPassword: 'Invalid email or password.', + redirectUrlMissing: 'Redirect URL is missing', }, license: { tip: 'Before starting Dify Community Edition, read the GitHub', diff --git a/web/i18n/en-US/plugin-trigger.ts b/web/i18n/en-US/plugin-trigger.ts index aedd0c6225..f1d697e507 100644 --- a/web/i18n/en-US/plugin-trigger.ts +++ b/web/i18n/en-US/plugin-trigger.ts @@ -158,6 +158,7 @@ const translation = { }, errors: { createFailed: 'Failed to create subscription', + updateFailed: 'Failed to update subscription', verifyFailed: 'Failed to verify credentials', authFailed: 'Authorization failed', networkError: 'Network error, please try again', diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 86c225c1b2..b2753a5721 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -14,6 +14,7 @@ const translation = { }, author: 'By', auth: { + unauthorized: 'Unauthorized', authorized: 'Authorized', setup: 'Set up authorization to use', setupModalTitle: 'Set Up Authorization', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index a023ac2b91..2122c20aaa 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -1106,6 +1106,9 @@ const translation = { lastDay: 'Last day', lastDayTooltip: 'Not all months have 31 days. Use the \'last day\' option to select each month\'s final day.', mode: 'Mode', + modeVisual: 'Visual', + modeCron: 'Cron', + selectTime: 'Select time', timezone: 'Timezone', visualConfig: 'Visual Configuration', monthlyDay: 'Monthly Day', diff --git a/web/i18n/ja-JP/app-api.ts b/web/i18n/ja-JP/app-api.ts index e344ad04a9..35203e53e0 100644 --- a/web/i18n/ja-JP/app-api.ts +++ b/web/i18n/ja-JP/app-api.ts @@ -78,6 +78,7 @@ const translation = { pathParams: 'パスパラメータ', query: 'クエリ', toc: '内容', + noContent: 'コンテンツなし', }, regenerate: '再生', } diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index 77d991974f..9d1deb47eb 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -32,6 +32,9 @@ const translation = { cancelDisagree: 'いいえをキャンセル', userAction: 'ユーザー', }, + code: { + instruction: '指示', + }, notSetAPIKey: { title: 'LLM プロバイダーキーが設定されていません', trailFinished: 'トライアル終了', diff --git a/web/i18n/ja-JP/app.ts b/web/i18n/ja-JP/app.ts index f084fc3b8c..899405e8e7 100644 --- a/web/i18n/ja-JP/app.ts +++ b/web/i18n/ja-JP/app.ts @@ -1,4 +1,9 @@ const translation = { + theme: { + switchDark: 'ダークテーマに切り替え', + switchLight: 'ライトテーマに切り替え', + }, + appNamePlaceholder: 'アプリに名前を付ける', createApp: 'アプリを作成する', types: { all: '全て', @@ -295,6 +300,7 @@ const translation = { commandHint: '@ を入力してカテゴリ別に参照', slashHint: '/ を入力してすべてのコマンドを表示', actions: { + slashTitle: 'コマンド', searchApplications: 'アプリケーションを検索', searchApplicationsDesc: 'アプリケーションを検索してナビゲート', searchPlugins: 'プラグインを検索', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index bde00cb66b..87d9fa1fb1 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -1,4 +1,6 @@ const translation = { + loading: '読み込み中', + error: 'エラー', theme: { theme: 'テーマ', light: '明るい', @@ -68,6 +70,8 @@ const translation = { selectAll: 'すべて選択', deSelectAll: 'すべて選択解除', now: '今', + back: '戻る', + imageDownloaded: '画像がダウンロードされました', config: 'コンフィグ', yes: 'はい', no: 'いいえ', @@ -248,6 +252,7 @@ const translation = { sendVerificationButton: '確認コードの送信', editWorkspaceInfo: 'ワークスペース情報を編集', workspaceName: 'ワークスペース名', + workspaceNamePlaceholder: 'ワークスペース名を入力', workspaceIcon: 'ワークスペースアイコン', changeEmail: { title: 'メールアドレスを変更', @@ -512,6 +517,7 @@ const translation = { authorizationError: '認証エラー', apiKeys: 'APIキー', unAuthorized: '無許可', + credentialRemoved: '認証情報が削除されました', configModel: 'モデルを構成する', addApiKey: 'APIキーを追加してください', addCredential: '認証情報を追加する', diff --git a/web/i18n/ja-JP/dataset.ts b/web/i18n/ja-JP/dataset.ts index a880dd4f5a..b6c4e1d40c 100644 --- a/web/i18n/ja-JP/dataset.ts +++ b/web/i18n/ja-JP/dataset.ts @@ -245,6 +245,7 @@ const translation = { button: 'ファイルまたはフォルダをドラッグアンドドロップ、または', browse: '閲覧', tip: '{{supportTypes}}(最大 {{batchCount}}、各 {{size}}MB)', + fileSizeLimitExceeded: 'ファイルサイズが {{size}}MB の制限を超えています', }, } diff --git a/web/i18n/ja-JP/login.ts b/web/i18n/ja-JP/login.ts index 7069315c9d..c9e0fe3e1e 100644 --- a/web/i18n/ja-JP/login.ts +++ b/web/i18n/ja-JP/login.ts @@ -57,6 +57,7 @@ const translation = { passwordInvalid: 'パスワードは文字と数字を含み、長さは 8 以上である必要があります', registrationNotAllowed: 'アカウントが見つかりません。登録するためにシステム管理者に連絡してください。', invalidEmailOrPassword: '無効なメールアドレスまたはパスワードです。', + redirectUrlMissing: 'リダイレクト URL が見つかりません', }, license: { tip: 'GitHub のオープンソースライセンスを確認してから、Dify Community Edition を開始してください。', diff --git a/web/i18n/ja-JP/plugin-trigger.ts b/web/i18n/ja-JP/plugin-trigger.ts index c7453cff42..7dbd861909 100644 --- a/web/i18n/ja-JP/plugin-trigger.ts +++ b/web/i18n/ja-JP/plugin-trigger.ts @@ -165,6 +165,7 @@ const translation = { verifyFailed: '認証情報の検証に失敗しました', authFailed: '認証に失敗しました', networkError: 'ネットワークエラーです。再試行してください', + updateFailed: 'サブスクリプションの更新に失敗しました', }, }, events: { diff --git a/web/i18n/ja-JP/tools.ts b/web/i18n/ja-JP/tools.ts index 30f623575f..41dc30ac30 100644 --- a/web/i18n/ja-JP/tools.ts +++ b/web/i18n/ja-JP/tools.ts @@ -15,6 +15,7 @@ const translation = { author: '著者:', auth: { authorized: '認証済み', + unauthorized: '未認証', setup: '使用するための認証を設定する', setupModalTitle: '認証の設定', setupModalTitleDescription: '資格情報を構成した後、ワークスペース内のすべてのメンバーがアプリケーションのオーケストレーション時にこのツールを使用できます。', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 7f4e7a3009..24f05d6c31 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -1062,6 +1062,9 @@ const translation = { useVisualPicker: 'ビジュアル設定を使用', nodeTitle: 'スケジュールトリガー', mode: 'モード', + modeVisual: 'ビジュアル', + modeCron: 'Cron', + selectTime: '時間を選択', timezone: 'タイムゾーン', visualConfig: 'ビジュアル設定', monthlyDay: '月の日', diff --git a/web/i18n/zh-Hans/app-api.ts b/web/i18n/zh-Hans/app-api.ts index 4fe97f8231..70219e0cc6 100644 --- a/web/i18n/zh-Hans/app-api.ts +++ b/web/i18n/zh-Hans/app-api.ts @@ -79,6 +79,7 @@ const translation = { pathParams: 'Path Params', query: 'Query', toc: '目录', + noContent: '暂无内容', }, } diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index 33f563af99..e3df0ea9ea 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -32,6 +32,9 @@ const translation = { cancelDisagree: '取消反对', userAction: '用户表示', }, + code: { + instruction: '指令', + }, notSetAPIKey: { title: 'LLM 提供者的密钥未设置', trailFinished: '试用已结束', diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 517c41de10..71edaa1629 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -1,4 +1,9 @@ const translation = { + theme: { + switchDark: '切换至深色主题', + switchLight: '切换至浅色主题', + }, + appNamePlaceholder: '给你的应用起个名字', createApp: '创建应用', types: { all: '全部', @@ -297,6 +302,7 @@ const translation = { commandHint: '输入 @ 按类别浏览', slashHint: '输入 / 查看所有可用命令', actions: { + slashTitle: '命令', searchApplications: '搜索应用程序', searchApplicationsDesc: '搜索并导航到您的应用程序', searchPlugins: '搜索插件', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index bd0e0e3ba4..977ffe1919 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -1,4 +1,6 @@ const translation = { + loading: '加载中', + error: '错误', theme: { theme: '主题', light: '浅色', @@ -78,6 +80,8 @@ const translation = { selectAll: '全选', deSelectAll: '取消全选', now: '现在', + back: '返回', + imageDownloaded: '图片已下载', }, errorMsg: { fieldRequired: '{{field}} 为必填项', @@ -252,6 +256,7 @@ const translation = { feedbackPlaceholder: '选填', editWorkspaceInfo: '编辑工作空间信息', workspaceName: '工作空间名称', + workspaceNamePlaceholder: '输入工作空间名称', workspaceIcon: '工作空间图标', changeEmail: { title: '更改邮箱', @@ -509,6 +514,7 @@ const translation = { emptyProviderTip: '请安装模型供应商。', auth: { unAuthorized: '未授权', + credentialRemoved: '凭据已移除', authRemoved: '授权已移除', apiKeys: 'API 密钥', addApiKey: '添加 API 密钥', diff --git a/web/i18n/zh-Hans/dataset.ts b/web/i18n/zh-Hans/dataset.ts index 7399604762..781fb5aa94 100644 --- a/web/i18n/zh-Hans/dataset.ts +++ b/web/i18n/zh-Hans/dataset.ts @@ -245,6 +245,7 @@ const translation = { tip: '支持 {{supportTypes}} (最多 {{batchCount}} 个,每个大小不超过 {{size}}MB)', button: '拖拽文件或文件夹,或', browse: '浏览', + fileSizeLimitExceeded: '文件大小超过 {{size}}MB 限制', }, } diff --git a/web/i18n/zh-Hans/login.ts b/web/i18n/zh-Hans/login.ts index 13a75eaaaa..d79005fbdd 100644 --- a/web/i18n/zh-Hans/login.ts +++ b/web/i18n/zh-Hans/login.ts @@ -64,6 +64,7 @@ const translation = { passwordLengthInValid: '密码必须至少为 8 个字符', registrationNotAllowed: '账户不存在,请联系系统管理员注册账户', invalidEmailOrPassword: '邮箱或密码错误', + redirectUrlMissing: '重定向 URL 缺失', }, license: { tip: '启动 Dify 社区版之前,请阅读 GitHub 上的', diff --git a/web/i18n/zh-Hans/plugin-trigger.ts b/web/i18n/zh-Hans/plugin-trigger.ts index 304cdd47bd..4f31f517eb 100644 --- a/web/i18n/zh-Hans/plugin-trigger.ts +++ b/web/i18n/zh-Hans/plugin-trigger.ts @@ -161,6 +161,7 @@ const translation = { verifyFailed: '验证凭据失败', authFailed: '授权失败', networkError: '网络错误,请重试', + updateFailed: '更新订阅失败', }, }, events: { diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 624fbb241a..7893a66f66 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -15,6 +15,7 @@ const translation = { author: '作者', auth: { authorized: '已授权', + unauthorized: '未授权', setup: '要使用请先授权', setupModalTitle: '设置授权', setupModalTitleDescription: '配置凭据后,工作区中的所有成员都可以在编排应用程序时使用此工具。', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index fd86292252..a6daa56667 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -1062,6 +1062,9 @@ const translation = { days: '天', notConfigured: '未配置', mode: '模式', + modeVisual: '可视化', + modeCron: 'Cron', + selectTime: '选择时间', timezone: '时区', visualConfig: '可视化配置', monthlyDay: '月份日期', diff --git a/web/package.json b/web/package.json index baecd2c5c7..f3cc095811 100644 --- a/web/package.json +++ b/web/package.json @@ -33,10 +33,8 @@ "prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky ./web/.husky", "gen-icons": "node ./app/components/base/icons/script.mjs", "uglify-embed": "node ./bin/uglify-embed", - "check-i18n": "node ./i18n-config/check-i18n.js", - "auto-gen-i18n": "node ./i18n-config/auto-gen-i18n.js", - "gen:i18n-types": "node ./i18n-config/generate-i18n-types.js", - "check:i18n-types": "node ./i18n-config/check-i18n-sync.js", + "check-i18n": "tsx ./i18n-config/check-i18n.js", + "auto-gen-i18n": "tsx ./i18n-config/auto-gen-i18n.js", "test": "vitest run", "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", @@ -212,7 +210,7 @@ "sass": "^1.93.2", "storybook": "9.1.17", "tailwindcss": "^3.4.18", - "ts-node": "^10.9.2", + "tsx": "^4.21.0", "typescript": "^5.9.3", "uglify-js": "^3.19.3", "vite": "^7.3.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 95d35c24d8..24e710534f 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -544,9 +544,9 @@ importers: tailwindcss: specifier: ^3.4.18 version: 3.4.18(tsx@4.21.0)(yaml@2.8.2) - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@types/node@18.15.0)(typescript@5.9.3) + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1360,10 +1360,6 @@ packages: '@code-inspector/webpack@1.2.9': resolution: {integrity: sha512-9YEykVrOIc0zMV7pyTyZhCprjScjn6gPPmxb4/OQXKCrP2fAm+NB188rg0s95e4sM7U3qRUpPA4NUH5F7Ogo+g==} - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -2175,9 +2171,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@lexical/clipboard@0.38.2': resolution: {integrity: sha512-dDShUplCu8/o6BB9ousr3uFZ9bltR+HtleF/Tl8FXFNPpZ4AXhbLKUoJuucRuIr+zqT7RxEv/3M6pk/HEoE6NQ==} @@ -3405,18 +3398,6 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -4094,9 +4075,6 @@ packages: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -4624,9 +4602,6 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cron-parser@5.4.0: resolution: {integrity: sha512-HxYB8vTvnQFx4dLsZpGRa0uHp6X3qIzS3ZJgJ9v6l/5TJMgeWQbLkR5yiJ5hOxGbc9+jCADDnydIe15ReLZnJA==} engines: {node: '>=18'} @@ -4936,10 +4911,6 @@ packages: resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} @@ -6364,9 +6335,6 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -8159,20 +8127,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} @@ -8407,9 +8361,6 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -8797,10 +8748,6 @@ packages: resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -9954,10 +9901,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -10600,11 +10543,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@lexical/clipboard@0.38.2': dependencies: '@lexical/html': 0.38.2 @@ -11923,14 +11861,6 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 - '@tsconfig/node10@1.0.12': {} - - '@tsconfig/node12@1.0.11': {} - - '@tsconfig/node14@1.0.3': {} - - '@tsconfig/node16@1.0.4': {} - '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -12742,8 +12672,6 @@ snapshots: are-docs-informative@0.0.2: {} - arg@4.1.3: {} - arg@5.0.2: {} argparse@2.0.1: {} @@ -13291,8 +13219,6 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 - create-require@1.1.1: {} - cron-parser@5.4.0: dependencies: luxon: 3.7.2 @@ -13625,8 +13551,6 @@ snapshots: diff-sequences@27.5.1: {} - diff@4.0.2: {} - diffie-hellman@5.0.3: dependencies: bn.js: 4.12.2 @@ -15350,8 +15274,6 @@ snapshots: dependencies: semver: 7.7.3 - make-error@1.3.6: {} - markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -17655,24 +17577,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@18.15.0)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.15.0 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - ts-pattern@5.9.0: {} tsconfck@3.1.6(typescript@5.9.3): @@ -17888,8 +17792,6 @@ snapshots: uuid@11.1.0: {} - v8-compile-cache-lib@3.0.1: {} - vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -18312,8 +18214,6 @@ snapshots: dependencies: lib0: 0.2.115 - yn@3.1.1: {} - yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} diff --git a/web/types/i18n.d.ts b/web/types/i18n.d.ts index b5e5b39aa7..3e5b10674a 100644 --- a/web/types/i18n.d.ts +++ b/web/types/i18n.d.ts @@ -1,74 +1,8 @@ -// TypeScript type definitions for Dify's i18next configuration -// This file is auto-generated. Do not edit manually. -// To regenerate, run: pnpm run gen:i18n-types +import type { messagesEN } from '../i18n-config/i18next-config' import 'react-i18next' -// Extract types from translation files using typeof import pattern - -type AppAnnotationMessages = typeof import('../i18n/en-US/app-annotation').default -type AppApiMessages = typeof import('../i18n/en-US/app-api').default -type AppDebugMessages = typeof import('../i18n/en-US/app-debug').default -type AppLogMessages = typeof import('../i18n/en-US/app-log').default -type AppOverviewMessages = typeof import('../i18n/en-US/app-overview').default -type AppMessages = typeof import('../i18n/en-US/app').default -type BillingMessages = typeof import('../i18n/en-US/billing').default -type CommonMessages = typeof import('../i18n/en-US/common').default -type CustomMessages = typeof import('../i18n/en-US/custom').default -type DatasetCreationMessages = typeof import('../i18n/en-US/dataset-creation').default -type DatasetDocumentsMessages = typeof import('../i18n/en-US/dataset-documents').default -type DatasetHitTestingMessages = typeof import('../i18n/en-US/dataset-hit-testing').default -type DatasetPipelineMessages = typeof import('../i18n/en-US/dataset-pipeline').default -type DatasetSettingsMessages = typeof import('../i18n/en-US/dataset-settings').default -type DatasetMessages = typeof import('../i18n/en-US/dataset').default -type EducationMessages = typeof import('../i18n/en-US/education').default -type ExploreMessages = typeof import('../i18n/en-US/explore').default -type LayoutMessages = typeof import('../i18n/en-US/layout').default -type LoginMessages = typeof import('../i18n/en-US/login').default -type OauthMessages = typeof import('../i18n/en-US/oauth').default -type PipelineMessages = typeof import('../i18n/en-US/pipeline').default -type PluginTagsMessages = typeof import('../i18n/en-US/plugin-tags').default -type PluginTriggerMessages = typeof import('../i18n/en-US/plugin-trigger').default -type PluginMessages = typeof import('../i18n/en-US/plugin').default -type RegisterMessages = typeof import('../i18n/en-US/register').default -type RunLogMessages = typeof import('../i18n/en-US/run-log').default -type ShareMessages = typeof import('../i18n/en-US/share').default -type TimeMessages = typeof import('../i18n/en-US/time').default -type ToolsMessages = typeof import('../i18n/en-US/tools').default -type WorkflowMessages = typeof import('../i18n/en-US/workflow').default - // Complete type structure that matches i18next-config.ts camelCase conversion -export type Messages = { - appAnnotation: AppAnnotationMessages - appApi: AppApiMessages - appDebug: AppDebugMessages - appLog: AppLogMessages - appOverview: AppOverviewMessages - app: AppMessages - billing: BillingMessages - common: CommonMessages - custom: CustomMessages - datasetCreation: DatasetCreationMessages - datasetDocuments: DatasetDocumentsMessages - datasetHitTesting: DatasetHitTestingMessages - datasetPipeline: DatasetPipelineMessages - datasetSettings: DatasetSettingsMessages - dataset: DatasetMessages - education: EducationMessages - explore: ExploreMessages - layout: LayoutMessages - login: LoginMessages - oauth: OauthMessages - pipeline: PipelineMessages - pluginTags: PluginTagsMessages - pluginTrigger: PluginTriggerMessages - plugin: PluginMessages - register: RegisterMessages - runLog: RunLogMessages - share: ShareMessages - time: TimeMessages - tools: ToolsMessages - workflow: WorkflowMessages -} +export type Messages = typeof messagesEN // Utility type to flatten nested object keys into dot notation type FlattenKeys<T> = T extends object @@ -81,19 +15,9 @@ type FlattenKeys<T> = T extends object export type ValidTranslationKeys = FlattenKeys<Messages> -// Extend react-i18next with Dify's type structure -declare module 'react-i18next' { - type CustomTypeOptions = { - defaultNS: 'translation' - resources: { - translation: Messages - } - } -} - -// Extend i18next for complete type safety declare module 'i18next' { - type CustomTypeOptions = { + // eslint-disable-next-line ts/consistent-type-definitions + interface CustomTypeOptions { defaultNS: 'translation' resources: { translation: Messages diff --git a/web/utils/format.ts b/web/utils/format.ts index a2c3ef9751..d087d690a2 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -1,5 +1,6 @@ import type { Dayjs } from 'dayjs' import type { Locale } from '@/i18n-config' +import { localeMap } from '@/i18n-config/language' import 'dayjs/locale/de' import 'dayjs/locale/es' import 'dayjs/locale/fa' @@ -21,30 +22,6 @@ import 'dayjs/locale/vi' import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-tw' -const localeMap: Record<Locale, string> = { - 'en-US': 'en', - 'zh-Hans': 'zh-cn', - 'zh-Hant': 'zh-tw', - 'pt-BR': 'pt-br', - 'es-ES': 'es', - 'fr-FR': 'fr', - 'de-DE': 'de', - 'ja-JP': 'ja', - 'ko-KR': 'ko', - 'ru-RU': 'ru', - 'it-IT': 'it', - 'th-TH': 'th', - 'id-ID': 'id', - 'uk-UA': 'uk', - 'vi-VN': 'vi', - 'ro-RO': 'ro', - 'pl-PL': 'pl', - 'hi-IN': 'hi', - 'tr-TR': 'tr', - 'fa-IR': 'fa', - 'sl-SI': 'sl', -} - /** * Formats a number with comma separators. * @example formatNumber(1234567) will return '1,234,567' @@ -149,6 +126,6 @@ export const formatNumberAbbreviated = (num: number) => { } } -export const formatToLocalTime = (time: Dayjs, local: string, format: string) => { +export const formatToLocalTime = (time: Dayjs, local: Locale, format: string) => { return time.locale(localeMap[local] ?? 'en').format(format) } From 18d69775ef6750e407409973ca4412e763bb214e Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:03:43 +0800 Subject: [PATCH 62/71] refactor(web): migrate explore app lists from useSWR to TanStack Query (#30076) --- .../app/create-app-dialog/app-list/index.tsx | 21 ++---- .../explore/app-list/index.spec.tsx | 22 +++--- web/app/components/explore/app-list/index.tsx | 21 ++---- .../components/workflow-header/index.spec.tsx | 67 +++++++++++++------ web/service/explore.ts | 14 +--- web/service/use-explore.ts | 27 +++++++- 6 files changed, 91 insertions(+), 81 deletions(-) diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index df54de2ff1..ee64478141 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -8,7 +8,6 @@ import { useRouter } from 'next/navigation' import * as React from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import { useContext } from 'use-context-selector' import AppTypeSelector from '@/app/components/app/type-selector' import { trackEvent } from '@/app/components/base/amplitude' @@ -24,7 +23,8 @@ import ExploreContext from '@/context/explore-context' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { DSLImportMode } from '@/models/app' import { importDSL } from '@/service/apps' -import { fetchAppDetail, fetchAppList } from '@/service/explore' +import { fetchAppDetail } from '@/service/explore' +import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { cn } from '@/utils/classnames' @@ -70,21 +70,8 @@ const Apps = ({ }) const { - data: { categories, allList }, - } = useSWR( - ['/explore/apps'], - () => - fetchAppList().then(({ categories, recommended_apps }) => ({ - categories, - allList: recommended_apps.sort((a, b) => a.position - b.position), - })), - { - fallbackData: { - categories: [], - allList: [], - }, - }, - ) + data: { categories, allList } = exploreAppListInitialData, + } = useExploreAppList() const filteredList = useMemo(() => { const filteredByCategory = allList.filter((item) => { diff --git a/web/app/components/explore/app-list/index.spec.tsx b/web/app/components/explore/app-list/index.spec.tsx index e73fcdf0ad..ebf2c9c075 100644 --- a/web/app/components/explore/app-list/index.spec.tsx +++ b/web/app/components/explore/app-list/index.spec.tsx @@ -10,7 +10,7 @@ import AppList from './index' const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en"}' let mockTabValue = allCategoriesEn const mockSetTab = vi.fn() -let mockSWRData: { categories: string[], allList: App[] } = { categories: [], allList: [] } +let mockExploreData: { categories: string[], allList: App[] } = { categories: [], allList: [] } const mockHandleImportDSL = vi.fn() const mockHandleImportDSLConfirm = vi.fn() @@ -33,9 +33,9 @@ vi.mock('ahooks', async () => { } }) -vi.mock('swr', () => ({ - __esModule: true, - default: () => ({ data: mockSWRData }), +vi.mock('@/service/use-explore', () => ({ + exploreAppListInitialData: { categories: [], allList: [] }, + useExploreAppList: () => ({ data: mockExploreData }), })) vi.mock('@/service/explore', () => ({ @@ -135,14 +135,14 @@ describe('AppList', () => { beforeEach(() => { vi.clearAllMocks() mockTabValue = allCategoriesEn - mockSWRData = { categories: [], allList: [] } + mockExploreData = { categories: [], allList: [] } }) // Rendering: show loading when categories are not ready. describe('Rendering', () => { it('should render loading when categories are empty', () => { // Arrange - mockSWRData = { categories: [], allList: [] } + mockExploreData = { categories: [], allList: [] } // Act renderWithContext() @@ -153,7 +153,7 @@ describe('AppList', () => { it('should render app cards when data is available', () => { // Arrange - mockSWRData = { + mockExploreData = { categories: ['Writing', 'Translate'], allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })], } @@ -172,7 +172,7 @@ describe('AppList', () => { it('should filter apps by selected category', () => { // Arrange mockTabValue = 'Writing' - mockSWRData = { + mockExploreData = { categories: ['Writing', 'Translate'], allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })], } @@ -190,7 +190,7 @@ describe('AppList', () => { describe('User Interactions', () => { it('should filter apps by search keywords', async () => { // Arrange - mockSWRData = { + mockExploreData = { categories: ['Writing'], allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })], } @@ -210,7 +210,7 @@ describe('AppList', () => { it('should handle create flow and confirm DSL when pending', async () => { // Arrange const onSuccess = vi.fn() - mockSWRData = { + mockExploreData = { categories: ['Writing'], allList: [createApp()], }; @@ -246,7 +246,7 @@ describe('AppList', () => { describe('Edge Cases', () => { it('should reset search results when clear icon is clicked', async () => { // Arrange - mockSWRData = { + mockExploreData = { categories: ['Writing'], allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })], } diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 585c4e60c1..244a116e36 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -6,7 +6,6 @@ import { useDebounceFn } from 'ahooks' import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import { useContext } from 'use-context-selector' import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal' import Input from '@/app/components/base/input' @@ -20,7 +19,8 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { DSLImportMode, } from '@/models/app' -import { fetchAppDetail, fetchAppList } from '@/service/explore' +import { fetchAppDetail } from '@/service/explore' +import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' import { cn } from '@/utils/classnames' import s from './style.module.css' @@ -58,21 +58,8 @@ const Apps = ({ }) const { - data: { categories, allList }, - } = useSWR( - ['/explore/apps'], - () => - fetchAppList().then(({ categories, recommended_apps }) => ({ - categories, - allList: recommended_apps.sort((a, b) => a.position - b.position), - })), - { - fallbackData: { - categories: [], - allList: [], - }, - }, - ) + data: { categories, allList } = exploreAppListInitialData, + } = useExploreAppList() const filteredList = allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) diff --git a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx index 8eee878cd9..87d7fb30e7 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx @@ -1,6 +1,6 @@ import type { HeaderProps } from '@/app/components/workflow/header' import type { App } from '@/types/app' -import { render, screen } from '@testing-library/react' +import { fireEvent, render, screen } from '@testing-library/react' import { AppModeEnum } from '@/types/app' import WorkflowHeader from './index' @@ -9,8 +9,47 @@ const mockSetCurrentLogItem = vi.fn() const mockSetShowMessageLogModal = vi.fn() const mockResetWorkflowVersionHistory = vi.fn() +const createMockApp = (overrides: Partial<App> = {}): App => ({ + id: 'app-id', + name: 'Workflow App', + description: 'Workflow app description', + author_name: 'Workflow app author', + icon_type: 'emoji', + icon: 'app-icon', + icon_background: '#FFFFFF', + icon_url: null, + use_icon_as_answer_icon: false, + mode: AppModeEnum.COMPLETION, + enable_site: true, + enable_api: true, + api_rpm: 60, + api_rph: 3600, + is_demo: false, + model_config: {} as App['model_config'], + app_model_config: {} as App['app_model_config'], + created_at: 0, + updated_at: 0, + site: { + access_token: 'token', + app_base_url: 'https://example.com', + } as App['site'], + api_base_url: 'https://api.example.com', + tags: [], + access_mode: 'public_access' as App['access_mode'], + ...overrides, +}) + let appDetail: App +const mockAppStore = (overrides: Partial<App> = {}) => { + appDetail = createMockApp(overrides) + mockUseAppStoreSelector.mockImplementation(selector => selector({ + appDetail, + setCurrentLogItem: mockSetCurrentLogItem, + setShowMessageLogModal: mockSetShowMessageLogModal, + })) +} + vi.mock('@/app/components/app/store', () => ({ __esModule: true, useStore: (selector: (state: { appDetail?: App, setCurrentLogItem: typeof mockSetCurrentLogItem, setShowMessageLogModal: typeof mockSetShowMessageLogModal }) => unknown) => mockUseAppStoreSelector(selector), @@ -60,13 +99,7 @@ vi.mock('@/service/use-workflow', () => ({ describe('WorkflowHeader', () => { beforeEach(() => { vi.clearAllMocks() - appDetail = { id: 'app-id', mode: AppModeEnum.COMPLETION } as unknown as App - - mockUseAppStoreSelector.mockImplementation(selector => selector({ - appDetail, - setCurrentLogItem: mockSetCurrentLogItem, - setShowMessageLogModal: mockSetShowMessageLogModal, - })) + mockAppStore() }) // Verifies the wrapper renders the workflow header shell. @@ -84,12 +117,7 @@ describe('WorkflowHeader', () => { describe('Props', () => { it('should configure preview mode when app is in advanced chat mode', () => { // Arrange - appDetail = { id: 'app-id', mode: AppModeEnum.ADVANCED_CHAT } as unknown as App - mockUseAppStoreSelector.mockImplementation(selector => selector({ - appDetail, - setCurrentLogItem: mockSetCurrentLogItem, - setShowMessageLogModal: mockSetShowMessageLogModal, - })) + mockAppStore({ mode: AppModeEnum.ADVANCED_CHAT }) // Act render(<WorkflowHeader />) @@ -104,12 +132,7 @@ describe('WorkflowHeader', () => { it('should configure run mode when app is not in advanced chat mode', () => { // Arrange - appDetail = { id: 'app-id', mode: AppModeEnum.COMPLETION } as unknown as App - mockUseAppStoreSelector.mockImplementation(selector => selector({ - appDetail, - setCurrentLogItem: mockSetCurrentLogItem, - setShowMessageLogModal: mockSetShowMessageLogModal, - })) + mockAppStore({ mode: AppModeEnum.COMPLETION }) // Act render(<WorkflowHeader />) @@ -130,7 +153,7 @@ describe('WorkflowHeader', () => { render(<WorkflowHeader />) // Act - screen.getByRole('button', { name: 'clear-history' }).click() + fireEvent.click(screen.getByRole('button', { name: /clear-history/i })) // Assert expect(mockSetCurrentLogItem).toHaveBeenCalledWith() @@ -145,7 +168,7 @@ describe('WorkflowHeader', () => { render(<WorkflowHeader />) // Assert - screen.getByRole('button', { name: 'restore-settled' }).click() + fireEvent.click(screen.getByRole('button', { name: /restore-settled/i })) expect(mockResetWorkflowVersionHistory).toHaveBeenCalled() }) }) diff --git a/web/service/explore.ts b/web/service/explore.ts index 70d5de37f2..b4056da4ab 100644 --- a/web/service/explore.ts +++ b/web/service/explore.ts @@ -1,6 +1,6 @@ import type { AccessMode } from '@/models/access-control' import type { App, AppCategory } from '@/models/explore' -import { del, get, patch, post } from './base' +import { del, get, patch } from './base' export const fetchAppList = () => { return get<{ @@ -17,14 +17,6 @@ export const fetchInstalledAppList = (app_id?: string | null) => { return get(`/installed-apps${app_id ? `?app_id=${app_id}` : ''}`) } -export const installApp = (id: string) => { - return post('/installed-apps', { - body: { - app_id: id, - }, - }) -} - export const uninstallApp = (id: string) => { return del(`/installed-apps/${id}`) } @@ -37,10 +29,6 @@ export const updatePinStatus = (id: string, isPinned: boolean) => { }) } -export const getToolProviders = () => { - return get('/workspaces/current/tool-providers') -} - export const getAppAccessModeByAppId = (appId: string) => { return get<{ accessMode: AccessMode }>(`/enterprise/webapp/app/access-mode?appId=${appId}`) } diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index 6e57599b69..8bda877908 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -1,11 +1,36 @@ +import type { App, AppCategory } from '@/models/explore' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useGlobalPublicStore } from '@/context/global-public-context' import { AccessMode } from '@/models/access-control' -import { fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore' +import { fetchAppList, fetchInstalledAppList, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore' import { fetchAppMeta, fetchAppParams } from './share' const NAME_SPACE = 'explore' +type ExploreAppListData = { + categories: AppCategory[] + allList: App[] +} + +export const exploreAppListInitialData: ExploreAppListData = { + categories: [], + allList: [], +} + +export const useExploreAppList = () => { + return useQuery<ExploreAppListData>({ + queryKey: [NAME_SPACE, 'appList'], + queryFn: async () => { + const { categories, recommended_apps } = await fetchAppList() + return { + categories, + allList: [...recommended_apps].sort((a, b) => a.position - b.position), + } + }, + placeholderData: exploreAppListInitialData, + }) +} + export const useGetInstalledApps = () => { return useQuery({ queryKey: [NAME_SPACE, 'installedApps'], From eb73f9a9b9fc9eee6be786d2bf5f41fc77e240c4 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 24 Dec 2025 17:17:36 +0800 Subject: [PATCH 63/71] chore: no template string in translation (#30101) --- web/i18n/de-DE/app-debug.ts | 4 +--- web/i18n/en-US/app-debug.ts | 4 +--- web/i18n/es-ES/app-debug.ts | 4 +--- web/i18n/fr-FR/app-debug.ts | 4 +--- web/i18n/it-IT/app-debug.ts | 4 +--- web/i18n/ja-JP/app-debug.ts | 4 +--- web/i18n/ko-KR/app-debug.ts | 4 +--- web/i18n/pl-PL/app-debug.ts | 4 +--- web/i18n/pt-BR/app-debug.ts | 4 +--- web/i18n/ro-RO/app-debug.ts | 4 +--- web/i18n/ru-RU/app-debug.ts | 4 +--- web/i18n/uk-UA/app-debug.ts | 4 +--- web/i18n/vi-VN/app-debug.ts | 4 +--- web/i18n/zh-Hans/app-debug.ts | 4 +--- web/i18n/zh-Hant/app-debug.ts | 4 +--- 15 files changed, 15 insertions(+), 45 deletions(-) diff --git a/web/i18n/de-DE/app-debug.ts b/web/i18n/de-DE/app-debug.ts index 07c9d4be99..c12a1f5291 100644 --- a/web/i18n/de-DE/app-debug.ts +++ b/web/i18n/de-DE/app-debug.ts @@ -357,9 +357,7 @@ const translation = { visionSettings: { title: 'Vision-Einstellungen', resolution: 'Auflösung', - resolutionTooltip: `Niedrige Auflösung ermöglicht es dem Modell, eine Bildversion mit niedriger Auflösung von 512 x 512 zu erhalten und das Bild mit einem Budget von 65 Tokens darzustellen. Dies ermöglicht schnellere Antworten des API und verbraucht weniger Eingabetokens für Anwendungsfälle, die kein hohes Detail benötigen. - \n - Hohe Auflösung ermöglicht zunächst, dass das Modell das Bild mit niedriger Auflösung sieht und dann detaillierte Ausschnitte von Eingabebildern als 512px Quadrate basierend auf der Größe des Eingabebildes erstellt. Jeder der detaillierten Ausschnitte verwendet das doppelte Token-Budget für insgesamt 129 Tokens.`, + resolutionTooltip: 'Niedrige Auflösung ermöglicht es dem Modell, eine Bildversion mit niedriger Auflösung von 512 x 512 zu erhalten und das Bild mit einem Budget von 65 Tokens darzustellen. Dies ermöglicht schnellere Antworten des API und verbraucht weniger Eingabetokens für Anwendungsfälle, die kein hohes Detail benötigen.\nHohe Auflösung ermöglicht zunächst, dass das Modell das Bild mit niedriger Auflösung sieht und dann detaillierte Ausschnitte von Eingabebildern als 512px Quadrate basierend auf der Größe des Eingabebildes erstellt. Jeder der detaillierten Ausschnitte verwendet das doppelte Token-Budget für insgesamt 129 Tokens.', high: 'Hoch', low: 'Niedrig', uploadMethod: 'Upload-Methode', diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 5604de3dd1..9d69c36d83 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -448,9 +448,7 @@ const translation = { visionSettings: { title: 'Vision Settings', resolution: 'Resolution', - resolutionTooltip: `low res will allow model receive a low-res 512 x 512 version of the image, and represent the image with a budget of 65 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail. - \n - high res will first allows the model to see the low res image and then creates detailed crops of input images as 512px squares based on the input image size. Each of the detailed crops uses twice the token budget for a total of 129 tokens.`, + resolutionTooltip: 'low res will allow model receive a low-res 512 x 512 version of the image, and represent the image with a budget of 65 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail.\nhigh res will first allows the model to see the low res image and then creates detailed crops of input images as 512px squares based on the input image size. Each of the detailed crops uses twice the token budget for a total of 129 tokens.', high: 'High', low: 'Low', uploadMethod: 'Upload Method', diff --git a/web/i18n/es-ES/app-debug.ts b/web/i18n/es-ES/app-debug.ts index 892e718d32..4252037e1f 100644 --- a/web/i18n/es-ES/app-debug.ts +++ b/web/i18n/es-ES/app-debug.ts @@ -353,9 +353,7 @@ const translation = { visionSettings: { title: 'Configuraciones de Visión', resolution: 'Resolución', - resolutionTooltip: `Baja resolución permitirá que el modelo reciba una versión de baja resolución de 512 x 512 de la imagen, y represente la imagen con un presupuesto de 65 tokens. Esto permite que la API devuelva respuestas más rápidas y consuma menos tokens de entrada para casos de uso que no requieren alta detalle. - \n - Alta resolución permitirá primero que el modelo vea la imagen de baja resolución y luego crea recortes detallados de las imágenes de entrada como cuadrados de 512px basados en el tamaño de la imagen de entrada. Cada uno de los recortes detallados usa el doble del presupuesto de tokens para un total de 129 tokens.`, + resolutionTooltip: 'Baja resolución permitirá que el modelo reciba una versión de baja resolución de 512 x 512 de la imagen, y represente la imagen con un presupuesto de 65 tokens. Esto permite que la API devuelva respuestas más rápidas y consuma menos tokens de entrada para casos de uso que no requieren alta detalle.\nAlta resolución permitirá primero que el modelo vea la imagen de baja resolución y luego crea recortes detallados de las imágenes de entrada como cuadrados de 512px basados en el tamaño de la imagen de entrada. Cada uno de los recortes detallados usa el doble del presupuesto de tokens para un total de 129 tokens.', high: 'Alta', low: 'Baja', uploadMethod: 'Método de carga', diff --git a/web/i18n/fr-FR/app-debug.ts b/web/i18n/fr-FR/app-debug.ts index 26ebeca68d..2e65e681ca 100644 --- a/web/i18n/fr-FR/app-debug.ts +++ b/web/i18n/fr-FR/app-debug.ts @@ -357,9 +357,7 @@ const translation = { visionSettings: { title: 'Paramètres de Vision', resolution: 'Résolution', - resolutionTooltip: `low res will allow model receive a low-res 512 x 512 version of the image, and represent the image with a budget of 65 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail. - \n - high res will first allows the model to see the low res image and then creates detailed crops of input images as 512px squares based on the input image size. Each of the detailed crops uses twice the token budget for a total of 129 tokens.`, + resolutionTooltip: 'low res will allow model receive a low-res 512 x 512 version of the image, and represent the image with a budget of 65 tokens. This allows the API to return faster responses and consume fewer input tokens for use cases that do not require high detail.\nhigh res will first allows the model to see the low res image and then creates detailed crops of input images as 512px squares based on the input image size. Each of the detailed crops uses twice the token budget for a total of 129 tokens.', high: 'Élevé', low: 'Faible', uploadMethod: 'Méthode de Téléchargement', diff --git a/web/i18n/it-IT/app-debug.ts b/web/i18n/it-IT/app-debug.ts index ecae1f3a2e..e81a83a3dd 100644 --- a/web/i18n/it-IT/app-debug.ts +++ b/web/i18n/it-IT/app-debug.ts @@ -384,9 +384,7 @@ const translation = { visionSettings: { title: 'Impostazioni di visione', resolution: 'Risoluzione', - resolutionTooltip: `La bassa risoluzione permetterà al modello di ricevere una versione a bassa risoluzione 512 x 512 dell\\'immagine e di rappresentare l\\'immagine con un budget di 65 token. Questo permette all\\'API di restituire risposte più veloci e di consumare meno token di input per casi d\\'uso che non richiedono alta definizione. - \n - L\\'alta risoluzione permetterà al modello di vedere prima l\\'immagine a bassa risoluzione e poi di creare ritagli dettagliati delle immagini di input come quadrati 512px basati sulla dimensione dell\\'immagine di input. Ciascuno dei ritagli dettagliati utilizza il doppio del budget dei token per un totale di 129 token.`, + resolutionTooltip: 'La bassa risoluzione permetterà al modello di ricevere una versione a bassa risoluzione 512 x 512 dell\'immagine e di rappresentare l\'immagine con un budget di 65 token. Questo permette all\'API di restituire risposte più veloci e di consumare meno token di input per casi d\'uso che non richiedono alta definizione.\nL\'alta risoluzione permetterà al modello di vedere prima l\'immagine a bassa risoluzione e poi di creare ritagli dettagliati delle immagini di input come quadrati 512px basati sulla dimensione dell\'immagine di input. Ciascuno dei ritagli dettagliati utilizza il doppio del budget dei token per un totale di 129 token.', high: 'Alta', low: 'Bassa', uploadMethod: 'Metodo di caricamento', diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index 9d1deb47eb..06b47c1a47 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -442,9 +442,7 @@ const translation = { visionSettings: { title: 'ビジョン設定', resolution: '解像度', - resolutionTooltip: `低解像度では、モデルに低解像度の 512 x 512 バージョンの画像を受け取らせ、画像を 65 トークンの予算で表現します。これにより、API がより迅速な応答を返し、高い詳細が必要なユースケースでは入力トークンを消費します。 - \n - 高解像度では、まずモデルに低解像度の画像を見せ、その後、入力画像サイズに基づいて 512px の正方形の詳細なクロップを作成します。詳細なクロップごとに 129 トークンの予算を使用します。`, + resolutionTooltip: '低解像度では、モデルに低解像度の 512 x 512 バージョンの画像を受け取らせ、画像を 65 トークンの予算で表現します。これにより、API がより迅速な応答を返し、高い詳細が必要なユースケースでは入力トークンを消費します。\n高解像度では、まずモデルに低解像度の画像を見せ、その後、入力画像サイズに基づいて 512px の正方形の詳細なクロップを作成します。詳細なクロップごとに 129 トークンの予算を使用します。', high: '高', low: '低', uploadMethod: 'アップロード方法', diff --git a/web/i18n/ko-KR/app-debug.ts b/web/i18n/ko-KR/app-debug.ts index c9e048df0e..5258287285 100644 --- a/web/i18n/ko-KR/app-debug.ts +++ b/web/i18n/ko-KR/app-debug.ts @@ -353,9 +353,7 @@ const translation = { visionSettings: { title: '비전 설정', resolution: '해상도', - resolutionTooltip: `저해상도는 모델에게 512 x 512 해상도의 저해상도 이미지를 제공하여 65 토큰의 예산으로 이미지를 표현합니다. 이로 인해 API 는 더 빠른 응답을 제공하며 높은 세부 정보가 필요한 경우 토큰 소모를 늘립니다. - \n - 고해상도는 먼저 모델에게 저해상도 이미지를 보여주고, 그 후 입력 이미지 크기에 따라 512px 의 정사각형 세부 사진을 만듭니다. 각 세부 사진에 대해 129 토큰의 예산을 사용합니다.`, + resolutionTooltip: '저해상도는 모델에게 512 x 512 해상도의 저해상도 이미지를 제공하여 65 토큰의 예산으로 이미지를 표현합니다. 이로 인해 API 는 더 빠른 응답을 제공하며 높은 세부 정보가 필요한 경우 토큰 소모를 늘립니다.\n고해상도는 먼저 모델에게 저해상도 이미지를 보여주고, 그 후 입력 이미지 크기에 따라 512px 의 정사각형 세부 사진을 만듭니다. 각 세부 사진에 대해 129 토큰의 예산을 사용합니다.', high: '고', low: '저', uploadMethod: '업로드 방식', diff --git a/web/i18n/pl-PL/app-debug.ts b/web/i18n/pl-PL/app-debug.ts index 262b4a204f..943896effc 100644 --- a/web/i18n/pl-PL/app-debug.ts +++ b/web/i18n/pl-PL/app-debug.ts @@ -379,9 +379,7 @@ const translation = { visionSettings: { title: 'Ustawienia Wizji', resolution: 'Rozdzielczość', - resolutionTooltip: `niska rozdzielczość pozwoli modelowi odbierać obrazy o rozdzielczości 512 x 512 i reprezentować obraz z limitem 65 tokenów. Pozwala to API na szybsze odpowiedzi i zużywa mniej tokenów wejściowych dla przypadków, które nie wymagają wysokiego szczegółu. - \n - wysoka rozdzielczość pozwala najpierw modelowi zobaczyć obraz niskiej rozdzielczości, a następnie tworzy szczegółowe przycięcia obrazów wejściowych jako 512px kwadratów w oparciu o rozmiar obrazu wejściowego. Każde z tych szczegółowych przycięć używa dwukrotności budżetu tokenów, co daje razem 129 tokenów.`, + resolutionTooltip: 'niska rozdzielczość pozwoli modelowi odbierać obrazy o rozdzielczości 512 x 512 i reprezentować obraz z limitem 65 tokenów. Pozwala to API na szybsze odpowiedzi i zużywa mniej tokenów wejściowych dla przypadków, które nie wymagają wysokiego szczegółu.\nwysoka rozdzielczość pozwala najpierw modelowi zobaczyć obraz niskiej rozdzielczości, a następnie tworzy szczegółowe przycięcia obrazów wejściowych jako 512px kwadratów w oparciu o rozmiar obrazu wejściowego. Każde z tych szczegółowych przycięć używa dwukrotności budżetu tokenów, co daje razem 129 tokenów.', high: 'Wysoka', low: 'Niska', uploadMethod: 'Metoda przesyłania', diff --git a/web/i18n/pt-BR/app-debug.ts b/web/i18n/pt-BR/app-debug.ts index d578cb5a84..30b9f59dd4 100644 --- a/web/i18n/pt-BR/app-debug.ts +++ b/web/i18n/pt-BR/app-debug.ts @@ -359,9 +359,7 @@ const translation = { visionSettings: { title: 'Configurações de Visão', resolution: 'Resolução', - resolutionTooltip: `Baixa resolução permitirá que o modelo receba uma versão de baixa resolução de 512 x 512 da imagem e represente a imagem com um orçamento de 65 tokens. Isso permite que a API retorne respostas mais rápidas e consuma menos tokens de entrada para casos de uso que não exigem alta precisão. - \n - Alta resolução permitirá que o modelo veja a imagem de baixa resolução e crie recortes detalhados das imagens de entrada como quadrados de 512px com base no tamanho da imagem de entrada. Cada um dos recortes detalhados usa o dobro do orçamento de tokens, totalizando 129 tokens.`, + resolutionTooltip: 'Baixa resolução permitirá que o modelo receba uma versão de baixa resolução de 512 x 512 da imagem e represente a imagem com um orçamento de 65 tokens. Isso permite que a API retorne respostas mais rápidas e consuma menos tokens de entrada para casos de uso que não exigem alta precisão.\nAlta resolução permitirá que o modelo veja a imagem de baixa resolução e crie recortes detalhados das imagens de entrada como quadrados de 512px com base no tamanho da imagem de entrada. Cada um dos recortes detalhados usa o dobro do orçamento de tokens, totalizando 129 tokens.', high: 'Alta', low: 'Baixa', uploadMethod: 'Método de Upload', diff --git a/web/i18n/ro-RO/app-debug.ts b/web/i18n/ro-RO/app-debug.ts index de8fd7a44f..8e36078be5 100644 --- a/web/i18n/ro-RO/app-debug.ts +++ b/web/i18n/ro-RO/app-debug.ts @@ -359,9 +359,7 @@ const translation = { visionSettings: { title: 'Setări Viziune', resolution: 'Rezoluție', - resolutionTooltip: `rezoluția joasă va permite modelului să primească o versiune de 512 x 512 pixeli a imaginii și să o reprezinte cu un buget de 65 de tokenuri. Acest lucru permite API-ului să returneze răspunsuri mai rapide și să consume mai puține tokenuri de intrare pentru cazurile de utilizare care nu necesită detalii ridicate. - \n - rezoluția ridicată va permite în primul rând modelului să vadă imaginea la rezoluție scăzută și apoi va crea decupaje detaliate ale imaginilor de intrare ca pătrate de 512 pixeli, în funcție de dimensiunea imaginii de intrare. Fiecare decupaj detaliat utilizează un buget de token dublu, pentru un total de 129 de tokenuri.`, + resolutionTooltip: 'rezoluția joasă va permite modelului să primească o versiune de 512 x 512 pixeli a imaginii și să o reprezinte cu un buget de 65 de tokenuri. Acest lucru permite API-ului să returneze răspunsuri mai rapide și să consume mai puține tokenuri de intrare pentru cazurile de utilizare care nu necesită detalii ridicate.\nrezoluția ridicată va permite în primul rând modelului să vadă imaginea la rezoluție scăzută și apoi va crea decupaje detaliate ale imaginilor de intrare ca pătrate de 512 pixeli, în funcție de dimensiunea imaginii de intrare. Fiecare decupaj detaliat utilizează un buget de token dublu, pentru un total de 129 de tokenuri.', high: 'Ridicat', low: 'Scăzut', uploadMethod: 'Metodă de încărcare', diff --git a/web/i18n/ru-RU/app-debug.ts b/web/i18n/ru-RU/app-debug.ts index 1d86e0778a..ea3b969df4 100644 --- a/web/i18n/ru-RU/app-debug.ts +++ b/web/i18n/ru-RU/app-debug.ts @@ -425,9 +425,7 @@ const translation = { visionSettings: { title: 'Настройки зрения', resolution: 'Разрешение', - resolutionTooltip: `Низкое разрешение позволит модели получать версию изображения с низким разрешением 512 x 512 и представлять изображение с бюджетом 65 токенов. Это позволяет API возвращать ответы быстрее и потреблять меньше входных токенов для случаев использования, не требующих высокой детализации. - \n - Высокое разрешение сначала позволит модели увидеть изображение с низким разрешением, а затем создаст детальные фрагменты входных изображений в виде квадратов 512 пикселей на основе размера входного изображения. Каждый из детальных фрагментов использует вдвое больший бюджет токенов, в общей сложности 129 токенов.`, + resolutionTooltip: 'Низкое разрешение позволит модели получать версию изображения с низким разрешением 512 x 512 и представлять изображение с бюджетом 65 токенов. Это позволяет API возвращать ответы быстрее и потреблять меньше входных токенов для случаев использования, не требующих высокой детализации.\nВысокое разрешение сначала позволит модели увидеть изображение с низким разрешением, а затем создаст детальные фрагменты входных изображений в виде квадратов 512 пикселей на основе размера входного изображения. Каждый из детальных фрагментов использует вдвое больший бюджет токенов, в общей сложности 129 токенов.', high: 'Высокое', low: 'Низкое', uploadMethod: 'Метод загрузки', diff --git a/web/i18n/uk-UA/app-debug.ts b/web/i18n/uk-UA/app-debug.ts index c593d2a730..18b4d32163 100644 --- a/web/i18n/uk-UA/app-debug.ts +++ b/web/i18n/uk-UA/app-debug.ts @@ -373,9 +373,7 @@ const translation = { visionSettings: { title: 'Налаштування зображень', // Vision Settings resolution: 'Роздільна здатність', // Resolution - resolutionTooltip: `низька роздільна здатність дозволить моделі отримати зображення з низькою роздільною здатністю 512 x 512 пікселів і представити зображення з обмеженням у 65 токенів. Це дозволяє API швидше повертати відповіді та споживати менше вхідних токенів для випадків використання, які не потребують високої деталізації. - \n - висока роздільна здатність спочатку дозволить моделі побачити зображення з низькою роздільною здатністю, а потім створити детальні фрагменти вхідних зображень у вигляді квадратів 512px на основі розміру вхідного зображення. Кожен із детальних фрагментів використовує подвійний запас токенів, загалом 129 токенів.`, + resolutionTooltip: 'низька роздільна здатність дозволить моделі отримати зображення з низькою роздільною здатністю 512 x 512 пікселів і представити зображення з обмеженням у 65 токенів. Це дозволяє API швидше повертати відповіді та споживати менше вхідних токенів для випадків використання, які не потребують високої деталізації.\nвисока роздільна здатність спочатку дозволить моделі побачити зображення з низькою роздільною здатністю, а потім створити детальні фрагменти вхідних зображень у вигляді квадратів 512px на основі розміру вхідного зображення. Кожен із детальних фрагментів використовує подвійний запас токенів, загалом 129 токенів.', high: 'Висока', // High low: 'Низька', // Low uploadMethod: 'Спосіб завантаження', // Upload Method diff --git a/web/i18n/vi-VN/app-debug.ts b/web/i18n/vi-VN/app-debug.ts index 150dfe488b..158a6b6ce9 100644 --- a/web/i18n/vi-VN/app-debug.ts +++ b/web/i18n/vi-VN/app-debug.ts @@ -353,9 +353,7 @@ const translation = { visionSettings: { title: 'Cài đặt thị giác', resolution: 'Độ phân giải', - resolutionTooltip: `Độ phân giải thấp sẽ cho phép mô hình nhận một phiên bản hình ảnh 512 x 512 thấp hơn, và đại diện cho hình ảnh với ngân sách 65 token. Điều này cho phép API trả về phản hồi nhanh hơn và tiêu thụ ít token đầu vào cho các trường hợp sử dụng không yêu cầu chi tiết cao. - \n - Độ phân giải cao sẽ đầu tiên cho phép mô hình nhìn thấy hình ảnh thấp hơn và sau đó tạo ra các cắt chi tiết của hình ảnh đầu vào dưới dạng hình vuông 512px dựa trên kích thước hình ảnh đầu vào. Mỗi cắt chi tiết sử dụng hai lần ngân sách token cho tổng cộng 129 token.`, + resolutionTooltip: 'Độ phân giải thấp sẽ cho phép mô hình nhận một phiên bản hình ảnh 512 x 512 thấp hơn, và đại diện cho hình ảnh với ngân sách 65 token. Điều này cho phép API trả về phản hồi nhanh hơn và tiêu thụ ít token đầu vào cho các trường hợp sử dụng không yêu cầu chi tiết cao.\nĐộ phân giải cao sẽ đầu tiên cho phép mô hình nhìn thấy hình ảnh thấp hơn và sau đó tạo ra các cắt chi tiết của hình ảnh đầu vào dưới dạng hình vuông 512px dựa trên kích thước hình ảnh đầu vào. Mỗi cắt chi tiết sử dụng hai lần ngân sách token cho tổng cộng 129 token.', high: 'Cao', low: 'Thấp', uploadMethod: 'Phương thức tải lên', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index e3df0ea9ea..5d6c2842a5 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -444,9 +444,7 @@ const translation = { visionSettings: { title: '视觉设置', resolution: '分辨率', - resolutionTooltip: `低分辨率模式将使模型接收图像的低分辨率版本,尺寸为 512 x 512,并使用 65 Tokens 来表示图像。这样可以使 API 更快地返回响应,并在不需要高细节的用例中消耗更少的输入。 - \n - 高分辨率模式将首先允许模型查看低分辨率图像,然后根据输入图像的大小创建 512 像素的详细裁剪图像。每个详细裁剪图像使用两倍的预算总共为 129 Tokens。`, + resolutionTooltip: '低分辨率模式将使模型接收图像的低分辨率版本,尺寸为 512 x 512,并使用 65 Tokens 来表示图像。这样可以使 API 更快地返回响应,并在不需要高细节的用例中消耗更少的输入。\n高分辨率模式将首先允许模型查看低分辨率图像,然后根据输入图像的大小创建 512 像素的详细裁剪图像。每个详细裁剪图像使用两倍的预算总共为 129 Tokens。', high: '高', low: '低', uploadMethod: '上传方式', diff --git a/web/i18n/zh-Hant/app-debug.ts b/web/i18n/zh-Hant/app-debug.ts index b66fcb9816..78b179097d 100644 --- a/web/i18n/zh-Hant/app-debug.ts +++ b/web/i18n/zh-Hant/app-debug.ts @@ -353,9 +353,7 @@ const translation = { visionSettings: { title: '視覺設定', resolution: '解析度', - resolutionTooltip: `低解析度模式將使模型接收影象的低解析度版本,尺寸為 512 x 512,並使用 65 Tokens 來表示影象。這樣可以使 API 更快地返回響應,並在不需要高細節的用例中消耗更少的輸入。 - \n - 高解析度模式將首先允許模型檢視低解析度影象,然後根據輸入影象的大小建立 512 畫素的詳細裁剪影象。每個詳細裁剪影象使用兩倍的預算總共為 129 Tokens。`, + resolutionTooltip: '低解析度模式將使模型接收影象的低解析度版本,尺寸為 512 x 512,並使用 65 Tokens 來表示影象。這樣可以使 API 更快地返回響應,並在不需要高細節的用例中消耗更少的輸入。\n高解析度模式將首先允許模型檢視低解析度影象,然後根據輸入影象的大小建立 512 畫素的詳細裁剪影象。每個詳細裁剪影象使用兩倍的預算總共為 129 Tokens。', high: '高', low: '低', uploadMethod: '上傳方式', From 2f9d718997393e38e369a8ab378ad12a8b89a743 Mon Sep 17 00:00:00 2001 From: wangxiaolei <fatelei@gmail.com> Date: Wed, 24 Dec 2025 17:23:30 +0800 Subject: [PATCH 64/71] fix: fix use build_request lead unexpect param (#30095) --- api/core/helper/ssrf_proxy.py | 10 ++- .../unit_tests/core/helper/test_ssrf_proxy.py | 61 ++----------------- 2 files changed, 8 insertions(+), 63 deletions(-) diff --git a/api/core/helper/ssrf_proxy.py b/api/core/helper/ssrf_proxy.py index f2172e4e2f..0b36969cf9 100644 --- a/api/core/helper/ssrf_proxy.py +++ b/api/core/helper/ssrf_proxy.py @@ -118,13 +118,11 @@ def make_request(method, url, max_retries=SSRF_DEFAULT_MAX_RETRIES, **kwargs): # Build the request manually to preserve the Host header # httpx may override the Host header when using a proxy, so we use # the request API to explicitly set headers before sending - request = client.build_request(method=method, url=url, **kwargs) - - # If user explicitly provided a Host header, ensure it's preserved + headers = {k: v for k, v in headers.items() if k.lower() != "host"} if user_provided_host is not None: - request.headers["Host"] = user_provided_host - - response = client.send(request) + headers["host"] = user_provided_host + kwargs["headers"] = headers + response = client.request(method=method, url=url, **kwargs) # Check for SSRF protection by Squid proxy if response.status_code in (401, 403): diff --git a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py index d5bc3283fe..beae1d0358 100644 --- a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py +++ b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py @@ -1,11 +1,9 @@ -import secrets from unittest.mock import MagicMock, patch import pytest from core.helper.ssrf_proxy import ( SSRF_DEFAULT_MAX_RETRIES, - STATUS_FORCELIST, _get_user_provided_host_header, make_request, ) @@ -14,11 +12,10 @@ from core.helper.ssrf_proxy import ( @patch("core.helper.ssrf_proxy._get_ssrf_client") def test_successful_request(mock_get_client): mock_client = MagicMock() - mock_request = MagicMock() mock_response = MagicMock() mock_response.status_code = 200 mock_client.send.return_value = mock_response - mock_client.build_request.return_value = mock_request + mock_client.request.return_value = mock_response mock_get_client.return_value = mock_client response = make_request("GET", "http://example.com") @@ -28,11 +25,10 @@ def test_successful_request(mock_get_client): @patch("core.helper.ssrf_proxy._get_ssrf_client") def test_retry_exceed_max_retries(mock_get_client): mock_client = MagicMock() - mock_request = MagicMock() mock_response = MagicMock() mock_response.status_code = 500 mock_client.send.return_value = mock_response - mock_client.build_request.return_value = mock_request + mock_client.request.return_value = mock_response mock_get_client.return_value = mock_client with pytest.raises(Exception) as e: @@ -40,32 +36,6 @@ def test_retry_exceed_max_retries(mock_get_client): assert str(e.value) == f"Reached maximum retries ({SSRF_DEFAULT_MAX_RETRIES - 1}) for URL http://example.com" -@patch("core.helper.ssrf_proxy._get_ssrf_client") -def test_retry_logic_success(mock_get_client): - mock_client = MagicMock() - mock_request = MagicMock() - mock_response = MagicMock() - mock_response.status_code = 200 - - side_effects = [] - for _ in range(SSRF_DEFAULT_MAX_RETRIES): - status_code = secrets.choice(STATUS_FORCELIST) - retry_response = MagicMock() - retry_response.status_code = status_code - side_effects.append(retry_response) - - side_effects.append(mock_response) - mock_client.send.side_effect = side_effects - mock_client.build_request.return_value = mock_request - mock_get_client.return_value = mock_client - - response = make_request("GET", "http://example.com", max_retries=SSRF_DEFAULT_MAX_RETRIES) - - assert response.status_code == 200 - assert mock_client.send.call_count == SSRF_DEFAULT_MAX_RETRIES + 1 - assert mock_client.build_request.call_count == SSRF_DEFAULT_MAX_RETRIES + 1 - - class TestGetUserProvidedHostHeader: """Tests for _get_user_provided_host_header function.""" @@ -111,14 +81,12 @@ def test_host_header_preservation_without_user_header(mock_get_client): mock_response = MagicMock() mock_response.status_code = 200 mock_client.send.return_value = mock_response - mock_client.build_request.return_value = mock_request + mock_client.request.return_value = mock_response mock_get_client.return_value = mock_client response = make_request("GET", "http://example.com") assert response.status_code == 200 - # build_request should be called without headers dict containing Host - mock_client.build_request.assert_called_once() # Host should not be set if not provided by user assert "Host" not in mock_request.headers or mock_request.headers.get("Host") is None @@ -132,31 +100,10 @@ def test_host_header_preservation_with_user_header(mock_get_client): mock_response = MagicMock() mock_response.status_code = 200 mock_client.send.return_value = mock_response - mock_client.build_request.return_value = mock_request + mock_client.request.return_value = mock_response mock_get_client.return_value = mock_client custom_host = "custom.example.com:8080" response = make_request("GET", "http://example.com", headers={"Host": custom_host}) assert response.status_code == 200 - # Verify build_request was called - mock_client.build_request.assert_called_once() - # Verify the Host header was set on the request object - assert mock_request.headers.get("Host") == custom_host - mock_client.send.assert_called_once_with(mock_request) - - -@patch("core.helper.ssrf_proxy._get_ssrf_client") -@pytest.mark.parametrize("host_key", ["host", "HOST"]) -def test_host_header_preservation_case_insensitive(mock_get_client, host_key): - """Test that Host header is preserved regardless of case.""" - mock_client = MagicMock() - mock_request = MagicMock() - mock_request.headers = {} - mock_response = MagicMock() - mock_response.status_code = 200 - mock_client.send.return_value = mock_response - mock_client.build_request.return_value = mock_request - mock_get_client.return_value = mock_client - response = make_request("GET", "http://example.com", headers={host_key: "api.example.com"}) - assert mock_request.headers.get("Host") == "api.example.com" From 64a14dcdbcc79498a22a36f592c711c0f28049f0 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:20:36 +0800 Subject: [PATCH 65/71] fix(web): remove incorrect placeholderData usage in useExploreAppList (#30102) --- .../app/create-app-dialog/app-list/index.tsx | 14 +++++++---- .../explore/app-list/index.spec.tsx | 18 ++++++++++---- web/app/components/explore/app-list/index.tsx | 24 ++++++++++++------- web/service/use-explore.ts | 6 ----- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index ee64478141..0df13e1ba1 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -24,7 +24,7 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { DSLImportMode } from '@/models/app' import { importDSL } from '@/service/apps' import { fetchAppDetail } from '@/service/explore' -import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' +import { useExploreAppList } from '@/service/use-explore' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { cn } from '@/utils/classnames' @@ -70,10 +70,14 @@ const Apps = ({ }) const { - data: { categories, allList } = exploreAppListInitialData, + data, + isLoading, } = useExploreAppList() const filteredList = useMemo(() => { + if (!data) + return [] + const { allList } = data const filteredByCategory = allList.filter((item) => { if (currCategory === allCategoriesEn) return true @@ -94,7 +98,7 @@ const Apps = ({ return true return false }) - }, [currentType, currCategory, allCategoriesEn, allList]) + }, [currentType, currCategory, allCategoriesEn, data]) const searchFilteredList = useMemo(() => { if (!searchKeywords || !filteredList || filteredList.length === 0) @@ -156,7 +160,7 @@ const Apps = ({ } } - if (!categories || categories.length === 0) { + if (isLoading) { return ( <div className="flex h-full items-center"> <Loading type="area" /> @@ -190,7 +194,7 @@ const Apps = ({ <div className="relative flex flex-1 overflow-y-auto"> {!searchKeywords && ( <div className="h-full w-[200px] p-4"> - <Sidebar current={currCategory as AppCategories} categories={categories} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> + <Sidebar current={currCategory as AppCategories} categories={data?.categories || []} onClick={(category) => { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> </div> )} <div className="h-full flex-1 shrink-0 grow overflow-auto border-l border-divider-burn p-6 pt-2"> diff --git a/web/app/components/explore/app-list/index.spec.tsx b/web/app/components/explore/app-list/index.spec.tsx index ebf2c9c075..c84da1931c 100644 --- a/web/app/components/explore/app-list/index.spec.tsx +++ b/web/app/components/explore/app-list/index.spec.tsx @@ -10,7 +10,9 @@ import AppList from './index' const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en"}' let mockTabValue = allCategoriesEn const mockSetTab = vi.fn() -let mockExploreData: { categories: string[], allList: App[] } = { categories: [], allList: [] } +let mockExploreData: { categories: string[], allList: App[] } | undefined = { categories: [], allList: [] } +let mockIsLoading = false +let mockIsError = false const mockHandleImportDSL = vi.fn() const mockHandleImportDSLConfirm = vi.fn() @@ -34,8 +36,11 @@ vi.mock('ahooks', async () => { }) vi.mock('@/service/use-explore', () => ({ - exploreAppListInitialData: { categories: [], allList: [] }, - useExploreAppList: () => ({ data: mockExploreData }), + useExploreAppList: () => ({ + data: mockExploreData, + isLoading: mockIsLoading, + isError: mockIsError, + }), })) vi.mock('@/service/explore', () => ({ @@ -136,13 +141,16 @@ describe('AppList', () => { vi.clearAllMocks() mockTabValue = allCategoriesEn mockExploreData = { categories: [], allList: [] } + mockIsLoading = false + mockIsError = false }) // Rendering: show loading when categories are not ready. describe('Rendering', () => { - it('should render loading when categories are empty', () => { + it('should render loading when the query is loading', () => { // Arrange - mockExploreData = { categories: [], allList: [] } + mockExploreData = undefined + mockIsLoading = true // Act renderWithContext() diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 244a116e36..5ab68f9b04 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -20,7 +20,7 @@ import { DSLImportMode, } from '@/models/app' import { fetchAppDetail } from '@/service/explore' -import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' +import { useExploreAppList } from '@/service/use-explore' import { cn } from '@/utils/classnames' import s from './style.module.css' @@ -28,11 +28,6 @@ type AppsProps = { onSuccess?: () => void } -export enum PageType { - EXPLORE = 'explore', - CREATE = 'create', -} - const Apps = ({ onSuccess, }: AppsProps) => { @@ -58,10 +53,16 @@ const Apps = ({ }) const { - data: { categories, allList } = exploreAppListInitialData, + data, + isLoading, + isError, } = useExploreAppList() - const filteredList = allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) + const filteredList = useMemo(() => { + if (!data) + return [] + return data.allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) + }, [data, currCategory, allCategoriesEn]) const searchFilteredList = useMemo(() => { if (!searchKeywords || !filteredList || filteredList.length === 0) @@ -119,7 +120,7 @@ const Apps = ({ }) }, [handleImportDSLConfirm, onSuccess]) - if (!categories || categories.length === 0) { + if (isLoading) { return ( <div className="flex h-full items-center"> <Loading type="area" /> @@ -127,6 +128,11 @@ const Apps = ({ ) } + if (isError || !data) + return null + + const { categories } = data + return ( <div className={cn( 'flex h-full flex-col border-l-[0.5px] border-divider-regular', diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index 8bda877908..68ddf966ab 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -12,11 +12,6 @@ type ExploreAppListData = { allList: App[] } -export const exploreAppListInitialData: ExploreAppListData = { - categories: [], - allList: [], -} - export const useExploreAppList = () => { return useQuery<ExploreAppListData>({ queryKey: [NAME_SPACE, 'appList'], @@ -27,7 +22,6 @@ export const useExploreAppList = () => { allList: [...recommended_apps].sort((a, b) => a.position - b.position), } }, - placeholderData: exploreAppListInitialData, }) } From 5896bc89f5e2d1f939fc5a3b35c63b43341a6121 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:21:01 +0800 Subject: [PATCH 66/71] refactor(web): migrate workflow run history from useSWR to TanStack Query (#30077) --- .../components/rag-pipeline-header/index.tsx | 2 -- .../components/workflow-header/index.spec.tsx | 11 -------- .../components/workflow-header/index.tsx | 4 --- .../workflow/header/view-history.tsx | 11 +++----- web/service/use-workflow.ts | 9 +++++++ web/service/workflow.ts | 27 +++++-------------- 6 files changed, 19 insertions(+), 45 deletions(-) diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx index fff720469c..fe2490f281 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx @@ -8,7 +8,6 @@ import Header from '@/app/components/workflow/header' import { useStore, } from '@/app/components/workflow/store' -import { fetchWorkflowRunHistory } from '@/service/workflow' import InputFieldButton from './input-field-button' import Publisher from './publisher' import RunMode from './run-mode' @@ -21,7 +20,6 @@ const RagPipelineHeader = () => { const viewHistoryProps = useMemo(() => { return { historyUrl: `/rag/pipelines/${pipelineId}/workflow-runs`, - historyFetcher: fetchWorkflowRunHistory, } }, [pipelineId]) diff --git a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx index 87d7fb30e7..5563af01d3 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx @@ -58,16 +58,12 @@ vi.mock('@/app/components/app/store', () => ({ vi.mock('@/app/components/workflow/header', () => ({ __esModule: true, default: (props: HeaderProps) => { - const historyFetcher = props.normal?.runAndHistoryProps?.viewHistoryProps?.historyFetcher - const hasHistoryFetcher = typeof historyFetcher === 'function' - return ( <div data-testid="workflow-header" data-show-run={String(Boolean(props.normal?.runAndHistoryProps?.showRunButton))} data-show-preview={String(Boolean(props.normal?.runAndHistoryProps?.showPreviewButton))} data-history-url={props.normal?.runAndHistoryProps?.viewHistoryProps?.historyUrl ?? ''} - data-has-history-fetcher={String(hasHistoryFetcher)} > <button type="button" @@ -86,11 +82,6 @@ vi.mock('@/app/components/workflow/header', () => ({ }, })) -vi.mock('@/service/workflow', () => ({ - __esModule: true, - fetchWorkflowRunHistory: vi.fn(), -})) - vi.mock('@/service/use-workflow', () => ({ __esModule: true, useResetWorkflowVersionHistory: () => mockResetWorkflowVersionHistory, @@ -127,7 +118,6 @@ describe('WorkflowHeader', () => { expect(header).toHaveAttribute('data-show-run', 'false') expect(header).toHaveAttribute('data-show-preview', 'true') expect(header).toHaveAttribute('data-history-url', '/apps/app-id/advanced-chat/workflow-runs') - expect(header).toHaveAttribute('data-has-history-fetcher', 'true') }) it('should configure run mode when app is not in advanced chat mode', () => { @@ -142,7 +132,6 @@ describe('WorkflowHeader', () => { expect(header).toHaveAttribute('data-show-run', 'true') expect(header).toHaveAttribute('data-show-preview', 'false') expect(header).toHaveAttribute('data-history-url', '/apps/app-id/workflow-runs') - expect(header).toHaveAttribute('data-has-history-fetcher', 'true') }) }) diff --git a/web/app/components/workflow-app/components/workflow-header/index.tsx b/web/app/components/workflow-app/components/workflow-header/index.tsx index 4acb721487..3fe679925a 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.tsx @@ -8,9 +8,6 @@ import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' import Header from '@/app/components/workflow/header' import { useResetWorkflowVersionHistory } from '@/service/use-workflow' -import { - fetchWorkflowRunHistory, -} from '@/service/workflow' import { useIsChatMode } from '../../hooks' import ChatVariableTrigger from './chat-variable-trigger' import FeaturesTrigger from './features-trigger' @@ -33,7 +30,6 @@ const WorkflowHeader = () => { return { onClearLogAndMessageModal: handleClearLogAndMessageModal, historyUrl: isChatMode ? `/apps/${appDetail!.id}/advanced-chat/workflow-runs` : `/apps/${appDetail!.id}/workflow-runs`, - historyFetcher: fetchWorkflowRunHistory, } }, [appDetail, isChatMode, handleClearLogAndMessageModal]) diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 63a2dd25ab..f6a2d207a4 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -1,17 +1,13 @@ -import type { Fetcher } from 'swr' -import type { WorkflowRunHistoryResponse } from '@/types/workflow' import { RiCheckboxCircleLine, RiCloseLine, RiErrorWarningLine, } from '@remixicon/react' -import { noop } from 'lodash-es' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' import { ClockPlay, @@ -30,6 +26,7 @@ import { useWorkflowStore, } from '@/app/components/workflow/store' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' +import { useWorkflowRunHistory } from '@/service/use-workflow' import { cn } from '@/utils/classnames' import { useIsChatMode, @@ -44,13 +41,11 @@ export type ViewHistoryProps = { withText?: boolean onClearLogAndMessageModal?: () => void historyUrl?: string - historyFetcher?: Fetcher<WorkflowRunHistoryResponse, string> } const ViewHistory = ({ withText, onClearLogAndMessageModal, historyUrl, - historyFetcher, }: ViewHistoryProps) => { const { t } = useTranslation() const isChatMode = useIsChatMode() @@ -68,11 +63,11 @@ const ViewHistory = ({ const { handleBackupDraft } = useWorkflowRun() const { closeAllInputFieldPanels } = useInputFieldPanel() - const fetcher = historyFetcher ?? (noop as Fetcher<WorkflowRunHistoryResponse, string>) + const shouldFetchHistory = open && !!historyUrl const { data, isLoading, - } = useSWR((open && historyUrl && historyFetcher) ? historyUrl : null, fetcher) + } = useWorkflowRunHistory(historyUrl, shouldFetchHistory) return ( ( diff --git a/web/service/use-workflow.ts b/web/service/use-workflow.ts index f5c3021c92..754fb6b003 100644 --- a/web/service/use-workflow.ts +++ b/web/service/use-workflow.ts @@ -9,6 +9,7 @@ import type { UpdateWorkflowParams, VarInInspect, WorkflowConfigResponse, + WorkflowRunHistoryResponse, } from '@/types/workflow' import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { del, get, patch, post, put } from './base' @@ -25,6 +26,14 @@ export const useAppWorkflow = (appID: string) => { }) } +export const useWorkflowRunHistory = (url?: string, enabled = true) => { + return useQuery<WorkflowRunHistoryResponse>({ + queryKey: [NAME_SPACE, 'runHistory', url], + queryFn: () => get<WorkflowRunHistoryResponse>(url as string), + enabled: !!url && enabled, + }) +} + export const useInvalidateAppWorkflow = () => { const queryClient = useQueryClient() return (appID: string) => { diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 96af869ba5..7571e804a9 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -1,14 +1,11 @@ -import type { Fetcher } from 'swr' import type { BlockEnum } from '@/app/components/workflow/types' import type { CommonResponse } from '@/models/common' import type { FlowType } from '@/types/common' import type { - ChatRunHistoryResponse, ConversationVariableResponse, FetchWorkflowDraftResponse, NodesDefaultConfigsResponse, VarInInspect, - WorkflowRunHistoryResponse, } from '@/types/workflow' import { get, post } from './base' import { getFlowPrefix } from './utils' @@ -24,18 +21,10 @@ export const syncWorkflowDraft = ({ url, params }: { return post<CommonResponse & { updated_at: number, hash: string }>(url, { body: params }, { silent: true }) } -export const fetchNodesDefaultConfigs: Fetcher<NodesDefaultConfigsResponse, string> = (url) => { +export const fetchNodesDefaultConfigs = (url: string) => { return get<NodesDefaultConfigsResponse>(url) } -export const fetchWorkflowRunHistory: Fetcher<WorkflowRunHistoryResponse, string> = (url) => { - return get<WorkflowRunHistoryResponse>(url) -} - -export const fetchChatRunHistory: Fetcher<ChatRunHistoryResponse, string> = (url) => { - return get<ChatRunHistoryResponse>(url) -} - export const singleNodeRun = (flowType: FlowType, flowId: string, nodeId: string, params: object) => { return post(`${getFlowPrefix(flowType)}/${flowId}/workflows/draft/nodes/${nodeId}/run`, { body: params }) } @@ -48,7 +37,7 @@ export const getLoopSingleNodeRunUrl = (flowType: FlowType, isChatFlow: boolean, return `${getFlowPrefix(flowType)}/${flowId}/${isChatFlow ? 'advanced-chat/' : ''}workflows/draft/loop/nodes/${nodeId}/run` } -export const fetchPublishedWorkflow: Fetcher<FetchWorkflowDraftResponse, string> = (url) => { +export const fetchPublishedWorkflow = (url: string) => { return get<FetchWorkflowDraftResponse>(url) } @@ -68,15 +57,13 @@ export const fetchPipelineNodeDefault = (pipelineId: string, blockType: BlockEnu }) } -// TODO: archived -export const updateWorkflowDraftFromDSL = (appId: string, data: string) => { - return post<FetchWorkflowDraftResponse>(`apps/${appId}/workflows/draft/import`, { body: { data } }) -} - -export const fetchCurrentValueOfConversationVariable: Fetcher<ConversationVariableResponse, { +export const fetchCurrentValueOfConversationVariable = ({ + url, + params, +}: { url: string params: { conversation_id: string } -}> = ({ url, params }) => { +}) => { return get<ConversationVariableResponse>(url, { params }) } From 02e0fadef78f0c888b79e7a52ae6714ac73effdc Mon Sep 17 00:00:00 2001 From: Maries <xh001x@hotmail.com> Date: Wed, 24 Dec 2025 19:15:54 +0800 Subject: [PATCH 67/71] feat: add editing support for trigger subscriptions (#29957) Co-authored-by: yyh <yuanyouhuilyz@gmail.com> --- api/controllers/console/workspace/plugin.py | 44 ++- .../console/workspace/trigger_providers.py | 148 +++++++- api/core/trigger/utils/encryption.py | 10 +- .../plugin/plugin_parameter_service.py | 46 +++ .../trigger/trigger_provider_service.py | 278 ++++++++++++-- .../trigger_subscription_builder_service.py | 9 +- .../plugins/plugin-detail-panel/index.tsx | 4 +- .../subscription-list/create/common-modal.tsx | 47 ++- .../subscription-list/create/oauth-client.tsx | 31 +- .../edit/apikey-edit-modal.tsx | 349 ++++++++++++++++++ .../subscription-list/edit/index.tsx | 28 ++ .../edit/manual-edit-modal.tsx | 164 ++++++++ .../edit/oauth-edit-modal.tsx | 178 +++++++++ .../subscription-list/index.tsx | 18 +- .../subscription-list/list-view.tsx | 4 + .../subscription-list/selector-entry.tsx | 5 +- .../subscription-list/subscription-card.tsx | 38 +- .../subscription-list/types.ts | 9 + web/app/components/plugins/types.ts | 2 +- .../workflow/block-selector/types.ts | 59 ++- .../hooks/use-trigger-auth-flow.ts | 39 +- web/i18n/en-US/common.ts | 1 + web/i18n/en-US/plugin-trigger.ts | 5 + web/service/use-triggers.ts | 105 +++++- 24 files changed, 1465 insertions(+), 156 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/edit/index.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py index 805058ba5a..ea74fc0337 100644 --- a/api/controllers/console/workspace/plugin.py +++ b/api/controllers/console/workspace/plugin.py @@ -1,5 +1,6 @@ import io -from typing import Literal +from collections.abc import Mapping +from typing import Any, Literal from flask import request, send_file from flask_restx import Resource @@ -141,6 +142,15 @@ class ParserDynamicOptions(BaseModel): provider_type: Literal["tool", "trigger"] +class ParserDynamicOptionsWithCredentials(BaseModel): + plugin_id: str + provider: str + action: str + parameter: str + credential_id: str + credentials: Mapping[str, Any] + + class PluginPermissionSettingsPayload(BaseModel): install_permission: TenantPluginPermission.InstallPermission = TenantPluginPermission.InstallPermission.EVERYONE debug_permission: TenantPluginPermission.DebugPermission = TenantPluginPermission.DebugPermission.EVERYONE @@ -183,6 +193,7 @@ reg(ParserGithubUpgrade) reg(ParserUninstall) reg(ParserPermissionChange) reg(ParserDynamicOptions) +reg(ParserDynamicOptionsWithCredentials) reg(ParserPreferencesChange) reg(ParserExcludePlugin) reg(ParserReadme) @@ -657,6 +668,37 @@ class PluginFetchDynamicSelectOptionsApi(Resource): return jsonable_encoder({"options": options}) +@console_ns.route("/workspaces/current/plugin/parameters/dynamic-options-with-credentials") +class PluginFetchDynamicSelectOptionsWithCredentialsApi(Resource): + @console_ns.expect(console_ns.models[ParserDynamicOptionsWithCredentials.__name__]) + @setup_required + @login_required + @is_admin_or_owner_required + @account_initialization_required + def post(self): + """Fetch dynamic options using credentials directly (for edit mode).""" + current_user, tenant_id = current_account_with_tenant() + user_id = current_user.id + + args = ParserDynamicOptionsWithCredentials.model_validate(console_ns.payload) + + try: + options = PluginParameterService.get_dynamic_select_options_with_credentials( + tenant_id=tenant_id, + user_id=user_id, + plugin_id=args.plugin_id, + provider=args.provider, + action=args.action, + parameter=args.parameter, + credential_id=args.credential_id, + credentials=args.credentials, + ) + except PluginDaemonClientSideError as e: + raise ValueError(e) + + return jsonable_encoder({"options": options}) + + @console_ns.route("/workspaces/current/plugin/preferences/change") class PluginChangePreferencesApi(Resource): @console_ns.expect(console_ns.models[ParserPreferencesChange.__name__]) diff --git a/api/controllers/console/workspace/trigger_providers.py b/api/controllers/console/workspace/trigger_providers.py index 268473d6d1..497e62b790 100644 --- a/api/controllers/console/workspace/trigger_providers.py +++ b/api/controllers/console/workspace/trigger_providers.py @@ -1,11 +1,15 @@ import logging +from collections.abc import Mapping +from typing import Any from flask import make_response, redirect, request from flask_restx import Resource, reqparse +from pydantic import BaseModel, Field from sqlalchemy.orm import Session from werkzeug.exceptions import BadRequest, Forbidden from configs import dify_config +from constants import HIDDEN_VALUE, UNKNOWN_VALUE from controllers.web.error import NotFoundError from core.model_runtime.utils.encoders import jsonable_encoder from core.plugin.entities.plugin_daemon import CredentialType @@ -32,6 +36,32 @@ from ..wraps import ( logger = logging.getLogger(__name__) +class TriggerSubscriptionUpdateRequest(BaseModel): + """Request payload for updating a trigger subscription""" + + name: str | None = Field(default=None, description="The name for the subscription") + credentials: Mapping[str, Any] | None = Field(default=None, description="The credentials for the subscription") + parameters: Mapping[str, Any] | None = Field(default=None, description="The parameters for the subscription") + properties: Mapping[str, Any] | None = Field(default=None, description="The properties for the subscription") + + +class TriggerSubscriptionVerifyRequest(BaseModel): + """Request payload for verifying subscription credentials.""" + + credentials: Mapping[str, Any] = Field(description="The credentials to verify") + + +console_ns.schema_model( + TriggerSubscriptionUpdateRequest.__name__, + TriggerSubscriptionUpdateRequest.model_json_schema(ref_template="#/definitions/{model}"), +) + +console_ns.schema_model( + TriggerSubscriptionVerifyRequest.__name__, + TriggerSubscriptionVerifyRequest.model_json_schema(ref_template="#/definitions/{model}"), +) + + @console_ns.route("/workspaces/current/trigger-provider/<path:provider>/icon") class TriggerProviderIconApi(Resource): @setup_required @@ -155,16 +185,16 @@ parser_api = ( @console_ns.route( - "/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/verify/<path:subscription_builder_id>", + "/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/verify-and-update/<path:subscription_builder_id>", ) -class TriggerSubscriptionBuilderVerifyApi(Resource): +class TriggerSubscriptionBuilderVerifyAndUpdateApi(Resource): @console_ns.expect(parser_api) @setup_required @login_required @edit_permission_required @account_initialization_required def post(self, provider, subscription_builder_id): - """Verify a subscription instance for a trigger provider""" + """Verify and update a subscription instance for a trigger provider""" user = current_user assert user.current_tenant_id is not None @@ -289,6 +319,83 @@ class TriggerSubscriptionBuilderBuildApi(Resource): raise ValueError(str(e)) from e +@console_ns.route( + "/workspaces/current/trigger-provider/<path:subscription_id>/subscriptions/update", +) +class TriggerSubscriptionUpdateApi(Resource): + @console_ns.expect(console_ns.models[TriggerSubscriptionUpdateRequest.__name__]) + @setup_required + @login_required + @edit_permission_required + @account_initialization_required + def post(self, subscription_id: str): + """Update a subscription instance""" + user = current_user + assert user.current_tenant_id is not None + + args = TriggerSubscriptionUpdateRequest.model_validate(console_ns.payload) + + subscription = TriggerProviderService.get_subscription_by_id( + tenant_id=user.current_tenant_id, + subscription_id=subscription_id, + ) + if not subscription: + raise NotFoundError(f"Subscription {subscription_id} not found") + + provider_id = TriggerProviderID(subscription.provider_id) + + try: + # rename only + if ( + args.name is not None + and args.credentials is None + and args.parameters is None + and args.properties is None + ): + TriggerProviderService.update_trigger_subscription( + tenant_id=user.current_tenant_id, + subscription_id=subscription_id, + name=args.name, + ) + return 200 + + # rebuild for create automatically by the provider + match subscription.credential_type: + case CredentialType.UNAUTHORIZED: + TriggerProviderService.update_trigger_subscription( + tenant_id=user.current_tenant_id, + subscription_id=subscription_id, + name=args.name, + properties=args.properties, + ) + return 200 + case CredentialType.API_KEY | CredentialType.OAUTH2: + if args.credentials: + new_credentials: dict[str, Any] = { + key: value if value != HIDDEN_VALUE else subscription.credentials.get(key, UNKNOWN_VALUE) + for key, value in args.credentials.items() + } + else: + new_credentials = subscription.credentials + + TriggerProviderService.rebuild_trigger_subscription( + tenant_id=user.current_tenant_id, + name=args.name, + provider_id=provider_id, + subscription_id=subscription_id, + credentials=new_credentials, + parameters=args.parameters or subscription.parameters, + ) + return 200 + case _: + raise BadRequest("Invalid credential type") + except ValueError as e: + raise BadRequest(str(e)) + except Exception as e: + logger.exception("Error updating subscription", exc_info=e) + raise + + @console_ns.route( "/workspaces/current/trigger-provider/<path:subscription_id>/subscriptions/delete", ) @@ -576,3 +683,38 @@ class TriggerOAuthClientManageApi(Resource): except Exception as e: logger.exception("Error removing OAuth client", exc_info=e) raise + + +@console_ns.route( + "/workspaces/current/trigger-provider/<path:provider>/subscriptions/verify/<path:subscription_id>", +) +class TriggerSubscriptionVerifyApi(Resource): + @console_ns.expect(console_ns.models[TriggerSubscriptionVerifyRequest.__name__]) + @setup_required + @login_required + @edit_permission_required + @account_initialization_required + def post(self, provider, subscription_id): + """Verify credentials for an existing subscription (edit mode only)""" + user = current_user + assert user.current_tenant_id is not None + + verify_request: TriggerSubscriptionVerifyRequest = TriggerSubscriptionVerifyRequest.model_validate( + console_ns.payload + ) + + try: + result = TriggerProviderService.verify_subscription_credentials( + tenant_id=user.current_tenant_id, + user_id=user.id, + provider_id=TriggerProviderID(provider), + subscription_id=subscription_id, + credentials=verify_request.credentials, + ) + return result + except ValueError as e: + logger.warning("Credential verification failed", exc_info=e) + raise BadRequest(str(e)) from e + except Exception as e: + logger.exception("Error verifying subscription credentials", exc_info=e) + raise BadRequest(str(e)) from e diff --git a/api/core/trigger/utils/encryption.py b/api/core/trigger/utils/encryption.py index 026a65aa23..b12291e299 100644 --- a/api/core/trigger/utils/encryption.py +++ b/api/core/trigger/utils/encryption.py @@ -67,12 +67,16 @@ def create_trigger_provider_encrypter_for_subscription( def delete_cache_for_subscription(tenant_id: str, provider_id: str, subscription_id: str): - cache = TriggerProviderCredentialsCache( + TriggerProviderCredentialsCache( tenant_id=tenant_id, provider_id=provider_id, credential_id=subscription_id, - ) - cache.delete() + ).delete() + TriggerProviderPropertiesCache( + tenant_id=tenant_id, + provider_id=provider_id, + subscription_id=subscription_id, + ).delete() def create_trigger_provider_encrypter_for_properties( diff --git a/api/services/plugin/plugin_parameter_service.py b/api/services/plugin/plugin_parameter_service.py index c517d9f966..5dcbf5fec5 100644 --- a/api/services/plugin/plugin_parameter_service.py +++ b/api/services/plugin/plugin_parameter_service.py @@ -105,3 +105,49 @@ class PluginParameterService: ) .options ) + + @staticmethod + def get_dynamic_select_options_with_credentials( + tenant_id: str, + user_id: str, + plugin_id: str, + provider: str, + action: str, + parameter: str, + credential_id: str, + credentials: Mapping[str, Any], + ) -> Sequence[PluginParameterOption]: + """ + Get dynamic select options using provided credentials directly. + Used for edit mode when credentials have been modified but not yet saved. + + Security: credential_id is validated against tenant_id to ensure + users can only access their own credentials. + """ + from constants import HIDDEN_VALUE + + # Get original subscription to replace hidden values (with tenant_id check for security) + original_subscription = TriggerProviderService.get_subscription_by_id(tenant_id, credential_id) + if not original_subscription: + raise ValueError(f"Subscription {credential_id} not found") + + # Replace [__HIDDEN__] with original values + resolved_credentials: dict[str, Any] = { + key: (original_subscription.credentials.get(key) if value == HIDDEN_VALUE else value) + for key, value in credentials.items() + } + + return ( + DynamicSelectClient() + .fetch_dynamic_select_options( + tenant_id, + user_id, + plugin_id, + provider, + action, + resolved_credentials, + CredentialType.API_KEY.value, + parameter, + ) + .options + ) diff --git a/api/services/trigger/trigger_provider_service.py b/api/services/trigger/trigger_provider_service.py index 668e4c5be2..71f35dada6 100644 --- a/api/services/trigger/trigger_provider_service.py +++ b/api/services/trigger/trigger_provider_service.py @@ -94,16 +94,23 @@ class TriggerProviderService: provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id) for subscription in subscriptions: - encrypter, _ = create_trigger_provider_encrypter_for_subscription( + credential_encrypter, _ = create_trigger_provider_encrypter_for_subscription( tenant_id=tenant_id, controller=provider_controller, subscription=subscription, ) subscription.credentials = dict( - encrypter.mask_credentials(dict(encrypter.decrypt(subscription.credentials))) + credential_encrypter.mask_credentials(dict(credential_encrypter.decrypt(subscription.credentials))) ) - subscription.properties = dict(encrypter.mask_credentials(dict(encrypter.decrypt(subscription.properties)))) - subscription.parameters = dict(encrypter.mask_credentials(dict(encrypter.decrypt(subscription.parameters)))) + properties_encrypter, _ = create_trigger_provider_encrypter_for_properties( + tenant_id=tenant_id, + controller=provider_controller, + subscription=subscription, + ) + subscription.properties = dict( + properties_encrypter.mask_credentials(dict(properties_encrypter.decrypt(subscription.properties))) + ) + subscription.parameters = dict(subscription.parameters) count = workflows_in_use_map.get(subscription.id) subscription.workflows_in_use = count if count is not None else 0 @@ -209,6 +216,101 @@ class TriggerProviderService: logger.exception("Failed to add trigger provider") raise ValueError(str(e)) + @classmethod + def update_trigger_subscription( + cls, + tenant_id: str, + subscription_id: str, + name: str | None = None, + properties: Mapping[str, Any] | None = None, + parameters: Mapping[str, Any] | None = None, + credentials: Mapping[str, Any] | None = None, + credential_expires_at: int | None = None, + expires_at: int | None = None, + ) -> None: + """ + Update an existing trigger subscription. + + :param tenant_id: Tenant ID + :param subscription_id: Subscription instance ID + :param name: Optional new name for this subscription + :param properties: Optional new properties + :param parameters: Optional new parameters + :param credentials: Optional new credentials + :param credential_expires_at: Optional new credential expiration timestamp + :param expires_at: Optional new expiration timestamp + :return: Success response with updated subscription info + """ + with Session(db.engine, expire_on_commit=False) as session: + # Use distributed lock to prevent race conditions on the same subscription + lock_key = f"trigger_subscription_update_lock:{tenant_id}_{subscription_id}" + with redis_client.lock(lock_key, timeout=20): + subscription: TriggerSubscription | None = ( + session.query(TriggerSubscription).filter_by(tenant_id=tenant_id, id=subscription_id).first() + ) + if not subscription: + raise ValueError(f"Trigger subscription {subscription_id} not found") + + provider_id = TriggerProviderID(subscription.provider_id) + provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id) + + # Check for name uniqueness if name is being updated + if name is not None and name != subscription.name: + existing = ( + session.query(TriggerSubscription) + .filter_by(tenant_id=tenant_id, provider_id=str(provider_id), name=name) + .first() + ) + if existing: + raise ValueError(f"Subscription name '{name}' already exists for this provider") + subscription.name = name + + # Update properties if provided + if properties is not None: + properties_encrypter, _ = create_provider_encrypter( + tenant_id=tenant_id, + config=provider_controller.get_properties_schema(), + cache=NoOpProviderCredentialCache(), + ) + # Handle hidden values - preserve original encrypted values + original_properties = properties_encrypter.decrypt(subscription.properties) + new_properties: dict[str, Any] = { + key: value if value != HIDDEN_VALUE else original_properties.get(key, UNKNOWN_VALUE) + for key, value in properties.items() + } + subscription.properties = dict(properties_encrypter.encrypt(new_properties)) + + # Update parameters if provided + if parameters is not None: + subscription.parameters = dict(parameters) + + # Update credentials if provided + if credentials is not None: + credential_type = CredentialType.of(subscription.credential_type) + credential_encrypter, _ = create_provider_encrypter( + tenant_id=tenant_id, + config=provider_controller.get_credential_schema_config(credential_type), + cache=NoOpProviderCredentialCache(), + ) + subscription.credentials = dict(credential_encrypter.encrypt(dict(credentials))) + + # Update credential expiration timestamp if provided + if credential_expires_at is not None: + subscription.credential_expires_at = credential_expires_at + + # Update expiration timestamp if provided + if expires_at is not None: + subscription.expires_at = expires_at + + session.commit() + + # Clear subscription cache + delete_cache_for_subscription( + tenant_id=tenant_id, + provider_id=subscription.provider_id, + subscription_id=subscription.id, + ) + @classmethod def get_subscription_by_id(cls, tenant_id: str, subscription_id: str | None = None) -> TriggerSubscription | None: """ @@ -258,30 +360,32 @@ class TriggerProviderService: credential_type: CredentialType = CredentialType.of(subscription.credential_type) is_auto_created: bool = credential_type in [CredentialType.OAUTH2, CredentialType.API_KEY] - if is_auto_created: - provider_id = TriggerProviderID(subscription.provider_id) - provider_controller: PluginTriggerProviderController = TriggerManager.get_trigger_provider( - tenant_id=tenant_id, provider_id=provider_id - ) - encrypter, _ = create_trigger_provider_encrypter_for_subscription( - tenant_id=tenant_id, - controller=provider_controller, - subscription=subscription, - ) - try: - TriggerManager.unsubscribe_trigger( - tenant_id=tenant_id, - user_id=subscription.user_id, - provider_id=provider_id, - subscription=subscription.to_entity(), - credentials=encrypter.decrypt(subscription.credentials), - credential_type=credential_type, - ) - except Exception as e: - logger.exception("Error unsubscribing trigger", exc_info=e) + if not is_auto_created: + return None + + provider_id = TriggerProviderID(subscription.provider_id) + provider_controller: PluginTriggerProviderController = TriggerManager.get_trigger_provider( + tenant_id=tenant_id, provider_id=provider_id + ) + encrypter, _ = create_trigger_provider_encrypter_for_subscription( + tenant_id=tenant_id, + controller=provider_controller, + subscription=subscription, + ) + try: + TriggerManager.unsubscribe_trigger( + tenant_id=tenant_id, + user_id=subscription.user_id, + provider_id=provider_id, + subscription=subscription.to_entity(), + credentials=encrypter.decrypt(subscription.credentials), + credential_type=credential_type, + ) + except Exception as e: + logger.exception("Error unsubscribing trigger", exc_info=e) - # Clear cache session.delete(subscription) + # Clear cache delete_cache_for_subscription( tenant_id=tenant_id, provider_id=subscription.provider_id, @@ -688,3 +792,125 @@ class TriggerProviderService: ) subscription.properties = dict(properties_encrypter.decrypt(subscription.properties)) return subscription + + @classmethod + def verify_subscription_credentials( + cls, + tenant_id: str, + user_id: str, + provider_id: TriggerProviderID, + subscription_id: str, + credentials: Mapping[str, Any], + ) -> dict[str, Any]: + """ + Verify credentials for an existing subscription without updating it. + + This is used in edit mode to validate new credentials before rebuild. + + :param tenant_id: Tenant ID + :param user_id: User ID + :param provider_id: Provider identifier + :param subscription_id: Subscription ID + :param credentials: New credentials to verify + :return: dict with 'verified' boolean + """ + provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id) + if not provider_controller: + raise ValueError(f"Provider {provider_id} not found") + + subscription = cls.get_subscription_by_id( + tenant_id=tenant_id, + subscription_id=subscription_id, + ) + if not subscription: + raise ValueError(f"Subscription {subscription_id} not found") + + credential_type = CredentialType.of(subscription.credential_type) + + # For API Key, validate the new credentials + if credential_type == CredentialType.API_KEY: + new_credentials: dict[str, Any] = { + key: value if value != HIDDEN_VALUE else subscription.credentials.get(key, UNKNOWN_VALUE) + for key, value in credentials.items() + } + try: + provider_controller.validate_credentials(user_id, credentials=new_credentials) + return {"verified": True} + except Exception as e: + raise ValueError(f"Invalid credentials: {e}") from e + + return {"verified": True} + + @classmethod + def rebuild_trigger_subscription( + cls, + tenant_id: str, + provider_id: TriggerProviderID, + subscription_id: str, + credentials: Mapping[str, Any], + parameters: Mapping[str, Any], + name: str | None = None, + ) -> None: + """ + Create a subscription builder for rebuilding an existing subscription. + + This method creates a builder pre-filled with data from the rebuild request, + keeping the same subscription_id and endpoint_id so the webhook URL remains unchanged. + + :param tenant_id: Tenant ID + :param name: Name for the subscription + :param subscription_id: Subscription ID + :param provider_id: Provider identifier + :param credentials: Credentials for the subscription + :param parameters: Parameters for the subscription + :return: SubscriptionBuilderApiEntity + """ + provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id) + if not provider_controller: + raise ValueError(f"Provider {provider_id} not found") + + subscription = TriggerProviderService.get_subscription_by_id( + tenant_id=tenant_id, + subscription_id=subscription_id, + ) + if not subscription: + raise ValueError(f"Subscription {subscription_id} not found") + + credential_type = CredentialType.of(subscription.credential_type) + if credential_type not in [CredentialType.OAUTH2, CredentialType.API_KEY]: + raise ValueError("Credential type not supported for rebuild") + + # TODO: Trying to invoke update api of the plugin trigger provider + + # FALLBACK: If the update api is not implemented, delete the previous subscription and create a new one + + # Delete the previous subscription + user_id = subscription.user_id + TriggerManager.unsubscribe_trigger( + tenant_id=tenant_id, + user_id=user_id, + provider_id=provider_id, + subscription=subscription.to_entity(), + credentials=subscription.credentials, + credential_type=credential_type, + ) + + # Create a new subscription with the same subscription_id and endpoint_id + new_subscription: TriggerSubscriptionEntity = TriggerManager.subscribe_trigger( + tenant_id=tenant_id, + user_id=user_id, + provider_id=provider_id, + endpoint=generate_plugin_trigger_endpoint_url(subscription.endpoint_id), + parameters=parameters, + credentials=credentials, + credential_type=credential_type, + ) + TriggerProviderService.update_trigger_subscription( + tenant_id=tenant_id, + subscription_id=subscription.id, + name=name, + parameters=parameters, + credentials=credentials, + properties=new_subscription.properties, + expires_at=new_subscription.expires_at, + ) diff --git a/api/services/trigger/trigger_subscription_builder_service.py b/api/services/trigger/trigger_subscription_builder_service.py index 571393c782..37f852da3e 100644 --- a/api/services/trigger/trigger_subscription_builder_service.py +++ b/api/services/trigger/trigger_subscription_builder_service.py @@ -453,11 +453,12 @@ class TriggerSubscriptionBuilderService: if not subscription_builder: return None - # response to validation endpoint - controller: PluginTriggerProviderController = TriggerManager.get_trigger_provider( - tenant_id=subscription_builder.tenant_id, provider_id=TriggerProviderID(subscription_builder.provider_id) - ) try: + # response to validation endpoint + controller: PluginTriggerProviderController = TriggerManager.get_trigger_provider( + tenant_id=subscription_builder.tenant_id, + provider_id=TriggerProviderID(subscription_builder.provider_id), + ) dispatch_response: TriggerDispatchResponse = controller.dispatch( request=request, subscription=subscription_builder.to_subscription(), diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index 142c781579..dee6ab1722 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -46,7 +46,7 @@ const PluginDetailPanel: FC<Props> = ({ name: detail.name, id: detail.id, }) - }, [detail]) + }, [detail, setDetail]) if (!detail) return null @@ -69,7 +69,7 @@ const PluginDetailPanel: FC<Props> = ({ <div className="flex-1"> {detail.declaration.category === PluginCategoryEnum.trigger && ( <> - <SubscriptionList /> + <SubscriptionList pluginDetail={detail} /> <TriggerEventsList /> </> )} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx index 31e0bd6a85..b0625d1e82 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx @@ -20,7 +20,7 @@ import { useCreateTriggerSubscriptionBuilder, useTriggerSubscriptionBuilderLogs, useUpdateTriggerSubscriptionBuilder, - useVerifyTriggerSubscriptionBuilder, + useVerifyAndUpdateTriggerSubscriptionBuilder, } from '@/service/use-triggers' import { parsePluginErrorMessage } from '@/utils/error-parser' import { isPrivateOrLocalAddress } from '@/utils/urlValidation' @@ -40,6 +40,15 @@ const CREDENTIAL_TYPE_MAP: Record<SupportedCreationMethods, TriggerCredentialTyp [SupportedCreationMethods.MANUAL]: TriggerCredentialTypeEnum.Unauthorized, } +const MODAL_TITLE_KEY_MAP: Record< + SupportedCreationMethods, + 'pluginTrigger.modal.apiKey.title' | 'pluginTrigger.modal.oauth.title' | 'pluginTrigger.modal.manual.title' +> = { + [SupportedCreationMethods.APIKEY]: 'pluginTrigger.modal.apiKey.title', + [SupportedCreationMethods.OAUTH]: 'pluginTrigger.modal.oauth.title', + [SupportedCreationMethods.MANUAL]: 'pluginTrigger.modal.manual.title', +} + enum ApiKeyStep { Verify = 'verify', Configuration = 'configuration', @@ -104,7 +113,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { const [subscriptionBuilder, setSubscriptionBuilder] = useState<TriggerSubscriptionBuilder | undefined>(builder) const isInitializedRef = useRef(false) - const { mutate: verifyCredentials, isPending: isVerifyingCredentials } = useVerifyTriggerSubscriptionBuilder() + const { mutate: verifyCredentials, isPending: isVerifyingCredentials } = useVerifyAndUpdateTriggerSubscriptionBuilder() const { mutateAsync: createBuilder /* isPending: isCreatingBuilder */ } = useCreateTriggerSubscriptionBuilder() const { mutate: buildSubscription, isPending: isBuilding } = useBuildTriggerSubscription() const { mutate: updateBuilder } = useUpdateTriggerSubscriptionBuilder() @@ -117,13 +126,13 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { const autoCommonParametersSchema = detail?.declaration.trigger?.subscription_constructor?.parameters || [] // apikey and oauth const autoCommonParametersFormRef = React.useRef<FormRefObject>(null) - const rawApiKeyCredentialsSchema = detail?.declaration.trigger?.subscription_constructor?.credentials_schema || [] const apiKeyCredentialsSchema = useMemo(() => { - return rawApiKeyCredentialsSchema.map(schema => ({ + const rawSchema = detail?.declaration?.trigger?.subscription_constructor?.credentials_schema || [] + return rawSchema.map(schema => ({ ...schema, tooltip: schema.help, })) - }, [rawApiKeyCredentialsSchema]) + }, [detail?.declaration?.trigger?.subscription_constructor?.credentials_schema]) const apiKeyCredentialsFormRef = React.useRef<FormRefObject>(null) const { data: logData } = useTriggerSubscriptionBuilderLogs( @@ -163,7 +172,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { if (form) form.setFieldValue('callback_url', subscriptionBuilder.endpoint) if (isPrivateOrLocalAddress(subscriptionBuilder.endpoint)) { - console.log('isPrivateOrLocalAddress', isPrivateOrLocalAddress(subscriptionBuilder.endpoint)) + console.warn('callback_url is private or local address', subscriptionBuilder.endpoint) subscriptionFormRef.current?.setFields([{ name: 'callback_url', warnings: [t('pluginTrigger.modal.form.callbackUrl.privateAddressWarning')], @@ -179,7 +188,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { }, [subscriptionBuilder?.endpoint, currentStep, t]) const debouncedUpdate = useMemo( - () => debounce((provider: string, builderId: string, properties: Record<string, any>) => { + () => debounce((provider: string, builderId: string, properties: Record<string, unknown>) => { updateBuilder( { provider, @@ -187,11 +196,12 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { properties, }, { - onError: (error: any) => { + onError: async (error: unknown) => { + const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.errors.updateFailed') console.error('Failed to update subscription builder:', error) Toast.notify({ type: 'error', - message: error?.message || t('pluginTrigger.modal.errors.updateFailed'), + message: errorMessage, }) }, }, @@ -246,7 +256,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { }) setCurrentStep(ApiKeyStep.Configuration) }, - onError: async (error: any) => { + onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.apiKey.verify.error') apiKeyCredentialsFormRef.current?.setFields([{ name: Object.keys(credentials)[0], @@ -303,7 +313,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { onClose() refetch?.() }, - onError: async (error: any) => { + onError: async (error: unknown) => { const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.subscription.createFailed') Toast.notify({ type: 'error', @@ -328,14 +338,17 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => { }]) } + const confirmButtonText = useMemo(() => { + if (currentStep === ApiKeyStep.Verify) + return isVerifyingCredentials ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify') + + return isBuilding ? t('pluginTrigger.modal.common.creating') : t('pluginTrigger.modal.common.create') + }, [currentStep, isVerifyingCredentials, isBuilding, t]) + return ( <Modal - title={t(`pluginTrigger.modal.${createType === SupportedCreationMethods.APIKEY ? 'apiKey' : createType.toLowerCase()}.title` as any)} - confirmButtonText={ - currentStep === ApiKeyStep.Verify - ? isVerifyingCredentials ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify') - : isBuilding ? t('pluginTrigger.modal.common.creating') : t('pluginTrigger.modal.common.create') - } + title={t(MODAL_TITLE_KEY_MAP[createType])} + confirmButtonText={confirmButtonText} onClose={onClose} onCancel={onClose} onConfirm={handleConfirm} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx index 6c1094559e..ff8a0d95af 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx @@ -19,7 +19,7 @@ import { useConfigureTriggerOAuth, useDeleteTriggerOAuth, useInitiateTriggerOAuth, - useVerifyTriggerSubscriptionBuilder, + useVerifyAndUpdateTriggerSubscriptionBuilder, } from '@/service/use-triggers' import { usePluginStore } from '../../store' @@ -65,10 +65,29 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate const providerName = detail?.provider || '' const { mutate: initiateOAuth } = useInitiateTriggerOAuth() - const { mutate: verifyBuilder } = useVerifyTriggerSubscriptionBuilder() + const { mutate: verifyBuilder } = useVerifyAndUpdateTriggerSubscriptionBuilder() const { mutate: configureOAuth } = useConfigureTriggerOAuth() const { mutate: deleteOAuth } = useDeleteTriggerOAuth() + const confirmButtonText = useMemo(() => { + if (authorizationStatus === AuthorizationStatusEnum.Pending) + return t('pluginTrigger.modal.common.authorizing') + if (authorizationStatus === AuthorizationStatusEnum.Success) + return t('pluginTrigger.modal.oauth.authorization.waitingJump') + return t('plugin.auth.saveAndAuth') + }, [authorizationStatus, t]) + + const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) + return error.message + if (typeof error === 'object' && error && 'message' in error) { + const message = (error as { message?: string }).message + if (typeof message === 'string' && message) + return message + } + return fallback + } + const handleAuthorization = () => { setAuthorizationStatus(AuthorizationStatusEnum.Pending) initiateOAuth(providerName, { @@ -130,10 +149,10 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate message: t('pluginTrigger.modal.oauth.remove.success'), }) }, - onError: (error: any) => { + onError: (error: unknown) => { Toast.notify({ type: 'error', - message: error?.message || t('pluginTrigger.modal.oauth.remove.failed'), + message: getErrorMessage(error, t('pluginTrigger.modal.oauth.remove.failed')), }) }, }) @@ -179,9 +198,7 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate return ( <Modal title={t('pluginTrigger.modal.oauth.title')} - confirmButtonText={authorizationStatus === AuthorizationStatusEnum.Pending - ? t('pluginTrigger.modal.common.authorizing') - : authorizationStatus === AuthorizationStatusEnum.Success ? t('pluginTrigger.modal.oauth.authorization.waitingJump') : t('plugin.auth.saveAndAuth')} + confirmButtonText={confirmButtonText} cancelButtonText={t('plugin.auth.saveOnly')} extraButtonText={t('common.operation.cancel')} showExtraButton diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx new file mode 100644 index 0000000000..18bebf8d95 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx @@ -0,0 +1,349 @@ +'use client' +import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' +import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' +import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' +import { isEqual } from 'lodash-es' +import { useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { EncryptedBottom } from '@/app/components/base/encrypted-bottom' +import { BaseForm } from '@/app/components/base/form/components/base' +import { FormTypeEnum } from '@/app/components/base/form/types' +import Modal from '@/app/components/base/modal/modal' +import Toast from '@/app/components/base/toast' +import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' +import { useUpdateTriggerSubscription, useVerifyTriggerSubscription } from '@/service/use-triggers' +import { parsePluginErrorMessage } from '@/utils/error-parser' +import { ReadmeShowType } from '../../../readme-panel/store' +import { usePluginStore } from '../../store' +import { useSubscriptionList } from '../use-subscription-list' + +type Props = { + onClose: () => void + subscription: TriggerSubscription + pluginDetail?: PluginDetail +} + +enum EditStep { + EditCredentials = 'edit_credentials', + EditConfiguration = 'edit_configuration', +} + +const normalizeFormType = (type: string): FormTypeEnum => { + switch (type) { + case 'string': + case 'text': + return FormTypeEnum.textInput + case 'password': + case 'secret': + return FormTypeEnum.secretInput + case 'number': + case 'integer': + return FormTypeEnum.textNumber + case 'boolean': + return FormTypeEnum.boolean + case 'select': + return FormTypeEnum.select + default: + if (Object.values(FormTypeEnum).includes(type as FormTypeEnum)) + return type as FormTypeEnum + return FormTypeEnum.textInput + } +} + +const HIDDEN_SECRET_VALUE = '[__HIDDEN__]' + +// Check if all credential values are hidden (meaning nothing was changed) +const areAllCredentialsHidden = (credentials: Record<string, unknown>): boolean => { + return Object.values(credentials).every(value => value === HIDDEN_SECRET_VALUE) +} + +const StatusStep = ({ isActive, text, onClick, clickable }: { + isActive: boolean + text: string + onClick?: () => void + clickable?: boolean +}) => { + return ( + <div + className={`system-2xs-semibold-uppercase flex items-center gap-1 ${isActive + ? 'text-state-accent-solid' + : 'text-text-tertiary'} ${clickable ? 'cursor-pointer hover:text-text-secondary' : ''}`} + onClick={clickable ? onClick : undefined} + > + {isActive && ( + <div className="h-1 w-1 rounded-full bg-state-accent-solid"></div> + )} + {text} + </div> + ) +} + +const MultiSteps = ({ currentStep, onStepClick }: { currentStep: EditStep, onStepClick?: (step: EditStep) => void }) => { + const { t } = useTranslation() + return ( + <div className="mb-6 flex w-1/3 items-center gap-2"> + <StatusStep + isActive={currentStep === EditStep.EditCredentials} + text={t('pluginTrigger.modal.steps.verify')} + onClick={() => onStepClick?.(EditStep.EditCredentials)} + clickable={currentStep === EditStep.EditConfiguration} + /> + <div className="h-px w-3 shrink-0 bg-divider-deep"></div> + <StatusStep + isActive={currentStep === EditStep.EditConfiguration} + text={t('pluginTrigger.modal.steps.configuration')} + /> + </div> + ) +} + +export const ApiKeyEditModal = ({ onClose, subscription, pluginDetail }: Props) => { + const { t } = useTranslation() + const detail = usePluginStore(state => state.detail) + const { refetch } = useSubscriptionList() + + const [currentStep, setCurrentStep] = useState<EditStep>(EditStep.EditCredentials) + const [verifiedCredentials, setVerifiedCredentials] = useState<Record<string, unknown> | null>(null) + + const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription() + const { mutate: verifyCredentials, isPending: isVerifying } = useVerifyTriggerSubscription() + + const parametersSchema = useMemo<ParametersSchema[]>( + () => detail?.declaration?.trigger?.subscription_constructor?.parameters || [], + [detail?.declaration?.trigger?.subscription_constructor?.parameters], + ) + + const apiKeyCredentialsSchema = useMemo(() => { + const rawSchema = detail?.declaration?.trigger?.subscription_constructor?.credentials_schema || [] + return rawSchema.map(schema => ({ + ...schema, + tooltip: schema.help, + })) + }, [detail?.declaration?.trigger?.subscription_constructor?.credentials_schema]) + + const basicFormRef = useRef<FormRefObject>(null) + const parametersFormRef = useRef<FormRefObject>(null) + const credentialsFormRef = useRef<FormRefObject>(null) + + const handleVerifyCredentials = () => { + const credentialsFormValues = credentialsFormRef.current?.getFormValues({ + needTransformWhenSecretFieldIsPristine: true, + }) || { values: {}, isCheckValidated: false } + + if (!credentialsFormValues.isCheckValidated) + return + + const credentials = credentialsFormValues.values + + verifyCredentials( + { + provider: subscription.provider, + subscriptionId: subscription.id, + credentials, + }, + { + onSuccess: () => { + Toast.notify({ + type: 'success', + message: t('pluginTrigger.modal.apiKey.verify.success'), + }) + // Only save credentials if any field was modified (not all hidden) + setVerifiedCredentials(areAllCredentialsHidden(credentials) ? null : credentials) + setCurrentStep(EditStep.EditConfiguration) + }, + onError: async (error: unknown) => { + const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.apiKey.verify.error') + Toast.notify({ + type: 'error', + message: errorMessage, + }) + }, + }, + ) + } + + const handleUpdate = () => { + const basicFormValues = basicFormRef.current?.getFormValues({}) + if (!basicFormValues?.isCheckValidated) + return + + const name = basicFormValues.values.subscription_name as string + + let parameters: Record<string, unknown> | undefined + + if (parametersSchema.length > 0) { + const paramsFormValues = parametersFormRef.current?.getFormValues({ + needTransformWhenSecretFieldIsPristine: true, + }) + if (!paramsFormValues?.isCheckValidated) + return + + // Only send parameters if changed + const hasChanged = !isEqual(paramsFormValues.values, subscription.parameters || {}) + parameters = hasChanged ? paramsFormValues.values : undefined + } + + updateSubscription( + { + subscriptionId: subscription.id, + name, + parameters, + credentials: verifiedCredentials || undefined, + }, + { + onSuccess: () => { + Toast.notify({ + type: 'success', + message: t('pluginTrigger.subscription.list.item.actions.edit.success'), + }) + refetch?.() + onClose() + }, + onError: async (error: unknown) => { + const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.subscription.list.item.actions.edit.error') + Toast.notify({ + type: 'error', + message: errorMessage, + }) + }, + }, + ) + } + + const handleConfirm = () => { + if (currentStep === EditStep.EditCredentials) + handleVerifyCredentials() + else + handleUpdate() + } + + const basicFormSchemas: FormSchema[] = useMemo(() => [ + { + name: 'subscription_name', + label: t('pluginTrigger.modal.form.subscriptionName.label'), + placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'), + type: FormTypeEnum.textInput, + required: true, + default: subscription.name, + }, + { + name: 'callback_url', + label: t('pluginTrigger.modal.form.callbackUrl.label'), + placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'), + type: FormTypeEnum.textInput, + required: false, + default: subscription.endpoint || '', + disabled: true, + tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'), + showCopy: true, + }, + ], [t, subscription.name, subscription.endpoint]) + + const credentialsFormSchemas: FormSchema[] = useMemo(() => { + return apiKeyCredentialsSchema.map(schema => ({ + ...schema, + type: normalizeFormType(schema.type as string), + tooltip: schema.help, + default: subscription.credentials?.[schema.name] || schema.default, + })) + }, [apiKeyCredentialsSchema, subscription.credentials]) + + const parametersFormSchemas: FormSchema[] = useMemo(() => { + return parametersSchema.map((schema: ParametersSchema) => { + const normalizedType = normalizeFormType(schema.type as string) + return { + ...schema, + type: normalizedType, + tooltip: schema.description, + default: subscription.parameters?.[schema.name] || schema.default, + dynamicSelectParams: normalizedType === FormTypeEnum.dynamicSelect + ? { + plugin_id: detail?.plugin_id || '', + provider: detail?.provider || '', + action: 'provider', + parameter: schema.name, + credential_id: subscription.id, + credentials: verifiedCredentials || undefined, + } + : undefined, + fieldClassName: schema.type === FormTypeEnum.boolean ? 'flex items-center justify-between' : undefined, + labelClassName: schema.type === FormTypeEnum.boolean ? 'mb-0' : undefined, + } + }) + }, [parametersSchema, subscription.parameters, subscription.id, detail?.plugin_id, detail?.provider, verifiedCredentials]) + + const getConfirmButtonText = () => { + if (currentStep === EditStep.EditCredentials) + return isVerifying ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify') + + return isUpdating ? t('common.operation.saving') : t('common.operation.save') + } + + const handleBack = () => { + setCurrentStep(EditStep.EditCredentials) + setVerifiedCredentials(null) + } + + return ( + <Modal + title={t('pluginTrigger.subscription.list.item.actions.edit.title')} + confirmButtonText={getConfirmButtonText()} + onClose={onClose} + onCancel={onClose} + onConfirm={handleConfirm} + disabled={isUpdating || isVerifying} + showExtraButton={currentStep === EditStep.EditConfiguration} + extraButtonText={t('pluginTrigger.modal.common.back')} + extraButtonVariant="secondary" + onExtraButtonClick={handleBack} + clickOutsideNotClose + wrapperClassName="!z-[101]" + bottomSlot={currentStep === EditStep.EditCredentials ? <EncryptedBottom /> : null} + > + {pluginDetail && ( + <ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} /> + )} + + {/* Multi-step indicator */} + <MultiSteps currentStep={currentStep} onStepClick={handleBack} /> + + {/* Step 1: Edit Credentials */} + {currentStep === EditStep.EditCredentials && ( + <div className="mb-4"> + {credentialsFormSchemas.length > 0 && ( + <BaseForm + formSchemas={credentialsFormSchemas} + ref={credentialsFormRef} + labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary" + formClassName="space-y-4" + preventDefaultSubmit={true} + /> + )} + </div> + )} + + {/* Step 2: Edit Configuration */} + {currentStep === EditStep.EditConfiguration && ( + <div className="max-h-[70vh]"> + {/* Basic form: subscription name and callback URL */} + <BaseForm + formSchemas={basicFormSchemas} + ref={basicFormRef} + labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary" + formClassName="space-y-4 mb-4" + /> + + {/* Parameters */} + {parametersFormSchemas.length > 0 && ( + <BaseForm + formSchemas={parametersFormSchemas} + ref={parametersFormRef} + labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary" + formClassName="space-y-4" + /> + )} + </div> + )} + </Modal> + ) +} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/index.tsx new file mode 100644 index 0000000000..90e89d043a --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/index.tsx @@ -0,0 +1,28 @@ +'use client' +import type { PluginDetail } from '@/app/components/plugins/types' +import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' +import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' +import { ApiKeyEditModal } from './apikey-edit-modal' +import { ManualEditModal } from './manual-edit-modal' +import { OAuthEditModal } from './oauth-edit-modal' + +type Props = { + onClose: () => void + subscription: TriggerSubscription + pluginDetail?: PluginDetail +} + +export const EditModal = ({ onClose, subscription, pluginDetail }: Props) => { + const credentialType = subscription.credential_type + + switch (credentialType) { + case TriggerCredentialTypeEnum.Unauthorized: + return <ManualEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} /> + case TriggerCredentialTypeEnum.Oauth2: + return <OAuthEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} /> + case TriggerCredentialTypeEnum.ApiKey: + return <ApiKeyEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} /> + default: + return null + } +} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx new file mode 100644 index 0000000000..96c1b6e278 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx @@ -0,0 +1,164 @@ +'use client' +import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' +import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' +import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' +import { isEqual } from 'lodash-es' +import { useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { BaseForm } from '@/app/components/base/form/components/base' +import { FormTypeEnum } from '@/app/components/base/form/types' +import Modal from '@/app/components/base/modal/modal' +import Toast from '@/app/components/base/toast' +import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' +import { useUpdateTriggerSubscription } from '@/service/use-triggers' +import { ReadmeShowType } from '../../../readme-panel/store' +import { usePluginStore } from '../../store' +import { useSubscriptionList } from '../use-subscription-list' + +type Props = { + onClose: () => void + subscription: TriggerSubscription + pluginDetail?: PluginDetail +} + +const normalizeFormType = (type: string): FormTypeEnum => { + switch (type) { + case 'string': + case 'text': + return FormTypeEnum.textInput + case 'password': + case 'secret': + return FormTypeEnum.secretInput + case 'number': + case 'integer': + return FormTypeEnum.textNumber + case 'boolean': + return FormTypeEnum.boolean + case 'select': + return FormTypeEnum.select + default: + if (Object.values(FormTypeEnum).includes(type as FormTypeEnum)) + return type as FormTypeEnum + return FormTypeEnum.textInput + } +} + +export const ManualEditModal = ({ onClose, subscription, pluginDetail }: Props) => { + const { t } = useTranslation() + const detail = usePluginStore(state => state.detail) + const { refetch } = useSubscriptionList() + + const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription() + + const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) + return error.message + if (typeof error === 'object' && error && 'message' in error) { + const message = (error as { message?: string }).message + if (typeof message === 'string' && message) + return message + } + return fallback + } + + const propertiesSchema = useMemo<ParametersSchema[]>( + () => detail?.declaration?.trigger?.subscription_schema || [], + [detail?.declaration?.trigger?.subscription_schema], + ) + + const formRef = useRef<FormRefObject>(null) + + const handleConfirm = () => { + const formValues = formRef.current?.getFormValues({ + needTransformWhenSecretFieldIsPristine: true, + }) + if (!formValues?.isCheckValidated) + return + + const name = formValues.values.subscription_name as string + + // Extract properties (exclude subscription_name and callback_url) + const newProperties = { ...formValues.values } + delete newProperties.subscription_name + delete newProperties.callback_url + + // Only send properties if changed + const hasChanged = !isEqual(newProperties, subscription.properties || {}) + const properties = hasChanged ? newProperties : undefined + + updateSubscription( + { + subscriptionId: subscription.id, + name, + properties, + }, + { + onSuccess: () => { + Toast.notify({ + type: 'success', + message: t('pluginTrigger.subscription.list.item.actions.edit.success'), + }) + refetch?.() + onClose() + }, + onError: (error: unknown) => { + Toast.notify({ + type: 'error', + message: getErrorMessage(error, t('pluginTrigger.subscription.list.item.actions.edit.error')), + }) + }, + }, + ) + } + + const formSchemas: FormSchema[] = useMemo(() => [ + { + name: 'subscription_name', + label: t('pluginTrigger.modal.form.subscriptionName.label'), + placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'), + type: FormTypeEnum.textInput, + required: true, + default: subscription.name, + }, + { + name: 'callback_url', + label: t('pluginTrigger.modal.form.callbackUrl.label'), + placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'), + type: FormTypeEnum.textInput, + required: false, + default: subscription.endpoint || '', + disabled: true, + tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'), + showCopy: true, + }, + ...propertiesSchema.map((schema: ParametersSchema) => ({ + ...schema, + type: normalizeFormType(schema.type as string), + tooltip: schema.description, + default: subscription.properties?.[schema.name] || schema.default, + })), + ], [t, subscription.name, subscription.endpoint, subscription.properties, propertiesSchema]) + + return ( + <Modal + title={t('pluginTrigger.subscription.list.item.actions.edit.title')} + confirmButtonText={isUpdating ? t('common.operation.saving') : t('common.operation.save')} + onClose={onClose} + onCancel={onClose} + onConfirm={handleConfirm} + disabled={isUpdating} + clickOutsideNotClose + wrapperClassName="!z-[101]" + > + {pluginDetail && ( + <ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} /> + )} + <BaseForm + formSchemas={formSchemas} + ref={formRef} + labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary" + formClassName="space-y-4" + /> + </Modal> + ) +} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx new file mode 100644 index 0000000000..9adee6cc34 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx @@ -0,0 +1,178 @@ +'use client' +import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' +import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' +import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' +import { isEqual } from 'lodash-es' +import { useMemo, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { BaseForm } from '@/app/components/base/form/components/base' +import { FormTypeEnum } from '@/app/components/base/form/types' +import Modal from '@/app/components/base/modal/modal' +import Toast from '@/app/components/base/toast' +import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' +import { useUpdateTriggerSubscription } from '@/service/use-triggers' +import { ReadmeShowType } from '../../../readme-panel/store' +import { usePluginStore } from '../../store' +import { useSubscriptionList } from '../use-subscription-list' + +type Props = { + onClose: () => void + subscription: TriggerSubscription + pluginDetail?: PluginDetail +} + +const normalizeFormType = (type: string): FormTypeEnum => { + switch (type) { + case 'string': + case 'text': + return FormTypeEnum.textInput + case 'password': + case 'secret': + return FormTypeEnum.secretInput + case 'number': + case 'integer': + return FormTypeEnum.textNumber + case 'boolean': + return FormTypeEnum.boolean + case 'select': + return FormTypeEnum.select + default: + if (Object.values(FormTypeEnum).includes(type as FormTypeEnum)) + return type as FormTypeEnum + return FormTypeEnum.textInput + } +} + +export const OAuthEditModal = ({ onClose, subscription, pluginDetail }: Props) => { + const { t } = useTranslation() + const detail = usePluginStore(state => state.detail) + const { refetch } = useSubscriptionList() + + const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription() + + const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) + return error.message + if (typeof error === 'object' && error && 'message' in error) { + const message = (error as { message?: string }).message + if (typeof message === 'string' && message) + return message + } + return fallback + } + + const parametersSchema = useMemo<ParametersSchema[]>( + () => detail?.declaration?.trigger?.subscription_constructor?.parameters || [], + [detail?.declaration?.trigger?.subscription_constructor?.parameters], + ) + + const formRef = useRef<FormRefObject>(null) + + const handleConfirm = () => { + const formValues = formRef.current?.getFormValues({ + needTransformWhenSecretFieldIsPristine: true, + }) + if (!formValues?.isCheckValidated) + return + + const name = formValues.values.subscription_name as string + + // Extract parameters (exclude subscription_name and callback_url) + const newParameters = { ...formValues.values } + delete newParameters.subscription_name + delete newParameters.callback_url + + // Only send parameters if changed + const hasChanged = !isEqual(newParameters, subscription.parameters || {}) + const parameters = hasChanged ? newParameters : undefined + + updateSubscription( + { + subscriptionId: subscription.id, + name, + parameters, + }, + { + onSuccess: () => { + Toast.notify({ + type: 'success', + message: t('pluginTrigger.subscription.list.item.actions.edit.success'), + }) + refetch?.() + onClose() + }, + onError: (error: unknown) => { + Toast.notify({ + type: 'error', + message: getErrorMessage(error, t('pluginTrigger.subscription.list.item.actions.edit.error')), + }) + }, + }, + ) + } + + const formSchemas: FormSchema[] = useMemo(() => [ + { + name: 'subscription_name', + label: t('pluginTrigger.modal.form.subscriptionName.label'), + placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'), + type: FormTypeEnum.textInput, + required: true, + default: subscription.name, + }, + { + name: 'callback_url', + label: t('pluginTrigger.modal.form.callbackUrl.label'), + placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'), + type: FormTypeEnum.textInput, + required: false, + default: subscription.endpoint || '', + disabled: true, + tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'), + showCopy: true, + }, + ...parametersSchema.map((schema: ParametersSchema) => { + const normalizedType = normalizeFormType(schema.type as string) + return { + ...schema, + type: normalizedType, + tooltip: schema.description, + default: subscription.parameters?.[schema.name] || schema.default, + dynamicSelectParams: normalizedType === FormTypeEnum.dynamicSelect + ? { + plugin_id: detail?.plugin_id || '', + provider: detail?.provider || '', + action: 'provider', + parameter: schema.name, + credential_id: subscription.id, + } + : undefined, + fieldClassName: schema.type === FormTypeEnum.boolean ? 'flex items-center justify-between' : undefined, + labelClassName: schema.type === FormTypeEnum.boolean ? 'mb-0' : undefined, + } + }), + ], [t, subscription.name, subscription.endpoint, subscription.parameters, subscription.id, parametersSchema, detail?.plugin_id, detail?.provider]) + + return ( + <Modal + title={t('pluginTrigger.subscription.list.item.actions.edit.title')} + confirmButtonText={isUpdating ? t('common.operation.saving') : t('common.operation.save')} + onClose={onClose} + onCancel={onClose} + onConfirm={handleConfirm} + disabled={isUpdating} + clickOutsideNotClose + wrapperClassName="!z-[101]" + > + {pluginDetail && ( + <ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} /> + )} + <BaseForm + formSchemas={formSchemas} + ref={formRef} + labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary" + formClassName="space-y-4" + /> + </Modal> + ) +} diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx index 9b7bcc461a..96ce983f38 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx @@ -1,31 +1,27 @@ +import type { SimpleSubscription } from './types' +import type { PluginDetail } from '@/app/components/plugins/types' import { withErrorBoundary } from '@/app/components/base/error-boundary' import Loading from '@/app/components/base/loading' import { SubscriptionListView } from './list-view' import { SubscriptionSelectorView } from './selector-view' +import { SubscriptionListMode } from './types' import { useSubscriptionList } from './use-subscription-list' -export enum SubscriptionListMode { - PANEL = 'panel', - SELECTOR = 'selector', -} - -export type SimpleSubscription = { - id: string - name: string -} - type SubscriptionListProps = { mode?: SubscriptionListMode selectedId?: string onSelect?: (v: SimpleSubscription, callback?: () => void) => void + pluginDetail?: PluginDetail } export { SubscriptionSelectorEntry } from './selector-entry' +export type { SimpleSubscription } from './types' export const SubscriptionList = withErrorBoundary(({ mode = SubscriptionListMode.PANEL, selectedId, onSelect, + pluginDetail, }: SubscriptionListProps) => { const { isLoading, refetch } = useSubscriptionList() if (isLoading) { @@ -47,5 +43,5 @@ export const SubscriptionList = withErrorBoundary(({ ) } - return <SubscriptionListView /> + return <SubscriptionListView pluginDetail={pluginDetail} /> }) diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx index 628f561ca2..1238935fa3 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx @@ -1,4 +1,5 @@ 'use client' +import type { PluginDetail } from '@/app/components/plugins/types' import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' @@ -9,10 +10,12 @@ import { useSubscriptionList } from './use-subscription-list' type SubscriptionListViewProps = { showTopBorder?: boolean + pluginDetail?: PluginDetail } export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({ showTopBorder = false, + pluginDetail, }) => { const { t } = useTranslation() const { subscriptions } = useSubscriptionList() @@ -41,6 +44,7 @@ export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({ <SubscriptionCard key={subscription.id} data={subscription} + pluginDetail={pluginDetail} /> ))} </div> diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx index a52e25e1d3..4bbad06b57 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-entry.tsx @@ -1,5 +1,5 @@ 'use client' -import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list' +import type { SimpleSubscription } from './types' import { RiArrowDownSLine, RiWebhookLine } from '@remixicon/react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -8,8 +8,9 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' -import { SubscriptionList, SubscriptionListMode } from '@/app/components/plugins/plugin-detail-panel/subscription-list' +import { SubscriptionList } from '@/app/components/plugins/plugin-detail-panel/subscription-list' import { cn } from '@/utils/classnames' +import { SubscriptionListMode } from './types' import { useSubscriptionList } from './use-subscription-list' type SubscriptionTriggerButtonProps = { diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx index af2ac50abf..61b510e05e 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx @@ -1,7 +1,9 @@ 'use client' +import type { PluginDetail } from '@/app/components/plugins/types' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' import { RiDeleteBinLine, + RiEditLine, RiWebhookLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' @@ -10,17 +12,23 @@ import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' import { DeleteConfirm } from './delete-confirm' +import { EditModal } from './edit' type Props = { data: TriggerSubscription + pluginDetail?: PluginDetail } -const SubscriptionCard = ({ data }: Props) => { +const SubscriptionCard = ({ data, pluginDetail }: Props) => { const { t } = useTranslation() const [isShowDeleteModal, { setTrue: showDeleteModal, setFalse: hideDeleteModal, }] = useBoolean(false) + const [isShowEditModal, { + setTrue: showEditModal, + setFalse: hideEditModal, + }] = useBoolean(false) return ( <> @@ -40,12 +48,20 @@ const SubscriptionCard = ({ data }: Props) => { </span> </div> - <ActionButton - onClick={showDeleteModal} - className="subscription-delete-btn hidden transition-colors hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block" - > - <RiDeleteBinLine className="h-4 w-4" /> - </ActionButton> + <div className="hidden items-center gap-1 group-hover:flex"> + <ActionButton + onClick={showEditModal} + className="transition-colors hover:bg-state-base-hover" + > + <RiEditLine className="h-4 w-4" /> + </ActionButton> + <ActionButton + onClick={showDeleteModal} + className="subscription-delete-btn transition-colors hover:bg-state-destructive-hover hover:text-text-destructive" + > + <RiDeleteBinLine className="h-4 w-4" /> + </ActionButton> + </div> </div> <div className="mt-1 flex items-center justify-between"> @@ -78,6 +94,14 @@ const SubscriptionCard = ({ data }: Props) => { workflowsInUse={data.workflows_in_use} /> )} + + {isShowEditModal && ( + <EditModal + onClose={hideEditModal} + subscription={data} + pluginDetail={pluginDetail} + /> + )} </> ) } diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts b/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts new file mode 100644 index 0000000000..adfda16547 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts @@ -0,0 +1,9 @@ +export enum SubscriptionListMode { + PANEL = 'panel', + SELECTOR = 'selector', +} + +export type SimpleSubscription = { + id: string + name: string +} diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 818c2a0388..4aa0326cb4 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -131,7 +131,7 @@ export type ParametersSchema = { scope: any required: boolean multiple: boolean - default?: string[] + default?: string | string[] min: any max: any precision: any diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 6ed4d7f2d5..07efb0d02f 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -39,9 +39,9 @@ export type TriggerDefaultValue = PluginCommonDefaultValue & { title: string plugin_unique_identifier: string is_team_authorization: boolean - params: Record<string, any> - paramSchemas: Record<string, any>[] - output_schema: Record<string, any> + params: Record<string, unknown> + paramSchemas: Record<string, unknown>[] + output_schema: Record<string, unknown> subscription_id?: string meta?: PluginMeta } @@ -52,9 +52,9 @@ export type ToolDefaultValue = PluginCommonDefaultValue & { tool_description: string title: string is_team_authorization: boolean - params: Record<string, any> - paramSchemas: Record<string, any>[] - output_schema?: Record<string, any> + params: Record<string, unknown> + paramSchemas: Record<string, unknown>[] + output_schema?: Record<string, unknown> credential_id?: string meta?: PluginMeta plugin_id?: string @@ -82,10 +82,10 @@ export type ToolValue = { tool_name: string tool_label: string tool_description?: string - settings?: Record<string, any> - parameters?: Record<string, any> + settings?: Record<string, unknown> + parameters?: Record<string, unknown> enabled?: boolean - extra?: Record<string, any> + extra?: { description?: string } & Record<string, unknown> credential_id?: string } @@ -94,7 +94,7 @@ export type DataSourceItem = { plugin_unique_identifier: string provider: string declaration: { - credentials_schema: any[] + credentials_schema: unknown[] provider_type: string identity: { author: string @@ -113,10 +113,10 @@ export type DataSourceItem = { name: string provider: string } - parameters: any[] + parameters: unknown[] output_schema?: { type: string - properties: Record<string, any> + properties: Record<string, unknown> } }[] } @@ -133,15 +133,15 @@ export type TriggerParameter = { | 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select' auto_generate?: { type: string - value?: any + value?: unknown } | null template?: { type: string - value?: any + value?: unknown } | null scope?: string | null required?: boolean - default?: any + default?: unknown min?: number | null max?: number | null precision?: number | null @@ -158,7 +158,7 @@ export type TriggerCredentialField = { name: string scope?: string | null required: boolean - default?: string | number | boolean | Array<any> | null + default?: string | number | boolean | Array<unknown> | null options?: Array<{ value: string label: TypeWithI18N @@ -191,7 +191,7 @@ export type TriggerApiEntity = { identity: TriggerIdentity description: TypeWithI18N parameters: TriggerParameter[] - output_schema?: Record<string, any> + output_schema?: Record<string, unknown> } export type TriggerProviderApiEntity = { @@ -237,32 +237,15 @@ type TriggerSubscriptionStructure = { name: string provider: string credential_type: TriggerCredentialTypeEnum - credentials: TriggerSubCredentials + credentials: Record<string, unknown> endpoint: string - parameters: TriggerSubParameters - properties: TriggerSubProperties + parameters: Record<string, unknown> + properties: Record<string, unknown> workflows_in_use: number } export type TriggerSubscription = TriggerSubscriptionStructure -export type TriggerSubCredentials = { - access_tokens: string -} - -export type TriggerSubParameters = { - repository: string - webhook_secret?: string -} - -export type TriggerSubProperties = { - active: boolean - events: string[] - external_id: string - repository: string - webhook_secret?: string -} - export type TriggerSubscriptionBuilder = TriggerSubscriptionStructure // OAuth configuration types @@ -275,7 +258,7 @@ export type TriggerOAuthConfig = { params: { client_id: string client_secret: string - [key: string]: any + [key: string]: string } system_configured: boolean } diff --git a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts index 36bcbf1cc7..f551f2f420 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts @@ -4,11 +4,11 @@ import { useBuildTriggerSubscription, useCreateTriggerSubscriptionBuilder, useUpdateTriggerSubscriptionBuilder, - useVerifyTriggerSubscriptionBuilder, + useVerifyAndUpdateTriggerSubscriptionBuilder, } from '@/service/use-triggers' // Helper function to serialize complex values to strings for backend encryption -const serializeFormValues = (values: Record<string, any>): Record<string, string> => { +const serializeFormValues = (values: Record<string, unknown>): Record<string, string> => { const result: Record<string, string> = {} for (const [key, value] of Object.entries(values)) { @@ -23,6 +23,17 @@ const serializeFormValues = (values: Record<string, any>): Record<string, string return result } +const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message) + return error.message + if (typeof error === 'object' && error && 'message' in error) { + const message = (error as { message?: string }).message + if (typeof message === 'string' && message) + return message + } + return fallback +} + export type AuthFlowStep = 'auth' | 'params' | 'complete' export type AuthFlowState = { @@ -34,8 +45,8 @@ export type AuthFlowState = { export type AuthFlowActions = { startAuth: () => Promise<void> - verifyAuth: (credentials: Record<string, any>) => Promise<void> - completeConfig: (parameters: Record<string, any>, properties?: Record<string, any>, name?: string) => Promise<void> + verifyAuth: (credentials: Record<string, unknown>) => Promise<void> + completeConfig: (parameters: Record<string, unknown>, properties?: Record<string, unknown>, name?: string) => Promise<void> reset: () => void } @@ -47,7 +58,7 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState const createBuilder = useCreateTriggerSubscriptionBuilder() const updateBuilder = useUpdateTriggerSubscriptionBuilder() - const verifyBuilder = useVerifyTriggerSubscriptionBuilder() + const verifyBuilder = useVerifyAndUpdateTriggerSubscriptionBuilder() const buildSubscription = useBuildTriggerSubscription() const startAuth = useCallback(async () => { @@ -64,8 +75,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setBuilderId(response.subscription_builder.id) setStep('auth') } - catch (err: any) { - setError(err.message || 'Failed to start authentication flow') + catch (err: unknown) { + setError(getErrorMessage(err, 'Failed to start authentication flow')) throw err } finally { @@ -73,7 +84,7 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState } }, [provider.name, createBuilder, builderId]) - const verifyAuth = useCallback(async (credentials: Record<string, any>) => { + const verifyAuth = useCallback(async (credentials: Record<string, unknown>) => { if (!builderId) { setError('No builder ID available') return @@ -96,8 +107,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setStep('params') } - catch (err: any) { - setError(err.message || 'Authentication verification failed') + catch (err: unknown) { + setError(getErrorMessage(err, 'Authentication verification failed')) throw err } finally { @@ -106,8 +117,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState }, [provider.name, builderId, updateBuilder, verifyBuilder]) const completeConfig = useCallback(async ( - parameters: Record<string, any>, - properties: Record<string, any> = {}, + parameters: Record<string, unknown>, + properties: Record<string, unknown> = {}, name?: string, ) => { if (!builderId) { @@ -134,8 +145,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setStep('complete') } - catch (err: any) { - setError(err.message || 'Configuration failed') + catch (err: unknown) { + setError(getErrorMessage(err, 'Configuration failed')) throw err } finally { diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 2e378afeda..0117f2ae00 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -21,6 +21,7 @@ const translation = { cancel: 'Cancel', clear: 'Clear', save: 'Save', + saving: 'Saving...', yes: 'Yes', no: 'No', deleteConfirmTitle: 'Delete?', diff --git a/web/i18n/en-US/plugin-trigger.ts b/web/i18n/en-US/plugin-trigger.ts index f1d697e507..16ee1e1362 100644 --- a/web/i18n/en-US/plugin-trigger.ts +++ b/web/i18n/en-US/plugin-trigger.ts @@ -30,6 +30,11 @@ const translation = { unauthorized: 'Manual', }, actions: { + edit: { + title: 'Edit Subscription', + success: 'Subscription updated successfully', + error: 'Failed to update subscription', + }, delete: 'Delete', deleteConfirm: { title: 'Delete {{name}}?', diff --git a/web/service/use-triggers.ts b/web/service/use-triggers.ts index c21d1aa979..6036f5ab34 100644 --- a/web/service/use-triggers.ts +++ b/web/service/use-triggers.ts @@ -1,3 +1,4 @@ +import type { FormOption } from '@/app/components/base/form/types' import type { TriggerLogEntity, TriggerOAuthClientParams, @@ -149,9 +150,9 @@ export const useUpdateTriggerSubscriptionBuilder = () => { provider: string subscriptionBuilderId: string name?: string - properties?: Record<string, any> - parameters?: Record<string, any> - credentials?: Record<string, any> + properties?: Record<string, unknown> + parameters?: Record<string, unknown> + credentials?: Record<string, unknown> }) => { const { provider, subscriptionBuilderId, ...body } = payload return post<TriggerSubscriptionBuilder>( @@ -162,17 +163,35 @@ export const useUpdateTriggerSubscriptionBuilder = () => { }) } -export const useVerifyTriggerSubscriptionBuilder = () => { +export const useVerifyAndUpdateTriggerSubscriptionBuilder = () => { return useMutation({ - mutationKey: [NAME_SPACE, 'verify-subscription-builder'], + mutationKey: [NAME_SPACE, 'verify-and-update-subscription-builder'], mutationFn: (payload: { provider: string subscriptionBuilderId: string - credentials?: Record<string, any> + credentials?: Record<string, unknown> }) => { const { provider, subscriptionBuilderId, ...body } = payload return post<{ verified: boolean }>( - `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify/${subscriptionBuilderId}`, + `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify-and-update/${subscriptionBuilderId}`, + { body }, + { silent: true }, + ) + }, + }) +} + +export const useVerifyTriggerSubscription = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'verify-subscription'], + mutationFn: (payload: { + provider: string + subscriptionId: string + credentials?: Record<string, unknown> + }) => { + const { provider, subscriptionId, ...body } = payload + return post<{ verified: boolean }>( + `/workspaces/current/trigger-provider/${provider}/subscriptions/verify/${subscriptionId}`, { body }, { silent: true }, ) @@ -184,7 +203,7 @@ export type BuildTriggerSubscriptionPayload = { provider: string subscriptionBuilderId: string name?: string - parameters?: Record<string, any> + parameters?: Record<string, unknown> } export const useBuildTriggerSubscription = () => { @@ -211,6 +230,27 @@ export const useDeleteTriggerSubscription = () => { }) } +export type UpdateTriggerSubscriptionPayload = { + subscriptionId: string + name?: string + properties?: Record<string, unknown> + parameters?: Record<string, unknown> + credentials?: Record<string, unknown> +} + +export const useUpdateTriggerSubscription = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'update-subscription'], + mutationFn: (payload: UpdateTriggerSubscriptionPayload) => { + const { subscriptionId, ...body } = payload + return post<{ result: string, id: string }>( + `/workspaces/current/trigger-provider/${subscriptionId}/subscriptions/update`, + { body }, + ) + }, + }) +} + export const useTriggerSubscriptionBuilderLogs = ( provider: string, subscriptionBuilderId: string, @@ -290,20 +330,45 @@ export const useTriggerPluginDynamicOptions = (payload: { action: string parameter: string credential_id: string - extra?: Record<string, any> + credentials?: Record<string, unknown> + extra?: Record<string, unknown> }, enabled = true) => { - return useQuery<{ options: Array<{ value: string, label: any }> }>({ - queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.extra], - queryFn: () => get<{ options: Array<{ value: string, label: any }> }>( - '/workspaces/current/plugin/parameters/dynamic-options', - { - params: { - ...payload, - provider_type: 'trigger', // Add required provider_type parameter + return useQuery<{ options: FormOption[] }>({ + queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.credentials, payload.extra], + queryFn: () => { + // Use new endpoint with POST when credentials provided (for edit mode) + if (payload.credentials) { + return post<{ options: FormOption[] }>( + '/workspaces/current/plugin/parameters/dynamic-options-with-credentials', + { + body: { + plugin_id: payload.plugin_id, + provider: payload.provider, + action: payload.action, + parameter: payload.parameter, + credential_id: payload.credential_id, + credentials: payload.credentials, + }, + }, + { silent: true }, + ) + } + // Use original GET endpoint for normal cases + return get<{ options: FormOption[] }>( + '/workspaces/current/plugin/parameters/dynamic-options', + { + params: { + plugin_id: payload.plugin_id, + provider: payload.provider, + action: payload.action, + parameter: payload.parameter, + credential_id: payload.credential_id, + provider_type: 'trigger', + }, }, - }, - { silent: true }, - ), + { silent: true }, + ) + }, enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter && !!payload.credential_id, retry: 0, }) From fdaeec7f7d4bbe76fea73be7bb71bb1d6bddf8de Mon Sep 17 00:00:00 2001 From: Maries <xh001x@hotmail.com> Date: Wed, 24 Dec 2025 20:23:52 +0800 Subject: [PATCH 68/71] fix: trigger subscription delete not working for non-auto-created credentials (#30122) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --- .../trigger/trigger_provider_service.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/api/services/trigger/trigger_provider_service.py b/api/services/trigger/trigger_provider_service.py index 71f35dada6..57de9b3cee 100644 --- a/api/services/trigger/trigger_provider_service.py +++ b/api/services/trigger/trigger_provider_service.py @@ -359,10 +359,6 @@ class TriggerProviderService: raise ValueError(f"Trigger provider subscription {subscription_id} not found") credential_type: CredentialType = CredentialType.of(subscription.credential_type) - is_auto_created: bool = credential_type in [CredentialType.OAUTH2, CredentialType.API_KEY] - if not is_auto_created: - return None - provider_id = TriggerProviderID(subscription.provider_id) provider_controller: PluginTriggerProviderController = TriggerManager.get_trigger_provider( tenant_id=tenant_id, provider_id=provider_id @@ -372,17 +368,20 @@ class TriggerProviderService: controller=provider_controller, subscription=subscription, ) - try: - TriggerManager.unsubscribe_trigger( - tenant_id=tenant_id, - user_id=subscription.user_id, - provider_id=provider_id, - subscription=subscription.to_entity(), - credentials=encrypter.decrypt(subscription.credentials), - credential_type=credential_type, - ) - except Exception as e: - logger.exception("Error unsubscribing trigger", exc_info=e) + + is_auto_created: bool = credential_type in [CredentialType.OAUTH2, CredentialType.API_KEY] + if is_auto_created: + try: + TriggerManager.unsubscribe_trigger( + tenant_id=tenant_id, + user_id=subscription.user_id, + provider_id=provider_id, + subscription=subscription.to_entity(), + credentials=encrypter.decrypt(subscription.credentials), + credential_type=credential_type, + ) + except Exception as e: + logger.exception("Error unsubscribing trigger", exc_info=e) session.delete(subscription) # Clear cache From 3cbbb06dc418786e78b260bce72c3b0d370ba8cf Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:44:57 +0800 Subject: [PATCH 69/71] chore(web): migrate lodash-es to es-toolkit compat (#30126) --- web/__mocks__/provider-context.ts | 2 +- .../time-range-picker/date-picker.tsx | 2 +- .../webapp-reset-password/page.tsx | 2 +- .../components/mail-and-code-auth.tsx | 2 +- .../components/mail-and-password-auth.tsx | 2 +- .../account-page/email-change-modal.tsx | 2 +- .../batch-add-annotation-modal/index.tsx | 2 +- .../base/operation-btn/index.tsx | 2 +- .../config-prompt/simple-prompt-input.tsx | 2 +- .../config/agent/prompt-editor.tsx | 2 +- .../dataset-config/index.spec.tsx | 2 +- .../configuration/dataset-config/index.tsx | 2 +- .../params-config/config-content.tsx | 13 ++++++---- .../params-config/weighted-score.tsx | 2 +- .../dataset-config/settings-modal/index.tsx | 2 +- .../debug-with-multiple-model/context.tsx | 2 +- .../text-generation-item.tsx | 2 +- .../app/configuration/debug/hooks.tsx | 2 +- .../app/configuration/debug/index.tsx | 3 +-- .../hooks/use-advanced-prompt-config.ts | 2 +- .../components/app/configuration/index.tsx | 2 +- .../tools/external-data-tool-modal.tsx | 2 +- .../app/create-from-dsl-modal/index.tsx | 2 +- .../components/app/duplicate-modal/index.tsx | 2 +- web/app/components/app/log/index.tsx | 2 +- web/app/components/app/log/list.tsx | 2 +- .../apikey-info-panel.test-utils.tsx | 2 +- web/app/components/app/overview/app-chart.tsx | 2 +- .../components/app/switch-app-modal/index.tsx | 2 +- web/app/components/app/workflow-log/index.tsx | 2 +- .../base/agent-log-modal/detail.tsx | 2 +- .../components/base/app-icon-picker/index.tsx | 2 +- .../base/chat/__tests__/utils.spec.ts | 2 +- .../base/chat/chat-with-history/context.tsx | 2 +- .../base/chat/chat-with-history/hooks.tsx | 2 +- web/app/components/base/chat/chat/hooks.ts | 2 +- web/app/components/base/chat/chat/index.tsx | 2 +- .../base/chat/embedded-chatbot/context.tsx | 2 +- .../base/chat/embedded-chatbot/hooks.tsx | 2 +- .../components/base/copy-feedback/index.tsx | 2 +- web/app/components/base/copy-icon/index.tsx | 2 +- .../components/base/emoji-picker/index.tsx | 2 +- .../conversation-opener/modal.tsx | 2 +- .../moderation/moderation-setting-modal.tsx | 2 +- .../components/base/file-uploader/hooks.ts | 2 +- .../base/file-uploader/pdf-preview.tsx | 2 +- .../components/base/file-uploader/store.tsx | 2 +- .../base/fullscreen-modal/index.tsx | 2 +- web/app/components/base/icons/script.mjs | 2 +- .../base/image-uploader/image-preview.tsx | 2 +- .../base/input-with-copy/index.spec.tsx | 4 +-- .../components/base/input-with-copy/index.tsx | 2 +- web/app/components/base/input/index.tsx | 2 +- web/app/components/base/markdown/index.tsx | 2 +- .../base/markdown/markdown-utils.ts | 2 +- web/app/components/base/modal/index.tsx | 2 +- web/app/components/base/modal/modal.tsx | 2 +- .../components/base/pagination/pagination.tsx | 2 +- .../context-block-replacement-block.tsx | 2 +- .../plugins/context-block/index.tsx | 2 +- .../history-block-replacement-block.tsx | 2 +- .../plugins/history-block/index.tsx | 2 +- web/app/components/base/radio-card/index.tsx | 2 +- .../components/base/tag-management/panel.tsx | 2 +- .../base/tag-management/tag-remove-modal.tsx | 2 +- web/app/components/base/toast/index.spec.tsx | 2 +- web/app/components/base/toast/index.tsx | 2 +- .../base/with-input-validation/index.spec.tsx | 2 +- .../index-failed.tsx | 2 +- .../create-from-dsl-modal/index.tsx | 2 +- .../datasets/create/step-two/index.tsx | 2 +- .../documents/detail/batch-modal/index.tsx | 2 +- .../completed/common/full-screen-drawer.tsx | 2 +- .../completed/common/regeneration-modal.tsx | 2 +- .../documents/detail/completed/index.tsx | 2 +- .../documents/detail/metadata/index.tsx | 2 +- .../settings/pipeline-settings/index.tsx | 2 +- .../components/datasets/documents/list.tsx | 2 +- .../datasets/documents/operations.tsx | 2 +- .../metadata/hooks/use-metadata-document.ts | 2 +- .../metadata-dataset/create-content.tsx | 2 +- .../datasets/rename-modal/index.tsx | 2 +- .../explore/create-app-modal/index.tsx | 2 +- .../api-based-extension-page/modal.tsx | 2 +- .../data-source-notion/index.tsx | 2 +- .../data-source-page/panel/config-item.tsx | 2 +- .../account-setting/key-validator/hooks.ts | 2 +- .../edit-workspace-modal/index.tsx | 2 +- .../members-page/invite-modal/index.tsx | 2 +- .../members-page/invited-modal/index.tsx | 2 +- .../transfer-ownership-modal/index.tsx | 2 +- .../header/account-setting/menu-dialog.tsx | 2 +- web/app/components/header/app-nav/index.tsx | 2 +- .../components/header/app-selector/index.tsx | 2 +- .../components/header/dataset-nav/index.tsx | 2 +- .../header/nav/nav-selector/index.tsx | 2 +- .../plugins/base/deprecation-notice.tsx | 2 +- .../plugins/install-plugin/utils.ts | 2 +- .../plugins/marketplace/context.tsx | 2 +- .../subscription-list/create/common-modal.tsx | 2 +- .../edit/apikey-edit-modal.tsx | 2 +- .../edit/manual-edit-modal.tsx | 2 +- .../edit/oauth-edit-modal.tsx | 2 +- .../plugins/plugin-page/context.tsx | 2 +- .../plugins/plugin-page/empty/index.tsx | 2 +- .../components/plugins/plugin-page/index.tsx | 2 +- .../plugin-page/install-plugin-dropdown.tsx | 2 +- .../field-list/field-list-container.tsx | 2 +- .../publish-as-knowledge-pipeline-modal.tsx | 2 +- .../rag-pipeline/hooks/use-pipeline.tsx | 2 +- web/app/components/tools/labels/selector.tsx | 2 +- web/app/components/tools/mcp/modal.tsx | 2 +- .../setting/build-in/config-credentials.tsx | 2 +- .../workflow-tool/confirm-modal/index.tsx | 2 +- .../workflow-app/hooks/use-workflow-run.ts | 2 +- .../workflow/block-selector/blocks.tsx | 2 +- .../market-place-plugin/list.tsx | 2 +- web/app/components/workflow/custom-edge.tsx | 2 +- .../workflow/dsl-export-confirm-modal.tsx | 2 +- .../components/workflow/hooks-store/store.ts | 2 +- .../workflow/hooks/use-nodes-layout.ts | 2 +- .../workflow/hooks/use-workflow-history.ts | 2 +- .../components/workflow/hooks/use-workflow.ts | 2 +- web/app/components/workflow/index.tsx | 2 +- .../nodes/_base/components/agent-strategy.tsx | 2 +- .../components/editor/code-editor/index.tsx | 2 +- .../nodes/_base/components/file-type-item.tsx | 2 +- .../components/input-support-select-var.tsx | 2 +- .../_base/components/next-step/index.tsx | 2 +- .../_base/components/next-step/operator.tsx | 2 +- .../panel-operator/change-block.tsx | 2 +- .../nodes/_base/components/variable/utils.ts | 2 +- .../variable/var-reference-picker.tsx | 2 +- .../variable/var-reference-vars.tsx | 2 +- .../variable-label/base/variable-label.tsx | 2 +- .../_base/components/workflow-panel/index.tsx | 2 +- .../nodes/_base/hooks/use-one-step-run.ts | 4 +-- .../assigner/components/var-list/index.tsx | 2 +- .../workflow/nodes/assigner/hooks.ts | 2 +- .../nodes/http/components/edit-body/index.tsx | 2 +- .../nodes/http/hooks/use-key-value-list.ts | 2 +- .../components/condition-number-input.tsx | 2 +- .../if-else/components/condition-wrap.tsx | 2 +- .../workflow/nodes/iteration/use-config.ts | 2 +- .../condition-list/condition-value-method.tsx | 2 +- .../metadata/metadata-filter/index.tsx | 2 +- .../components/retrieval-config.tsx | 5 ++-- .../nodes/knowledge-retrieval/panel.tsx | 4 +-- .../nodes/knowledge-retrieval/use-config.ts | 2 +- .../nodes/knowledge-retrieval/utils.ts | 2 +- .../visual-editor/context.tsx | 2 +- .../visual-editor/hooks.ts | 2 +- .../nodes/llm/use-single-run-form-params.ts | 2 +- .../components/condition-number-input.tsx | 2 +- .../use-single-run-form-params.ts | 2 +- .../components/class-item.tsx | 2 +- .../components/class-list.tsx | 2 +- .../use-single-run-form-params.ts | 2 +- .../nodes/start/components/var-item.tsx | 2 +- .../nodes/tool/components/copy-id.tsx | 2 +- .../nodes/tool/components/input-var-list.tsx | 2 +- .../components/var-list/index.tsx | 2 +- .../workflow/nodes/variable-assigner/hooks.ts | 4 +-- .../plugins/link-editor-plugin/component.tsx | 2 +- .../plugins/link-editor-plugin/hooks.ts | 2 +- .../components/variable-item.tsx | 2 +- .../conversation-variable-modal.tsx | 2 +- .../workflow/panel/debug-and-preview/hooks.ts | 2 +- .../panel/debug-and-preview/index.tsx | 2 +- .../workflow/panel/env-panel/env-item.tsx | 2 +- .../panel/global-variable-panel/item.tsx | 2 +- .../run/utils/format-log/agent/index.ts | 2 +- .../workflow/run/utils/format-log/index.ts | 2 +- .../utils/format-log/iteration/index.spec.ts | 2 +- .../run/utils/format-log/loop/index.spec.ts | 2 +- .../store/workflow/workflow-draft-slice.ts | 2 +- .../components/workflow/utils/elk-layout.ts | 2 +- .../workflow/utils/workflow-init.ts | 2 +- web/app/components/workflow/utils/workflow.ts | 2 +- .../workflow/variable-inspect/index.tsx | 2 +- .../workflow/workflow-history-store.tsx | 2 +- .../education-apply/education-apply-page.tsx | 2 +- web/app/reset-password/page.tsx | 2 +- .../components/mail-and-password-auth.tsx | 2 +- web/app/signin/invite-settings/page.tsx | 2 +- web/app/signup/components/input-mail.tsx | 2 +- web/context/app-context.tsx | 2 +- web/context/datasets-context.tsx | 2 +- web/context/debug-configuration.ts | 2 +- web/context/explore-context.ts | 2 +- web/context/i18n.ts | 2 +- web/context/mitt-context.tsx | 2 +- web/context/modal-context.tsx | 2 +- web/context/provider-context.tsx | 2 +- web/i18n-config/i18next-config.ts | 2 +- web/i18n-config/server.ts | 2 +- web/package.json | 3 +-- web/pnpm-lock.yaml | 26 ++++++------------- web/service/knowledge/use-create-dataset.ts | 2 +- web/service/use-flow.ts | 2 +- web/service/use-plugins.ts | 2 +- web/utils/index.ts | 2 +- 202 files changed, 222 insertions(+), 230 deletions(-) diff --git a/web/__mocks__/provider-context.ts b/web/__mocks__/provider-context.ts index 3ecb4e9c0e..c69a2ad1d2 100644 --- a/web/__mocks__/provider-context.ts +++ b/web/__mocks__/provider-context.ts @@ -1,6 +1,6 @@ import type { Plan, UsagePlanInfo } from '@/app/components/billing/type' import type { ProviderContextState } from '@/context/provider-context' -import { merge, noop } from 'lodash-es' +import { merge, noop } from 'es-toolkit/compat' import { defaultPlan } from '@/app/components/billing/config' // Avoid being mocked in tests diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx index ab39846a36..004f83afc5 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx @@ -4,7 +4,7 @@ import type { FC } from 'react' import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types' import { RiCalendarLine } from '@remixicon/react' import dayjs from 'dayjs' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback } from 'react' import Picker from '@/app/components/base/date-and-time-picker/date-picker' diff --git a/web/app/(shareLayout)/webapp-reset-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/page.tsx index 92c39eb729..108bd4b22e 100644 --- a/web/app/(shareLayout)/webapp-reset-password/page.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/page.tsx @@ -1,6 +1,6 @@ 'use client' import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx index 9020858347..8b611b9eea 100644 --- a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx index 67e4bf7af2..46645ed68c 100644 --- a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx @@ -1,5 +1,5 @@ 'use client' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useState } from 'react' diff --git a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx index 99b4f5c686..655452ea24 100644 --- a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx +++ b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx @@ -1,6 +1,6 @@ import type { ResponseError } from '@/service/fetch' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import * as React from 'react' import { useState } from 'react' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx index 7fbb745c48..7289ed384d 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/base/operation-btn/index.tsx b/web/app/components/app/configuration/base/operation-btn/index.tsx index b9f55de26b..6a22dc6d3b 100644 --- a/web/app/components/app/configuration/base/operation-btn/index.tsx +++ b/web/app/components/app/configuration/base/operation-btn/index.tsx @@ -4,7 +4,7 @@ import { RiAddLine, RiEditLine, } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index 9b558b58c1..3f39828e79 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -4,8 +4,8 @@ import type { ExternalDataTool } from '@/models/common' import type { PromptVariable } from '@/models/debug' import type { GenRes } from '@/service/debug' import { useBoolean } from 'ahooks' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/config/agent/prompt-editor.tsx b/web/app/components/app/configuration/config/agent/prompt-editor.tsx index 0a09609cca..7399aaeac9 100644 --- a/web/app/components/app/configuration/config/agent/prompt-editor.tsx +++ b/web/app/components/app/configuration/config/agent/prompt-editor.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { ExternalDataTool } from '@/models/common' import copy from 'copy-to-clipboard' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/configuration/dataset-config/index.spec.tsx b/web/app/components/app/configuration/dataset-config/index.spec.tsx index acccdec98e..e3791db9c0 100644 --- a/web/app/components/app/configuration/dataset-config/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/index.spec.tsx @@ -52,7 +52,7 @@ vi.mock('../debug/hooks', () => ({ useFormattingChangedDispatcher: vi.fn(() => vi.fn()), })) -vi.mock('lodash-es', () => ({ +vi.mock('es-toolkit/compat', () => ({ intersectionBy: vi.fn((...arrays) => { // Mock realistic intersection behavior based on metadata name const validArrays = arrays.filter(Array.isArray) diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index 9ac1729590..2fc82c82b6 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -8,8 +8,8 @@ import type { MetadataFilteringModeEnum, } from '@/app/components/workflow/nodes/knowledge-retrieval/types' import type { DataSet } from '@/models/datasets' +import { intersectionBy } from 'es-toolkit/compat' import { produce } from 'immer' -import { intersectionBy } from 'lodash-es' import * as React from 'react' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx index ad5199fd55..f7aadc0c3f 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' +import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import type { ModelConfig } from '@/app/components/workflow/types' import type { DataSet, @@ -8,7 +9,6 @@ import type { import type { DatasetConfigs, } from '@/models/debug' -import { noop } from 'lodash-es' import { memo, useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' @@ -33,17 +33,20 @@ type Props = { selectedDatasets?: DataSet[] isInWorkflow?: boolean singleRetrievalModelConfig?: ModelConfig - onSingleRetrievalModelChange?: (config: ModelConfig) => void - onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void + onSingleRetrievalModelChange?: ModelParameterModalProps['setModel'] + onSingleRetrievalModelParamsChange?: ModelParameterModalProps['onCompletionParamsChange'] } +const noopModelChange: ModelParameterModalProps['setModel'] = () => {} +const noopParamsChange: ModelParameterModalProps['onCompletionParamsChange'] = () => {} + const ConfigContent: FC<Props> = ({ datasetConfigs, onChange, isInWorkflow, singleRetrievalModelConfig: singleRetrievalConfig = {} as ModelConfig, - onSingleRetrievalModelChange = noop, - onSingleRetrievalModelParamsChange = noop, + onSingleRetrievalModelChange = noopModelChange, + onSingleRetrievalModelParamsChange = noopParamsChange, selectedDatasets = [], }) => { const { t } = useTranslation() diff --git a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx index 77f3ac0eb8..3a62a66525 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { memo } from 'react' import { useTranslation } from 'react-i18next' import Slider from '@/app/components/base/slider' diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 243aafdbdd..0d8a0934e5 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -3,7 +3,7 @@ import type { Member } from '@/models/common' import type { DataSet } from '@/models/datasets' import type { RetrievalConfig } from '@/types/app' import { RiCloseLine } from '@remixicon/react' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx index 9d058186cf..3f0381074f 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx @@ -1,7 +1,7 @@ 'use client' import type { ModelAndParameter } from '../types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext } from 'use-context-selector' export type DebugWithMultipleModelContextType = { diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx index 8dda5b3879..9113f782d9 100644 --- a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx +++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx @@ -4,7 +4,7 @@ import type { OnSend, TextGenerationConfig, } from '@/app/components/base/text-generation/types' -import { cloneDeep, noop } from 'lodash-es' +import { cloneDeep, noop } from 'es-toolkit/compat' import { memo } from 'react' import TextGeneration from '@/app/components/app/text-generate/item' import { TransferMethod } from '@/app/components/base/chat/types' diff --git a/web/app/components/app/configuration/debug/hooks.tsx b/web/app/components/app/configuration/debug/hooks.tsx index 786d43bdb9..e66185e284 100644 --- a/web/app/components/app/configuration/debug/hooks.tsx +++ b/web/app/components/app/configuration/debug/hooks.tsx @@ -6,7 +6,7 @@ import type { ChatConfig, ChatItem, } from '@/app/components/base/chat/types' -import cloneDeep from 'lodash-es/cloneDeep' +import { cloneDeep } from 'es-toolkit/compat' import { useCallback, useRef, diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index fe1c6550f5..140f9421c9 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -11,9 +11,8 @@ import { RiSparklingFill, } from '@remixicon/react' import { useBoolean } from 'ahooks' +import { cloneDeep, noop } from 'es-toolkit/compat' import { produce, setAutoFreeze } from 'immer' -import { noop } from 'lodash-es' -import cloneDeep from 'lodash-es/cloneDeep' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts index 63603033d3..3e8f7c5b3a 100644 --- a/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts +++ b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts @@ -1,7 +1,7 @@ import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug' +import { clone } from 'es-toolkit/compat' import { produce } from 'immer' -import { clone } from 'lodash-es' import { useState } from 'react' import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock, PRE_PROMPT_PLACEHOLDER_TEXT } from '@/app/components/base/prompt-editor/constants' import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index eb7a9f5a32..4b5bcafc9b 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -20,8 +20,8 @@ import type { import type { ModelConfig as BackendModelConfig, UserInputFormItem, VisionSettings } from '@/types/app' import { CodeBracketIcon } from '@heroicons/react/20/solid' import { useBoolean, useGetState } from 'ahooks' +import { clone, isEqual } from 'es-toolkit/compat' import { produce } from 'immer' -import { clone, isEqual } from 'lodash-es' import { usePathname } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' diff --git a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx index 74f436289f..ccd0b1288e 100644 --- a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx +++ b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx @@ -3,7 +3,7 @@ import type { CodeBasedExtensionItem, ExternalDataTool, } from '@/models/common' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index 3d6cabedf6..809859d3ad 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -3,7 +3,7 @@ import type { MouseEventHandler } from 'react' import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react' import { useDebounceFn, useKeyPress } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/duplicate-modal/index.tsx b/web/app/components/app/duplicate-modal/index.tsx index 420a6b159a..f670b37076 100644 --- a/web/app/components/app/duplicate-modal/index.tsx +++ b/web/app/components/app/duplicate-modal/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { AppIconType } from '@/types/app' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx index 0e6e4bba2d..52485f5dd2 100644 --- a/web/app/components/app/log/index.tsx +++ b/web/app/components/app/log/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import type { App } from '@/types/app' import { useDebounce } from 'ahooks' import dayjs from 'dayjs' -import { omit } from 'lodash-es' +import { omit } from 'es-toolkit/compat' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 2b13a09b3a..cf10cff327 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -12,7 +12,7 @@ import { RiCloseLine, RiEditFill } from '@remixicon/react' import dayjs from 'dayjs' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' -import { get, noop } from 'lodash-es' +import { get, noop } from 'es-toolkit/compat' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' diff --git a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx index 0e8f82b00d..5cffa1143b 100644 --- a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx +++ b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx @@ -2,7 +2,7 @@ import type { RenderOptions } from '@testing-library/react' import type { Mock, MockedFunction } from 'vitest' import type { ModalContextState } from '@/context/modal-context' import { fireEvent, render } from '@testing-library/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { defaultPlan } from '@/app/components/billing/config' import { useModalContext as actualUseModalContext } from '@/context/modal-context' diff --git a/web/app/components/app/overview/app-chart.tsx b/web/app/components/app/overview/app-chart.tsx index d876dbda27..114ef7d5db 100644 --- a/web/app/components/app/overview/app-chart.tsx +++ b/web/app/components/app/overview/app-chart.tsx @@ -6,7 +6,7 @@ import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyM import dayjs from 'dayjs' import Decimal from 'decimal.js' import ReactECharts from 'echarts-for-react' -import { get } from 'lodash-es' +import { get } from 'es-toolkit/compat' import * as React from 'react' import { useTranslation } from 'react-i18next' import Basic from '@/app/components/app-sidebar/basic' diff --git a/web/app/components/app/switch-app-modal/index.tsx b/web/app/components/app/switch-app-modal/index.tsx index 257f7b6788..83f2efc49d 100644 --- a/web/app/components/app/switch-app-modal/index.tsx +++ b/web/app/components/app/switch-app-modal/index.tsx @@ -2,7 +2,7 @@ import type { App } from '@/types/app' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/app/workflow-log/index.tsx b/web/app/components/app/workflow-log/index.tsx index 12438d6d17..5aa467d03d 100644 --- a/web/app/components/app/workflow-log/index.tsx +++ b/web/app/components/app/workflow-log/index.tsx @@ -5,7 +5,7 @@ import { useDebounce } from 'ahooks' import dayjs from 'dayjs' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' -import { omit } from 'lodash-es' +import { omit } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/agent-log-modal/detail.tsx b/web/app/components/base/agent-log-modal/detail.tsx index 5451126c9e..0b8dc302fb 100644 --- a/web/app/components/base/agent-log-modal/detail.tsx +++ b/web/app/components/base/agent-log-modal/detail.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { IChatItem } from '@/app/components/base/chat/chat/type' import type { AgentIteration, AgentLogDetailResponse } from '@/models/log' -import { flatten, uniq } from 'lodash-es' +import { flatten, uniq } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx index ce73af36a2..99ba7eb544 100644 --- a/web/app/components/base/app-icon-picker/index.tsx +++ b/web/app/components/base/app-icon-picker/index.tsx @@ -3,7 +3,7 @@ import type { Area } from 'react-easy-crop' import type { OnImageInput } from './ImageInput' import type { AppIconType, ImageFile } from '@/types/app' import { RiImageCircleAiLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config' diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index 2e86e13733..d3e77732a5 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -1,5 +1,5 @@ import type { ChatItemInTree } from '../types' -import { get } from 'lodash-es' +import { get } from 'es-toolkit/compat' import { buildChatItemTree, getThreadMessages } from '../utils' import branchedTestMessages from './branchedTestMessages.json' import legacyTestMessages from './legacyTestMessages.json' diff --git a/web/app/components/base/chat/chat-with-history/context.tsx b/web/app/components/base/chat/chat-with-history/context.tsx index 120b6ed4bd..d1496f8278 100644 --- a/web/app/components/base/chat/chat-with-history/context.tsx +++ b/web/app/components/base/chat/chat-with-history/context.tsx @@ -14,7 +14,7 @@ import type { AppMeta, ConversationItem, } from '@/models/share' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext } from 'use-context-selector' export type ChatWithHistoryContextValue = { diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index 67c46b2d10..3acc480518 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -10,8 +10,8 @@ import type { ConversationItem, } from '@/models/share' import { useLocalStorageState } from 'ahooks' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 739fe644fe..64ba5f0aec 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -8,8 +8,8 @@ import type { InputForm } from './type' import type AudioPlayer from '@/app/components/base/audio-btn/audio' import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { Annotation } from '@/models/log' +import { noop, uniqBy } from 'es-toolkit/compat' import { produce, setAutoFreeze } from 'immer' -import { noop, uniqBy } from 'lodash-es' import { useParams, usePathname } from 'next/navigation' import { useCallback, diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 5f5faab240..9c27b61e1f 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -13,7 +13,7 @@ import type { import type { InputForm } from './type' import type { Emoji } from '@/app/components/tools/types' import type { AppData } from '@/models/share' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx index 2738d25c75..97d3dd53cf 100644 --- a/web/app/components/base/chat/embedded-chatbot/context.tsx +++ b/web/app/components/base/chat/embedded-chatbot/context.tsx @@ -13,7 +13,7 @@ import type { AppMeta, ConversationItem, } from '@/models/share' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext } from 'use-context-selector' export type EmbeddedChatbotContextValue = { diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 3c7fd576a3..9e9125fc45 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -9,8 +9,8 @@ import type { ConversationItem, } from '@/models/share' import { useLocalStorageState } from 'ahooks' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/base/copy-feedback/index.tsx b/web/app/components/base/copy-feedback/index.tsx index bf809a2d18..bb71d62c11 100644 --- a/web/app/components/base/copy-feedback/index.tsx +++ b/web/app/components/base/copy-feedback/index.tsx @@ -4,7 +4,7 @@ import { RiClipboardLine, } from '@remixicon/react' import copy from 'copy-to-clipboard' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/copy-icon/index.tsx b/web/app/components/base/copy-icon/index.tsx index 935444a3c1..73a0d80b45 100644 --- a/web/app/components/base/copy-icon/index.tsx +++ b/web/app/components/base/copy-icon/index.tsx @@ -1,6 +1,6 @@ 'use client' import copy from 'copy-to-clipboard' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index 53bef278f6..4fc146ab59 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx index a1b66ae0fc..361f24465f 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -3,8 +3,8 @@ import type { InputVar } from '@/app/components/workflow/types' import type { PromptVariable } from '@/models/debug' import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react' import { useBoolean } from 'ahooks' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx b/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx index d1e6aba6b7..7de0119e59 100644 --- a/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx +++ b/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx @@ -2,7 +2,7 @@ import type { ChangeEvent, FC } from 'react' import type { CodeBasedExtensionItem } from '@/models/common' import type { ModerationConfig, ModerationContentConfig } from '@/models/debug' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts index e62addee2d..6678368462 100644 --- a/web/app/components/base/file-uploader/hooks.ts +++ b/web/app/components/base/file-uploader/hooks.ts @@ -2,8 +2,8 @@ import type { ClipboardEvent } from 'react' import type { FileEntity } from './types' import type { FileUpload } from '@/app/components/base/features/types' import type { FileUploadConfigResponse } from '@/models/common' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import { useParams } from 'next/navigation' import { useCallback, diff --git a/web/app/components/base/file-uploader/pdf-preview.tsx b/web/app/components/base/file-uploader/pdf-preview.tsx index 04a90a414c..b4f057e91e 100644 --- a/web/app/components/base/file-uploader/pdf-preview.tsx +++ b/web/app/components/base/file-uploader/pdf-preview.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiCloseLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react' +import { noop } from 'es-toolkit/compat' import { t } from 'i18next' -import { noop } from 'lodash-es' import * as React from 'react' import { useState } from 'react' import { createPortal } from 'react-dom' diff --git a/web/app/components/base/file-uploader/store.tsx b/web/app/components/base/file-uploader/store.tsx index db3e3622f9..2172733f20 100644 --- a/web/app/components/base/file-uploader/store.tsx +++ b/web/app/components/base/file-uploader/store.tsx @@ -1,7 +1,7 @@ import type { FileEntity, } from './types' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { createContext, useContext, diff --git a/web/app/components/base/fullscreen-modal/index.tsx b/web/app/components/base/fullscreen-modal/index.tsx index b822d21921..cad91b2452 100644 --- a/web/app/components/base/fullscreen-modal/index.tsx +++ b/web/app/components/base/fullscreen-modal/index.tsx @@ -1,6 +1,6 @@ import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react' import { RiCloseLargeLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { cn } from '@/utils/classnames' type IModal = { diff --git a/web/app/components/base/icons/script.mjs b/web/app/components/base/icons/script.mjs index 1cee66d1db..81566cc4cf 100644 --- a/web/app/components/base/icons/script.mjs +++ b/web/app/components/base/icons/script.mjs @@ -2,7 +2,7 @@ import { access, appendFile, mkdir, open, readdir, rm, writeFile } from 'node:fs import path from 'node:path' import { fileURLToPath } from 'node:url' import { parseXml } from '@rgrove/parse-xml' -import { camelCase, template } from 'lodash-es' +import { camelCase, template } from 'es-toolkit/compat' const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/web/app/components/base/image-uploader/image-preview.tsx b/web/app/components/base/image-uploader/image-preview.tsx index bfabe5e247..519bed4d25 100644 --- a/web/app/components/base/image-uploader/image-preview.tsx +++ b/web/app/components/base/image-uploader/image-preview.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiAddBoxLine, RiCloseLine, RiDownloadCloud2Line, RiFileCopyLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react' +import { noop } from 'es-toolkit/compat' import { t } from 'i18next' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' diff --git a/web/app/components/base/input-with-copy/index.spec.tsx b/web/app/components/base/input-with-copy/index.spec.tsx index 782b2bab25..a5fde4ea44 100644 --- a/web/app/components/base/input-with-copy/index.spec.tsx +++ b/web/app/components/base/input-with-copy/index.spec.tsx @@ -25,8 +25,8 @@ vi.mock('react-i18next', () => ({ }), })) -// Mock lodash-es debounce -vi.mock('lodash-es', () => ({ +// Mock es-toolkit/compat debounce +vi.mock('es-toolkit/compat', () => ({ debounce: (fn: any) => fn, })) diff --git a/web/app/components/base/input-with-copy/index.tsx b/web/app/components/base/input-with-copy/index.tsx index 151fa435e7..41bb8f3dec 100644 --- a/web/app/components/base/input-with-copy/index.tsx +++ b/web/app/components/base/input-with-copy/index.tsx @@ -2,7 +2,7 @@ import type { InputProps } from '../input' import { RiClipboardFill, RiClipboardLine } from '@remixicon/react' import copy from 'copy-to-clipboard' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx index 6c6a0c6a75..db7d1f0990 100644 --- a/web/app/components/base/input/index.tsx +++ b/web/app/components/base/input/index.tsx @@ -2,7 +2,7 @@ import type { VariantProps } from 'class-variance-authority' import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react' import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react' import { cva } from 'class-variance-authority' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/markdown/index.tsx b/web/app/components/base/markdown/index.tsx index e698f3bebe..c487b20550 100644 --- a/web/app/components/base/markdown/index.tsx +++ b/web/app/components/base/markdown/index.tsx @@ -1,5 +1,5 @@ import type { ReactMarkdownWrapperProps, SimplePluginInfo } from './react-markdown-wrapper' -import { flow } from 'lodash-es' +import { flow } from 'es-toolkit/compat' import dynamic from 'next/dynamic' import { cn } from '@/utils/classnames' import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils' diff --git a/web/app/components/base/markdown/markdown-utils.ts b/web/app/components/base/markdown/markdown-utils.ts index 59326a7a19..94ad31d1de 100644 --- a/web/app/components/base/markdown/markdown-utils.ts +++ b/web/app/components/base/markdown/markdown-utils.ts @@ -3,7 +3,7 @@ * These functions were extracted from the main markdown renderer for better separation of concerns. * Includes preprocessing for LaTeX and custom "think" tags. */ -import { flow } from 'lodash-es' +import { flow } from 'es-toolkit/compat' import { ALLOW_UNSAFE_DATA_SCHEME } from '@/config' export const preprocessLaTeX = (content: string) => { diff --git a/web/app/components/base/modal/index.tsx b/web/app/components/base/modal/index.tsx index 1b0ff22873..7270af1c77 100644 --- a/web/app/components/base/modal/index.tsx +++ b/web/app/components/base/modal/index.tsx @@ -1,6 +1,6 @@ import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { Fragment } from 'react' import { cn } from '@/utils/classnames' // https://headlessui.com/react/dialog diff --git a/web/app/components/base/modal/modal.tsx b/web/app/components/base/modal/modal.tsx index 69bca1ccbb..6fa44d42d0 100644 --- a/web/app/components/base/modal/modal.tsx +++ b/web/app/components/base/modal/modal.tsx @@ -1,6 +1,6 @@ import type { ButtonProps } from '@/app/components/base/button' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { memo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/base/pagination/pagination.tsx b/web/app/components/base/pagination/pagination.tsx index 25bdf5a31b..dafe0e4ab9 100644 --- a/web/app/components/base/pagination/pagination.tsx +++ b/web/app/components/base/pagination/pagination.tsx @@ -4,7 +4,7 @@ import type { IPaginationProps, PageButtonProps, } from './type' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { cn } from '@/utils/classnames' import usePagination from './hook' diff --git a/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx index 2d2a0b6263..c4a246c40d 100644 --- a/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx @@ -1,8 +1,8 @@ import type { ContextBlockType } from '../../types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' +import { noop } from 'es-toolkit/compat' import { $applyNodeReplacement } from 'lexical' -import { noop } from 'lodash-es' import { memo, useCallback, diff --git a/web/app/components/base/prompt-editor/plugins/context-block/index.tsx b/web/app/components/base/prompt-editor/plugins/context-block/index.tsx index 9f2102bc62..ce3ed4c210 100644 --- a/web/app/components/base/prompt-editor/plugins/context-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/context-block/index.tsx @@ -1,12 +1,12 @@ import type { ContextBlockType } from '../../types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' +import { noop } from 'es-toolkit/compat' import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand, } from 'lexical' -import { noop } from 'lodash-es' import { memo, useEffect, diff --git a/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx index ba695ec95d..f62fb6886b 100644 --- a/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx +++ b/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx @@ -1,8 +1,8 @@ import type { HistoryBlockType } from '../../types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' +import { noop } from 'es-toolkit/compat' import { $applyNodeReplacement } from 'lexical' -import { noop } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/base/prompt-editor/plugins/history-block/index.tsx b/web/app/components/base/prompt-editor/plugins/history-block/index.tsx index a853de5162..dc75fc230d 100644 --- a/web/app/components/base/prompt-editor/plugins/history-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/history-block/index.tsx @@ -1,12 +1,12 @@ import type { HistoryBlockType } from '../../types' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { mergeRegister } from '@lexical/utils' +import { noop } from 'es-toolkit/compat' import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand, } from 'lexical' -import { noop } from 'lodash-es' import { memo, useEffect, diff --git a/web/app/components/base/radio-card/index.tsx b/web/app/components/base/radio-card/index.tsx index ae8bb00099..678d5b6dee 100644 --- a/web/app/components/base/radio-card/index.tsx +++ b/web/app/components/base/radio-card/index.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { cn } from '@/utils/classnames' diff --git a/web/app/components/base/tag-management/panel.tsx b/web/app/components/base/tag-management/panel.tsx index 854de012a5..0023a003c5 100644 --- a/web/app/components/base/tag-management/panel.tsx +++ b/web/app/components/base/tag-management/panel.tsx @@ -3,7 +3,7 @@ import type { HtmlContentProps } from '@/app/components/base/popover' import type { Tag } from '@/app/components/base/tag-management/constant' import { RiAddLine, RiPriceTag3Line } from '@remixicon/react' import { useUnmount } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/base/tag-management/tag-remove-modal.tsx b/web/app/components/base/tag-management/tag-remove-modal.tsx index 8e796f8e0f..8061cde5c1 100644 --- a/web/app/components/base/tag-management/tag-remove-modal.tsx +++ b/web/app/components/base/tag-management/tag-remove-modal.tsx @@ -2,7 +2,7 @@ import type { Tag } from '@/app/components/base/tag-management/constant' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' diff --git a/web/app/components/base/toast/index.spec.tsx b/web/app/components/base/toast/index.spec.tsx index d32619f59a..59314063dd 100644 --- a/web/app/components/base/toast/index.spec.tsx +++ b/web/app/components/base/toast/index.spec.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import { act, render, screen, waitFor } from '@testing-library/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import Toast, { ToastProvider, useToastContext } from '.' diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index a016778996..cf9e1cd909 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -7,7 +7,7 @@ import { RiErrorWarningFill, RiInformation2Fill, } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' diff --git a/web/app/components/base/with-input-validation/index.spec.tsx b/web/app/components/base/with-input-validation/index.spec.tsx index c1a9d55f0a..b2e67ce056 100644 --- a/web/app/components/base/with-input-validation/index.spec.tsx +++ b/web/app/components/base/with-input-validation/index.spec.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { z } from 'zod' import withValidation from '.' diff --git a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx index 1635720037..f685eb1f2e 100644 --- a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx +++ b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { IndexingStatusResponse } from '@/models/datasets' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useReducer } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx index d5955d9bc6..b8c413ba8f 100644 --- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx @@ -1,6 +1,6 @@ 'use client' import { useDebounceFn, useKeyPress } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index 981b6c5a8f..e0a330507c 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -9,7 +9,7 @@ import { RiArrowLeftLine, RiSearchEyeLine, } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Image from 'next/image' import Link from 'next/link' import * as React from 'react' diff --git a/web/app/components/datasets/documents/detail/batch-modal/index.tsx b/web/app/components/datasets/documents/detail/batch-modal/index.tsx index 091d5c493e..0d7199c6c6 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/index.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { ChunkingMode, FileItem } from '@/models/datasets' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx index 5f62bf0185..73b5cbdef9 100644 --- a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { cn } from '@/utils/classnames' import Drawer from './drawer' diff --git a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx index 4957104e25..114d713170 100644 --- a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx +++ b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import { RiLoader2Line } from '@remixicon/react' import { useCountDown } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 1b4aadfa50..e149125865 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -4,7 +4,7 @@ import type { Item } from '@/app/components/base/select' import type { FileEntity } from '@/app/components/datasets/common/image-uploader/types' import type { ChildChunkDetail, SegmentDetailModel, SegmentUpdater } from '@/models/datasets' import { useDebounceFn } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { usePathname } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' diff --git a/web/app/components/datasets/documents/detail/metadata/index.tsx b/web/app/components/datasets/documents/detail/metadata/index.tsx index 87d136c3fe..dbae76491e 100644 --- a/web/app/components/datasets/documents/detail/metadata/index.tsx +++ b/web/app/components/datasets/documents/detail/metadata/index.tsx @@ -4,7 +4,7 @@ import type { inputType, metadataType } from '@/hooks/use-metadata' import type { CommonResponse } from '@/models/common' import type { DocType, FullDocumentDetail } from '@/models/datasets' import { PencilIcon } from '@heroicons/react/24/outline' -import { get } from 'lodash-es' +import { get } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx index 2d71fa9e7d..2eac6b788d 100644 --- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx +++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx @@ -1,7 +1,7 @@ import type { NotionPage } from '@/models/common' import type { CrawlResultItem, CustomFile, FileIndexingEstimateResponse } from '@/models/datasets' import type { OnlineDriveFile, PublishedPipelineRunPreviewResponse } from '@/models/pipeline' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index 0b06d5fe15..670cdd659c 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -9,7 +9,7 @@ import { RiGlobalLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { pick, uniq } from 'lodash-es' +import { pick, uniq } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import * as React from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' diff --git a/web/app/components/datasets/documents/operations.tsx b/web/app/components/datasets/documents/operations.tsx index 825a315178..038a89b56e 100644 --- a/web/app/components/datasets/documents/operations.tsx +++ b/web/app/components/datasets/documents/operations.tsx @@ -11,7 +11,7 @@ import { RiPlayCircleLine, } from '@remixicon/react' import { useBoolean, useDebounceFn } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import * as React from 'react' import { useCallback, useState } from 'react' diff --git a/web/app/components/datasets/metadata/hooks/use-metadata-document.ts b/web/app/components/datasets/metadata/hooks/use-metadata-document.ts index 66dfc0384f..4de18f6e50 100644 --- a/web/app/components/datasets/metadata/hooks/use-metadata-document.ts +++ b/web/app/components/datasets/metadata/hooks/use-metadata-document.ts @@ -1,6 +1,6 @@ import type { BuiltInMetadataItem, MetadataItemWithValue } from '../types' import type { FullDocumentDetail } from '@/models/datasets' -import { get } from 'lodash-es' +import { get } from 'es-toolkit/compat' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx index d31e9d7957..85fd01b2bb 100644 --- a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx +++ b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { RiArrowLeftLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/datasets/rename-modal/index.tsx b/web/app/components/datasets/rename-modal/index.tsx index 8b152b8f02..ef988fc9bc 100644 --- a/web/app/components/datasets/rename-modal/index.tsx +++ b/web/app/components/datasets/rename-modal/index.tsx @@ -4,7 +4,7 @@ import type { MouseEventHandler } from 'react' import type { AppIconSelection } from '../../base/app-icon-picker' import type { DataSet } from '@/models/datasets' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index dac89bc776..b05188fe4d 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -2,7 +2,7 @@ import type { AppIconType } from '@/types/app' import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react' import { useDebounceFn, useKeyPress } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx index d1f64f0d59..f691044bd3 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import type { ApiBasedExtension } from '@/models/common' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx index d139ab39df..2766dc016c 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { DataSourceNotion as TDataSourceNotion } from '@/models/common' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx index b98dd7933d..c57ef96bf9 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import { RiDeleteBinLine, } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' diff --git a/web/app/components/header/account-setting/key-validator/hooks.ts b/web/app/components/header/account-setting/key-validator/hooks.ts index eeb7b63955..4d4cf8d8a4 100644 --- a/web/app/components/header/account-setting/key-validator/hooks.ts +++ b/web/app/components/header/account-setting/key-validator/hooks.ts @@ -1,4 +1,4 @@ -import type { DebouncedFunc } from 'lodash-es' +import type { DebouncedFunc } from 'es-toolkit/compat' import type { ValidateCallback, ValidatedStatusState, ValidateValue } from './declarations' import { useDebounceFn } from 'ahooks' import { useState } from 'react' diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx index 7d609dba68..31f3fc0afe 100644 --- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx @@ -1,6 +1,6 @@ 'use client' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 9b80cd343d..93d88fc4ea 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -2,7 +2,7 @@ import type { InvitationResult } from '@/models/common' import { RiCloseLine, RiErrorWarningFill } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ReactMultiEmail } from 'react-multi-email' diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx index 5fb6c410c4..829eb364a4 100644 --- a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx @@ -2,7 +2,7 @@ import type { InvitationResult } from '@/models/common' import { XMarkIcon } from '@heroicons/react/24/outline' import { CheckCircleIcon } from '@heroicons/react/24/solid' import { RiQuestionLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx index 2c6a33dc1f..323e8f300c 100644 --- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx @@ -1,5 +1,5 @@ import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' diff --git a/web/app/components/header/account-setting/menu-dialog.tsx b/web/app/components/header/account-setting/menu-dialog.tsx index 0b2d2208cd..cc5adbc18f 100644 --- a/web/app/components/header/account-setting/menu-dialog.tsx +++ b/web/app/components/header/account-setting/menu-dialog.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { Fragment, useCallback, useEffect } from 'react' import { cn } from '@/utils/classnames' diff --git a/web/app/components/header/app-nav/index.tsx b/web/app/components/header/app-nav/index.tsx index ab567540d4..34d192de41 100644 --- a/web/app/components/header/app-nav/index.tsx +++ b/web/app/components/header/app-nav/index.tsx @@ -5,8 +5,8 @@ import { RiRobot2Fill, RiRobot2Line, } from '@remixicon/react' +import { flatten } from 'es-toolkit/compat' import { produce } from 'immer' -import { flatten } from 'lodash-es' import { useParams } from 'next/navigation' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/header/app-selector/index.tsx b/web/app/components/header/app-selector/index.tsx index c15ed3e79c..a9aab59356 100644 --- a/web/app/components/header/app-selector/index.tsx +++ b/web/app/components/header/app-selector/index.tsx @@ -2,7 +2,7 @@ import type { AppDetailResponse } from '@/models/app' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' import { ChevronDownIcon, PlusIcon } from '@heroicons/react/24/solid' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { Fragment, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/header/dataset-nav/index.tsx b/web/app/components/header/dataset-nav/index.tsx index e1943a4ec2..4cdc259d67 100644 --- a/web/app/components/header/dataset-nav/index.tsx +++ b/web/app/components/header/dataset-nav/index.tsx @@ -6,7 +6,7 @@ import { RiBook2Fill, RiBook2Line, } from '@remixicon/react' -import { flatten } from 'lodash-es' +import { flatten } from 'es-toolkit/compat' import { useParams, useRouter } from 'next/navigation' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/header/nav/nav-selector/index.tsx b/web/app/components/header/nav/nav-selector/index.tsx index 905597f021..c12be7e035 100644 --- a/web/app/components/header/nav/nav-selector/index.tsx +++ b/web/app/components/header/nav/nav-selector/index.tsx @@ -6,7 +6,7 @@ import { RiArrowDownSLine, RiArrowRightSLine, } from '@remixicon/react' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import { useRouter } from 'next/navigation' import { Fragment, useCallback } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/plugins/base/deprecation-notice.tsx b/web/app/components/plugins/base/deprecation-notice.tsx index 76d64a45f4..efdd903883 100644 --- a/web/app/components/plugins/base/deprecation-notice.tsx +++ b/web/app/components/plugins/base/deprecation-notice.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { RiAlertFill } from '@remixicon/react' -import { camelCase } from 'lodash-es' +import { camelCase } from 'es-toolkit/compat' import Link from 'next/link' import * as React from 'react' import { useMemo } from 'react' diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts index 32d3e54225..549bdc6241 100644 --- a/web/app/components/plugins/install-plugin/utils.ts +++ b/web/app/components/plugins/install-plugin/utils.ts @@ -1,6 +1,6 @@ import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types' import type { GitHubUrlInfo } from '@/app/components/plugins/types' -import { isEmpty } from 'lodash-es' +import { isEmpty } from 'es-toolkit/compat' export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { return { diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index ec76d3440f..4053c4a556 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -10,7 +10,7 @@ import type { SearchParams, SearchParamsFromCollection, } from './types' -import { debounce, noop } from 'lodash-es' +import { debounce, noop } from 'es-toolkit/compat' import { useCallback, useEffect, diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx index b0625d1e82..d0326f6b43 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.tsx @@ -3,7 +3,7 @@ import type { FormRefObject } from '@/app/components/base/form/types' import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types' import type { BuildTriggerSubscriptionPayload } from '@/service/use-triggers' import { RiLoader2Line } from '@remixicon/react' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx index 18bebf8d95..539544f435 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx @@ -2,7 +2,7 @@ import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { EncryptedBottom } from '@/app/components/base/encrypted-bottom' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx index 96c1b6e278..404766ae43 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx @@ -2,7 +2,7 @@ import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { BaseForm } from '@/app/components/base/form/components/base' diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx index 9adee6cc34..53bb6aa69d 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx @@ -2,7 +2,7 @@ import type { FormRefObject, FormSchema } from '@/app/components/base/form/types' import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { BaseForm } from '@/app/components/base/form/components/base' diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index 8a560ac90a..146353da4f 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -2,7 +2,7 @@ import type { ReactNode, RefObject } from 'react' import type { FilterState } from './filter-management' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useMemo, useRef, diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index 4d8904d293..90854bda5d 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -1,5 +1,5 @@ 'use client' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 2c6f90caa5..afa85d7010 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -7,7 +7,7 @@ import { RiEqualizer2Line, } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx index 60c18dca12..22ed35e95b 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -1,7 +1,7 @@ 'use client' import { RiAddLine, RiArrowDownSLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx index 3c4faad439..108f3a642f 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx @@ -1,6 +1,6 @@ import type { SortableItem } from './types' import type { InputVar } from '@/models/pipeline' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx index b11bae2449..303a8caaf5 100644 --- a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx +++ b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx @@ -2,7 +2,7 @@ import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import type { IconInfo } from '@/models/datasets' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline.tsx b/web/app/components/rag-pipeline/hooks/use-pipeline.tsx index 779a38df99..c84bba660d 100644 --- a/web/app/components/rag-pipeline/hooks/use-pipeline.tsx +++ b/web/app/components/rag-pipeline/hooks/use-pipeline.tsx @@ -1,6 +1,6 @@ import type { DataSourceNodeType } from '../../workflow/nodes/data-source/types' import type { Node, ValueSelector } from '../../workflow/types' -import { uniqBy } from 'lodash-es' +import { uniqBy } from 'es-toolkit/compat' import { useCallback } from 'react' import { getOutgoers, useStoreApi } from 'reactflow' import { findUsedVarNodes, updateNodeVars } from '../../workflow/nodes/_base/components/variable/utils' diff --git a/web/app/components/tools/labels/selector.tsx b/web/app/components/tools/labels/selector.tsx index e3afea41a8..43143e5a05 100644 --- a/web/app/components/tools/labels/selector.tsx +++ b/web/app/components/tools/labels/selector.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { Label } from '@/app/components/tools/labels/constant' import { RiArrowDownSLine } from '@remixicon/react' import { useDebounceFn } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index eb8f280484..c5cde65674 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -5,7 +5,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types' import type { AppIconType } from '@/types/app' import { RiCloseLine, RiEditLine } from '@remixicon/react' import { useHover } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/tools/setting/build-in/config-credentials.tsx b/web/app/components/tools/setting/build-in/config-credentials.tsx index 43383cdb51..033052e8a1 100644 --- a/web/app/components/tools/setting/build-in/config-credentials.tsx +++ b/web/app/components/tools/setting/build-in/config-credentials.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import type { Collection } from '../../types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx index f90774cb32..e1a7dff113 100644 --- a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx +++ b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx @@ -1,7 +1,7 @@ 'use client' import { RiCloseLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index 16c7bab6d4..031e2949cb 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -2,8 +2,8 @@ import type AudioPlayer from '@/app/components/base/audio-btn/audio' import type { Node } from '@/app/components/workflow/types' import type { IOtherOptions } from '@/service/base' import type { VersionHistory } from '@/types/workflow' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import { usePathname } from 'next/navigation' import { useCallback, useRef } from 'react' import { diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 51d08d15df..388532f7a2 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -1,5 +1,5 @@ import type { NodeDefault } from '../types' -import { groupBy } from 'lodash-es' +import { groupBy } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index 58724b4621..d6f3007b51 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -2,7 +2,7 @@ import type { RefObject } from 'react' import type { Plugin, PluginCategoryEnum } from '@/app/components/plugins/types' import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useEffect, useImperativeHandle, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 3b73e07536..6427520d81 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -3,7 +3,7 @@ import type { Edge, OnSelectBlock, } from './types' -import { intersection } from 'lodash-es' +import { intersection } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/dsl-export-confirm-modal.tsx b/web/app/components/workflow/dsl-export-confirm-modal.tsx index b616ec5fb5..c5ae8e7cff 100644 --- a/web/app/components/workflow/dsl-export-confirm-modal.tsx +++ b/web/app/components/workflow/dsl-export-confirm-modal.tsx @@ -1,7 +1,7 @@ 'use client' import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiCloseLine, RiLock2Line } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 79cfb7dbce..3aa4ba3d91 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -12,7 +12,7 @@ import type { FlowType } from '@/types/common' import type { VarInInspect } from '@/types/workflow' import { noop, -} from 'lodash-es' +} from 'es-toolkit/compat' import { useContext } from 'react' import { useStore as useZustandStore, diff --git a/web/app/components/workflow/hooks/use-nodes-layout.ts b/web/app/components/workflow/hooks/use-nodes-layout.ts index 653b37008c..2ad89ff100 100644 --- a/web/app/components/workflow/hooks/use-nodes-layout.ts +++ b/web/app/components/workflow/hooks/use-nodes-layout.ts @@ -3,7 +3,7 @@ import type { Node, } from '../types' import ELK from 'elkjs/lib/elk.bundled.js' -import { cloneDeep } from 'lodash-es' +import { cloneDeep } from 'es-toolkit/compat' import { useCallback } from 'react' import { useReactFlow, diff --git a/web/app/components/workflow/hooks/use-workflow-history.ts b/web/app/components/workflow/hooks/use-workflow-history.ts index 3e6043a1fd..0171271c3b 100644 --- a/web/app/components/workflow/hooks/use-workflow-history.ts +++ b/web/app/components/workflow/hooks/use-workflow-history.ts @@ -1,5 +1,5 @@ import type { WorkflowHistoryEventMeta } from '../workflow-history-store' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import { useCallback, useRef, diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index c958bb6b83..990c8c950d 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -9,7 +9,7 @@ import type { Node, ValueSelector, } from '../types' -import { uniqBy } from 'lodash-es' +import { uniqBy } from 'es-toolkit/compat' import { useCallback, } from 'react' diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 1d0c594c23..2b2b1ee543 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -13,8 +13,8 @@ import type { VarInInspect } from '@/types/workflow' import { useEventListener, } from 'ahooks' +import { isEqual } from 'es-toolkit/compat' import { setAutoFreeze } from 'immer' -import { isEqual } from 'lodash-es' import dynamic from 'next/dynamic' import { memo, diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index d7592b3c6f..a390ccec3c 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -4,7 +4,7 @@ import type { NodeOutPutVar } from '../../../types' import type { ToolVarInputs } from '../../tool/types' import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { PluginMeta } from '@/app/components/plugins/types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { memo } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index fa7aef90a5..ad5410986c 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import Editor, { loader } from '@monaco-editor/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { diff --git a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx index b354d35276..9498edba8f 100644 --- a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index 348bb23302..e0bb6f6df5 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -5,7 +5,7 @@ import type { NodeOutPutVar, } from '@/app/components/workflow/types' import { useBoolean } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx index 0f23161261..b9a4404dbb 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx @@ -1,7 +1,7 @@ import type { Node, } from '../../../../types' -import { isEqual } from 'lodash-es' +import { isEqual } from 'es-toolkit/compat' import { memo, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx index 1b550f035d..906dad4bc9 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx @@ -3,7 +3,7 @@ import type { OnSelectBlock, } from '@/app/components/workflow/types' import { RiMoreFill } from '@remixicon/react' -import { intersection } from 'lodash-es' +import { intersection } from 'es-toolkit/compat' import { useCallback, } from 'react' diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx index 47e1c4b22b..9236c022b9 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx @@ -2,7 +2,7 @@ import type { Node, OnSelectBlock, } from '@/app/components/workflow/types' -import { intersection } from 'lodash-es' +import { intersection } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 5a5a9b826a..a7dc04e571 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -33,8 +33,8 @@ import type { import type { PromptItem } from '@/models/debug' import type { RAGPipelineVariable } from '@/models/pipeline' import type { SchemaTypeDefinition } from '@/service/use-common' +import { isArray, uniq } from 'es-toolkit/compat' import { produce } from 'immer' -import { isArray, uniq } from 'lodash-es' import { AGENT_OUTPUT_STRUCT, FILE_STRUCT, diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 05e2c913ce..e92fc9a3b7 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -11,8 +11,8 @@ import { RiLoader4Line, RiMoreLine, } from '@remixicon/react' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index dd0dfa8682..078ba1ef4f 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -4,7 +4,7 @@ import type { StructuredOutput } from '../../../llm/types' import type { Field } from '@/app/components/workflow/nodes/llm/types' import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { useHover } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx index 63b392482f..828f7e5ebe 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx @@ -3,7 +3,7 @@ import { RiErrorWarningFill, RiMoreLine, } from '@remixicon/react' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo } from 'react' import Tooltip from '@/app/components/base/tooltip' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index 8e684afa87..2e25d029b4 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -6,7 +6,7 @@ import { RiCloseLine, RiPlayLargeLine, } from '@remixicon/react' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { cloneElement, diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index 59774ab96a..d8fd4d2c57 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -1,9 +1,9 @@ import type { CommonNodeType, InputVar, TriggerNodeType, ValueSelector, Var, Variable } from '@/app/components/workflow/types' import type { FlowType } from '@/types/common' import type { NodeRunResult, NodeTracing } from '@/types/workflow' -import { produce } from 'immer' +import { noop, unionBy } from 'es-toolkit/compat' -import { noop, unionBy } from 'lodash-es' +import { produce } from 'immer' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx index 422cd5a486..f5ea45d60e 100644 --- a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react' import type { AssignerNodeOperation } from '../../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { RiDeleteBinLine } from '@remixicon/react' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/assigner/hooks.ts b/web/app/components/workflow/nodes/assigner/hooks.ts index d708222868..a3a5db8bfb 100644 --- a/web/app/components/workflow/nodes/assigner/hooks.ts +++ b/web/app/components/workflow/nodes/assigner/hooks.ts @@ -2,7 +2,7 @@ import type { Node, Var, } from '../../types' -import { uniqBy } from 'lodash-es' +import { uniqBy } from 'es-toolkit/compat' import { useCallback } from 'react' import { useNodes } from 'reactflow' import { diff --git a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx index c475f1234a..90c230b6bb 100644 --- a/web/app/components/workflow/nodes/http/components/edit-body/index.tsx +++ b/web/app/components/workflow/nodes/http/components/edit-body/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { uniqueId } from 'es-toolkit/compat' import { produce } from 'immer' -import { uniqueId } from 'lodash-es' import * as React from 'react' import { useCallback, useMemo } from 'react' import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor' diff --git a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts index b174b7e6de..650ae47156 100644 --- a/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts +++ b/web/app/components/workflow/nodes/http/hooks/use-key-value-list.ts @@ -1,6 +1,6 @@ import type { KeyValue } from '../types' import { useBoolean } from 'ahooks' -import { uniqueId } from 'lodash-es' +import { uniqueId } from 'es-toolkit/compat' import { useCallback, useEffect, useState } from 'react' const UNIQUE_ID_PREFIX = 'key-value-' diff --git a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx index 29419be011..9133271161 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx @@ -4,7 +4,7 @@ import type { } from '@/app/components/workflow/types' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx index cdcd7561db..b829ebb040 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx @@ -7,7 +7,7 @@ import { RiDeleteBinLine, RiDraggable, } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/iteration/use-config.ts b/web/app/components/workflow/nodes/iteration/use-config.ts index 50cee67f81..3106577085 100644 --- a/web/app/components/workflow/nodes/iteration/use-config.ts +++ b/web/app/components/workflow/nodes/iteration/use-config.ts @@ -2,8 +2,8 @@ import type { ErrorHandleMode, ValueSelector, Var } from '../../types' import type { IterationNodeType } from './types' import type { Item } from '@/app/components/base/select' import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { isEqual } from 'es-toolkit/compat' import { produce } from 'immer' -import { isEqual } from 'lodash-es' import { useCallback } from 'react' import { useAllBuiltInTools, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx index c930387c82..574501a27d 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx @@ -1,5 +1,5 @@ import { RiArrowDownSLine } from '@remixicon/react' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { useState } from 'react' import Button from '@/app/components/base/button' import { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx index 9b541b9ea6..8a90a8bf5d 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx @@ -1,5 +1,5 @@ import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useState, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx index 02ae01ba16..610aaf5d63 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import type { ModelConfig } from '../../../types' import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' +import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import type { DataSet } from '@/models/datasets' import type { DatasetConfigs } from '@/models/debug' import { RiEqualizer2Line } from '@remixicon/react' @@ -28,8 +29,8 @@ type Props = { onRetrievalModeChange: (mode: RETRIEVE_TYPE) => void onMultipleRetrievalConfigChange: (config: MultipleRetrievalConfig) => void singleRetrievalModelConfig?: ModelConfig - onSingleRetrievalModelChange?: (config: ModelConfig) => void - onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void + onSingleRetrievalModelChange?: ModelParameterModalProps['setModel'] + onSingleRetrievalModelParamsChange?: ModelParameterModalProps['onCompletionParamsChange'] readonly?: boolean rerankModalOpen: boolean onRerankModelOpenChange: (open: boolean) => void diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index ff5a9e2292..1471be9741 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react' import type { KnowledgeRetrievalNodeType } from './types' import type { NodePanelProps } from '@/app/components/workflow/types' -import { intersectionBy } from 'lodash-es' +import { intersectionBy } from 'es-toolkit/compat' import { memo, useMemo, @@ -104,7 +104,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ onRetrievalModeChange={handleRetrievalModeChange} onMultipleRetrievalConfigChange={handleMultipleRetrievalConfigChange} singleRetrievalModelConfig={inputs.single_retrieval_config?.model} - onSingleRetrievalModelChange={handleModelChanged as any} + onSingleRetrievalModelChange={handleModelChanged} onSingleRetrievalModelParamsChange={handleCompletionParamsChange} readonly={readOnly || !selectedDatasets.length} rerankModalOpen={rerankModelOpen} diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts index d0846b3a34..9a63bf96de 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts @@ -9,8 +9,8 @@ import type { MultipleRetrievalConfig, } from './types' import type { DataSet } from '@/models/datasets' +import { isEqual } from 'es-toolkit/compat' import { produce } from 'immer' -import { isEqual } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts index 12cf8c053c..d6cd69b39a 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts @@ -6,7 +6,7 @@ import type { import { uniq, xorBy, -} from 'lodash-es' +} from 'es-toolkit/compat' import { DATASET_DEFAULT } from '@/config' import { DEFAULT_WEIGHTED_SCORE, diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx index cfe63159d3..557cddfb61 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext, diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts index 84c28b236e..1673c80f4f 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts @@ -1,8 +1,8 @@ import type { VisualEditorProps } from '.' import type { Field } from '../../../types' import type { EditData } from './edit-card' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import Toast from '@/app/components/base/toast' import { ArrayType, Type } from '../../../types' import { findPropertyWithPath } from '../../../utils' diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts index ae500074ff..f7cb609f0e 100644 --- a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts @@ -2,7 +2,7 @@ import type { RefObject } from 'react' import type { LLMNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, PromptItem, Var, Variable } from '@/app/components/workflow/types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { InputVarType, VarType } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx index 29419be011..9133271161 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx @@ -4,7 +4,7 @@ import type { } from '@/app/components/workflow/types' import { RiArrowDownSLine } from '@remixicon/react' import { useBoolean } from 'ahooks' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts index abf187d6e5..c53df96688 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts @@ -2,7 +2,7 @@ import type { RefObject } from 'react' import type { ParameterExtractorNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, Var, Variable } from '@/app/components/workflow/types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { InputVarType, VarType } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx index 2af2f8036a..a33ba03550 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import type { Topic } from '../types' import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { uniqueId } from 'lodash-es' +import { uniqueId } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx index 8e61f918a5..8527ce3ad3 100644 --- a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx +++ b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react' import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import { RiDraggable } from '@remixicon/react' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts index 095809eba2..9f2ad5fa39 100644 --- a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts +++ b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts @@ -2,7 +2,7 @@ import type { RefObject } from 'react' import type { QuestionClassifierNodeType } from './types' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import type { InputVar, Var, Variable } from '@/app/components/workflow/types' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { InputVarType, VarType } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/start/components/var-item.tsx b/web/app/components/workflow/nodes/start/components/var-item.tsx index a506c51e31..83676ed21f 100644 --- a/web/app/components/workflow/nodes/start/components/var-item.tsx +++ b/web/app/components/workflow/nodes/start/components/var-item.tsx @@ -5,7 +5,7 @@ import { RiDeleteBinLine, } from '@remixicon/react' import { useBoolean, useHover } from 'ahooks' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/tool/components/copy-id.tsx b/web/app/components/workflow/nodes/tool/components/copy-id.tsx index 8e53970749..6a608fa0b6 100644 --- a/web/app/components/workflow/nodes/tool/components/copy-id.tsx +++ b/web/app/components/workflow/nodes/tool/components/copy-id.tsx @@ -1,7 +1,7 @@ 'use client' import { RiFileCopyLine } from '@remixicon/react' import copy from 'copy-to-clipboard' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index 8b1bd46eeb..23f3868c59 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -4,8 +4,8 @@ import type { ToolVarInputs } from '../types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Tool } from '@/app/components/tools/types' import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx index 19ead7ead1..85ff2def9a 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import type { ValueSelector, Var } from '@/app/components/workflow/types' +import { noop } from 'es-toolkit/compat' import { produce } from 'immer' -import { noop } from 'lodash-es' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/nodes/variable-assigner/hooks.ts b/web/app/components/workflow/nodes/variable-assigner/hooks.ts index b20cee79c7..5c2fe36922 100644 --- a/web/app/components/workflow/nodes/variable-assigner/hooks.ts +++ b/web/app/components/workflow/nodes/variable-assigner/hooks.ts @@ -7,9 +7,9 @@ import type { VarGroupItem, VariableAssignerNodeType, } from './types' -import { produce } from 'immer' +import { uniqBy } from 'es-toolkit/compat' -import { uniqBy } from 'lodash-es' +import { produce } from 'immer' import { useCallback } from 'react' import { useNodes, diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx index 3f5472ad56..d1b178ec4c 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx @@ -11,7 +11,7 @@ import { RiLinkUnlinkM, } from '@remixicon/react' import { useClickAway } from 'ahooks' -import { escape } from 'lodash-es' +import { escape } from 'es-toolkit/compat' import { memo, useEffect, diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts index 2c6e014b15..fb191bc05a 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts @@ -5,11 +5,11 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext import { mergeRegister, } from '@lexical/utils' +import { escape } from 'es-toolkit/compat' import { CLICK_COMMAND, COMMAND_PRIORITY_LOW, } from 'lexical' -import { escape } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx index a43179026e..9fec4f7d01 100644 --- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx +++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx @@ -1,6 +1,6 @@ import type { ConversationVariable } from '@/app/components/workflow/types' import { RiDeleteBinLine, RiEditLine } from '@remixicon/react' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo, useState } from 'react' import { BubbleX } from '@/app/components/base/icons/src/vender/line/others' import { cn } from '@/utils/classnames' diff --git a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx index 6e130180d0..4542e1fb90 100644 --- a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx @@ -5,7 +5,7 @@ import type { import { RiCloseLine } from '@remixicon/react' import { useMount } from 'ahooks' import copy from 'copy-to-clipboard' -import { capitalize, noop } from 'lodash-es' +import { capitalize, noop } from 'es-toolkit/compat' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 6eb1ea0b76..b771d97006 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -5,8 +5,8 @@ import type { Inputs, } from '@/app/components/base/chat/types' import type { FileEntity } from '@/app/components/base/file-uploader/types' +import { uniqBy } from 'es-toolkit/compat' import { produce, setAutoFreeze } from 'immer' -import { uniqBy } from 'lodash-es' import { useCallback, useEffect, diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index 3005b68a9c..2b63cf4751 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -1,7 +1,7 @@ import type { StartNodeType } from '../../nodes/start/types' import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' -import { debounce, noop } from 'lodash-es' +import { debounce, noop } from 'es-toolkit/compat' import { memo, useCallback, diff --git a/web/app/components/workflow/panel/env-panel/env-item.tsx b/web/app/components/workflow/panel/env-panel/env-item.tsx index 64d6610643..582539b85b 100644 --- a/web/app/components/workflow/panel/env-panel/env-item.tsx +++ b/web/app/components/workflow/panel/env-panel/env-item.tsx @@ -1,6 +1,6 @@ import type { EnvironmentVariable } from '@/app/components/workflow/types' import { RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo, useState } from 'react' import { Env } from '@/app/components/base/icons/src/vender/line/others' import { useStore } from '@/app/components/workflow/store' diff --git a/web/app/components/workflow/panel/global-variable-panel/item.tsx b/web/app/components/workflow/panel/global-variable-panel/item.tsx index f82579dedb..458dd27692 100644 --- a/web/app/components/workflow/panel/global-variable-panel/item.tsx +++ b/web/app/components/workflow/panel/global-variable-panel/item.tsx @@ -1,5 +1,5 @@ import type { GlobalVariable } from '@/app/components/workflow/types' -import { capitalize } from 'lodash-es' +import { capitalize } from 'es-toolkit/compat' import { memo } from 'react' import { GlobalVariable as GlobalVariableIcon } from '@/app/components/base/icons/src/vender/line/others' diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.ts b/web/app/components/workflow/run/utils/format-log/agent/index.ts index a4c1ea5167..f86e4b33bb 100644 --- a/web/app/components/workflow/run/utils/format-log/agent/index.ts +++ b/web/app/components/workflow/run/utils/format-log/agent/index.ts @@ -1,5 +1,5 @@ import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow' -import { cloneDeep } from 'lodash-es' +import { cloneDeep } from 'es-toolkit/compat' import { BlockEnum } from '@/app/components/workflow/types' const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool] diff --git a/web/app/components/workflow/run/utils/format-log/index.ts b/web/app/components/workflow/run/utils/format-log/index.ts index 2c89e91571..1dbe8f1682 100644 --- a/web/app/components/workflow/run/utils/format-log/index.ts +++ b/web/app/components/workflow/run/utils/format-log/index.ts @@ -1,5 +1,5 @@ import type { NodeTracing } from '@/types/workflow' -import { cloneDeep } from 'lodash-es' +import { cloneDeep } from 'es-toolkit/compat' import { BlockEnum } from '../../../types' import formatAgentNode from './agent' import { addChildrenToIterationNode } from './iteration' diff --git a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts index 855ac4c69d..8b4416f529 100644 --- a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import format from '.' import graphToLogStruct from '../graph-to-log-struct' diff --git a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts index aee2a432c3..3d31e43ba3 100644 --- a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts +++ b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import format from '.' import graphToLogStruct from '../graph-to-log-struct' diff --git a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts index 83792e84a6..68265a8eba 100644 --- a/web/app/components/workflow/store/workflow/workflow-draft-slice.ts +++ b/web/app/components/workflow/store/workflow/workflow-draft-slice.ts @@ -5,7 +5,7 @@ import type { EnvironmentVariable, Node, } from '@/app/components/workflow/types' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' type DebouncedFunc = { (fn: () => void): void diff --git a/web/app/components/workflow/utils/elk-layout.ts b/web/app/components/workflow/utils/elk-layout.ts index 05f81872bb..1a4bbf2d50 100644 --- a/web/app/components/workflow/utils/elk-layout.ts +++ b/web/app/components/workflow/utils/elk-layout.ts @@ -5,7 +5,7 @@ import type { Node, } from '@/app/components/workflow/types' import ELK from 'elkjs/lib/elk.bundled.js' -import { cloneDeep } from 'lodash-es' +import { cloneDeep } from 'es-toolkit/compat' import { CUSTOM_NODE, NODE_LAYOUT_HORIZONTAL_PADDING, diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 18ba643d30..fa211934e4 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -9,7 +9,7 @@ import type { } from '../types' import { cloneDeep, -} from 'lodash-es' +} from 'es-toolkit/compat' import { getConnectedEdges, } from 'reactflow' diff --git a/web/app/components/workflow/utils/workflow.ts b/web/app/components/workflow/utils/workflow.ts index 43fbd687c1..7fabc51a45 100644 --- a/web/app/components/workflow/utils/workflow.ts +++ b/web/app/components/workflow/utils/workflow.ts @@ -4,7 +4,7 @@ import type { } from '../types' import { uniqBy, -} from 'lodash-es' +} from 'es-toolkit/compat' import { getOutgoers, } from 'reactflow' diff --git a/web/app/components/workflow/variable-inspect/index.tsx b/web/app/components/workflow/variable-inspect/index.tsx index ced7861e00..775c761eca 100644 --- a/web/app/components/workflow/variable-inspect/index.tsx +++ b/web/app/components/workflow/variable-inspect/index.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import { debounce } from 'lodash-es' +import { debounce } from 'es-toolkit/compat' import { useCallback, useMemo, diff --git a/web/app/components/workflow/workflow-history-store.tsx b/web/app/components/workflow/workflow-history-store.tsx index 502cb733cb..6729fe50e3 100644 --- a/web/app/components/workflow/workflow-history-store.tsx +++ b/web/app/components/workflow/workflow-history-store.tsx @@ -3,8 +3,8 @@ import type { TemporalState } from 'zundo' import type { StoreApi } from 'zustand' import type { WorkflowHistoryEventT } from './hooks' import type { Edge, Node } from './types' +import { noop } from 'es-toolkit/compat' import isDeepEqual from 'fast-deep-equal' -import { noop } from 'lodash-es' import { createContext, useContext, useMemo, useState } from 'react' import { temporal } from 'zundo' import { create } from 'zustand' diff --git a/web/app/education-apply/education-apply-page.tsx b/web/app/education-apply/education-apply-page.tsx index 5f2446352e..efd74d42d5 100644 --- a/web/app/education-apply/education-apply-page.tsx +++ b/web/app/education-apply/education-apply-page.tsx @@ -1,7 +1,7 @@ 'use client' import { RiExternalLinkLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useRouter, useSearchParams, diff --git a/web/app/reset-password/page.tsx b/web/app/reset-password/page.tsx index c7e15f8b3f..479f550ae9 100644 --- a/web/app/reset-password/page.tsx +++ b/web/app/reset-password/page.tsx @@ -1,6 +1,6 @@ 'use client' import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' diff --git a/web/app/signin/components/mail-and-password-auth.tsx b/web/app/signin/components/mail-and-password-auth.tsx index 9ab2d9314c..2e95ac8663 100644 --- a/web/app/signin/components/mail-and-password-auth.tsx +++ b/web/app/signin/components/mail-and-password-auth.tsx @@ -1,5 +1,5 @@ import type { ResponseError } from '@/service/fetch' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useState } from 'react' diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index e3bbe420bc..c4923959ab 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -1,7 +1,7 @@ 'use client' import type { Locale } from '@/i18n-config' import { RiAccountCircleLine } from '@remixicon/react' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useState } from 'react' diff --git a/web/app/signup/components/input-mail.tsx b/web/app/signup/components/input-mail.tsx index b001e1f8b0..26ac99d2c6 100644 --- a/web/app/signup/components/input-mail.tsx +++ b/web/app/signup/components/input-mail.tsx @@ -1,6 +1,6 @@ 'use client' import type { MailSendResponse } from '@/service/use-common' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import Link from 'next/link' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index b7a47048f3..cb4cab65b7 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -3,7 +3,7 @@ import type { FC, ReactNode } from 'react' import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import { useQueryClient } from '@tanstack/react-query' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useCallback, useEffect, useMemo } from 'react' import { createContext, useContext, useContextSelector } from 'use-context-selector' import { setUserId, setUserProperties } from '@/app/components/base/amplitude' diff --git a/web/context/datasets-context.tsx b/web/context/datasets-context.tsx index 4ca7ad311e..f35767bc21 100644 --- a/web/context/datasets-context.tsx +++ b/web/context/datasets-context.tsx @@ -1,7 +1,7 @@ 'use client' import type { DataSet } from '@/models/datasets' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext } from 'use-context-selector' export type DatasetsContextValue = { diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts index 51ba4ab626..2518af6260 100644 --- a/web/context/debug-configuration.ts +++ b/web/context/debug-configuration.ts @@ -22,7 +22,7 @@ import type { TextToSpeechConfig, } from '@/models/debug' import type { VisionSettings } from '@/types/app' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext } from 'use-context-selector' import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' import { PromptMode } from '@/models/debug' diff --git a/web/context/explore-context.ts b/web/context/explore-context.ts index 688b9036f9..1a7b35a09b 100644 --- a/web/context/explore-context.ts +++ b/web/context/explore-context.ts @@ -1,5 +1,5 @@ import type { InstalledApp } from '@/models/explore' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext } from 'use-context-selector' type IExplore = { diff --git a/web/context/i18n.ts b/web/context/i18n.ts index 773569fa21..92d66a1b2f 100644 --- a/web/context/i18n.ts +++ b/web/context/i18n.ts @@ -1,5 +1,5 @@ import type { Locale } from '@/i18n-config' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext, diff --git a/web/context/mitt-context.tsx b/web/context/mitt-context.tsx index 6c6209b5a5..0fc160613a 100644 --- a/web/context/mitt-context.tsx +++ b/web/context/mitt-context.tsx @@ -1,4 +1,4 @@ -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { createContext, useContext, useContextSelector } from 'use-context-selector' import { useMitt } from '@/hooks/use-mitt' diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 2afd1b7b2f..5b417a64ff 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -22,7 +22,7 @@ import type { ExternalDataTool, } from '@/models/common' import type { ModerationConfig, PromptVariable } from '@/models/debug' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import dynamic from 'next/dynamic' import { useSearchParams } from 'next/navigation' import { useCallback, useEffect, useState } from 'react' diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index eb2a034f3b..3394ea20f6 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -5,7 +5,7 @@ import type { Model, ModelProvider } from '@/app/components/header/account-setti import type { RETRIEVE_METHOD } from '@/types/app' import { useQueryClient } from '@tanstack/react-query' import dayjs from 'dayjs' -import { noop } from 'lodash-es' +import { noop } from 'es-toolkit/compat' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { createContext, useContext, useContextSelector } from 'use-context-selector' diff --git a/web/i18n-config/i18next-config.ts b/web/i18n-config/i18next-config.ts index e82f5f2acb..8dce79a5e5 100644 --- a/web/i18n-config/i18next-config.ts +++ b/web/i18n-config/i18next-config.ts @@ -1,7 +1,7 @@ 'use client' import type { Locale } from '.' +import { camelCase, kebabCase } from 'es-toolkit/compat' import i18n from 'i18next' -import { camelCase, kebabCase } from 'lodash-es' import { initReactI18next } from 'react-i18next' import app from '../i18n/en-US/app' diff --git a/web/i18n-config/server.ts b/web/i18n-config/server.ts index 75a5f3943b..c92a5a6025 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n-config/server.ts @@ -1,9 +1,9 @@ import type { Locale } from '.' import type { KeyPrefix, Namespace } from './i18next-config' import { match } from '@formatjs/intl-localematcher' +import { camelCase } from 'es-toolkit/compat' import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' -import { camelCase } from 'lodash-es' import Negotiator from 'negotiator' import { cookies, headers } from 'next/headers' import { initReactI18next } from 'react-i18next/initReactI18next' diff --git a/web/package.json b/web/package.json index f3cc095811..a27ac234ad 100644 --- a/web/package.json +++ b/web/package.json @@ -85,6 +85,7 @@ "echarts-for-react": "^3.0.5", "elkjs": "^0.9.3", "emoji-mart": "^5.6.0", + "es-toolkit": "^1.43.0", "fast-deep-equal": "^3.1.3", "html-entities": "^2.6.0", "html-to-image": "1.11.13", @@ -100,7 +101,6 @@ "lamejs": "^1.2.1", "lexical": "^0.38.2", "line-clamp": "^1.0.0", - "lodash-es": "^4.17.21", "mermaid": "~11.11.0", "mime": "^4.1.0", "mitt": "^3.0.1", @@ -169,7 +169,6 @@ "@testing-library/user-event": "^14.6.1", "@types/js-cookie": "^3.0.6", "@types/js-yaml": "^4.0.9", - "@types/lodash-es": "^4.17.12", "@types/negotiator": "^0.6.4", "@types/node": "18.15.0", "@types/qs": "^6.14.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 24e710534f..51f421e90b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -174,6 +174,9 @@ importers: emoji-mart: specifier: ^5.6.0 version: 5.6.0 + es-toolkit: + specifier: ^1.43.0 + version: 1.43.0 fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 @@ -219,9 +222,6 @@ importers: line-clamp: specifier: ^1.0.0 version: 1.0.0 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 mermaid: specifier: ~11.11.0 version: 11.11.0 @@ -421,9 +421,6 @@ importers: '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 '@types/negotiator': specifier: ^0.6.4 version: 0.6.4 @@ -3575,12 +3572,6 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/lodash-es@4.17.12': - resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - - '@types/lodash@4.17.21': - resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==} - '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -5042,6 +5033,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-toolkit@1.43.0: + resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -12077,12 +12071,6 @@ snapshots: dependencies: '@types/node': 18.15.0 - '@types/lodash-es@4.17.12': - dependencies: - '@types/lodash': 4.17.21 - - '@types/lodash@4.17.21': {} - '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -13685,6 +13673,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-toolkit@1.43.0: {} + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 diff --git a/web/service/knowledge/use-create-dataset.ts b/web/service/knowledge/use-create-dataset.ts index eb656c2994..a0d55eeb99 100644 --- a/web/service/knowledge/use-create-dataset.ts +++ b/web/service/knowledge/use-create-dataset.ts @@ -18,7 +18,7 @@ import type { ProcessRuleResponse, } from '@/models/datasets' import { useMutation } from '@tanstack/react-query' -import groupBy from 'lodash-es/groupBy' +import { groupBy } from 'es-toolkit/compat' import { post } from '../base' import { createDocument, createFirstDocument, fetchDefaultProcessRule, fetchFileIndexingEstimate } from '../datasets' diff --git a/web/service/use-flow.ts b/web/service/use-flow.ts index 30bec6dd23..74aa78ec10 100644 --- a/web/service/use-flow.ts +++ b/web/service/use-flow.ts @@ -1,5 +1,5 @@ import type { FlowType } from '@/types/common' -import { curry } from 'lodash-es' +import { curry } from 'es-toolkit/compat' import { useDeleteAllInspectorVars as useDeleteAllInspectorVarsInner, useDeleteInspectVar as useDeleteInspectVarInner, diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 58454125ed..32ea4f35fd 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -33,7 +33,7 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' -import { cloneDeep } from 'lodash-es' +import { cloneDeep } from 'es-toolkit/compat' import { useCallback, useEffect, useState } from 'react' import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils' diff --git a/web/utils/index.ts b/web/utils/index.ts index 263d415479..ebb8b90645 100644 --- a/web/utils/index.ts +++ b/web/utils/index.ts @@ -1,4 +1,4 @@ -import { escape } from 'lodash-es' +import { escape } from 'es-toolkit/compat' export const sleep = (ms: number) => { return new Promise(resolve => setTimeout(resolve, ms)) From 9000fa1a8821bd6fed3291646ffee75b08d67aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=80=89=E5=B6=8B=20=E5=B0=86=E7=9F=A2?= <66870512+krap730@users.noreply.github.com> Date: Thu, 25 Dec 2025 11:19:50 +0900 Subject: [PATCH 70/71] fix: handle list content type in Parameter Extraction node (#30070) --- .../nodes/parameter_extractor/parameter_extractor_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py b/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py index 93db417b15..08e0542d61 100644 --- a/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py +++ b/api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py @@ -281,7 +281,7 @@ class ParameterExtractorNode(Node[ParameterExtractorNodeData]): # handle invoke result - text = invoke_result.message.content or "" + text = invoke_result.message.get_text_content() if not isinstance(text, str): raise InvalidTextContentTypeError(f"Invalid text content type: {type(text)}. Expected str.") From a26b2d74d4c2ef1cc481ca7e5b14335808cbd80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=80=89=E5=B6=8B=20=E5=B0=86=E7=9F=A2?= <66870512+krap730@users.noreply.github.com> Date: Thu, 25 Dec 2025 11:20:25 +0900 Subject: [PATCH 71/71] fix: allow None values in VariableMessage validation (#30082) --- api/core/tools/entities/tool_entities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index 353f3a646a..583a3584f7 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -153,11 +153,11 @@ class ToolInvokeMessage(BaseModel): @classmethod def transform_variable_value(cls, values): """ - Only basic types and lists are allowed. + Only basic types, lists, and None are allowed. """ value = values.get("variable_value") - if not isinstance(value, dict | list | str | int | float | bool): - raise ValueError("Only basic types and lists are allowed.") + if value is not None and not isinstance(value, dict | list | str | int | float | bool): + raise ValueError("Only basic types, lists, and None are allowed.") # if stream is true, the value must be a string if values.get("stream"):