Skip to content

Commit 970f7ea

Browse files
committed
Replace all files except .github/workflows
1 parent a4b470d commit 970f7ea

9 files changed

+171
-37
lines changed

index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
7+
<title>Kanz.AI</title>
88
</head>
99
<body>
1010
<div id="root"></div>
1111
<script type="module" src="/src/main.tsx"></script>
1212
</body>
13-
</html>
13+
</html>

package-lock.json

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react-dom": "^18.2.0",
1616
"react-router-dom": "^6.20.0",
1717
"react-markdown": "^8.0.7",
18-
"firebase": "^10.7.1"
18+
"firebase": "^10.7.1",
19+
"react-hot-toast": "^2.4.1"
1920
},
2021
"devDependencies": {
2122
"@types/react": "^18.2.37",

src/components/DocumentList.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import React from 'react';
22
import { Download, Trash2, FileText } from 'lucide-react';
33
import type { Document } from '../types/document';
4+
import { getDocumentUrl } from '../utils/documentStorage';
45

56
interface DocumentListProps {
67
documents: Document[];
78
isAuthenticated: boolean;
8-
onDelete: (docId: string) => Promise<void>;
9+
onDelete: (docId: string) => void;
910
}
1011

11-
const DocumentList: React.FC<DocumentListProps> = ({ documents, isAuthenticated, onDelete }) => {
12+
const DocumentList: React.FC<DocumentListProps> = ({
13+
documents,
14+
isAuthenticated,
15+
onDelete
16+
}) => {
1217
if (documents.length === 0) {
1318
return (
1419
<div className="text-center py-12">
1520
<FileText className="mx-auto h-12 w-12 text-gray-400" />
1621
<h3 className="mt-2 text-sm font-medium text-gray-900">No documents found</h3>
1722
<p className="mt-1 text-sm text-gray-500">
18-
Upload some documents to get started
23+
{isAuthenticated ? 'Upload some documents to get started' : 'Check back later for new documents'}
1924
</p>
2025
</div>
2126
);
@@ -35,17 +40,18 @@ const DocumentList: React.FC<DocumentListProps> = ({ documents, isAuthenticated,
3540
</div>
3641
<div className="flex items-center space-x-4">
3742
<a
38-
href={doc.pdfUrl}
39-
target="_blank"
40-
rel="noopener noreferrer"
43+
href={getDocumentUrl(doc.id) || '#'}
44+
download={doc.fileName}
4145
className="inline-flex items-center text-blue-600 hover:text-blue-700"
46+
title="Download document"
4247
>
4348
<Download className="h-5 w-5" />
4449
</a>
4550
{isAuthenticated && (
4651
<button
4752
onClick={() => onDelete(doc.id)}
4853
className="text-red-600 hover:text-red-700"
54+
title="Delete document"
4955
>
5056
<Trash2 className="h-5 w-5" />
5157
</button>

src/components/DocumentUploadForm.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@ const DocumentUploadForm: React.FC<DocumentUploadFormProps> = ({ onUpload, isUpl
1414
const handleSubmit = async (e: React.FormEvent) => {
1515
e.preventDefault();
1616
if (!file || !title || !brief) return;
17+
18+
if (!file.type.includes('pdf')) {
19+
alert('Please upload a PDF file');
20+
return;
21+
}
22+
1723
await onUpload(title, brief, file);
1824
setTitle('');
1925
setBrief('');
2026
setFile(null);
27+
28+
// Reset file input
29+
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
30+
if (fileInput) fileInput.value = '';
2131
};
2232

2333
return (

src/pages/KnowledgeBase.tsx

+21-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React, { useState, useEffect } from 'react';
22
import { useAuth } from '../contexts/AuthContext';
33
import { Search } from 'lucide-react';
4+
import { Toaster, toast } from 'react-hot-toast';
45
import DocumentUploadForm from '../components/DocumentUploadForm';
56
import DocumentList from '../components/DocumentList';
6-
import { uploadDocument, fetchDocuments, deleteDocument } from '../utils/storage';
7+
import { saveDocument, getDocuments, deleteDocument } from '../utils/documentStorage';
78
import type { Document } from '../types/document';
89

910
const KnowledgeBase = () => {
@@ -16,32 +17,36 @@ const KnowledgeBase = () => {
1617
loadDocuments();
1718
}, []);
1819

19-
const loadDocuments = async () => {
20-
try {
21-
const docs = await fetchDocuments();
22-
setDocuments(docs);
23-
} catch (error) {
24-
console.error('Error fetching documents:', error);
25-
}
20+
const loadDocuments = () => {
21+
const docs = getDocuments();
22+
setDocuments(docs);
2623
};
2724

2825
const handleUpload = async (title: string, brief: string, file: File) => {
2926
setIsUploading(true);
3027
try {
31-
await uploadDocument(title, brief, file);
32-
await loadDocuments();
28+
await saveDocument(title, brief, file);
29+
loadDocuments();
30+
toast.success('Document uploaded successfully!');
3331
} catch (error) {
3432
console.error('Error uploading document:', error);
33+
toast.error('Failed to upload document. Please try again.');
34+
} finally {
35+
setIsUploading(false);
3536
}
36-
setIsUploading(false);
3737
};
3838

39-
const handleDelete = async (docId: string) => {
39+
const handleDelete = (docId: string) => {
40+
const confirmed = window.confirm('Are you sure you want to delete this document?');
41+
if (!confirmed) return;
42+
4043
try {
41-
await deleteDocument(docId);
42-
await loadDocuments();
44+
deleteDocument(docId);
45+
loadDocuments();
46+
toast.success('Document deleted successfully!');
4347
} catch (error) {
4448
console.error('Error deleting document:', error);
49+
toast.error('Failed to delete document. Please try again.');
4550
}
4651
};
4752

@@ -52,6 +57,8 @@ const KnowledgeBase = () => {
5257

5358
return (
5459
<div>
60+
<Toaster position="top-right" />
61+
5562
{/* Hero Section */}
5663
<section className="bg-gradient-to-r from-blue-600 to-blue-800 text-white py-20">
5764
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">

src/types/document.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export interface Document {
22
id: string;
33
title: string;
44
brief: string;
5-
pdfUrl: string;
5+
fileName: string;
66
uploadDate: string;
77
uploadedBy: string;
88
}

src/utils/documentStorage.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Document } from '../types/document';
2+
3+
const STORAGE_KEY = 'documents';
4+
5+
export const saveDocument = async (title: string, brief: string, file: File): Promise<void> => {
6+
const documents = getDocuments();
7+
const id = crypto.randomUUID();
8+
const timestamp = new Date().toISOString();
9+
10+
// Create a blob URL for the file
11+
const fileUrl = URL.createObjectURL(file);
12+
13+
const newDocument: Document = {
14+
id,
15+
title,
16+
brief,
17+
fileName: file.name,
18+
uploadDate: timestamp,
19+
uploadedBy: 'admin'
20+
};
21+
22+
// Store the blob URL in sessionStorage to persist across page reloads
23+
sessionStorage.setItem(`file_${id}`, fileUrl);
24+
25+
documents.push(newDocument);
26+
localStorage.setItem(STORAGE_KEY, JSON.stringify(documents));
27+
};
28+
29+
export const getDocuments = (): Document[] => {
30+
const stored = localStorage.getItem(STORAGE_KEY);
31+
return stored ? JSON.parse(stored) : [];
32+
};
33+
34+
export const deleteDocument = (id: string): void => {
35+
const documents = getDocuments().filter(doc => doc.id !== id);
36+
localStorage.setItem(STORAGE_KEY, JSON.stringify(documents));
37+
38+
// Clean up the blob URL
39+
const fileUrl = sessionStorage.getItem(`file_${id}`);
40+
if (fileUrl) {
41+
URL.revokeObjectURL(fileUrl);
42+
sessionStorage.removeItem(`file_${id}`);
43+
}
44+
};
45+
46+
export const getDocumentUrl = (id: string): string | null => {
47+
return sessionStorage.getItem(`file_${id}`);
48+
};

src/utils/storage.ts

+51-13
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,73 @@ import { ref, uploadBytes, getDownloadURL, listAll, deleteObject } from 'firebas
22
import { storage } from '../config/firebase';
33
import type { Document } from '../types/document';
44

5-
export const uploadDocument = async (title: string, brief: string, file: File) => {
6-
const fileName = `${title}__${brief}__${new Date().toISOString()}__admin`;
5+
const MAX_RETRIES = 3;
6+
const RETRY_DELAY = 1000;
7+
8+
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
9+
10+
const retry = async <T>(
11+
operation: () => Promise<T>,
12+
retries = MAX_RETRIES,
13+
delay = RETRY_DELAY
14+
): Promise<T> => {
15+
try {
16+
return await operation();
17+
} catch (error) {
18+
if (retries > 0) {
19+
await sleep(delay);
20+
return retry(operation, retries - 1, delay * 2);
21+
}
22+
throw error;
23+
}
24+
};
25+
26+
export const uploadDocument = async (title: string, brief: string, file: File): Promise<void> => {
27+
const sanitizedTitle = title.replace(/[^a-zA-Z0-9-_]/g, '_');
28+
const timestamp = new Date().toISOString();
29+
const fileName = `${sanitizedTitle}__${timestamp}__admin.pdf`;
730
const storageRef = ref(storage, `documents/${fileName}`);
8-
await uploadBytes(storageRef, file);
31+
32+
const metadata = {
33+
customMetadata: {
34+
title,
35+
brief,
36+
uploadDate: timestamp,
37+
uploadedBy: 'admin'
38+
}
39+
};
40+
41+
await retry(() => uploadBytes(storageRef, file, metadata));
942
};
1043

1144
export const fetchDocuments = async (): Promise<Document[]> => {
1245
const storageRef = ref(storage, 'documents');
13-
const result = await listAll(storageRef);
46+
47+
const result = await retry(() => listAll(storageRef));
1448

1549
const docs = await Promise.all(
1650
result.items.map(async (item) => {
17-
const url = await getDownloadURL(item);
18-
const metadata = item.name.split('__');
51+
const url = await retry(() => getDownloadURL(item));
52+
const [fileName] = item.name.split('__');
53+
const metadata = await retry(() => item.getMetadata());
54+
1955
return {
2056
id: item.name,
21-
title: metadata[0],
22-
brief: metadata[1],
57+
title: metadata.customMetadata?.title || fileName,
58+
brief: metadata.customMetadata?.brief || '',
2359
pdfUrl: url,
24-
uploadDate: metadata[2],
25-
uploadedBy: metadata[3]
60+
uploadDate: metadata.customMetadata?.uploadDate || metadata.timeCreated,
61+
uploadedBy: metadata.customMetadata?.uploadedBy || 'admin'
2662
};
2763
})
2864
);
2965

30-
return docs;
66+
return docs.sort((a, b) =>
67+
new Date(b.uploadDate).getTime() - new Date(a.uploadDate).getTime()
68+
);
3169
};
3270

33-
export const deleteDocument = async (docId: string) => {
71+
export const deleteDocument = async (docId: string): Promise<void> => {
3472
const docRef = ref(storage, `documents/${docId}`);
35-
await deleteObject(docRef);
73+
await retry(() => deleteObject(docRef));
3674
};

0 commit comments

Comments
 (0)