From 5d00aa98c4e4b2a1534daae53e2f892681a553b4 Mon Sep 17 00:00:00 2001 From: Guo Ziang Date: Mon, 23 Jun 2025 20:04:33 +0800 Subject: [PATCH] feat: add index update timestamps to document schema and service --- aperag/api/components/schemas/document.yaml | 12 ++ aperag/schema/view_models.py | 11 +- aperag/service/document_service.py | 31 ++++ frontend/openapitools.json | 2 +- frontend/src/api/models/document.ts | 18 ++ frontend/src/api/openapi.merged.yaml | 13 ++ .../collections/$collectionId/documents.tsx | 154 +++++++----------- 7 files changed, 148 insertions(+), 93 deletions(-) diff --git a/aperag/api/components/schemas/document.yaml b/aperag/api/components/schemas/document.yaml index 78a75ff34..fdeefc025 100644 --- a/aperag/api/components/schemas/document.yaml +++ b/aperag/api/components/schemas/document.yaml @@ -39,6 +39,18 @@ document: - COMPLETE - FAILED - SKIPPED + vector_index_updated: + type: string + format: date-time + description: Vector index last updated time + fulltext_index_updated: + type: string + format: date-time + description: Fulltext index last updated time + graph_index_updated: + type: string + format: date-time + description: Graph index last updated time config: type: string size: diff --git a/aperag/schema/view_models.py b/aperag/schema/view_models.py index d91941ae3..d34cbdcbf 100644 --- a/aperag/schema/view_models.py +++ b/aperag/schema/view_models.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: openapi.merged.yaml -# timestamp: 2025-06-23T09:37:51+00:00 +# timestamp: 2025-06-23T10:33:05+00:00 from __future__ import annotations @@ -547,6 +547,15 @@ class Document(BaseModel): graph_index_status: Optional[ Literal['PENDING', 'RUNNING', 'COMPLETE', 'FAILED', 'SKIPPED'] ] = None + vector_index_updated: Optional[datetime] = Field( + None, description='Vector index last updated time' + ) + fulltext_index_updated: Optional[datetime] = Field( + None, description='Fulltext index last updated time' + ) + graph_index_updated: Optional[datetime] = Field( + None, description='Graph index last updated time' + ) config: Optional[str] = None size: Optional[float] = None created: Optional[datetime] = None diff --git a/aperag/service/document_service.py b/aperag/service/document_service.py index 96e2c4619..b1809e699 100644 --- a/aperag/service/document_service.py +++ b/aperag/service/document_service.py @@ -100,6 +100,34 @@ def map_state_to_old_enum(actual_state: str): else: return "PENDING" + # Get individual index update times from DocumentIndex table + from sqlalchemy import select + from aperag.db.models import DocumentIndex, DocumentIndexType + + vector_updated = None + fulltext_updated = None + graph_updated = None + + # Query for each index type's update time + for index_type, var_name in [ + (DocumentIndexType.VECTOR, 'vector_updated'), + (DocumentIndexType.FULLTEXT, 'fulltext_updated'), + (DocumentIndexType.GRAPH, 'graph_updated') + ]: + stmt = select(DocumentIndex).where( + DocumentIndex.document_id == document.id, + DocumentIndex.index_type == index_type + ) + result = await session.execute(stmt) + index_record = result.scalar_one_or_none() + if index_record: + if var_name == 'vector_updated': + vector_updated = index_record.gmt_updated + elif var_name == 'fulltext_updated': + fulltext_updated = index_record.gmt_updated + elif var_name == 'graph_updated': + graph_updated = index_record.gmt_updated + return Document( id=document.id, name=document.name, @@ -107,6 +135,9 @@ def map_state_to_old_enum(actual_state: str): vector_index_status=map_state_to_old_enum(indexes.get("vector", {}).get("actual_state", "absent")), fulltext_index_status=map_state_to_old_enum(indexes.get("fulltext", {}).get("actual_state", "absent")), graph_index_status=map_state_to_old_enum(indexes.get("graph", {}).get("actual_state", "absent")), + vector_index_updated=vector_updated, + fulltext_index_updated=fulltext_updated, + graph_index_updated=graph_updated, size=document.size, created=document.gmt_created, updated=document.gmt_updated, diff --git a/frontend/openapitools.json b/frontend/openapitools.json index 4ecb9705d..4196e204e 100644 --- a/frontend/openapitools.json +++ b/frontend/openapitools.json @@ -3,7 +3,7 @@ "spaces": 2, "generator-cli": { "version": "7.12.0", - "useDocker": true, + "useDocker": false, "generators": { "v3.0": { "generatorName": "typescript-axios", diff --git a/frontend/src/api/models/document.ts b/frontend/src/api/models/document.ts index cc7b1c99d..24cb7774e 100644 --- a/frontend/src/api/models/document.ts +++ b/frontend/src/api/models/document.ts @@ -56,6 +56,24 @@ export interface Document { * @memberof Document */ 'graph_index_status'?: DocumentGraphIndexStatusEnum; + /** + * Vector index last updated time + * @type {string} + * @memberof Document + */ + 'vector_index_updated'?: string; + /** + * Fulltext index last updated time + * @type {string} + * @memberof Document + */ + 'fulltext_index_updated'?: string; + /** + * Graph index last updated time + * @type {string} + * @memberof Document + */ + 'graph_index_updated'?: string; /** * * @type {string} diff --git a/frontend/src/api/openapi.merged.yaml b/frontend/src/api/openapi.merged.yaml index a2176db17..dc843b41b 100644 --- a/frontend/src/api/openapi.merged.yaml +++ b/frontend/src/api/openapi.merged.yaml @@ -2793,6 +2793,18 @@ components: - COMPLETE - FAILED - SKIPPED + vector_index_updated: + type: string + format: date-time + description: Vector index last updated time + fulltext_index_updated: + type: string + format: date-time + description: Fulltext index last updated time + graph_index_updated: + type: string + format: date-time + description: Graph index last updated time config: type: string size: @@ -2845,6 +2857,7 @@ components: - fulltext - graph description: Types of indexes to rebuild + minItems: 1 required: - index_types vectorSearchParams: diff --git a/frontend/src/pages/collections/$collectionId/documents.tsx b/frontend/src/pages/collections/$collectionId/documents.tsx index 8db28f53e..3a2120089 100644 --- a/frontend/src/pages/collections/$collectionId/documents.tsx +++ b/frontend/src/pages/collections/$collectionId/documents.tsx @@ -38,6 +38,7 @@ import { Typography, Upload, UploadProps, + Tooltip, } from 'antd'; import byteSize from 'byte-size'; import alpha from 'color-alpha'; @@ -91,41 +92,35 @@ export default () => { [collectionId], ); - const rebuildIndexes = useCallback( - async (documentId: string, indexTypes: string[]) => { - if (!collectionId || !documentId || indexTypes.length === 0) return; - - try { - await api.collectionsCollectionIdDocumentsDocumentIdRebuildIndexesPost({ - collectionId, - documentId, - rebuildIndexesRequest: { - index_types: indexTypes as any, - }, - }); - toast.success(formatMessage({ id: 'document.index.rebuild.success' })); - getDocuments(); - } catch (error) { - toast.error(formatMessage({ id: 'document.index.rebuild.failed' })); - } - }, - [collectionId, formatMessage], - ); - - const handleRebuildIndex = useCallback((record: ApeDocument) => { - setRebuildSelectedDocument(record); - setRebuildSelectedTypes([]); + const handleRebuildIndex = (document: ApeDocument) => { + setRebuildSelectedDocument(document); + setRebuildSelectedTypes(['vector', 'fulltext', 'graph']); setRebuildModalVisible(true); - }, []); + }; + + const handleRebuildConfirm = async () => { + if (!rebuildSelectedDocument || rebuildSelectedTypes.length === 0) return; - const handleRebuildConfirm = useCallback(() => { - if (rebuildSelectedDocument && rebuildSelectedTypes.length > 0) { - rebuildIndexes(rebuildSelectedDocument.id!, rebuildSelectedTypes); + try { + setLoading(true); + await api.collectionsCollectionIdDocumentsDocumentIdRebuildIndexesPost({ + collectionId: collectionId!, + documentId: rebuildSelectedDocument.id!, + rebuildIndexesRequest: { + index_types: rebuildSelectedTypes as ('vector' | 'fulltext' | 'graph')[], + }, + }); + toast.success(formatMessage({ id: 'document.index.rebuild.success' })); setRebuildModalVisible(false); setRebuildSelectedDocument(null); setRebuildSelectedTypes([]); + getDocuments(); + } catch (error) { + toast.error(formatMessage({ id: 'document.index.rebuild.failed' })); + } finally { + setLoading(false); } - }, [rebuildSelectedDocument, rebuildSelectedTypes, rebuildIndexes]); + }; const indexTypeOptions = [ { label: formatMessage({ id: 'document.index.type.vector' }), value: 'vector' }, @@ -134,62 +129,25 @@ export default () => { ]; const renderIndexStatus = ( - vectorStatus?: DocumentVectorIndexStatusEnum, - fulltextStatus?: DocumentFulltextIndexStatusEnum, - graphStatus?: DocumentGraphIndexStatusEnum, + status?: DocumentVectorIndexStatusEnum | DocumentFulltextIndexStatusEnum | DocumentGraphIndexStatusEnum, + updatedTime?: string ) => { - const indexTypes = [ - { nameKey: 'document.index.type.vector', status: vectorStatus }, - { nameKey: 'document.index.type.fulltext', status: fulltextStatus }, - { nameKey: 'document.index.type.graph', status: graphStatus }, - ]; - return ( - - {indexTypes.map(({ nameKey, status }, index) => ( -
- - {formatMessage({ id: nameKey })} - - - : - -
- - {formatMessage({ id: `document.index.status.${status}` })} - - } - /> -
-
- ))} -
+ const statusBadge = ( + ); + + if (updatedTime) { + return ( + + {statusBadge} + + ); + } + + return statusBadge; }; const columns: TableProps['columns'] = [ @@ -224,16 +182,30 @@ export default () => { }, }, { - title: formatMessage({ id: 'document.status' }), - dataIndex: 'status', - width: 190, + title: formatMessage({ id: 'document.index.type.vector' }), + dataIndex: 'vector_index_status', + width: 120, align: 'center', render: (value, record) => { - return renderIndexStatus( - record.vector_index_status, - record.fulltext_index_status, - record.graph_index_status, - ); + return renderIndexStatus(record.vector_index_status, record.vector_index_updated); + }, + }, + { + title: formatMessage({ id: 'document.index.type.fulltext' }), + dataIndex: 'fulltext_index_status', + width: 120, + align: 'center', + render: (value, record) => { + return renderIndexStatus(record.fulltext_index_status, record.fulltext_index_updated); + }, + }, + { + title: formatMessage({ id: 'document.index.type.graph' }), + dataIndex: 'graph_index_status', + width: 120, + align: 'center', + render: (value, record) => { + return renderIndexStatus(record.graph_index_status, record.graph_index_updated); }, }, {