Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions aperag/api/components/schemas/document.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 10 additions & 1 deletion aperag/schema/view_models.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions aperag/service/document_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,44 @@ 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,
status=document.status,
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,
Expand Down
2 changes: 1 addition & 1 deletion frontend/openapitools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"spaces": 2,
"generator-cli": {
"version": "7.12.0",
"useDocker": true,
"useDocker": false,
"generators": {
"v3.0": {
"generatorName": "typescript-axios",
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/api/models/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/api/openapi.merged.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -2845,6 +2857,7 @@ components:
- fulltext
- graph
description: Types of indexes to rebuild
minItems: 1
required:
- index_types
vectorSearchParams:
Expand Down
154 changes: 63 additions & 91 deletions frontend/src/pages/collections/$collectionId/documents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
Typography,
Upload,
UploadProps,
Tooltip,
} from 'antd';
import byteSize from 'byte-size';
import alpha from 'color-alpha';
Expand Down Expand Up @@ -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' },
Expand All @@ -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 (
<Space direction="vertical" size="small">
{indexTypes.map(({ nameKey, status }, index) => (
<div
key={index}
style={{
fontSize: '12px',
lineHeight: '18px',
display: 'flex',
alignItems: 'center',
whiteSpace: 'nowrap'
}}
>
<span
style={{
color: '#666',
width: '100px',
textAlign: 'right',
display: 'inline-block'
}}
>
{formatMessage({ id: nameKey })}
</span>
<span
style={{
color: '#666',
width: '12px',
textAlign: 'center',
display: 'inline-block'
}}
>
</span>
<div style={{ width: '80px' }}>
<Badge
status={UI_INDEX_STATUS[status as keyof typeof UI_INDEX_STATUS]}
text={
<span style={{ display: 'inline-block', width: '70px' }}>
{formatMessage({ id: `document.index.status.${status}` })}
</span>
}
/>
</div>
</div>
))}
</Space>
const statusBadge = (
<Badge
status={UI_INDEX_STATUS[status as keyof typeof UI_INDEX_STATUS]}
text={formatMessage({ id: `document.index.status.${status}` })}
/>
);

if (updatedTime) {
return (
<Tooltip title={`${formatMessage({ id: 'text.updatedAt' })}: ${moment(updatedTime).format(DATETIME_FORMAT)}`}>
{statusBadge}
</Tooltip>
);
}

return statusBadge;
};

const columns: TableProps<ApeDocument>['columns'] = [
Expand Down Expand Up @@ -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);
},
},
{
Expand Down
Loading