diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 2b48afd550..77aff5e58c 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -123,6 +123,17 @@ class AnnotationListApi(Resource): } return response, 200 + @setup_required + @login_required + @account_initialization_required + def delete(self, app_id): + if not current_user.is_editor: + raise Forbidden() + + app_id = str(app_id) + AppAnnotationService.clear_all_annotations(app_id) + return {"result": "success"}, 200 + class AnnotationExportApi(Resource): @setup_required diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 7cb0b46517..34506f5da4 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -440,3 +440,39 @@ class AppAnnotationService: "embedding_model_name": collection_binding_detail.model_name, }, } + + @classmethod + def clear_all_annotations(cls, app_id: str) -> dict: + app = ( + db.session.query(App) + .filter(App.id == app_id, App.tenant_id == current_user.current_tenant_id, App.status == "normal") + .first() + ) + + if not app: + raise NotFound("App not found") + + annotations = db.session.query(MessageAnnotation).filter(MessageAnnotation.app_id == app_id).all() + for annotation in annotations: + annotation_hit_histories = ( + db.session.query(AppAnnotationHitHistory) + .filter(AppAnnotationHitHistory.annotation_id == annotation.id) + .all() + ) + for annotation_hit_history in annotation_hit_histories: + db.session.delete(annotation_hit_history) + + db.session.delete(annotation) + + db.session.commit() + + annotation_setting = ( + db.session.query(AppAnnotationSetting).filter(AppAnnotationSetting.app_id == app_id).first() + ) + if annotation_setting: + for annotation in annotations: + delete_annotation_index_task.delay( + annotation.id, app_id, current_user.current_tenant_id, annotation_setting.collection_binding_id + ) + + return {"result": "success"} 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 new file mode 100644 index 0000000000..7316ee10a1 --- /dev/null +++ b/web/app/components/app/annotation/clear-all-annotations-confirm-modal/index.tsx @@ -0,0 +1,32 @@ +'use client' + +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Confirm from '@/app/components/base/confirm' + +type Props = { + isShow: boolean + onHide: () => void + onConfirm: () => void +} + +const ClearAllAnnotationsConfirmModal: FC = ({ + isShow, + onHide, + onConfirm, +}) => { + const { t } = useTranslation() + + return ( + + ) +} + +export default React.memo(ClearAllAnnotationsConfirmModal) diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index eb397db55f..e2d5edb793 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -1,9 +1,11 @@ 'use client' import type { FC } from 'react' import React, { Fragment, useEffect, useState } from 'react' +import ClearAllAnnotationsConfirmModal from '../clear-all-annotations-confirm-modal' import { useTranslation } from 'react-i18next' import { RiAddLine, + RiDeleteBinLine, RiMoreFill, } from '@remixicon/react' import { useContext } from 'use-context-selector' @@ -22,6 +24,7 @@ import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows import I18n from '@/context/i18n' import { fetchExportAnnotationList } from '@/service/annotation' +import { clearAllAnnotations } from '@/service/annotation' import { LanguagesSupported } from '@/i18n/language' const CSV_HEADER_QA_EN = ['Question', 'Answer'] @@ -76,7 +79,19 @@ const HeaderOptions: FC = ({ }, [controlUpdateList]) const [showBulkImportModal, setShowBulkImportModal] = useState(false) - + const [showClearConfirm, setShowClearConfirm] = useState(false) + const handleClearAll = () => { + setShowClearConfirm(true) + } + const handleConfirmed = async () => { + setShowClearConfirm(false) + try { + await clearAllAnnotations(appId) + onAdded() + } + catch (_) { + } +} const Operations = () => { return (
@@ -125,6 +140,15 @@ const HeaderOptions: FC = ({ +
) } @@ -169,6 +193,15 @@ const HeaderOptions: FC = ({ /> ) } + { + showClearConfirm && ( + setShowClearConfirm(false)} + onConfirm={handleConfirmed} + /> + ) + } ) } diff --git a/web/i18n/en-US/app-annotation.ts b/web/i18n/en-US/app-annotation.ts index 43f24a7619..c0a8008d9a 100644 --- a/web/i18n/en-US/app-annotation.ts +++ b/web/i18n/en-US/app-annotation.ts @@ -16,7 +16,8 @@ const translation = { addAnnotation: 'Add Annotation', bulkImport: 'Bulk Import', bulkExport: 'Bulk Export', - clearAll: 'Clear All Annotation', + clearAll: 'Delete All', + clearAllConfirm: 'Delete all annotations?', }, }, editModal: { diff --git a/web/i18n/zh-Hans/app-annotation.ts b/web/i18n/zh-Hans/app-annotation.ts index 3a6cacf5b5..44d075715f 100644 --- a/web/i18n/zh-Hans/app-annotation.ts +++ b/web/i18n/zh-Hans/app-annotation.ts @@ -18,7 +18,8 @@ const translation = { addAnnotation: '添加标注', bulkImport: '批量导入', bulkExport: '批量导出', - clearAll: '删除所有标注', + clearAll: '删除所有', + clearAllConfirm: '删除所有标注?', }, }, editModal: { diff --git a/web/service/annotation.ts b/web/service/annotation.ts index 5096a4f58a..9f025f8eb9 100644 --- a/web/service/annotation.ts +++ b/web/service/annotation.ts @@ -63,3 +63,7 @@ export const delAnnotation = (appId: string, annotationId: string) => { export const fetchHitHistoryList = (appId: string, annotationId: string, params: Record) => { return get(`apps/${appId}/annotations/${annotationId}/hit-histories`, { params }) } + +export const clearAllAnnotations = (appId: string): Promise => { + return del(`apps/${appId}/annotations`) +}