Skip to content

Commit adb54ec

Browse files
committed
Merge branch 'feat/admin-flow' of https://github.com/ProjectTech4DevAI/kaapi-frontend into feat/google-integration
2 parents 275fcfc + e223533 commit adb54ec

File tree

11 files changed

+500
-744
lines changed

11 files changed

+500
-744
lines changed

app/(main)/document/page.tsx

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useRef, useState } from "react";
44
import { useAuth } from "@/app/lib/context/AuthContext";
55
import { useApp } from "@/app/lib/context/AppContext";
66
import Sidebar from "@/app/components/Sidebar";
77
import PageHeader from "@/app/components/PageHeader";
88
import { useToast } from "@/app/components/Toast";
99
import { usePaginatedList, useInfiniteScroll } from "@/app/hooks";
10-
import { apiFetch } from "@/app/lib/apiClient";
10+
import {
11+
apiFetch,
12+
uploadWithProgress,
13+
type UploadPhase,
14+
} from "@/app/lib/apiClient";
1115
import { DocumentListing } from "@/app/components/document/DocumentListing";
1216
import { DocumentPreview } from "@/app/components/document/DocumentPreview";
1317
import { UploadDocumentModal } from "@/app/components/document/UploadDocumentModal";
14-
import { DEFAULT_PAGE_LIMIT } from "@/app/lib/constants";
18+
import {
19+
DEFAULT_PAGE_LIMIT,
20+
MAX_DOCUMENT_SIZE_BYTES,
21+
MAX_DOCUMENT_SIZE_MB,
22+
} from "@/app/lib/constants";
1523
import { Document } from "@/app/lib/types/document";
1624

17-
export type { Document } from "@/app/lib/types/document";
18-
1925
export default function DocumentPage() {
2026
const toast = useToast();
2127
const { sidebarCollapsed } = useApp();
@@ -26,6 +32,9 @@ export default function DocumentPage() {
2632
const [isLoadingDocument, setIsLoadingDocument] = useState(false);
2733
const [selectedFile, setSelectedFile] = useState<File | null>(null);
2834
const [isUploading, setIsUploading] = useState(false);
35+
const [uploadProgress, setUploadProgress] = useState(0);
36+
const [uploadPhase, setUploadPhase] = useState<UploadPhase>("uploading");
37+
const abortUploadRef = useRef<(() => void) | null>(null);
2938
const { activeKey: apiKey, isAuthenticated } = useAuth();
3039

3140
const {
@@ -51,23 +60,39 @@ export default function DocumentPage() {
5160
const file = event.target.files?.[0];
5261
if (!file) return;
5362

63+
if (file.size > MAX_DOCUMENT_SIZE_BYTES) {
64+
toast.error(
65+
`File size exceeds ${MAX_DOCUMENT_SIZE_MB} MB limit. Please select a smaller file within ${MAX_DOCUMENT_SIZE_MB} MB.`,
66+
);
67+
event.target.value = "";
68+
return;
69+
}
70+
5471
setSelectedFile(file);
5572
};
5673

5774
const handleUpload = async () => {
5875
if (!isAuthenticated || !selectedFile) return;
5976

6077
setIsUploading(true);
78+
setUploadProgress(0);
79+
setUploadPhase("uploading");
6180

6281
try {
6382
const formData = new FormData();
6483
formData.append("src", selectedFile);
6584

66-
const data = await apiFetch<{ data?: { id: string } }>(
85+
const { promise, abort } = uploadWithProgress<{ data?: { id: string } }>(
6786
"/api/document",
6887
apiKey?.key ?? "",
69-
{ method: "POST", body: formData },
88+
formData,
89+
(percent, phase) => {
90+
setUploadProgress(percent);
91+
setUploadPhase(phase);
92+
},
7093
);
94+
abortUploadRef.current = abort;
95+
const data = await promise;
7196
if (selectedFile && data.data?.id) {
7297
const fileSizeMap = JSON.parse(
7398
localStorage.getItem("document_file_sizes") || "{}",
@@ -91,6 +116,7 @@ export default function DocumentPage() {
91116
);
92117
} finally {
93118
setIsUploading(false);
119+
abortUploadRef.current = null;
94120
}
95121
};
96122

@@ -189,18 +215,22 @@ export default function DocumentPage() {
189215
</div>
190216
</div>
191217

192-
{isModalOpen && (
193-
<UploadDocumentModal
194-
selectedFile={selectedFile}
195-
isUploading={isUploading}
196-
onFileSelect={handleFileSelect}
197-
onUpload={handleUpload}
198-
onClose={() => {
199-
setIsModalOpen(false);
200-
setSelectedFile(null);
201-
}}
202-
/>
203-
)}
218+
<UploadDocumentModal
219+
open={isModalOpen}
220+
selectedFile={selectedFile}
221+
isUploading={isUploading}
222+
uploadProgress={uploadProgress}
223+
uploadPhase={uploadPhase}
224+
onFileSelect={handleFileSelect}
225+
onUpload={handleUpload}
226+
onClose={() => {
227+
abortUploadRef.current?.();
228+
setIsModalOpen(false);
229+
setSelectedFile(null);
230+
setUploadProgress(0);
231+
setUploadPhase("uploading");
232+
}}
233+
/>
204234
</div>
205235
);
206236
}

app/(main)/knowledge-base/page.tsx

Lines changed: 36 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@ import { Button, Field } from "@/app/components";
1515
import { useAuth } from "@/app/lib/context/AuthContext";
1616
import { useApp } from "@/app/lib/context/AppContext";
1717
import { apiFetch } from "@/app/lib/apiClient";
18-
import { Document } from "@/app/lib/types/document";
1918
import {
20-
Collection,
2119
JobStatusData,
2220
CollectionResponse,
2321
DocumentResponse,
2422
CreateCollectionResponse,
2523
DeleteCollectionResponse,
2624
DocumentDetailResponse,
2725
} from "@/app/lib/types/knowledgeBase";
26+
import { Document, Collection } from "@/app/lib/types/document";
2827

2928
export default function KnowledgeBasePage() {
3029
const { sidebarCollapsed } = useApp();
@@ -757,6 +756,23 @@ export default function KnowledgeBasePage() {
757756
}
758757
};
759758

759+
// Fetch document details and set preview
760+
const fetchAndPreviewDoc = async (doc: Document) => {
761+
setPreviewDoc(doc);
762+
if (isAuthenticated) {
763+
try {
764+
const data = await apiFetch<DocumentDetailResponse & Document>(
765+
`/api/document/${doc.id}`,
766+
apiKey?.key ?? "",
767+
);
768+
const documentDetails = (data.data || data) as Document;
769+
setPreviewDoc(documentDetails);
770+
} catch (err) {
771+
console.error("Failed to fetch document details:", err);
772+
}
773+
}
774+
};
775+
760776
// Toggle document selection
761777
const toggleDocumentSelection = (documentId: string) => {
762778
const newSelection = new Set(selectedDocuments);
@@ -839,7 +855,7 @@ export default function KnowledgeBasePage() {
839855
{/* Left Panel - Collections List */}
840856
<div className="w-1/3 border-r border-border flex flex-col">
841857
{/* Create Button */}
842-
<div className="p-6 flex justify-end">
858+
<div className="px-6 py-4 flex justify-end">
843859
<Button
844860
variant="outline"
845861
size="sm"
@@ -852,7 +868,6 @@ export default function KnowledgeBasePage() {
852868
</Button>
853869
</div>
854870

855-
{/* Collections List */}
856871
<div className="flex-1 overflow-y-auto px-6 pb-6">
857872
{isLoading && collections.length === 0 ? (
858873
<div className="text-center py-8 text-text-secondary">
@@ -983,7 +998,6 @@ export default function KnowledgeBasePage() {
983998
<ChevronRightIcon className="w-4 h-4 text-text-secondary" />
984999
</button>
9851000

986-
{/* Show selected documents */}
9871001
{selectedDocuments.size > 0 && (
9881002
<div className="mt-3 space-y-2">
9891003
{Array.from(selectedDocuments).map((docId) => {
@@ -1012,7 +1026,6 @@ export default function KnowledgeBasePage() {
10121026
)}
10131027
</div>
10141028

1015-
{/* Create Button */}
10161029
<div className="flex justify-end">
10171030
<Button
10181031
onClick={handleCreateClick}
@@ -1028,7 +1041,6 @@ export default function KnowledgeBasePage() {
10281041
</div>
10291042
) : selectedCollection ? (
10301043
<>
1031-
{/* Preview Header */}
10321044
<div className="p-6 border-b border-border">
10331045
<div className="flex items-start justify-between">
10341046
<div className="flex-1">
@@ -1093,7 +1105,6 @@ export default function KnowledgeBasePage() {
10931105
</div>
10941106
</div>
10951107

1096-
{/* Documents in Collection */}
10971108
<div className="flex-1 overflow-y-auto px-6 pt-6 pb-4">
10981109
<div className="flex items-center justify-between mb-4">
10991110
<h3 className="text-sm font-semibold text-text-primary">
@@ -1103,30 +1114,11 @@ export default function KnowledgeBasePage() {
11031114
{selectedCollection.documents &&
11041115
selectedCollection.documents.length > 0 && (
11051116
<button
1106-
onClick={async () => {
1117+
onClick={() => {
11071118
setShowDocPreviewModal(true);
1108-
// Fetch the first document with signed_url
1109-
const firstDoc = selectedCollection.documents![0];
1110-
setPreviewDoc(firstDoc);
1111-
1112-
if (isAuthenticated) {
1113-
try {
1114-
const data = await apiFetch<
1115-
DocumentDetailResponse & Document
1116-
>(
1117-
`/api/document/${firstDoc.id}`,
1118-
apiKey?.key ?? "",
1119-
);
1120-
const documentDetails = (data.data ||
1121-
data) as Document;
1122-
setPreviewDoc(documentDetails);
1123-
} catch (err) {
1124-
console.error(
1125-
"Failed to fetch document details for preview:",
1126-
err,
1127-
);
1128-
}
1129-
}
1119+
fetchAndPreviewDoc(
1120+
selectedCollection.documents![0],
1121+
);
11301122
}}
11311123
className="text-xs px-3 py-1.5 rounded-md border border-border bg-bg-secondary text-text-primary hover:bg-neutral-100 transition-colors"
11321124
>
@@ -1310,58 +1302,27 @@ export default function KnowledgeBasePage() {
13101302
</div>
13111303
</Modal>
13121304

1313-
{showDocPreviewModal && !!selectedCollection?.documents && (
1314-
<div
1315-
className="fixed inset-0 bg-black/30 backdrop-blur-[2px] flex items-center justify-center z-50"
1316-
onClick={() => {
1317-
setShowDocPreviewModal(false);
1318-
setPreviewDoc(null);
1319-
}}
1320-
>
1305+
<Modal
1306+
open={showDocPreviewModal && !!selectedCollection?.documents}
1307+
onClose={() => {
1308+
setShowDocPreviewModal(false);
1309+
setPreviewDoc(null);
1310+
}}
1311+
title="Document Preview"
1312+
maxWidth="max-w-5xl"
1313+
maxHeight="h-[80vh]"
1314+
>
1315+
<div className="flex flex-1 overflow-hidden h-full">
13211316
<div
13221317
className="bg-white rounded-2xl shadow-xl border border-border w-full max-w-5xl h-[85vh] flex flex-col"
13231318
onClick={(e) => e.stopPropagation()}
13241319
>
1325-
<div className="flex items-center justify-between px-6 py-4 shrink-0 border-b border-border">
1326-
<h2 className="text-lg font-semibold text-text-primary">
1327-
Document Preview
1328-
</h2>
1329-
<button
1330-
onClick={() => {
1331-
setShowDocPreviewModal(false);
1332-
setPreviewDoc(null);
1333-
}}
1334-
className="p-1 rounded-md text-text-secondary transition-colors hover:bg-neutral-100 hover:text-text-primary cursor-pointer"
1335-
>
1336-
<CloseIcon className="w-5 h-5" />
1337-
</button>
1338-
</div>
1339-
1340-
{/* Two-pane body */}
13411320
<div className="flex flex-1 min-h-0">
1342-
{/* Left pane — doc list */}
13431321
<div className="w-56 shrink-0 border-r border-border overflow-y-auto">
13441322
{selectedCollection?.documents?.map((doc: Document) => (
13451323
<button
13461324
key={doc.id}
1347-
onClick={async () => {
1348-
setPreviewDoc(doc);
1349-
if (isAuthenticated) {
1350-
try {
1351-
const data = await apiFetch<
1352-
DocumentDetailResponse & Document
1353-
>(`/api/document/${doc.id}`, apiKey?.key ?? "");
1354-
const documentDetails = (data.data ||
1355-
data) as Document;
1356-
setPreviewDoc(documentDetails);
1357-
} catch (err) {
1358-
console.error(
1359-
"Failed to fetch document details:",
1360-
err,
1361-
);
1362-
}
1363-
}
1364-
}}
1325+
onClick={() => fetchAndPreviewDoc(doc)}
13651326
className={`w-full text-left px-4 py-3 border-b border-border transition-colors ${
13661327
previewDoc?.id === doc.id
13671328
? "bg-neutral-100"
@@ -1380,7 +1341,6 @@ export default function KnowledgeBasePage() {
13801341
))}
13811342
</div>
13821343

1383-
{/* Right pane — preview */}
13841344
<div className="flex-1 min-h-0 bg-neutral-50">
13851345
{previewDoc?.signed_url ? (
13861346
<iframe
@@ -1402,7 +1362,7 @@ export default function KnowledgeBasePage() {
14021362
</div>
14031363
</div>
14041364
</div>
1405-
)}
1365+
</Modal>
14061366
</div>
14071367
);
14081368
}

0 commit comments

Comments
 (0)