| external, | external, | ||||
| hit_testing, | hit_testing, | ||||
| metadata, | metadata, | ||||
| upload_file, | |||||
| website, | website, | ||||
| ) | ) | ||||
| from flask_login import current_user | |||||
| from flask_restx import Resource | |||||
| from werkzeug.exceptions import NotFound | |||||
| from controllers.console import api | |||||
| from controllers.console.wraps import ( | |||||
| account_initialization_required, | |||||
| setup_required, | |||||
| ) | |||||
| from core.file import helpers as file_helpers | |||||
| from extensions.ext_database import db | |||||
| from models.dataset import Dataset | |||||
| from models.model import UploadFile | |||||
| from services.dataset_service import DocumentService | |||||
| class UploadFileApi(Resource): | |||||
| @setup_required | |||||
| @account_initialization_required | |||||
| def get(self, dataset_id, document_id): | |||||
| """Get upload file.""" | |||||
| # check dataset | |||||
| dataset_id = str(dataset_id) | |||||
| dataset = ( | |||||
| db.session.query(Dataset) | |||||
| .filter(Dataset.tenant_id == current_user.current_tenant_id, Dataset.id == dataset_id) | |||||
| .first() | |||||
| ) | |||||
| if not dataset: | |||||
| raise NotFound("Dataset not found.") | |||||
| # check document | |||||
| document_id = str(document_id) | |||||
| document = DocumentService.get_document(dataset.id, document_id) | |||||
| if not document: | |||||
| raise NotFound("Document not found.") | |||||
| # check upload file | |||||
| if document.data_source_type != "upload_file": | |||||
| raise ValueError(f"Document data source type ({document.data_source_type}) is not upload_file.") | |||||
| data_source_info = document.data_source_info_dict | |||||
| if data_source_info and "upload_file_id" in data_source_info: | |||||
| file_id = data_source_info["upload_file_id"] | |||||
| upload_file = db.session.query(UploadFile).where(UploadFile.id == file_id).first() | |||||
| if not upload_file: | |||||
| raise NotFound("UploadFile not found.") | |||||
| else: | |||||
| raise ValueError("Upload file id not found in document data source info.") | |||||
| url = file_helpers.get_signed_file_url(upload_file_id=upload_file.id) | |||||
| return { | |||||
| "id": upload_file.id, | |||||
| "name": upload_file.name, | |||||
| "size": upload_file.size, | |||||
| "extension": upload_file.extension, | |||||
| "url": url, | |||||
| "download_url": f"{url}&as_attachment=true", | |||||
| "mime_type": upload_file.mime_type, | |||||
| "created_by": upload_file.created_by, | |||||
| "created_at": upload_file.created_at.timestamp(), | |||||
| }, 200 | |||||
| api.add_resource(UploadFileApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/upload-file") |
| import { | import { | ||||
| RiArchive2Line, | RiArchive2Line, | ||||
| RiDeleteBinLine, | RiDeleteBinLine, | ||||
| RiDownloadLine, | |||||
| RiEditLine, | RiEditLine, | ||||
| RiEqualizer2Line, | RiEqualizer2Line, | ||||
| RiLoopLeftLine, | RiLoopLeftLine, | ||||
| import Indicator from '@/app/components/header/indicator' | import Indicator from '@/app/components/header/indicator' | ||||
| import { asyncRunSafe } from '@/utils' | import { asyncRunSafe } from '@/utils' | ||||
| import { formatNumber } from '@/utils/format' | import { formatNumber } from '@/utils/format' | ||||
| import { useDocumentDownload } from '@/service/knowledge/use-document' | |||||
| import NotionIcon from '@/app/components/base/notion-icon' | import NotionIcon from '@/app/components/base/notion-icon' | ||||
| import ProgressBar from '@/app/components/base/progress-bar' | import ProgressBar from '@/app/components/base/progress-bar' | ||||
| import { ChunkingMode, DataSourceType, DocumentActionType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets' | import { ChunkingMode, DataSourceType, DocumentActionType, type DocumentDisplayStatus, type SimpleDocumentDetail } from '@/models/datasets' | ||||
| scene?: 'list' | 'detail' | scene?: 'list' | 'detail' | ||||
| className?: string | className?: string | ||||
| }> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => { | }> = ({ embeddingAvailable, datasetId, detail, onUpdate, scene = 'list', className = '' }) => { | ||||
| const downloadDocument = useDocumentDownload() | |||||
| const { id, enabled = false, archived = false, data_source_type, display_status } = detail || {} | const { id, enabled = false, archived = false, data_source_type, display_status } = detail || {} | ||||
| const [showModal, setShowModal] = useState(false) | const [showModal, setShowModal] = useState(false) | ||||
| const [deleting, setDeleting] = useState(false) | const [deleting, setDeleting] = useState(false) | ||||
| )} | )} | ||||
| {embeddingAvailable && ( | {embeddingAvailable && ( | ||||
| <> | <> | ||||
| <Tooltip | |||||
| popupContent={t('datasetDocuments.list.action.download')} | |||||
| popupClassName='text-text-secondary system-xs-medium' | |||||
| needsDelay={false} | |||||
| > | |||||
| <button | |||||
| className={cn('mr-2 cursor-pointer rounded-lg', | |||||
| !isListScene | |||||
| ? 'shadow-shadow-3 border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg p-2 shadow-xs backdrop-blur-[5px] hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover' | |||||
| : 'p-0.5 hover:bg-state-base-hover')} | |||||
| onClick={() => { | |||||
| downloadDocument.mutateAsync({ | |||||
| datasetId, | |||||
| documentId: detail.id, | |||||
| }).then((response) => { | |||||
| if (response.download_url) | |||||
| window.location.href = response.download_url | |||||
| }).catch((error) => { | |||||
| console.error(error) | |||||
| notify({ type: 'error', message: t('common.actionMsg.downloadFailed') }) | |||||
| }) | |||||
| }} | |||||
| > | |||||
| <RiDownloadLine className='h-4 w-4 text-components-button-secondary-text' /> | |||||
| </button> | |||||
| </Tooltip> | |||||
| <Tooltip | <Tooltip | ||||
| popupContent={t('datasetDocuments.list.action.settings')} | popupContent={t('datasetDocuments.list.action.settings')} | ||||
| popupClassName='text-text-secondary system-xs-medium' | popupClassName='text-text-secondary system-xs-medium' |
| sync: 'Sync', | sync: 'Sync', | ||||
| pause: 'Pause', | pause: 'Pause', | ||||
| resume: 'Resume', | resume: 'Resume', | ||||
| download: 'Download File', | |||||
| }, | }, | ||||
| index: { | index: { | ||||
| enable: 'Enable', | enable: 'Enable', |
| import { pauseDocIndexing, resumeDocIndexing } from '../datasets' | import { pauseDocIndexing, resumeDocIndexing } from '../datasets' | ||||
| import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets' | import type { DocumentDetailResponse, DocumentListResponse, UpdateDocumentBatchParams } from '@/models/datasets' | ||||
| import { DocumentActionType } from '@/models/datasets' | import { DocumentActionType } from '@/models/datasets' | ||||
| import type { CommonResponse, FileDownloadResponse } from '@/models/common' | |||||
| // Download document with authentication (sends Authorization header) | |||||
| import Toast from '@/app/components/base/toast' | |||||
| import type { CommonResponse } from '@/models/common' | |||||
| const NAME_SPACE = 'knowledge/document' | const NAME_SPACE = 'knowledge/document' | ||||
| }) | }) | ||||
| } | } | ||||
| // Download document with authentication (sends Authorization header) | |||||
| export const useDocumentDownload = () => { | |||||
| return useMutation({ | |||||
| mutationFn: async ({ datasetId, documentId }: { datasetId: string; documentId: string }) => { | |||||
| // The get helper automatically adds the Authorization header from localStorage | |||||
| return get<FileDownloadResponse>(`/datasets/${datasetId}/documents/${documentId}/upload-file`) | |||||
| }, | |||||
| onError: (error: any) => { | |||||
| // Show a toast notification if download fails | |||||
| const message = error?.message || 'Download failed.' | |||||
| Toast.notify({ type: 'error', message }) | |||||
| }, | |||||
| }) | |||||
| } | |||||
| export const useSyncWebsite = () => { | export const useSyncWebsite = () => { | ||||
| return useMutation({ | return useMutation({ | ||||
| mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => { | mutationFn: ({ datasetId, documentId }: UpdateDocumentBatchParams) => { |