{
- 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 }) => {