Spaces:
Running
Running
Add 3 files
Browse files- README.md +7 -5
- index.html +1387 -19
- prompts.txt +1 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: quote-for
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: pink
|
| 5 |
+
colorTo: yellow
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,1387 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>QuoteSense - Local Quotation Management</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<style>
|
| 10 |
+
.sidebar {
|
| 11 |
+
transition: all 0.3s ease;
|
| 12 |
+
}
|
| 13 |
+
.sidebar.collapsed {
|
| 14 |
+
width: 70px;
|
| 15 |
+
}
|
| 16 |
+
.sidebar.collapsed .nav-text {
|
| 17 |
+
display: none;
|
| 18 |
+
}
|
| 19 |
+
.sidebar.collapsed .logo-text {
|
| 20 |
+
display: none;
|
| 21 |
+
}
|
| 22 |
+
.sidebar.collapsed .expand-icon {
|
| 23 |
+
transform: rotate(180deg);
|
| 24 |
+
}
|
| 25 |
+
.drag-active {
|
| 26 |
+
border-color: #4f46e5 !important;
|
| 27 |
+
background-color: #eef2ff !important;
|
| 28 |
+
}
|
| 29 |
+
.progress-bar {
|
| 30 |
+
transition: width 0.3s ease;
|
| 31 |
+
}
|
| 32 |
+
.fade-in {
|
| 33 |
+
animation: fadeIn 0.3s ease-in;
|
| 34 |
+
}
|
| 35 |
+
@keyframes fadeIn {
|
| 36 |
+
from { opacity: 0; }
|
| 37 |
+
to { opacity: 1; }
|
| 38 |
+
}
|
| 39 |
+
.rotate {
|
| 40 |
+
animation: spin 1s linear infinite;
|
| 41 |
+
}
|
| 42 |
+
@keyframes spin {
|
| 43 |
+
0% { transform: rotate(0deg); }
|
| 44 |
+
100% { transform: rotate(360deg); }
|
| 45 |
+
}
|
| 46 |
+
.document-card:hover {
|
| 47 |
+
transform: translateY(-2px);
|
| 48 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
| 49 |
+
}
|
| 50 |
+
.document-card {
|
| 51 |
+
transition: all 0.2s ease;
|
| 52 |
+
}
|
| 53 |
+
.tab-content {
|
| 54 |
+
display: none;
|
| 55 |
+
}
|
| 56 |
+
.tab-content.active {
|
| 57 |
+
display: block;
|
| 58 |
+
animation: fadeIn 0.3s ease-in;
|
| 59 |
+
}
|
| 60 |
+
</style>
|
| 61 |
+
</head>
|
| 62 |
+
<body class="bg-gray-50 font-sans">
|
| 63 |
+
<div class="flex h-screen overflow-hidden">
|
| 64 |
+
<!-- Sidebar -->
|
| 65 |
+
<div class="sidebar bg-indigo-700 text-white w-64 flex flex-col">
|
| 66 |
+
<div class="p-4 flex items-center justify-between border-b border-indigo-600">
|
| 67 |
+
<div class="flex items-center">
|
| 68 |
+
<i class="fas fa-file-invoice-dollar text-2xl mr-3"></i>
|
| 69 |
+
<span class="logo-text text-xl font-bold">QuoteSense</span>
|
| 70 |
+
</div>
|
| 71 |
+
<button id="toggle-sidebar" class="expand-icon text-white p-1 rounded-full hover:bg-indigo-600 transition">
|
| 72 |
+
<i class="fas fa-chevron-left"></i>
|
| 73 |
+
</button>
|
| 74 |
+
</div>
|
| 75 |
+
<nav class="flex-1 overflow-y-auto py-4">
|
| 76 |
+
<ul>
|
| 77 |
+
<li>
|
| 78 |
+
<a href="#" class="nav-item flex items-center px-4 py-3 hover:bg-indigo-600 transition" data-tab="dashboard">
|
| 79 |
+
<i class="fas fa-tachometer-alt mr-3"></i>
|
| 80 |
+
<span class="nav-text">Dashboard</span>
|
| 81 |
+
</a>
|
| 82 |
+
</li>
|
| 83 |
+
<li>
|
| 84 |
+
<a href="#" class="nav-item flex items-center px-4 py-3 hover:bg-indigo-600 transition" data-tab="upload">
|
| 85 |
+
<i class="fas fa-cloud-upload-alt mr-3"></i>
|
| 86 |
+
<span class="nav-text">Upload Documents</span>
|
| 87 |
+
</a>
|
| 88 |
+
</li>
|
| 89 |
+
<li>
|
| 90 |
+
<a href="#" class="nav-item flex items-center px-4 py-3 hover:bg-indigo-600 transition" data-tab="documents">
|
| 91 |
+
<i class="fas fa-file-alt mr-3"></i>
|
| 92 |
+
<span class="nav-text">My Documents</span>
|
| 93 |
+
</a>
|
| 94 |
+
</li>
|
| 95 |
+
<li>
|
| 96 |
+
<a href="#" class="nav-item flex items-center px-4 py-3 hover:bg-indigo-600 transition" data-tab="items">
|
| 97 |
+
<i class="fas fa-list-ul mr-3"></i>
|
| 98 |
+
<span class="nav-text">Item Database</span>
|
| 99 |
+
</a>
|
| 100 |
+
</li>
|
| 101 |
+
<li>
|
| 102 |
+
<a href="#" class="nav-item flex items-center px-4 py-3 hover:bg-indigo-600 transition" data-tab="assistant">
|
| 103 |
+
<i class="fas fa-robot mr-3"></i>
|
| 104 |
+
<span class="nav-text">RAG Assistant</span>
|
| 105 |
+
</a>
|
| 106 |
+
</li>
|
| 107 |
+
</ul>
|
| 108 |
+
</nav>
|
| 109 |
+
<div class="p-4 border-t border-indigo-600">
|
| 110 |
+
<div class="flex items-center">
|
| 111 |
+
<div class="w-8 h-8 rounded-full bg-indigo-500 flex items-center justify-center mr-2">
|
| 112 |
+
<i class="fas fa-user text-sm"></i>
|
| 113 |
+
</div>
|
| 114 |
+
<div class="nav-text">
|
| 115 |
+
<div class="text-sm font-medium">Local User</div>
|
| 116 |
+
<div class="text-xs text-indigo-200">Offline Mode</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<!-- Main Content -->
|
| 123 |
+
<div class="flex-1 overflow-auto">
|
| 124 |
+
<header class="bg-white shadow-sm p-4">
|
| 125 |
+
<div class="flex justify-between items-center">
|
| 126 |
+
<h1 class="text-2xl font-bold text-gray-800" id="page-title">Dashboard</h1>
|
| 127 |
+
<div class="flex items-center space-x-4">
|
| 128 |
+
<button id="rebuild-index" class="bg-indigo-100 text-indigo-700 px-3 py-1 rounded-md text-sm font-medium hover:bg-indigo-200 transition hidden">
|
| 129 |
+
<i class="fas fa-sync-alt mr-1"></i> Rebuild Index
|
| 130 |
+
</button>
|
| 131 |
+
<div class="relative">
|
| 132 |
+
<button class="bg-gray-100 p-2 rounded-full hover:bg-gray-200 transition">
|
| 133 |
+
<i class="fas fa-bell text-gray-600"></i>
|
| 134 |
+
</button>
|
| 135 |
+
<span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
</header>
|
| 140 |
+
|
| 141 |
+
<main class="p-4">
|
| 142 |
+
<!-- Dashboard Tab -->
|
| 143 |
+
<div id="dashboard" class="tab-content active">
|
| 144 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
| 145 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 146 |
+
<div class="flex items-center">
|
| 147 |
+
<div class="p-3 rounded-full bg-indigo-100 text-indigo-600 mr-4">
|
| 148 |
+
<i class="fas fa-file-alt text-xl"></i>
|
| 149 |
+
</div>
|
| 150 |
+
<div>
|
| 151 |
+
<p class="text-gray-500 text-sm">Documents</p>
|
| 152 |
+
<h3 class="text-2xl font-bold" id="doc-count">0</h3>
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 157 |
+
<div class="flex items-center">
|
| 158 |
+
<div class="p-3 rounded-full bg-green-100 text-green-600 mr-4">
|
| 159 |
+
<i class="fas fa-list-ul text-xl"></i>
|
| 160 |
+
</div>
|
| 161 |
+
<div>
|
| 162 |
+
<p class="text-gray-500 text-sm">Extracted Items</p>
|
| 163 |
+
<h3 class="text-2xl font-bold" id="item-count">0</h3>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 168 |
+
<div class="flex items-center">
|
| 169 |
+
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4">
|
| 170 |
+
<i class="fas fa-percentage text-xl"></i>
|
| 171 |
+
</div>
|
| 172 |
+
<div>
|
| 173 |
+
<p class="text-gray-500 text-sm">Avg Confidence</p>
|
| 174 |
+
<h3 class="text-2xl font-bold" id="avg-confidence">0%</h3>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 179 |
+
<div class="flex items-center">
|
| 180 |
+
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4">
|
| 181 |
+
<i class="fas fa-database text-xl"></i>
|
| 182 |
+
</div>
|
| 183 |
+
<div>
|
| 184 |
+
<p class="text-gray-500 text-sm">Storage Used</p>
|
| 185 |
+
<h3 class="text-2xl font-bold" id="storage-used">0 MB</h3>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
| 192 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 193 |
+
<div class="flex justify-between items-center mb-4">
|
| 194 |
+
<h2 class="text-lg font-semibold">Recent Uploads</h2>
|
| 195 |
+
<a href="#" class="text-sm text-indigo-600 hover:underline" data-tab="documents">View All</a>
|
| 196 |
+
</div>
|
| 197 |
+
<div class="space-y-3" id="recent-uploads">
|
| 198 |
+
<div class="text-center py-8 text-gray-400">
|
| 199 |
+
<i class="fas fa-file-upload text-4xl mb-2"></i>
|
| 200 |
+
<p>No recent uploads</p>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
</div>
|
| 204 |
+
|
| 205 |
+
<div class="bg-white rounded-lg shadow p-4">
|
| 206 |
+
<div class="flex justify-between items-center mb-4">
|
| 207 |
+
<h2 class="text-lg font-semibold">Quick Actions</h2>
|
| 208 |
+
</div>
|
| 209 |
+
<div class="grid grid-cols-2 gap-4">
|
| 210 |
+
<a href="#" class="quick-action bg-indigo-50 text-indigo-700 rounded-lg p-4 flex flex-col items-center justify-center hover:bg-indigo-100 transition" data-tab="upload">
|
| 211 |
+
<i class="fas fa-cloud-upload-alt text-2xl mb-2"></i>
|
| 212 |
+
<span>Upload Documents</span>
|
| 213 |
+
</a>
|
| 214 |
+
<a href="#" class="quick-action bg-green-50 text-green-700 rounded-lg p-4 flex flex-col items-center justify-center hover:bg-green-100 transition" data-tab="items">
|
| 215 |
+
<i class="fas fa-search text-2xl mb-2"></i>
|
| 216 |
+
<span>Search Items</span>
|
| 217 |
+
</a>
|
| 218 |
+
<a href="#" class="quick-action bg-blue-50 text-blue-700 rounded-lg p-4 flex flex-col items-center justify-center hover:bg-blue-100 transition" data-tab="assistant">
|
| 219 |
+
<i class="fas fa-robot text-2xl mb-2"></i>
|
| 220 |
+
<span>Ask Assistant</span>
|
| 221 |
+
</a>
|
| 222 |
+
<button id="clear-data" class="quick-action bg-red-50 text-red-700 rounded-lg p-4 flex flex-col items-center justify-center hover:bg-red-100 transition">
|
| 223 |
+
<i class="fas fa-trash-alt text-2xl mb-2"></i>
|
| 224 |
+
<span>Clear All Data</span>
|
| 225 |
+
</button>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
|
| 231 |
+
<!-- Upload Documents Tab -->
|
| 232 |
+
<div id="upload" class="tab-content">
|
| 233 |
+
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
| 234 |
+
<h2 class="text-xl font-semibold mb-4">Upload Quotation Documents</h2>
|
| 235 |
+
<div class="mb-6">
|
| 236 |
+
<div id="drop-zone" class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer hover:border-indigo-300 transition">
|
| 237 |
+
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i>
|
| 238 |
+
<h3 class="text-lg font-medium text-gray-700 mb-1">Drag & drop files here</h3>
|
| 239 |
+
<p class="text-gray-500 mb-4">or click to browse files</p>
|
| 240 |
+
<input type="file" id="file-input" class="hidden" multiple accept=".pdf,.jpg,.jpeg,.png,.webp">
|
| 241 |
+
<button id="browse-btn" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 transition">
|
| 242 |
+
Select Files
|
| 243 |
+
</button>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
|
| 247 |
+
<div class="mb-4">
|
| 248 |
+
<h3 class="font-medium mb-2">Processing Options</h3>
|
| 249 |
+
<div class="flex flex-wrap gap-4">
|
| 250 |
+
<label class="flex items-center">
|
| 251 |
+
<input type="checkbox" class="form-checkbox text-indigo-600" checked>
|
| 252 |
+
<span class="ml-2">Extract line items</span>
|
| 253 |
+
</label>
|
| 254 |
+
<label class="flex items-center">
|
| 255 |
+
<input type="checkbox" class="form-checkbox text-indigo-600" checked>
|
| 256 |
+
<span class="ml-2">Identify suppliers</span>
|
| 257 |
+
</label>
|
| 258 |
+
<label class="flex items-center">
|
| 259 |
+
<input type="checkbox" class="form-checkbox text-indigo-600" checked>
|
| 260 |
+
<span class="ml-2">Extract prices</span>
|
| 261 |
+
</label>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
|
| 265 |
+
<div id="upload-queue" class="mt-6 space-y-3">
|
| 266 |
+
<h3 class="font-medium">Upload Queue</h3>
|
| 267 |
+
<div id="queue-empty" class="text-center py-4 text-gray-400">
|
| 268 |
+
<i class="fas fa-inbox text-3xl mb-2"></i>
|
| 269 |
+
<p>No files in queue</p>
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
+
</div>
|
| 273 |
+
</div>
|
| 274 |
+
|
| 275 |
+
<!-- My Documents Tab -->
|
| 276 |
+
<div id="documents" class="tab-content">
|
| 277 |
+
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
| 278 |
+
<div class="flex justify-between items-center mb-6">
|
| 279 |
+
<h2 class="text-xl font-semibold">My Documents</h2>
|
| 280 |
+
<div class="relative">
|
| 281 |
+
<input type="text" placeholder="Search documents..." class="pl-8 pr-4 py-2 border rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
| 282 |
+
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
|
| 286 |
+
<div class="overflow-x-auto">
|
| 287 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 288 |
+
<thead class="bg-gray-50">
|
| 289 |
+
<tr>
|
| 290 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Document</th>
|
| 291 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Supplier</th>
|
| 292 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Items</th>
|
| 293 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 294 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
| 295 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
| 296 |
+
</tr>
|
| 297 |
+
</thead>
|
| 298 |
+
<tbody class="bg-white divide-y divide-gray-200" id="documents-list">
|
| 299 |
+
<tr>
|
| 300 |
+
<td colspan="6" class="px-6 py-4 text-center text-gray-400">
|
| 301 |
+
<i class="fas fa-file-alt text-3xl mb-2"></i>
|
| 302 |
+
<p>No documents uploaded yet</p>
|
| 303 |
+
</td>
|
| 304 |
+
</tr>
|
| 305 |
+
</tbody>
|
| 306 |
+
</table>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
</div>
|
| 310 |
+
|
| 311 |
+
<!-- Item Database Tab -->
|
| 312 |
+
<div id="items" class="tab-content">
|
| 313 |
+
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
| 314 |
+
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-6 gap-4">
|
| 315 |
+
<h2 class="text-xl font-semibold">Item Database</h2>
|
| 316 |
+
<div class="flex flex-col md:flex-row gap-3">
|
| 317 |
+
<div class="relative flex-1">
|
| 318 |
+
<input type="text" id="item-search" placeholder="Search items..." class="pl-8 pr-4 py-2 border rounded-md w-full focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
| 319 |
+
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
|
| 320 |
+
</div>
|
| 321 |
+
<button id="export-csv" class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition flex items-center justify-center">
|
| 322 |
+
<i class="fas fa-file-export mr-2"></i> Export CSV
|
| 323 |
+
</button>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div class="mb-4">
|
| 328 |
+
<div class="flex flex-wrap gap-4">
|
| 329 |
+
<div class="flex-1 min-w-[200px]">
|
| 330 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Supplier</label>
|
| 331 |
+
<select class="filter-select w-full border rounded-md p-2">
|
| 332 |
+
<option value="">All Suppliers</option>
|
| 333 |
+
</select>
|
| 334 |
+
</div>
|
| 335 |
+
<div class="flex-1 min-w-[200px]">
|
| 336 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Price Range</label>
|
| 337 |
+
<select class="filter-select w-full border rounded-md p-2">
|
| 338 |
+
<option value="">Any Price</option>
|
| 339 |
+
<option value="0-100">$0 - $100</option>
|
| 340 |
+
<option value="100-500">$100 - $500</option>
|
| 341 |
+
<option value="500-1000">$500 - $1,000</option>
|
| 342 |
+
<option value="1000+">$1,000+</option>
|
| 343 |
+
</select>
|
| 344 |
+
</div>
|
| 345 |
+
<div class="flex-1 min-w-[200px]">
|
| 346 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Sort By</label>
|
| 347 |
+
<select class="filter-select w-full border rounded-md p-2">
|
| 348 |
+
<option value="date-desc">Date (Newest)</option>
|
| 349 |
+
<option value="date-asc">Date (Oldest)</option>
|
| 350 |
+
<option value="price-desc">Price (High to Low)</option>
|
| 351 |
+
<option value="price-asc">Price (Low to High)</option>
|
| 352 |
+
</select>
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
|
| 357 |
+
<div class="overflow-x-auto">
|
| 358 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 359 |
+
<thead class="bg-gray-50">
|
| 360 |
+
<tr>
|
| 361 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Item</th>
|
| 362 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
|
| 363 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Supplier</th>
|
| 364 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Price</th>
|
| 365 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Document</th>
|
| 366 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confidence</th>
|
| 367 |
+
</tr>
|
| 368 |
+
</thead>
|
| 369 |
+
<tbody class="bg-white divide-y divide-gray-200" id="items-list">
|
| 370 |
+
<tr>
|
| 371 |
+
<td colspan="6" class="px-6 py-4 text-center text-gray-400">
|
| 372 |
+
<i class="fas fa-list-ul text-3xl mb-2"></i>
|
| 373 |
+
<p>No items extracted yet</p>
|
| 374 |
+
</td>
|
| 375 |
+
</tr>
|
| 376 |
+
</tbody>
|
| 377 |
+
</table>
|
| 378 |
+
</div>
|
| 379 |
+
|
| 380 |
+
<div class="mt-4 flex justify-between items-center">
|
| 381 |
+
<div class="text-sm text-gray-500">
|
| 382 |
+
Showing <span id="items-start">0</span> to <span id="items-end">0</span> of <span id="items-total">0</span> items
|
| 383 |
+
</div>
|
| 384 |
+
<div class="flex space-x-2">
|
| 385 |
+
<button class="pagination-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-md hover:bg-gray-200 disabled:opacity-50" disabled>
|
| 386 |
+
<i class="fas fa-chevron-left"></i>
|
| 387 |
+
</button>
|
| 388 |
+
<button class="pagination-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-md hover:bg-gray-200 disabled:opacity-50" disabled>
|
| 389 |
+
<i class="fas fa-chevron-right"></i>
|
| 390 |
+
</button>
|
| 391 |
+
</div>
|
| 392 |
+
</div>
|
| 393 |
+
</div>
|
| 394 |
+
</div>
|
| 395 |
+
|
| 396 |
+
<!-- RAG Assistant Tab -->
|
| 397 |
+
<div id="assistant" class="tab-content">
|
| 398 |
+
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
| 399 |
+
<h2 class="text-xl font-semibold mb-4">RAG Assistant</h2>
|
| 400 |
+
<p class="text-gray-600 mb-6">Ask natural language questions about your quotation data. All processing happens locally in your browser.</p>
|
| 401 |
+
|
| 402 |
+
<div class="mb-6">
|
| 403 |
+
<div class="relative">
|
| 404 |
+
<textarea id="assistant-query" rows="3" class="w-full p-4 border rounded-lg focus:outline-none focus:ring-1 focus:ring-indigo-500" placeholder="Example: What are the cheapest office chairs from IKEA?"></textarea>
|
| 405 |
+
<button id="ask-assistant" class="absolute right-3 bottom-3 bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 transition flex items-center">
|
| 406 |
+
<i class="fas fa-paper-plane mr-2"></i> Ask
|
| 407 |
+
</button>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
|
| 411 |
+
<div id="assistant-response" class="hidden">
|
| 412 |
+
<div class="bg-indigo-50 rounded-lg p-4 mb-4">
|
| 413 |
+
<div class="flex items-start">
|
| 414 |
+
<div class="flex-shrink-0 mr-3">
|
| 415 |
+
<div class="w-8 h-8 rounded-full bg-indigo-100 flex items-center justify-center">
|
| 416 |
+
<i class="fas fa-robot text-indigo-600"></i>
|
| 417 |
+
</div>
|
| 418 |
+
</div>
|
| 419 |
+
<div class="flex-1 min-w-0">
|
| 420 |
+
<h3 class="font-medium mb-2">Answer</h3>
|
| 421 |
+
<div id="assistant-answer" class="prose max-w-none">
|
| 422 |
+
<!-- Answer will be inserted here -->
|
| 423 |
+
</div>
|
| 424 |
+
</div>
|
| 425 |
+
</div>
|
| 426 |
+
</div>
|
| 427 |
+
|
| 428 |
+
<div class="bg-gray-50 rounded-lg p-4">
|
| 429 |
+
<h3 class="font-medium mb-3">Sources</h3>
|
| 430 |
+
<div id="assistant-sources" class="space-y-3">
|
| 431 |
+
<!-- Sources will be inserted here -->
|
| 432 |
+
</div>
|
| 433 |
+
</div>
|
| 434 |
+
</div>
|
| 435 |
+
|
| 436 |
+
<div id="assistant-empty" class="text-center py-8 text-gray-400">
|
| 437 |
+
<i class="fas fa-comment-alt text-4xl mb-2"></i>
|
| 438 |
+
<p>Ask a question about your quotation data</p>
|
| 439 |
+
</div>
|
| 440 |
+
</div>
|
| 441 |
+
|
| 442 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 443 |
+
<h2 class="text-xl font-semibold mb-4">Example Questions</h2>
|
| 444 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 445 |
+
<button class="example-question bg-gray-50 hover:bg-gray-100 p-3 rounded-lg text-left transition">
|
| 446 |
+
<div class="font-medium">What are the average prices for Dell laptops?</div>
|
| 447 |
+
<div class="text-sm text-gray-500 mt-1">Get price statistics for a specific product</div>
|
| 448 |
+
</button>
|
| 449 |
+
<button class="example-question bg-gray-50 hover:bg-gray-100 p-3 rounded-lg text-left transition">
|
| 450 |
+
<div class="font-medium">Find the cheapest office chairs</div>
|
| 451 |
+
<div class="text-sm text-gray-500 mt-1">Find budget-friendly options</div>
|
| 452 |
+
</button>
|
| 453 |
+
<button class="example-question bg-gray-50 hover:bg-gray-100 p-3 rounded-lg text-left transition">
|
| 454 |
+
<div class="font-medium">Show me all items from IKEA</div>
|
| 455 |
+
<div class="text-sm text-gray-500 mt-1">Filter by supplier</div>
|
| 456 |
+
</button>
|
| 457 |
+
<button class="example-question bg-gray-50 hover:bg-gray-100 p-3 rounded-lg text-left transition">
|
| 458 |
+
<div class="font-medium">What furniture items are priced over $500?</div>
|
| 459 |
+
<div class="text-sm text-gray-500 mt-1">Find premium items</div>
|
| 460 |
+
</button>
|
| 461 |
+
</div>
|
| 462 |
+
</div>
|
| 463 |
+
</div>
|
| 464 |
+
</main>
|
| 465 |
+
</div>
|
| 466 |
+
</div>
|
| 467 |
+
|
| 468 |
+
<!-- Modal -->
|
| 469 |
+
<div id="modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 470 |
+
<div class="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
|
| 471 |
+
<div class="p-4 border-b">
|
| 472 |
+
<h3 class="text-lg font-semibold" id="modal-title">Modal Title</h3>
|
| 473 |
+
<button id="close-modal" class="absolute top-4 right-4 text-gray-400 hover:text-gray-500">
|
| 474 |
+
<i class="fas fa-times"></i>
|
| 475 |
+
</button>
|
| 476 |
+
</div>
|
| 477 |
+
<div class="p-4" id="modal-content">
|
| 478 |
+
<!-- Modal content will be inserted here -->
|
| 479 |
+
</div>
|
| 480 |
+
<div class="p-4 border-t flex justify-end space-x-3">
|
| 481 |
+
<button id="cancel-modal" class="px-4 py-2 border rounded-md hover:bg-gray-50">Cancel</button>
|
| 482 |
+
<button id="confirm-modal" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Confirm</button>
|
| 483 |
+
</div>
|
| 484 |
+
</div>
|
| 485 |
+
</div>
|
| 486 |
+
|
| 487 |
+
<script>
|
| 488 |
+
// DOM Elements
|
| 489 |
+
const sidebar = document.querySelector('.sidebar');
|
| 490 |
+
const toggleSidebar = document.getElementById('toggle-sidebar');
|
| 491 |
+
const navItems = document.querySelectorAll('.nav-item');
|
| 492 |
+
const tabContents = document.querySelectorAll('.tab-content');
|
| 493 |
+
const pageTitle = document.getElementById('page-title');
|
| 494 |
+
const dropZone = document.getElementById('drop-zone');
|
| 495 |
+
const fileInput = document.getElementById('file-input');
|
| 496 |
+
const browseBtn = document.getElementById('browse-btn');
|
| 497 |
+
const uploadQueue = document.getElementById('upload-queue');
|
| 498 |
+
const queueEmpty = document.getElementById('queue-empty');
|
| 499 |
+
const clearDataBtn = document.getElementById('clear-data');
|
| 500 |
+
const rebuildIndexBtn = document.getElementById('rebuild-index');
|
| 501 |
+
const assistantQuery = document.getElementById('assistant-query');
|
| 502 |
+
const askAssistantBtn = document.getElementById('ask-assistant');
|
| 503 |
+
const assistantResponse = document.getElementById('assistant-response');
|
| 504 |
+
const assistantEmpty = document.getElementById('assistant-empty');
|
| 505 |
+
const exampleQuestions = document.querySelectorAll('.example-question');
|
| 506 |
+
const modal = document.getElementById('modal');
|
| 507 |
+
const closeModal = document.getElementById('close-modal');
|
| 508 |
+
const cancelModal = document.getElementById('cancel-modal');
|
| 509 |
+
const confirmModal = document.getElementById('confirm-modal');
|
| 510 |
+
const modalTitle = document.getElementById('modal-title');
|
| 511 |
+
const modalContent = document.getElementById('modal-content');
|
| 512 |
+
|
| 513 |
+
// Mock database (in a real app, this would be IndexedDB)
|
| 514 |
+
let mockDB = {
|
| 515 |
+
documents: [],
|
| 516 |
+
items: [],
|
| 517 |
+
settings: {
|
| 518 |
+
vectorIndexBuilt: false
|
| 519 |
+
}
|
| 520 |
+
};
|
| 521 |
+
|
| 522 |
+
// Initialize the app
|
| 523 |
+
function init() {
|
| 524 |
+
// Load mock data
|
| 525 |
+
loadMockData();
|
| 526 |
+
|
| 527 |
+
// Update dashboard stats
|
| 528 |
+
updateDashboardStats();
|
| 529 |
+
|
| 530 |
+
// Set up event listeners
|
| 531 |
+
setupEventListeners();
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
// Load some mock data for demonstration
|
| 535 |
+
function loadMockData() {
|
| 536 |
+
// Sample documents
|
| 537 |
+
mockDB.documents = [
|
| 538 |
+
{
|
| 539 |
+
id: 'doc1',
|
| 540 |
+
name: 'IKEA_Office_Furniture_Quote.pdf',
|
| 541 |
+
supplier: 'IKEA',
|
| 542 |
+
date: '2023-05-15',
|
| 543 |
+
status: 'processed',
|
| 544 |
+
items: 12,
|
| 545 |
+
confidence: 92
|
| 546 |
+
},
|
| 547 |
+
{
|
| 548 |
+
id: 'doc2',
|
| 549 |
+
name: 'Dell_Laptops_Quote.pdf',
|
| 550 |
+
supplier: 'Dell',
|
| 551 |
+
date: '2023-06-02',
|
| 552 |
+
status: 'processed',
|
| 553 |
+
items: 8,
|
| 554 |
+
confidence: 88
|
| 555 |
+
},
|
| 556 |
+
{
|
| 557 |
+
id: 'doc3',
|
| 558 |
+
name: 'Staples_Supplies_Quote.jpg',
|
| 559 |
+
supplier: 'Staples',
|
| 560 |
+
date: '2023-06-10',
|
| 561 |
+
status: 'processing',
|
| 562 |
+
items: 0,
|
| 563 |
+
confidence: 0
|
| 564 |
+
}
|
| 565 |
+
];
|
| 566 |
+
|
| 567 |
+
// Sample items
|
| 568 |
+
mockDB.items = [
|
| 569 |
+
{
|
| 570 |
+
id: 'item1',
|
| 571 |
+
documentId: 'doc1',
|
| 572 |
+
name: 'MARKUS Office Chair',
|
| 573 |
+
description: 'Ergonomic office chair with adjustable height',
|
| 574 |
+
supplier: 'IKEA',
|
| 575 |
+
price: 199.99,
|
| 576 |
+
date: '2023-05-15',
|
| 577 |
+
confidence: 95
|
| 578 |
+
},
|
| 579 |
+
{
|
| 580 |
+
id: 'item2',
|
| 581 |
+
documentId: 'doc1',
|
| 582 |
+
name: 'BEKANT Desk',
|
| 583 |
+
description: 'Large office desk with adjustable legs',
|
| 584 |
+
supplier: 'IKEA',
|
| 585 |
+
price: 249.99,
|
| 586 |
+
date: '2023-05-15',
|
| 587 |
+
confidence: 92
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
id: 'item3',
|
| 591 |
+
documentId: 'doc1',
|
| 592 |
+
name: 'ALEX Drawer Unit',
|
| 593 |
+
description: '5-drawer storage unit on casters',
|
| 594 |
+
supplier: 'IKEA',
|
| 595 |
+
price: 129.99,
|
| 596 |
+
date: '2023-05-15',
|
| 597 |
+
confidence: 90
|
| 598 |
+
},
|
| 599 |
+
{
|
| 600 |
+
id: 'item4',
|
| 601 |
+
documentId: 'doc2',
|
| 602 |
+
name: 'XPS 13 Laptop',
|
| 603 |
+
description: '13.4" FHD+ InfinityEdge Touch Laptop',
|
| 604 |
+
supplier: 'Dell',
|
| 605 |
+
price: 1299.99,
|
| 606 |
+
date: '2023-06-02',
|
| 607 |
+
confidence: 88
|
| 608 |
+
},
|
| 609 |
+
{
|
| 610 |
+
id: 'item5',
|
| 611 |
+
documentId: 'doc2',
|
| 612 |
+
name: 'Latitude 5420',
|
| 613 |
+
description: '14" Business Laptop, Core i7',
|
| 614 |
+
supplier: 'Dell',
|
| 615 |
+
price: 1599.99,
|
| 616 |
+
date: '2023-06-02',
|
| 617 |
+
confidence: 85
|
| 618 |
+
},
|
| 619 |
+
{
|
| 620 |
+
id: 'item6',
|
| 621 |
+
documentId: 'doc2',
|
| 622 |
+
name: 'UltraSharp 27 Monitor',
|
| 623 |
+
description: '27" 4K UHD Monitor with USB-C',
|
| 624 |
+
supplier: 'Dell',
|
| 625 |
+
price: 699.99,
|
| 626 |
+
date: '2023-06-02',
|
| 627 |
+
confidence: 90
|
| 628 |
+
}
|
| 629 |
+
];
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
// Update dashboard statistics
|
| 633 |
+
function updateDashboardStats() {
|
| 634 |
+
document.getElementById('doc-count').textContent = mockDB.documents.length;
|
| 635 |
+
document.getElementById('item-count').textContent = mockDB.items.length;
|
| 636 |
+
|
| 637 |
+
// Calculate average confidence
|
| 638 |
+
let totalConfidence = 0;
|
| 639 |
+
let count = 0;
|
| 640 |
+
|
| 641 |
+
mockDB.documents.forEach(doc => {
|
| 642 |
+
if (doc.status === 'processed') {
|
| 643 |
+
totalConfidence += doc.confidence;
|
| 644 |
+
count++;
|
| 645 |
+
}
|
| 646 |
+
});
|
| 647 |
+
|
| 648 |
+
const avgConfidence = count > 0 ? Math.round(totalConfidence / count) : 0;
|
| 649 |
+
document.getElementById('avg-confidence').textContent = `${avgConfidence}%`;
|
| 650 |
+
|
| 651 |
+
// Mock storage used (in a real app, calculate actual IndexedDB usage)
|
| 652 |
+
const storageMB = (mockDB.documents.length * 0.5 + mockDB.items.length * 0.01).toFixed(2);
|
| 653 |
+
document.getElementById('storage-used').textContent = `${storageMB} MB`;
|
| 654 |
+
|
| 655 |
+
// Update recent uploads
|
| 656 |
+
updateRecentUploads();
|
| 657 |
+
|
| 658 |
+
// Update documents list
|
| 659 |
+
updateDocumentsList();
|
| 660 |
+
|
| 661 |
+
// Update items list
|
| 662 |
+
updateItemsList();
|
| 663 |
+
|
| 664 |
+
// Show rebuild index button if we have items but no index
|
| 665 |
+
if (mockDB.items.length > 0 && !mockDB.settings.vectorIndexBuilt) {
|
| 666 |
+
rebuildIndexBtn.classList.remove('hidden');
|
| 667 |
+
} else {
|
| 668 |
+
rebuildIndexBtn.classList.add('hidden');
|
| 669 |
+
}
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
// Update recent uploads section
|
| 673 |
+
function updateRecentUploads() {
|
| 674 |
+
const recentUploadsContainer = document.getElementById('recent-uploads');
|
| 675 |
+
|
| 676 |
+
if (mockDB.documents.length === 0) {
|
| 677 |
+
recentUploadsContainer.innerHTML = `
|
| 678 |
+
<div class="text-center py-8 text-gray-400">
|
| 679 |
+
<i class="fas fa-file-upload text-4xl mb-2"></i>
|
| 680 |
+
<p>No recent uploads</p>
|
| 681 |
+
</div>
|
| 682 |
+
`;
|
| 683 |
+
return;
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
// Sort by date (newest first) and take up to 3
|
| 687 |
+
const sortedDocs = [...mockDB.documents].sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 3);
|
| 688 |
+
|
| 689 |
+
let html = '';
|
| 690 |
+
sortedDocs.forEach(doc => {
|
| 691 |
+
const statusColor = doc.status === 'processed' ? 'bg-green-100 text-green-800' :
|
| 692 |
+
doc.status === 'processing' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800';
|
| 693 |
+
|
| 694 |
+
html += `
|
| 695 |
+
<div class="document-card bg-white border rounded-lg p-3 flex items-center justify-between">
|
| 696 |
+
<div class="flex items-center">
|
| 697 |
+
<div class="w-10 h-10 rounded-md bg-indigo-50 flex items-center justify-center mr-3">
|
| 698 |
+
<i class="fas fa-file-pdf text-indigo-500"></i>
|
| 699 |
+
</div>
|
| 700 |
+
<div>
|
| 701 |
+
<div class="font-medium truncate max-w-[180px]">${doc.name}</div>
|
| 702 |
+
<div class="text-xs text-gray-500">${formatDate(doc.date)}</div>
|
| 703 |
+
</div>
|
| 704 |
+
</div>
|
| 705 |
+
<div class="flex items-center">
|
| 706 |
+
<span class="text-xs px-2 py-1 rounded-full ${statusColor}">${doc.status}</span>
|
| 707 |
+
<button class="ml-2 text-gray-400 hover:text-indigo-600" data-doc-id="${doc.id}">
|
| 708 |
+
<i class="fas fa-ellipsis-v"></i>
|
| 709 |
+
</button>
|
| 710 |
+
</div>
|
| 711 |
+
</div>
|
| 712 |
+
`;
|
| 713 |
+
});
|
| 714 |
+
|
| 715 |
+
recentUploadsContainer.innerHTML = html;
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
// Update documents list
|
| 719 |
+
function updateDocumentsList() {
|
| 720 |
+
const documentsList = document.getElementById('documents-list');
|
| 721 |
+
|
| 722 |
+
if (mockDB.documents.length === 0) {
|
| 723 |
+
documentsList.innerHTML = `
|
| 724 |
+
<tr>
|
| 725 |
+
<td colspan="6" class="px-6 py-4 text-center text-gray-400">
|
| 726 |
+
<i class="fas fa-file-alt text-3xl mb-2"></i>
|
| 727 |
+
<p>No documents uploaded yet</p>
|
| 728 |
+
</td>
|
| 729 |
+
</tr>
|
| 730 |
+
`;
|
| 731 |
+
return;
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
let html = '';
|
| 735 |
+
mockDB.documents.forEach(doc => {
|
| 736 |
+
const statusColor = doc.status === 'processed' ? 'text-green-600' :
|
| 737 |
+
doc.status === 'processing' ? 'text-yellow-600' : 'text-red-600';
|
| 738 |
+
|
| 739 |
+
html += `
|
| 740 |
+
<tr>
|
| 741 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 742 |
+
<div class="flex items-center">
|
| 743 |
+
<div class="flex-shrink-0 h-10 w-10">
|
| 744 |
+
<div class="h-10 w-10 rounded-md bg-indigo-50 flex items-center justify-center">
|
| 745 |
+
<i class="fas fa-file-pdf text-indigo-500"></i>
|
| 746 |
+
</div>
|
| 747 |
+
</div>
|
| 748 |
+
<div class="ml-4">
|
| 749 |
+
<div class="text-sm font-medium text-gray-900">${doc.name}</div>
|
| 750 |
+
</div>
|
| 751 |
+
</div>
|
| 752 |
+
</td>
|
| 753 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 754 |
+
<div class="text-sm text-gray-900">${doc.supplier || 'Unknown'}</div>
|
| 755 |
+
</td>
|
| 756 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 757 |
+
<div class="text-sm text-gray-900">${doc.items}</div>
|
| 758 |
+
</td>
|
| 759 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 760 |
+
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusColor}">
|
| 761 |
+
${doc.status}
|
| 762 |
+
</span>
|
| 763 |
+
</td>
|
| 764 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
| 765 |
+
${formatDate(doc.date)}
|
| 766 |
+
</td>
|
| 767 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 768 |
+
<button class="text-indigo-600 hover:text-indigo-900 mr-3" data-doc-id="${doc.id}">
|
| 769 |
+
<i class="fas fa-eye"></i>
|
| 770 |
+
</button>
|
| 771 |
+
<button class="text-red-600 hover:text-red-900" data-doc-id="${doc.id}">
|
| 772 |
+
<i class="fas fa-trash-alt"></i>
|
| 773 |
+
</button>
|
| 774 |
+
</td>
|
| 775 |
+
</tr>
|
| 776 |
+
`;
|
| 777 |
+
});
|
| 778 |
+
|
| 779 |
+
documentsList.innerHTML = html;
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
// Update items list
|
| 783 |
+
function updateItemsList() {
|
| 784 |
+
const itemsList = document.getElementById('items-list');
|
| 785 |
+
|
| 786 |
+
if (mockDB.items.length === 0) {
|
| 787 |
+
itemsList.innerHTML = `
|
| 788 |
+
<tr>
|
| 789 |
+
<td colspan="6" class="px-6 py-4 text-center text-gray-400">
|
| 790 |
+
<i class="fas fa-list-ul text-3xl mb-2"></i>
|
| 791 |
+
<p>No items extracted yet</p>
|
| 792 |
+
</td>
|
| 793 |
+
</tr>
|
| 794 |
+
`;
|
| 795 |
+
return;
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
let html = '';
|
| 799 |
+
mockDB.items.forEach(item => {
|
| 800 |
+
const doc = mockDB.documents.find(d => d.id === item.documentId) || {};
|
| 801 |
+
|
| 802 |
+
html += `
|
| 803 |
+
<tr>
|
| 804 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 805 |
+
<div class="text-sm font-medium text-gray-900">${item.name}</div>
|
| 806 |
+
</td>
|
| 807 |
+
<td class="px-6 py-4">
|
| 808 |
+
<div class="text-sm text-gray-500 max-w-xs truncate">${item.description}</div>
|
| 809 |
+
</td>
|
| 810 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 811 |
+
<div class="text-sm text-gray-900">${item.supplier}</div>
|
| 812 |
+
</td>
|
| 813 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 814 |
+
<div class="text-sm text-gray-900">$${item.price.toFixed(2)}</div>
|
| 815 |
+
</td>
|
| 816 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 817 |
+
<div class="text-sm text-gray-500">${doc.name || 'Unknown'}</div>
|
| 818 |
+
</td>
|
| 819 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 820 |
+
<div class="flex items-center">
|
| 821 |
+
<div class="w-16 bg-gray-200 rounded-full h-2 mr-2">
|
| 822 |
+
<div class="bg-green-500 h-2 rounded-full" style="width: ${item.confidence}%"></div>
|
| 823 |
+
</div>
|
| 824 |
+
<span class="text-xs text-gray-500">${item.confidence}%</span>
|
| 825 |
+
</div>
|
| 826 |
+
</td>
|
| 827 |
+
</tr>
|
| 828 |
+
`;
|
| 829 |
+
});
|
| 830 |
+
|
| 831 |
+
itemsList.innerHTML = html;
|
| 832 |
+
|
| 833 |
+
// Update pagination info
|
| 834 |
+
document.getElementById('items-start').textContent = 1;
|
| 835 |
+
document.getElementById('items-end').textContent = mockDB.items.length;
|
| 836 |
+
document.getElementById('items-total').textContent = mockDB.items.length;
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
// Set up event listeners
|
| 840 |
+
function setupEventListeners() {
|
| 841 |
+
// Toggle sidebar
|
| 842 |
+
toggleSidebar.addEventListener('click', () => {
|
| 843 |
+
sidebar.classList.toggle('collapsed');
|
| 844 |
+
});
|
| 845 |
+
|
| 846 |
+
// Tab navigation
|
| 847 |
+
navItems.forEach(item => {
|
| 848 |
+
item.addEventListener('click', (e) => {
|
| 849 |
+
e.preventDefault();
|
| 850 |
+
const tabId = item.getAttribute('data-tab');
|
| 851 |
+
|
| 852 |
+
// Update active tab
|
| 853 |
+
navItems.forEach(navItem => navItem.classList.remove('bg-indigo-600'));
|
| 854 |
+
item.classList.add('bg-indigo-600');
|
| 855 |
+
|
| 856 |
+
// Show corresponding content
|
| 857 |
+
tabContents.forEach(content => content.classList.remove('active'));
|
| 858 |
+
document.getElementById(tabId).classList.add('active');
|
| 859 |
+
|
| 860 |
+
// Update page title
|
| 861 |
+
pageTitle.textContent = item.querySelector('.nav-text').textContent;
|
| 862 |
+
|
| 863 |
+
// Special handling for certain tabs
|
| 864 |
+
if (tabId === 'assistant') {
|
| 865 |
+
assistantResponse.classList.add('hidden');
|
| 866 |
+
assistantEmpty.classList.remove('hidden');
|
| 867 |
+
}
|
| 868 |
+
});
|
| 869 |
+
});
|
| 870 |
+
|
| 871 |
+
// File upload handling
|
| 872 |
+
dropZone.addEventListener('dragover', (e) => {
|
| 873 |
+
e.preventDefault();
|
| 874 |
+
dropZone.classList.add('drag-active');
|
| 875 |
+
});
|
| 876 |
+
|
| 877 |
+
dropZone.addEventListener('dragleave', () => {
|
| 878 |
+
dropZone.classList.remove('drag-active');
|
| 879 |
+
});
|
| 880 |
+
|
| 881 |
+
dropZone.addEventListener('drop', (e) => {
|
| 882 |
+
e.preventDefault();
|
| 883 |
+
dropZone.classList.remove('drag-active');
|
| 884 |
+
|
| 885 |
+
if (e.dataTransfer.files.length > 0) {
|
| 886 |
+
handleFiles(e.dataTransfer.files);
|
| 887 |
+
}
|
| 888 |
+
});
|
| 889 |
+
|
| 890 |
+
browseBtn.addEventListener('click', () => {
|
| 891 |
+
fileInput.click();
|
| 892 |
+
});
|
| 893 |
+
|
| 894 |
+
fileInput.addEventListener('change', () => {
|
| 895 |
+
if (fileInput.files.length > 0) {
|
| 896 |
+
handleFiles(fileInput.files);
|
| 897 |
+
}
|
| 898 |
+
});
|
| 899 |
+
|
| 900 |
+
// Clear data button
|
| 901 |
+
clearDataBtn.addEventListener('click', () => {
|
| 902 |
+
showModal(
|
| 903 |
+
'Clear All Data',
|
| 904 |
+
'Are you sure you want to delete all uploaded documents and extracted data? This action cannot be undone.',
|
| 905 |
+
() => {
|
| 906 |
+
mockDB.documents = [];
|
| 907 |
+
mockDB.items = [];
|
| 908 |
+
mockDB.settings.vectorIndexBuilt = false;
|
| 909 |
+
updateDashboardStats();
|
| 910 |
+
showToast('All data has been cleared', 'success');
|
| 911 |
+
}
|
| 912 |
+
);
|
| 913 |
+
});
|
| 914 |
+
|
| 915 |
+
// Rebuild index button
|
| 916 |
+
rebuildIndexBtn.addEventListener('click', () => {
|
| 917 |
+
showModal(
|
| 918 |
+
'Rebuild Vector Index',
|
| 919 |
+
'This will rebuild the local search index to improve query performance. It may take a few moments.',
|
| 920 |
+
() => {
|
| 921 |
+
// Simulate processing
|
| 922 |
+
rebuildIndexBtn.innerHTML = '<i class="fas fa-spinner rotate mr-1"></i> Rebuilding...';
|
| 923 |
+
rebuildIndexBtn.disabled = true;
|
| 924 |
+
|
| 925 |
+
setTimeout(() => {
|
| 926 |
+
mockDB.settings.vectorIndexBuilt = true;
|
| 927 |
+
rebuildIndexBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Index Rebuilt';
|
| 928 |
+
setTimeout(() => {
|
| 929 |
+
rebuildIndexBtn.innerHTML = '<i class="fas fa-sync-alt mr-1"></i> Rebuild Index';
|
| 930 |
+
rebuildIndexBtn.disabled = false;
|
| 931 |
+
rebuildIndexBtn.classList.add('hidden');
|
| 932 |
+
showToast('Vector index rebuilt successfully', 'success');
|
| 933 |
+
}, 2000);
|
| 934 |
+
}, 1500);
|
| 935 |
+
}
|
| 936 |
+
);
|
| 937 |
+
});
|
| 938 |
+
|
| 939 |
+
// RAG Assistant
|
| 940 |
+
askAssistantBtn.addEventListener('click', askAssistant);
|
| 941 |
+
|
| 942 |
+
assistantQuery.addEventListener('keypress', (e) => {
|
| 943 |
+
if (e.key === 'Enter') {
|
| 944 |
+
askAssistant();
|
| 945 |
+
}
|
| 946 |
+
});
|
| 947 |
+
|
| 948 |
+
exampleQuestions.forEach(question => {
|
| 949 |
+
question.addEventListener('click', () => {
|
| 950 |
+
assistantQuery.value = question.querySelector('.font-medium').textContent;
|
| 951 |
+
askAssistant();
|
| 952 |
+
});
|
| 953 |
+
});
|
| 954 |
+
|
| 955 |
+
// Modal
|
| 956 |
+
closeModal.addEventListener('click', () => modal.classList.add('hidden'));
|
| 957 |
+
cancelModal.addEventListener('click', () => modal.classList.add('hidden'));
|
| 958 |
+
|
| 959 |
+
// Quick actions
|
| 960 |
+
document.querySelectorAll('.quick-action').forEach(action => {
|
| 961 |
+
action.addEventListener('click', (e) => {
|
| 962 |
+
if (action.id !== 'clear-data') {
|
| 963 |
+
e.preventDefault();
|
| 964 |
+
const tabId = action.getAttribute('data-tab');
|
| 965 |
+
|
| 966 |
+
// Update active tab
|
| 967 |
+
navItems.forEach(navItem => navItem.classList.remove('bg-indigo-600'));
|
| 968 |
+
document.querySelector(`.nav-item[data-tab="${tabId}"]`).classList.add('bg-indigo-600');
|
| 969 |
+
|
| 970 |
+
// Show corresponding content
|
| 971 |
+
tabContents.forEach(content => content.classList.remove('active'));
|
| 972 |
+
document.getElementById(tabId).classList.add('active');
|
| 973 |
+
|
| 974 |
+
// Update page title
|
| 975 |
+
pageTitle.textContent = document.querySelector(`.nav-item[data-tab="${tabId}"] .nav-text`).textContent;
|
| 976 |
+
}
|
| 977 |
+
});
|
| 978 |
+
});
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
// Handle uploaded files
|
| 982 |
+
function handleFiles(files) {
|
| 983 |
+
queueEmpty.classList.add('hidden');
|
| 984 |
+
|
| 985 |
+
for (let i = 0; i < files.length; i++) {
|
| 986 |
+
const file = files[i];
|
| 987 |
+
|
| 988 |
+
// Check if file type is supported
|
| 989 |
+
if (!file.type.match(/(pdf|image)/) && !file.name.match(/\.(pdf|jpg|jpeg|png|webp)$/i)) {
|
| 990 |
+
showToast(`File type not supported: ${file.name}`, 'error');
|
| 991 |
+
continue;
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
// Add to upload queue
|
| 995 |
+
const fileId = 'file-' + Date.now() + '-' + Math.floor(Math.random() * 1000);
|
| 996 |
+
const fileCard = createFileCard(file, fileId);
|
| 997 |
+
uploadQueue.appendChild(fileCard);
|
| 998 |
+
|
| 999 |
+
// Simulate processing
|
| 1000 |
+
setTimeout(() => {
|
| 1001 |
+
const progressBar = fileCard.querySelector('.progress-bar');
|
| 1002 |
+
let progress = 0;
|
| 1003 |
+
|
| 1004 |
+
const interval = setInterval(() => {
|
| 1005 |
+
progress += Math.random() * 10;
|
| 1006 |
+
if (progress >= 100) {
|
| 1007 |
+
progress = 100;
|
| 1008 |
+
clearInterval(interval);
|
| 1009 |
+
|
| 1010 |
+
// Update status to processing
|
| 1011 |
+
const status = fileCard.querySelector('.file-status');
|
| 1012 |
+
status.textContent = 'Processing';
|
| 1013 |
+
status.className = 'file-status text-yellow-600';
|
| 1014 |
+
|
| 1015 |
+
// Simulate OCR and extraction
|
| 1016 |
+
setTimeout(() => {
|
| 1017 |
+
// Add to mock database
|
| 1018 |
+
const docId = 'doc-' + Date.now();
|
| 1019 |
+
const newDoc = {
|
| 1020 |
+
id: docId,
|
| 1021 |
+
name: file.name,
|
| 1022 |
+
supplier: guessSupplier(file.name),
|
| 1023 |
+
date: new Date().toISOString().split('T')[0],
|
| 1024 |
+
status: 'processed',
|
| 1025 |
+
items: Math.floor(Math.random() * 10) + 1,
|
| 1026 |
+
confidence: Math.floor(Math.random() * 10) + 85
|
| 1027 |
+
};
|
| 1028 |
+
|
| 1029 |
+
mockDB.documents.push(newDoc);
|
| 1030 |
+
|
| 1031 |
+
// Add some mock items
|
| 1032 |
+
const supplier = newDoc.supplier;
|
| 1033 |
+
const itemsCount = newDoc.items;
|
| 1034 |
+
const items = generateMockItems(docId, supplier, itemsCount);
|
| 1035 |
+
mockDB.items.push(...items);
|
| 1036 |
+
|
| 1037 |
+
// Update UI
|
| 1038 |
+
updateDashboardStats();
|
| 1039 |
+
|
| 1040 |
+
// Update file card status
|
| 1041 |
+
status.textContent = 'Completed';
|
| 1042 |
+
status.className = 'file-status text-green-600';
|
| 1043 |
+
fileCard.querySelector('.file-actions').innerHTML = `
|
| 1044 |
+
<button class="text-green-600 hover:text-green-800" data-doc-id="${docId}">
|
| 1045 |
+
<i class="fas fa-check-circle"></i>
|
| 1046 |
+
</button>
|
| 1047 |
+
`;
|
| 1048 |
+
|
| 1049 |
+
showToast(`${file.name} processed successfully`, 'success');
|
| 1050 |
+
}, 1500);
|
| 1051 |
+
}
|
| 1052 |
+
|
| 1053 |
+
progressBar.style.width = `${progress}%`;
|
| 1054 |
+
fileCard.querySelector('.progress-text').textContent = `${Math.round(progress)}%`;
|
| 1055 |
+
}, 200);
|
| 1056 |
+
}, 500);
|
| 1057 |
+
}
|
| 1058 |
+
}
|
| 1059 |
+
|
| 1060 |
+
// Create file card for upload queue
|
| 1061 |
+
function createFileCard(file, fileId) {
|
| 1062 |
+
const card = document.createElement('div');
|
| 1063 |
+
card.className = 'file-card bg-gray-50 rounded-lg p-3 flex items-center justify-between';
|
| 1064 |
+
card.dataset.fileId = fileId;
|
| 1065 |
+
|
| 1066 |
+
card.innerHTML = `
|
| 1067 |
+
<div class="flex items-center">
|
| 1068 |
+
<div class="w-10 h-10 rounded-md bg-indigo-50 flex items-center justify-center mr-3">
|
| 1069 |
+
<i class="fas ${getFileIcon(file)} text-indigo-500"></i>
|
| 1070 |
+
</div>
|
| 1071 |
+
<div>
|
| 1072 |
+
<div class="font-medium truncate max-w-[180px]">${file.name}</div>
|
| 1073 |
+
<div class="text-xs text-gray-500">${formatFileSize(file.size)}</div>
|
| 1074 |
+
</div>
|
| 1075 |
+
</div>
|
| 1076 |
+
<div class="flex items-center">
|
| 1077 |
+
<div class="mr-4 text-xs">
|
| 1078 |
+
<span class="file-status text-blue-600">Uploading</span>
|
| 1079 |
+
<div class="w-20 bg-gray-200 rounded-full h-1 mt-1">
|
| 1080 |
+
<div class="progress-bar bg-blue-500 h-1 rounded-full" style="width: 0%"></div>
|
| 1081 |
+
</div>
|
| 1082 |
+
<span class="progress-text text-gray-500">0%</span>
|
| 1083 |
+
</div>
|
| 1084 |
+
<div class="file-actions">
|
| 1085 |
+
<button class="text-red-600 hover:text-red-800">
|
| 1086 |
+
<i class="fas fa-times"></i>
|
| 1087 |
+
</button>
|
| 1088 |
+
</div>
|
| 1089 |
+
</div>
|
| 1090 |
+
`;
|
| 1091 |
+
|
| 1092 |
+
return card;
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
+
// Ask the assistant a question
|
| 1096 |
+
function askAssistant() {
|
| 1097 |
+
const query = assistantQuery.value.trim();
|
| 1098 |
+
|
| 1099 |
+
if (!query) {
|
| 1100 |
+
showToast('Please enter a question', 'warning');
|
| 1101 |
+
return;
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
if (mockDB.items.length === 0) {
|
| 1105 |
+
showToast('No items available to query. Please upload documents first.', 'warning');
|
| 1106 |
+
return;
|
| 1107 |
+
}
|
| 1108 |
+
|
| 1109 |
+
// Show loading state
|
| 1110 |
+
askAssistantBtn.innerHTML = '<i class="fas fa-spinner rotate mr-1"></i> Processing';
|
| 1111 |
+
askAssistantBtn.disabled = true;
|
| 1112 |
+
|
| 1113 |
+
// Simulate processing delay
|
| 1114 |
+
setTimeout(() => {
|
| 1115 |
+
// Generate mock response based on query
|
| 1116 |
+
const response = generateMockResponse(query);
|
| 1117 |
+
|
| 1118 |
+
// Update UI
|
| 1119 |
+
assistantEmpty.classList.add('hidden');
|
| 1120 |
+
assistantResponse.classList.remove('hidden');
|
| 1121 |
+
|
| 1122 |
+
document.getElementById('assistant-answer').innerHTML = response.answer;
|
| 1123 |
+
|
| 1124 |
+
const sourcesContainer = document.getElementById('assistant-sources');
|
| 1125 |
+
sourcesContainer.innerHTML = '';
|
| 1126 |
+
|
| 1127 |
+
response.sources.forEach(source => {
|
| 1128 |
+
const sourceElement = document.createElement('div');
|
| 1129 |
+
sourceElement.className = 'bg-white border rounded p-3 flex items-start';
|
| 1130 |
+
|
| 1131 |
+
sourceElement.innerHTML = `
|
| 1132 |
+
<div class="flex-shrink-0 mr-3">
|
| 1133 |
+
<div class="w-6 h-6 rounded-full bg-indigo-100 flex items-center justify-center">
|
| 1134 |
+
<i class="fas fa-file-alt text-indigo-600 text-xs"></i>
|
| 1135 |
+
</div>
|
| 1136 |
+
</div>
|
| 1137 |
+
<div class="flex-1 min-w-0">
|
| 1138 |
+
<div class="text-sm font-medium">${source.name}</div>
|
| 1139 |
+
<div class="text-xs text-gray-500">${source.supplier} • $${source.price.toFixed(2)}</div>
|
| 1140 |
+
<div class="text-xs mt-1">${source.description}</div>
|
| 1141 |
+
</div>
|
| 1142 |
+
`;
|
| 1143 |
+
|
| 1144 |
+
sourcesContainer.appendChild(sourceElement);
|
| 1145 |
+
});
|
| 1146 |
+
|
| 1147 |
+
// Reset button state
|
| 1148 |
+
askAssistantBtn.innerHTML = '<i class="fas fa-paper-plane mr-2"></i> Ask';
|
| 1149 |
+
askAssistantBtn.disabled = false;
|
| 1150 |
+
|
| 1151 |
+
// Rebuild index button if not built
|
| 1152 |
+
if (!mockDB.settings.vectorIndexBuilt) {
|
| 1153 |
+
rebuildIndexBtn.classList.remove('hidden');
|
| 1154 |
+
}
|
| 1155 |
+
}, 1500);
|
| 1156 |
+
}
|
| 1157 |
+
|
| 1158 |
+
// Show modal dialog
|
| 1159 |
+
function showModal(title, content, confirmCallback) {
|
| 1160 |
+
modalTitle.textContent = title;
|
| 1161 |
+
modalContent.textContent = content;
|
| 1162 |
+
modal.classList.remove('hidden');
|
| 1163 |
+
|
| 1164 |
+
// Set up confirm button
|
| 1165 |
+
confirmModal.onclick = () => {
|
| 1166 |
+
modal.classList.add('hidden');
|
| 1167 |
+
if (confirmCallback) confirmCallback();
|
| 1168 |
+
};
|
| 1169 |
+
}
|
| 1170 |
+
|
| 1171 |
+
// Show toast notification
|
| 1172 |
+
function showToast(message, type = 'info') {
|
| 1173 |
+
const colors = {
|
| 1174 |
+
info: 'bg-blue-500',
|
| 1175 |
+
success: 'bg-green-500',
|
| 1176 |
+
warning: 'bg-yellow-500',
|
| 1177 |
+
error: 'bg-red-500'
|
| 1178 |
+
};
|
| 1179 |
+
|
| 1180 |
+
const toast = document.createElement('div');
|
| 1181 |
+
toast.className = `fixed bottom-4 right-4 text-white px-4 py-2 rounded-md shadow-lg flex items-center ${colors[type]} fade-in`;
|
| 1182 |
+
|
| 1183 |
+
toast.innerHTML = `
|
| 1184 |
+
<i class="fas ${getToastIcon(type)} mr-2"></i>
|
| 1185 |
+
<span>${message}</span>
|
| 1186 |
+
`;
|
| 1187 |
+
|
| 1188 |
+
document.body.appendChild(toast);
|
| 1189 |
+
|
| 1190 |
+
setTimeout(() => {
|
| 1191 |
+
toast.classList.add('opacity-0');
|
| 1192 |
+
setTimeout(() => toast.remove(), 300);
|
| 1193 |
+
}, 3000);
|
| 1194 |
+
}
|
| 1195 |
+
|
| 1196 |
+
// Helper functions
|
| 1197 |
+
function formatDate(dateString) {
|
| 1198 |
+
const options = { year: 'numeric', month: 'short', day: 'numeric' };
|
| 1199 |
+
return new Date(dateString).toLocaleDateString(undefined, options);
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
function formatFileSize(bytes) {
|
| 1203 |
+
if (bytes === 0) return '0 Bytes';
|
| 1204 |
+
const k = 1024;
|
| 1205 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
| 1206 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 1207 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
function getFileIcon(file) {
|
| 1211 |
+
if (file.type.match(/pdf/)) return 'fa-file-pdf';
|
| 1212 |
+
if (file.type.match(/image/)) return 'fa-file-image';
|
| 1213 |
+
return 'fa-file-alt';
|
| 1214 |
+
}
|
| 1215 |
+
|
| 1216 |
+
function getToastIcon(type) {
|
| 1217 |
+
switch (type) {
|
| 1218 |
+
case 'success': return 'fa-check-circle';
|
| 1219 |
+
case 'warning': return 'fa-exclamation-triangle';
|
| 1220 |
+
case 'error': return 'fa-times-circle';
|
| 1221 |
+
default: return 'fa-info-circle';
|
| 1222 |
+
}
|
| 1223 |
+
}
|
| 1224 |
+
|
| 1225 |
+
function guessSupplier(filename) {
|
| 1226 |
+
const lowerName = filename.toLowerCase();
|
| 1227 |
+
if (lowerName.includes('ikea')) return 'IKEA';
|
| 1228 |
+
if (lowerName.includes('dell')) return 'Dell';
|
| 1229 |
+
if (lowerName.includes('hp')) return 'HP';
|
| 1230 |
+
if (lowerName.includes('lenovo')) return 'Lenovo';
|
| 1231 |
+
if (lowerName.includes('staples')) return 'Staples';
|
| 1232 |
+
if (lowerName.includes('amazon')) return 'Amazon';
|
| 1233 |
+
return 'Unknown Supplier';
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
function generateMockItems(docId, supplier, count) {
|
| 1237 |
+
const items = [];
|
| 1238 |
+
const supplierItems = {
|
| 1239 |
+
'IKEA': [
|
| 1240 |
+
{ name: 'MARKUS Office Chair', desc: 'Ergonomic office chair with adjustable height', price: 199.99 },
|
| 1241 |
+
{ name: 'BEKANT Desk', desc: 'Large office desk with adjustable legs', price: 249.99 },
|
| 1242 |
+
{ name: 'ALEX Drawer Unit', desc: '5-drawer storage unit on casters', price: 129.99 },
|
| 1243 |
+
{ name: 'MILLBERGET Chair', desc: 'Comfortable office chair', price: 79.99 },
|
| 1244 |
+
{ name: 'LACK Side Table', desc: 'Lightweight side table', price: 29.99 }
|
| 1245 |
+
],
|
| 1246 |
+
'Dell': [
|
| 1247 |
+
{ name: 'XPS 13 Laptop', desc: '13.4" FHD+ InfinityEdge Touch Laptop', price: 1299.99 },
|
| 1248 |
+
{ name: 'Latitude 5420', desc: '14" Business Laptop, Core i7', price: 1599.99 },
|
| 1249 |
+
{ name: 'UltraSharp 27 Monitor', desc: '27" 4K UHD Monitor with USB-C', price: 699.99 },
|
| 1250 |
+
{ name: 'OptiPlex 7080', desc: 'Compact desktop computer', price: 899.99 },
|
| 1251 |
+
{ name: 'Wireless Keyboard and Mouse', desc: 'Premium keyboard and mouse combo', price: 89.99 }
|
| 1252 |
+
],
|
| 1253 |
+
'Staples': [
|
| 1254 |
+
{ name: 'Copy Paper', desc: '8.5 x 11 inch copy paper, 500 sheets', price: 5.99 },
|
| 1255 |
+
{ name: 'Stapler', desc: 'Heavy-duty stapler', price: 12.99 },
|
| 1256 |
+
{ name: 'Ballpoint Pens', desc: '12-pack of black ballpoint pens', price: 8.99 },
|
| 1257 |
+
{ name: 'Sticky Notes', desc: '3x3 inch sticky notes, 12 pads', price: 9.99 },
|
| 1258 |
+
{ name: 'File Folders', desc: 'Letter-size file folders, 25 count', price: 11.99 }
|
| 1259 |
+
],
|
| 1260 |
+
'Default': [
|
| 1261 |
+
{ name: 'Office Chair', desc: 'Standard office chair', price: 149.99 },
|
| 1262 |
+
{ name: 'Desk', desc: 'Standard office desk', price: 199.99 },
|
| 1263 |
+
{ name: 'Monitor', desc: '24-inch HD monitor', price: 179.99 },
|
| 1264 |
+
{ name: 'Keyboard', desc: 'USB keyboard', price: 29.99 },
|
| 1265 |
+
{ name: 'Mouse', desc: 'Optical mouse', price: 19.99 }
|
| 1266 |
+
]
|
| 1267 |
+
};
|
| 1268 |
+
|
| 1269 |
+
const sourceItems = supplierItems[supplier] || supplierItems['Default'];
|
| 1270 |
+
|
| 1271 |
+
for (let i = 0; i < count; i++) {
|
| 1272 |
+
const sourceItem = sourceItems[i % sourceItems.length];
|
| 1273 |
+
const variation = Math.random() * 0.3 - 0.15; // -15% to +15% variation
|
| 1274 |
+
|
| 1275 |
+
items.push({
|
| 1276 |
+
id: 'item-' + Date.now() + '-' + i,
|
| 1277 |
+
documentId: docId,
|
| 1278 |
+
name: sourceItem.name,
|
| 1279 |
+
description: sourceItem.desc,
|
| 1280 |
+
supplier: supplier,
|
| 1281 |
+
price: sourceItem.price * (1 + variation),
|
| 1282 |
+
date: new Date().toISOString().split('T')[0],
|
| 1283 |
+
confidence: Math.floor(Math.random() * 10) + 85
|
| 1284 |
+
});
|
| 1285 |
+
}
|
| 1286 |
+
|
| 1287 |
+
return items;
|
| 1288 |
+
}
|
| 1289 |
+
|
| 1290 |
+
function generateMockResponse(query) {
|
| 1291 |
+
const lowerQuery = query.toLowerCase();
|
| 1292 |
+
let answer = '';
|
| 1293 |
+
let relevantItems = [];
|
| 1294 |
+
|
| 1295 |
+
if (lowerQuery.includes('average') || lowerQuery.includes('price')) {
|
| 1296 |
+
// Price statistics question
|
| 1297 |
+
const product = lowerQuery.includes('laptop') ? 'laptops' :
|
| 1298 |
+
lowerQuery.includes('chair') ? 'office chairs' :
|
| 1299 |
+
lowerQuery.includes('desk') ? 'desks' : 'items';
|
| 1300 |
+
|
| 1301 |
+
const supplierFilter = lowerQuery.includes('ikea') ? 'IKEA' :
|
| 1302 |
+
lowerQuery.includes('dell') ? 'Dell' :
|
| 1303 |
+
lowerQuery.includes('staples') ? 'Staples' : null;
|
| 1304 |
+
|
| 1305 |
+
const filteredItems = mockDB.items.filter(item => {
|
| 1306 |
+
const matchesProduct = item.name.toLowerCase().includes(product.replace(' ', ''));
|
| 1307 |
+
const matchesSupplier = supplierFilter ? item.supplier === supplierFilter : true;
|
| 1308 |
+
return matchesProduct && matchesSupplier;
|
| 1309 |
+
});
|
| 1310 |
+
|
| 1311 |
+
if (filteredItems.length > 0) {
|
| 1312 |
+
const total = filteredItems.reduce((sum, item) => sum + item.price, 0);
|
| 1313 |
+
const average = total / filteredItems.length;
|
| 1314 |
+
const min = Math.min(...filteredItems.map(item => item.price));
|
| 1315 |
+
const max = Math.max(...filteredItems.map(item => item.price));
|
| 1316 |
+
|
| 1317 |
+
answer = `The average price for ${supplierFilter ? supplierFilter + ' ' : ''}${product} is $${average.toFixed(2)}. `;
|
| 1318 |
+
answer += `Prices range from $${min.toFixed(2)} to $${max.toFixed(2)}.`;
|
| 1319 |
+
|
| 1320 |
+
relevantItems = filteredItems.slice(0, 3);
|
| 1321 |
+
} else {
|
| 1322 |
+
answer = `I couldn't find any ${supplierFilter ? supplierFilter + ' ' : ''}${product} in your quotation data.`;
|
| 1323 |
+
}
|
| 1324 |
+
} else if (lowerQuery.includes('cheap') || lowerQuery.includes('lowest')) {
|
| 1325 |
+
// Cheapest items question
|
| 1326 |
+
const product = lowerQuery.includes('laptop') ? 'laptop' :
|
| 1327 |
+
lowerQuery.includes('chair') ? 'chair' :
|
| 1328 |
+
lowerQuery.includes('desk') ? 'desk' : 'item';
|
| 1329 |
+
|
| 1330 |
+
const supplierFilter = lowerQuery.includes('ikea') ? 'IKEA' :
|
| 1331 |
+
lowerQuery.includes('dell') ? 'Dell' :
|
| 1332 |
+
lowerQuery.includes('staples') ? 'Staples' : null;
|
| 1333 |
+
|
| 1334 |
+
const filteredItems = mockDB.items.filter(item => {
|
| 1335 |
+
const matchesProduct = item.name.toLowerCase().includes(product);
|
| 1336 |
+
const matchesSupplier = supplierFilter ? item.supplier === supplierFilter : true;
|
| 1337 |
+
return matchesProduct && matchesSupplier;
|
| 1338 |
+
}).sort((a, b) => a.price - b.price);
|
| 1339 |
+
|
| 1340 |
+
if (filteredItems.length > 0) {
|
| 1341 |
+
answer = `The cheapest ${supplierFilter ? supplierFilter + ' ' : ''}${product}s in your quotations are:`;
|
| 1342 |
+
|
| 1343 |
+
relevantItems = filteredItems.slice(0, 3);
|
| 1344 |
+
|
| 1345 |
+
relevantItems.forEach((item, index) => {
|
| 1346 |
+
answer += `<br>${index + 1}. ${item.name} - $${item.price.toFixed(2)}`;
|
| 1347 |
+
});
|
| 1348 |
+
} else {
|
| 1349 |
+
answer = `I couldn't find any ${supplierFilter ? supplierFilter + ' ' : ''}${product}s in your quotation data.`;
|
| 1350 |
+
}
|
| 1351 |
+
} else if (lowerQuery.includes('ikea') || lowerQuery.includes('dell') || lowerQuery.includes('staples')) {
|
| 1352 |
+
// Supplier-specific question
|
| 1353 |
+
const supplier = lowerQuery.includes('ikea') ? 'IKEA' :
|
| 1354 |
+
lowerQuery.includes('dell') ? 'Dell' : 'Staples';
|
| 1355 |
+
|
| 1356 |
+
const filteredItems = mockDB.items.filter(item => item.supplier === supplier);
|
| 1357 |
+
|
| 1358 |
+
if (filteredItems.length > 0) {
|
| 1359 |
+
answer = `You have ${filteredItems.length} items from ${supplier} in your quotations. `;
|
| 1360 |
+
answer += `Here are some examples:`;
|
| 1361 |
+
|
| 1362 |
+
relevantItems = filteredItems.slice(0, 3);
|
| 1363 |
+
} else {
|
| 1364 |
+
answer = `I couldn't find any items from ${supplier} in your quotation data.`;
|
| 1365 |
+
}
|
| 1366 |
+
} else {
|
| 1367 |
+
// General question
|
| 1368 |
+
answer = `I found some relevant items in your quotations:`;
|
| 1369 |
+
relevantItems = mockDB.items.slice(0, 3);
|
| 1370 |
+
}
|
| 1371 |
+
|
| 1372 |
+
return {
|
| 1373 |
+
answer: answer,
|
| 1374 |
+
sources: relevantItems.map(item => ({
|
| 1375 |
+
name: item.name,
|
| 1376 |
+
description: item.description,
|
| 1377 |
+
supplier: item.supplier,
|
| 1378 |
+
price: item.price
|
| 1379 |
+
}))
|
| 1380 |
+
};
|
| 1381 |
+
}
|
| 1382 |
+
|
| 1383 |
+
// Initialize the app
|
| 1384 |
+
document.addEventListener('DOMContentLoaded', init);
|
| 1385 |
+
</script>
|
| 1386 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Ultronprime/quote-for" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 1387 |
+
</html>
|
prompts.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# QuoteSense - Local Quotation Management & Query Tool ## Overview QuoteSense is a web-based application designed for managing, processing, and querying quotation documents entirely within the user's browser. It leverages local storage (IndexedDB) and in-browser processing (Web Workers) to provide a secure, private, and offline-first experience for handling sensitive quotation data. Users can upload documents (PDFs, images), have the app extract key information like line items, prices, and suppliers, and then query this extracted database using natural language. ## Key Features 1. **Secure Document Upload:** Upload quotation documents directly through a drag-and-drop interface or file selector. Supported formats include PDF, JPG, PNG, and WEBP. 2. **Offline Local Processing:** * All document processing, including Optical Character Recognition (OCR) and structured data extraction (identifying items, quantities, prices), happens **entirely on the user's device** using Web Workers. * No documents or extracted data are ever sent to an external server, ensuring complete privacy. * Uses local ML models (like Transformers.js or Tesseract.js via WebAssembly) for processing. 3. **Local Data Storage:** * Uploaded documents (as data URLs or file references) and all extracted quotation items are stored persistently in the browser's IndexedDB. * Data remains available even when offline. 4. **Dashboard Overview:** * Provides key statistics like the total number of documents processed, total extracted line items, average extraction confidence score, and estimated local storage usage. * Quick access to upload functionality and recent uploads status. 5. **Item Database:** * View all extracted quotation items in a comprehensive table. * Search, filter (by text, supplier, price range), and sort extracted items. * View items specific to a single uploaded document. * Export search results to CSV. * Delete items based on search criteria. 6. **RAG Assistant (Local Querying):** * Ask natural language questions about the extracted quotation data (e.g., "What are the average prices for Dell laptops?", "Find cheapest office chairs"). * Uses a local vector index (built using a simplified TF-IDF approach) for semantic search relevance. * Generates answers based on the retrieved relevant items directly in the browser. * Provides source items used to generate the answer. * Ability to rebuild the local vector index after adding new documents. 7. **Responsive UI:** Adapts to different screen sizes (desktop and mobile) with a collapsible sidebar and mobile navigation. 8. **Status Tracking:** Monitor the progress of uploads and background processing tasks (queued, processing, completed, failed). Ability to retry failed processing attempts. 9. **Data Management:** Option to clear all locally stored documents and extracted data from the IndexedDB database. ## How It Works 1. **Upload:** User uploads documents via the UI. 2. **Storage:** The document file (or a reference/data URL) is stored locally in IndexedDB. A processing job is added to a queue. 3. **Processing (Worker):** A Web Worker picks up the job. It uses an OCR/document analysis library (like Transformers.js or Tesseract.js loaded locally) to extract text and structure. 4. **Extraction:** The worker attempts to identify and extract relevant quotation line items (item name, quantity, price, supplier, etc.). 5. **Update Storage:** Extracted items and updated document metadata (processing status, supplier name, etc.) are saved back to IndexedDB. 6. **Vector Indexing (Optional):** Extracted item text is processed, vectorized (using TF-IDF), and stored in the vector index within IndexedDB for RAG querying. The user can trigger a rebuild of this index. 7. **Querying:** When a user asks a question via the RAG Assistant: * The query is vectorized. * The local vector index is searched for relevant items based on cosine similarity. * A simple generation logic synthesizes an answer based on the retrieved items. * The answer and source items are displayed. 8. **Viewing:** The Item Database page directly queries IndexedDB based on user filters and displays the results. ## Privacy **Privacy is a core design principle.** All user data, including uploaded documents and extracted information, remains strictly on the user's local device and is managed via the browser's IndexedDB. No data is transmitted to external servers during upload, processing, or querying.
|