Add 3 files
Browse files- README.md +6 -4
- index.html +1673 -19
- prompts.txt +2 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: green
|
| 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: snapeidit
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: gray
|
| 5 |
colorTo: green
|
| 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,1673 @@
|
|
| 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>SnapEdit Pro - AI-Powered Screenshot Editor</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 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
| 10 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
| 11 |
+
<style>
|
| 12 |
+
.tooltip {
|
| 13 |
+
position: relative;
|
| 14 |
+
display: inline-block;
|
| 15 |
+
}
|
| 16 |
+
.tooltip .tooltiptext {
|
| 17 |
+
visibility: hidden;
|
| 18 |
+
width: 120px;
|
| 19 |
+
background-color: #555;
|
| 20 |
+
color: #fff;
|
| 21 |
+
text-align: center;
|
| 22 |
+
border-radius: 6px;
|
| 23 |
+
padding: 5px;
|
| 24 |
+
position: absolute;
|
| 25 |
+
z-index: 1;
|
| 26 |
+
bottom: 125%;
|
| 27 |
+
left: 50%;
|
| 28 |
+
margin-left: -60px;
|
| 29 |
+
opacity: 0;
|
| 30 |
+
transition: opacity 0.3s;
|
| 31 |
+
}
|
| 32 |
+
.tooltip:hover .tooltiptext {
|
| 33 |
+
visibility: visible;
|
| 34 |
+
opacity: 1;
|
| 35 |
+
}
|
| 36 |
+
.canvas-container {
|
| 37 |
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
| 38 |
+
}
|
| 39 |
+
.sidebar {
|
| 40 |
+
transition: all 0.3s ease;
|
| 41 |
+
}
|
| 42 |
+
.ai-processing {
|
| 43 |
+
animation: pulse 2s infinite;
|
| 44 |
+
}
|
| 45 |
+
@keyframes pulse {
|
| 46 |
+
0% { opacity: 0.6; }
|
| 47 |
+
50% { opacity: 1; }
|
| 48 |
+
100% { opacity: 0.6; }
|
| 49 |
+
}
|
| 50 |
+
.blur-effect {
|
| 51 |
+
filter: url(#blur-filter);
|
| 52 |
+
}
|
| 53 |
+
</style>
|
| 54 |
+
</head>
|
| 55 |
+
<body class="bg-gray-50 font-sans">
|
| 56 |
+
<div class="flex flex-col h-screen">
|
| 57 |
+
<!-- Top Navigation -->
|
| 58 |
+
<header class="bg-indigo-600 text-white shadow-md">
|
| 59 |
+
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
|
| 60 |
+
<div class="flex items-center space-x-2">
|
| 61 |
+
<i class="fas fa-camera-retro text-2xl"></i>
|
| 62 |
+
<h1 class="text-xl font-bold">SnapEdit Pro</h1>
|
| 63 |
+
</div>
|
| 64 |
+
<div class="flex items-center space-x-4">
|
| 65 |
+
<button id="apiKeyBtn" class="px-4 py-2 bg-indigo-700 rounded-md font-medium hover:bg-indigo-800 transition">
|
| 66 |
+
<i class="fas fa-key mr-2"></i>API Key
|
| 67 |
+
</button>
|
| 68 |
+
<button id="saveBtn" class="px-4 py-2 bg-white text-indigo-600 rounded-md font-medium hover:bg-gray-100 transition">
|
| 69 |
+
<i class="fas fa-save mr-2"></i>Save
|
| 70 |
+
</button>
|
| 71 |
+
<button id="exportBtn" class="px-4 py-2 bg-indigo-700 rounded-md font-medium hover:bg-indigo-800 transition">
|
| 72 |
+
<i class="fas fa-file-export mr-2"></i>Export
|
| 73 |
+
</button>
|
| 74 |
+
<div class="relative">
|
| 75 |
+
<img src="https://via.placeholder.com/40" alt="User" class="rounded-full w-10 h-10 cursor-pointer" id="userMenuBtn">
|
| 76 |
+
<div id="userMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
|
| 77 |
+
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Profile</a>
|
| 78 |
+
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Settings</a>
|
| 79 |
+
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Sign out</a>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
</header>
|
| 85 |
+
|
| 86 |
+
<div class="flex flex-1 overflow-hidden">
|
| 87 |
+
<!-- Left Sidebar - Tools -->
|
| 88 |
+
<div class="sidebar w-16 bg-white shadow-md flex flex-col items-center py-4 space-y-6">
|
| 89 |
+
<div class="tooltip">
|
| 90 |
+
<button id="uploadBtn" class="p-2 rounded-full hover:bg-gray-100 text-indigo-600">
|
| 91 |
+
<i class="fas fa-upload text-xl"></i>
|
| 92 |
+
</button>
|
| 93 |
+
<span class="tooltiptext">Upload Image</span>
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
<div class="border-t border-gray-200 w-8"></div>
|
| 97 |
+
|
| 98 |
+
<div class="tooltip">
|
| 99 |
+
<button id="selectTool" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 100 |
+
<i class="fas fa-mouse-pointer text-xl"></i>
|
| 101 |
+
</button>
|
| 102 |
+
<span class="tooltiptext">Select Tool</span>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div class="tooltip">
|
| 106 |
+
<button id="textTool" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 107 |
+
<i class="fas fa-font text-xl"></i>
|
| 108 |
+
</button>
|
| 109 |
+
<span class="tooltiptext">Text Tool</span>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div class="tooltip">
|
| 113 |
+
<button id="drawTool" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 114 |
+
<i class="fas fa-pencil-alt text-xl"></i>
|
| 115 |
+
</button>
|
| 116 |
+
<span class="tooltiptext">Draw Tool</span>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<div class="tooltip">
|
| 120 |
+
<button id="shapeTool" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 121 |
+
<i class="fas fa-square text-xl"></i>
|
| 122 |
+
</button>
|
| 123 |
+
<span class="tooltiptext">Shapes</span>
|
| 124 |
+
</div>
|
| 125 |
+
|
| 126 |
+
<div class="tooltip">
|
| 127 |
+
<button id="blurTool" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 128 |
+
<i class="fas fa-eye-slash text-xl"></i>
|
| 129 |
+
</button>
|
| 130 |
+
<span class="tooltiptext">Blur Tool</span>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
<div class="border-t border-gray-200 w-8"></div>
|
| 134 |
+
|
| 135 |
+
<div class="tooltip">
|
| 136 |
+
<button id="aiRemoveBg" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 137 |
+
<i class="fas fa-robot text-xl"></i>
|
| 138 |
+
</button>
|
| 139 |
+
<span class="tooltiptext">AI Background Remove</span>
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<div class="tooltip">
|
| 143 |
+
<button id="aiEnhance" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 144 |
+
<i class="fas fa-magic text-xl"></i>
|
| 145 |
+
</button>
|
| 146 |
+
<span class="tooltiptext">AI Enhance</span>
|
| 147 |
+
</div>
|
| 148 |
+
|
| 149 |
+
<div class="tooltip">
|
| 150 |
+
<button id="aiTextRecognition" class="p-2 rounded-full hover:bg-gray-100 text-gray-700">
|
| 151 |
+
<i class="fas fa-text-height text-xl"></i>
|
| 152 |
+
</button>
|
| 153 |
+
<span class="tooltiptext">OCR Text Recognition</span>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
<!-- Main Canvas Area -->
|
| 158 |
+
<div class="flex-1 flex flex-col overflow-auto bg-gray-100 p-4">
|
| 159 |
+
<div class="flex justify-between items-center mb-4">
|
| 160 |
+
<div class="flex space-x-2">
|
| 161 |
+
<div class="relative">
|
| 162 |
+
<button id="zoomIn" class="px-3 py-1 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50">
|
| 163 |
+
<i class="fas fa-search-plus"></i>
|
| 164 |
+
</button>
|
| 165 |
+
<button id="zoomOut" class="px-3 py-1 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50">
|
| 166 |
+
<i class="fas fa-search-minus"></i>
|
| 167 |
+
</button>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="relative">
|
| 170 |
+
<button id="undoBtn" class="px-3 py-1 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50">
|
| 171 |
+
<i class="fas fa-undo"></i>
|
| 172 |
+
</button>
|
| 173 |
+
<button id="redoBtn" class="px-3 py-1 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50">
|
| 174 |
+
<i class="fas fa-redo"></i>
|
| 175 |
+
</button>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
<div class="flex items-center space-x-4">
|
| 179 |
+
<div id="aiProcessing" class="hidden items-center space-x-2 text-indigo-600 ai-processing">
|
| 180 |
+
<i class="fas fa-cog fa-spin"></i>
|
| 181 |
+
<span>AI Processing...</span>
|
| 182 |
+
</div>
|
| 183 |
+
<div class="text-sm text-gray-500">
|
| 184 |
+
<span id="imageInfo">No image loaded</span>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
<div class="flex-1 flex justify-center items-center overflow-auto">
|
| 190 |
+
<div id="canvas-container" class="canvas-container bg-white shadow-lg">
|
| 191 |
+
<canvas id="editorCanvas" width="800" height="600"></canvas>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
|
| 196 |
+
<!-- Right Sidebar - Properties -->
|
| 197 |
+
<div class="sidebar w-64 bg-white shadow-md p-4 overflow-y-auto">
|
| 198 |
+
<h2 class="font-bold text-lg mb-4 text-gray-700">Properties</h2>
|
| 199 |
+
|
| 200 |
+
<div id="generalProps" class="mb-6">
|
| 201 |
+
<h3 class="font-medium text-gray-600 mb-2">Canvas</h3>
|
| 202 |
+
<div class="space-y-3">
|
| 203 |
+
<div>
|
| 204 |
+
<label class="block text-sm text-gray-500 mb-1">Background</label>
|
| 205 |
+
<input type="color" id="canvasBg" value="#ffffff" class="w-full h-8">
|
| 206 |
+
</div>
|
| 207 |
+
<div>
|
| 208 |
+
<label class="block text-sm text-gray-500 mb-1">Opacity</label>
|
| 209 |
+
<input type="range" id="canvasOpacity" min="0" max="100" value="100" class="w-full">
|
| 210 |
+
</div>
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
|
| 214 |
+
<div id="textProps" class="mb-6 hidden">
|
| 215 |
+
<h3 class="font-medium text-gray-600 mb-2">Text</h3>
|
| 216 |
+
<div class="space-y-3">
|
| 217 |
+
<div>
|
| 218 |
+
<label class="block text-sm text-gray-500 mb-1">Content</label>
|
| 219 |
+
<textarea id="textContent" class="w-full border border-gray-300 rounded p-2 text-sm" rows="2"></textarea>
|
| 220 |
+
</div>
|
| 221 |
+
<div>
|
| 222 |
+
<label class="block text-sm text-gray-500 mb-1">Font</label>
|
| 223 |
+
<select id="textFont" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 224 |
+
<option value="Arial">Arial</option>
|
| 225 |
+
<option value="Verdana">Verdana</option>
|
| 226 |
+
<option value="Helvetica">Helvetica</option>
|
| 227 |
+
<option value="Times New Roman">Times New Roman</option>
|
| 228 |
+
<option value="Courier New">Courier New</option>
|
| 229 |
+
</select>
|
| 230 |
+
</div>
|
| 231 |
+
<div>
|
| 232 |
+
<label class="block text-sm text-gray-500 mb-1">Size</label>
|
| 233 |
+
<input type="range" id="textSize" min="8" max="72" value="16" class="w-full">
|
| 234 |
+
</div>
|
| 235 |
+
<div>
|
| 236 |
+
<label class="block text-sm text-gray-500 mb-1">Color</label>
|
| 237 |
+
<input type="color" id="textColor" value="#000000" class="w-full h-8">
|
| 238 |
+
</div>
|
| 239 |
+
<div>
|
| 240 |
+
<label class="block text-sm text-gray-500 mb-1">Background</label>
|
| 241 |
+
<input type="color" id="textBgColor" value="transparent" class="w-full h-8">
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
<div id="drawProps" class="mb-6 hidden">
|
| 247 |
+
<h3 class="font-medium text-gray-600 mb-2">Drawing</h3>
|
| 248 |
+
<div class="space-y-3">
|
| 249 |
+
<div>
|
| 250 |
+
<label class="block text-sm text-gray-500 mb-1">Brush Size</label>
|
| 251 |
+
<input type="range" id="brushSize" min="1" max="50" value="5" class="w-full">
|
| 252 |
+
</div>
|
| 253 |
+
<div>
|
| 254 |
+
<label class="block text-sm text-gray-500 mb-1">Color</label>
|
| 255 |
+
<input type="color" id="brushColor" value="#000000" class="w-full h-8">
|
| 256 |
+
</div>
|
| 257 |
+
<div>
|
| 258 |
+
<label class="block text-sm text-gray-500 mb-1">Opacity</label>
|
| 259 |
+
<input type="range" id="brushOpacity" min="0" max="100" value="100" class="w-full">
|
| 260 |
+
</div>
|
| 261 |
+
<div>
|
| 262 |
+
<label class="block text-sm text-gray-500 mb-1">Brush Type</label>
|
| 263 |
+
<select id="brushType" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 264 |
+
<option value="pencil">Pencil</option>
|
| 265 |
+
<option value="circle">Circle</option>
|
| 266 |
+
<option value="spray">Spray</option>
|
| 267 |
+
<option value="pattern">Pattern</option>
|
| 268 |
+
</select>
|
| 269 |
+
</div>
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
+
|
| 273 |
+
<div id="shapeProps" class="mb-6 hidden">
|
| 274 |
+
<h3 class="font-medium text-gray-600 mb-2">Shape</h3>
|
| 275 |
+
<div class="space-y-3">
|
| 276 |
+
<div>
|
| 277 |
+
<label class="block text-sm text-gray-500 mb-1">Type</label>
|
| 278 |
+
<select id="shapeType" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 279 |
+
<option value="rect">Rectangle</option>
|
| 280 |
+
<option value="circle">Circle</option>
|
| 281 |
+
<option value="triangle">Triangle</option>
|
| 282 |
+
<option value="line">Line</option>
|
| 283 |
+
<option value="polygon">Polygon</option>
|
| 284 |
+
</select>
|
| 285 |
+
</div>
|
| 286 |
+
<div>
|
| 287 |
+
<label class="block text-sm text-gray-500 mb-1">Fill Color</label>
|
| 288 |
+
<input type="color" id="shapeFill" value="#000000" class="w-full h-8">
|
| 289 |
+
</div>
|
| 290 |
+
<div>
|
| 291 |
+
<label class="block text-sm text-gray-500 mb-1">Stroke Color</label>
|
| 292 |
+
<input type="color" id="shapeStroke" value="#000000" class="w-full h-8">
|
| 293 |
+
</div>
|
| 294 |
+
<div>
|
| 295 |
+
<label class="block text-sm text-gray-500 mb-1">Stroke Width</label>
|
| 296 |
+
<input type="range" id="shapeStrokeWidth" min="1" max="20" value="1" class="w-full">
|
| 297 |
+
</div>
|
| 298 |
+
<div id="polygonSidesContainer" class="hidden">
|
| 299 |
+
<label class="block text-sm text-gray-500 mb-1">Sides</label>
|
| 300 |
+
<input type="number" id="polygonSides" min="3" max="12" value="5" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 301 |
+
</div>
|
| 302 |
+
</div>
|
| 303 |
+
</div>
|
| 304 |
+
|
| 305 |
+
<div id="blurProps" class="mb-6 hidden">
|
| 306 |
+
<h3 class="font-medium text-gray-600 mb-2">Blur</h3>
|
| 307 |
+
<div class="space-y-3">
|
| 308 |
+
<div>
|
| 309 |
+
<label class="block text-sm text-gray-500 mb-1">Intensity</label>
|
| 310 |
+
<input type="range" id="blurIntensity" min="0" max="20" value="5" class="w-full">
|
| 311 |
+
</div>
|
| 312 |
+
<div>
|
| 313 |
+
<label class="block text-sm text-gray-500 mb-1">Radius</label>
|
| 314 |
+
<input type="range" id="blurRadius" min="10" max="200" value="50" class="w-full">
|
| 315 |
+
</div>
|
| 316 |
+
<div>
|
| 317 |
+
<label class="block text-sm text-gray-500 mb-1">Type</label>
|
| 318 |
+
<select id="blurType" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 319 |
+
<option value="gaussian">Gaussian</option>
|
| 320 |
+
<option value="pixelate">Pixelate</option>
|
| 321 |
+
<option value="mask">Mask</option>
|
| 322 |
+
</select>
|
| 323 |
+
</div>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div id="aiProps" class="mb-6 hidden">
|
| 328 |
+
<h3 class="font-medium text-gray-600 mb-2">AI Tools</h3>
|
| 329 |
+
<div class="space-y-3">
|
| 330 |
+
<div id="apiKeyStatus" class="text-xs p-2 rounded bg-gray-100 text-gray-600">
|
| 331 |
+
<i class="fas fa-circle text-red-500 mr-1"></i> No API Key
|
| 332 |
+
</div>
|
| 333 |
+
<div>
|
| 334 |
+
<label class="block text-sm text-gray-500 mb-1">Enhance Mode</label>
|
| 335 |
+
<select id="aiEnhanceMode" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 336 |
+
<option value="auto">Auto Enhance</option>
|
| 337 |
+
<option value="sharpness">Sharpness</option>
|
| 338 |
+
<option value="contrast">Contrast</option>
|
| 339 |
+
<option value="color">Color Boost</option>
|
| 340 |
+
<option value="denoise">Denoise</option>
|
| 341 |
+
</select>
|
| 342 |
+
</div>
|
| 343 |
+
<div>
|
| 344 |
+
<label class="block text-sm text-gray-500 mb-1">OCR Language</label>
|
| 345 |
+
<select id="ocrLanguage" class="w-full border border-gray-300 rounded p-2 text-sm">
|
| 346 |
+
<option value="eng">English</option>
|
| 347 |
+
<option value="spa">Spanish</option>
|
| 348 |
+
<option value="fra">French</option>
|
| 349 |
+
<option value="deu">German</option>
|
| 350 |
+
<option value="chi_sim">Chinese</option>
|
| 351 |
+
</select>
|
| 352 |
+
</div>
|
| 353 |
+
<button id="runAi" class="w-full bg-indigo-600 text-white py-2 rounded hover:bg-indigo-700 transition">
|
| 354 |
+
Apply AI Processing
|
| 355 |
+
</button>
|
| 356 |
+
</div>
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
|
| 362 |
+
<!-- Modals -->
|
| 363 |
+
<div id="uploadModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 364 |
+
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
| 365 |
+
<div class="flex justify-between items-center mb-4">
|
| 366 |
+
<h3 class="text-lg font-bold">Upload Image</h3>
|
| 367 |
+
<button id="closeUploadModal" class="text-gray-500 hover:text-gray-700">
|
| 368 |
+
<i class="fas fa-times"></i>
|
| 369 |
+
</button>
|
| 370 |
+
</div>
|
| 371 |
+
<div class="space-y-4">
|
| 372 |
+
<div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
|
| 373 |
+
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-2"></i>
|
| 374 |
+
<p class="text-gray-600 mb-4">Drag & drop your image here or click to browse</p>
|
| 375 |
+
<input type="file" id="fileInput" class="hidden" accept="image/*">
|
| 376 |
+
<button id="browseBtn" class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition">
|
| 377 |
+
Browse Files
|
| 378 |
+
</button>
|
| 379 |
+
</div>
|
| 380 |
+
<div class="text-center text-sm text-gray-500">
|
| 381 |
+
<p>Supports: JPG, PNG, GIF (Max 10MB)</p>
|
| 382 |
+
</div>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
|
| 387 |
+
<div id="exportModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 388 |
+
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
| 389 |
+
<div class="flex justify-between items-center mb-4">
|
| 390 |
+
<h3 class="text-lg font-bold">Export Options</h3>
|
| 391 |
+
<button id="closeExportModal" class="text-gray-500 hover:text-gray-700">
|
| 392 |
+
<i class="fas fa-times"></i>
|
| 393 |
+
</button>
|
| 394 |
+
</div>
|
| 395 |
+
<div class="space-y-4">
|
| 396 |
+
<div>
|
| 397 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Format</label>
|
| 398 |
+
<select id="exportFormat" class="w-full border border-gray-300 rounded p-2">
|
| 399 |
+
<option value="png">PNG</option>
|
| 400 |
+
<option value="jpg">JPG</option>
|
| 401 |
+
<option value="webp">WebP</option>
|
| 402 |
+
<option value="pdf">PDF</option>
|
| 403 |
+
</select>
|
| 404 |
+
</div>
|
| 405 |
+
<div id="qualityContainer" class="hidden">
|
| 406 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Quality (%)</label>
|
| 407 |
+
<input type="range" id="exportQuality" min="1" max="100" value="90" class="w-full">
|
| 408 |
+
</div>
|
| 409 |
+
<div>
|
| 410 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Size</label>
|
| 411 |
+
<select id="exportSize" class="w-full border border-gray-300 rounded p-2">
|
| 412 |
+
<option value="original">Original Size</option>
|
| 413 |
+
<option value="1024">1024px (width)</option>
|
| 414 |
+
<option value="800">800px (width)</option>
|
| 415 |
+
<option value="640">640px (width)</option>
|
| 416 |
+
</select>
|
| 417 |
+
</div>
|
| 418 |
+
<div class="flex space-x-2 pt-4">
|
| 419 |
+
<button id="downloadBtn" class="flex-1 bg-indigo-600 text-white py-2 rounded hover:bg-indigo-700 transition">
|
| 420 |
+
<i class="fas fa-download mr-2"></i>Download
|
| 421 |
+
</button>
|
| 422 |
+
<button id="copyToClipboardBtn" class="flex-1 bg-gray-200 text-gray-700 py-2 rounded hover:bg-gray-300 transition">
|
| 423 |
+
<i class="fas fa-copy mr-2"></i>Copy
|
| 424 |
+
</button>
|
| 425 |
+
</div>
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
|
| 430 |
+
<div id="apiKeyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
| 431 |
+
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
| 432 |
+
<div class="flex justify-between items-center mb-4">
|
| 433 |
+
<h3 class="text-lg font-bold">DeepSeek API Key</h3>
|
| 434 |
+
<button id="closeApiKeyModal" class="text-gray-500 hover:text-gray-700">
|
| 435 |
+
<i class="fas fa-times"></i>
|
| 436 |
+
</button>
|
| 437 |
+
</div>
|
| 438 |
+
<div class="space-y-4">
|
| 439 |
+
<div>
|
| 440 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Enter your DeepSeek API Key</label>
|
| 441 |
+
<input type="password" id="apiKeyInput" class="w-full border border-gray-300 rounded p-2" placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxx">
|
| 442 |
+
</div>
|
| 443 |
+
<div class="text-xs text-gray-500">
|
| 444 |
+
<p>Your API key is encrypted and stored locally in your browser. We never send it to our servers.</p>
|
| 445 |
+
</div>
|
| 446 |
+
<div class="flex space-x-2 pt-2">
|
| 447 |
+
<button id="saveApiKeyBtn" class="flex-1 bg-indigo-600 text-white py-2 rounded hover:bg-indigo-700 transition">
|
| 448 |
+
<i class="fas fa-save mr-2"></i>Save Key
|
| 449 |
+
</button>
|
| 450 |
+
<button id="clearApiKeyBtn" class="flex-1 bg-gray-200 text-gray-700 py-2 rounded hover:bg-gray-300 transition">
|
| 451 |
+
<i class="fas fa-trash mr-2"></i>Clear Key
|
| 452 |
+
</button>
|
| 453 |
+
</div>
|
| 454 |
+
</div>
|
| 455 |
+
</div>
|
| 456 |
+
</div>
|
| 457 |
+
|
| 458 |
+
<!-- SVG Filter for Blur Effect -->
|
| 459 |
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" class="hidden">
|
| 460 |
+
<defs>
|
| 461 |
+
<filter id="blur-filter">
|
| 462 |
+
<feGaussianBlur stdDeviation="5" />
|
| 463 |
+
</filter>
|
| 464 |
+
</defs>
|
| 465 |
+
</svg>
|
| 466 |
+
|
| 467 |
+
<script>
|
| 468 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 469 |
+
// Initialize Fabric.js canvas with improved settings
|
| 470 |
+
const canvas = new fabric.Canvas('editorCanvas', {
|
| 471 |
+
backgroundColor: '#ffffff',
|
| 472 |
+
preserveObjectStacking: true,
|
| 473 |
+
selection: true,
|
| 474 |
+
selectionColor: 'rgba(99, 102, 241, 0.3)',
|
| 475 |
+
selectionBorderColor: '#6366f1',
|
| 476 |
+
selectionLineWidth: 1,
|
| 477 |
+
selectionDashArray: [5, 5],
|
| 478 |
+
allowTouchScrolling: true,
|
| 479 |
+
enableRetinaScaling: true
|
| 480 |
+
});
|
| 481 |
+
|
| 482 |
+
// State management with improved history
|
| 483 |
+
let currentTool = null;
|
| 484 |
+
let activeObject = null;
|
| 485 |
+
let history = [];
|
| 486 |
+
let historyIndex = -1;
|
| 487 |
+
let isDrawing = false;
|
| 488 |
+
let drawingPath = null;
|
| 489 |
+
let blurArea = null;
|
| 490 |
+
let polygonPoints = [];
|
| 491 |
+
let apiKey = null;
|
| 492 |
+
|
| 493 |
+
// Brush settings
|
| 494 |
+
let brushSettings = {
|
| 495 |
+
size: 5,
|
| 496 |
+
color: '#000000',
|
| 497 |
+
opacity: 1,
|
| 498 |
+
type: 'pencil'
|
| 499 |
+
};
|
| 500 |
+
|
| 501 |
+
// Load API key if exists
|
| 502 |
+
loadApiKey();
|
| 503 |
+
|
| 504 |
+
// UI Elements
|
| 505 |
+
const uploadModal = document.getElementById('uploadModal');
|
| 506 |
+
const exportModal = document.getElementById('exportModal');
|
| 507 |
+
const apiKeyModal = document.getElementById('apiKeyModal');
|
| 508 |
+
const fileInput = document.getElementById('fileInput');
|
| 509 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 510 |
+
const uploadBtn = document.getElementById('uploadBtn');
|
| 511 |
+
const closeUploadModal = document.getElementById('closeUploadModal');
|
| 512 |
+
const closeExportModal = document.getElementById('closeExportModal');
|
| 513 |
+
const closeApiKeyModal = document.getElementById('closeApiKeyModal');
|
| 514 |
+
const saveBtn = document.getElementById('saveBtn');
|
| 515 |
+
const exportBtn = document.getElementById('exportBtn');
|
| 516 |
+
const apiKeyBtn = document.getElementById('apiKeyBtn');
|
| 517 |
+
const saveApiKeyBtn = document.getElementById('saveApiKeyBtn');
|
| 518 |
+
const clearApiKeyBtn = document.getElementById('clearApiKeyBtn');
|
| 519 |
+
const apiKeyInput = document.getElementById('apiKeyInput');
|
| 520 |
+
const apiKeyStatus = document.getElementById('apiKeyStatus');
|
| 521 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 522 |
+
const copyToClipboardBtn = document.getElementById('copyToClipboardBtn');
|
| 523 |
+
const exportFormat = document.getElementById('exportFormat');
|
| 524 |
+
const exportQuality = document.getElementById('exportQuality');
|
| 525 |
+
const exportSize = document.getElementById('exportSize');
|
| 526 |
+
const qualityContainer = document.getElementById('qualityContainer');
|
| 527 |
+
const aiProcessing = document.getElementById('aiProcessing');
|
| 528 |
+
const imageInfo = document.getElementById('imageInfo');
|
| 529 |
+
|
| 530 |
+
// Tool buttons
|
| 531 |
+
const selectTool = document.getElementById('selectTool');
|
| 532 |
+
const textTool = document.getElementById('textTool');
|
| 533 |
+
const drawTool = document.getElementById('drawTool');
|
| 534 |
+
const shapeTool = document.getElementById('shapeTool');
|
| 535 |
+
const blurTool = document.getElementById('blurTool');
|
| 536 |
+
const aiRemoveBg = document.getElementById('aiRemoveBg');
|
| 537 |
+
const aiEnhance = document.getElementById('aiEnhance');
|
| 538 |
+
const aiTextRecognition = document.getElementById('aiTextRecognition');
|
| 539 |
+
const runAi = document.getElementById('runAi');
|
| 540 |
+
|
| 541 |
+
// Property panels
|
| 542 |
+
const generalProps = document.getElementById('generalProps');
|
| 543 |
+
const textProps = document.getElementById('textProps');
|
| 544 |
+
const drawProps = document.getElementById('drawProps');
|
| 545 |
+
const shapeProps = document.getElementById('shapeProps');
|
| 546 |
+
const blurProps = document.getElementById('blurProps');
|
| 547 |
+
const aiProps = document.getElementById('aiProps');
|
| 548 |
+
const polygonSidesContainer = document.getElementById('polygonSidesContainer');
|
| 549 |
+
|
| 550 |
+
// Property controls
|
| 551 |
+
const canvasBg = document.getElementById('canvasBg');
|
| 552 |
+
const canvasOpacity = document.getElementById('canvasOpacity');
|
| 553 |
+
const textContent = document.getElementById('textContent');
|
| 554 |
+
const textFont = document.getElementById('textFont');
|
| 555 |
+
const textSize = document.getElementById('textSize');
|
| 556 |
+
const textColor = document.getElementById('textColor');
|
| 557 |
+
const textBgColor = document.getElementById('textBgColor');
|
| 558 |
+
const brushSize = document.getElementById('brushSize');
|
| 559 |
+
const brushColor = document.getElementById('brushColor');
|
| 560 |
+
const brushOpacity = document.getElementById('brushOpacity');
|
| 561 |
+
const brushType = document.getElementById('brushType');
|
| 562 |
+
const shapeType = document.getElementById('shapeType');
|
| 563 |
+
const shapeFill = document.getElementById('shapeFill');
|
| 564 |
+
const shapeStroke = document.getElementById('shapeStroke');
|
| 565 |
+
const shapeStrokeWidth = document.getElementById('shapeStrokeWidth');
|
| 566 |
+
const polygonSides = document.getElementById('polygonSides');
|
| 567 |
+
const blurIntensity = document.getElementById('blurIntensity');
|
| 568 |
+
const blurRadius = document.getElementById('blurRadius');
|
| 569 |
+
const blurType = document.getElementById('blurType');
|
| 570 |
+
const aiEnhanceMode = document.getElementById('aiEnhanceMode');
|
| 571 |
+
const ocrLanguage = document.getElementById('ocrLanguage');
|
| 572 |
+
|
| 573 |
+
// Event Listeners
|
| 574 |
+
uploadBtn.addEventListener('click', () => uploadModal.classList.remove('hidden'));
|
| 575 |
+
closeUploadModal.addEventListener('click', () => uploadModal.classList.add('hidden'));
|
| 576 |
+
browseBtn.addEventListener('click', () => fileInput.click());
|
| 577 |
+
exportBtn.addEventListener('click', () => exportModal.classList.remove('hidden'));
|
| 578 |
+
closeExportModal.addEventListener('click', () => exportModal.classList.add('hidden'));
|
| 579 |
+
apiKeyBtn.addEventListener('click', () => apiKeyModal.classList.remove('hidden'));
|
| 580 |
+
closeApiKeyModal.addEventListener('click', () => apiKeyModal.classList.add('hidden'));
|
| 581 |
+
saveBtn.addEventListener('click', saveCanvasState);
|
| 582 |
+
downloadBtn.addEventListener('click', downloadCanvas);
|
| 583 |
+
copyToClipboardBtn.addEventListener('click', copyToClipboard);
|
| 584 |
+
saveApiKeyBtn.addEventListener('click', saveApiKey);
|
| 585 |
+
clearApiKeyBtn.addEventListener('click', clearApiKey);
|
| 586 |
+
|
| 587 |
+
fileInput.addEventListener('change', handleFileUpload);
|
| 588 |
+
exportFormat.addEventListener('change', toggleQualityControl);
|
| 589 |
+
shapeType.addEventListener('change', togglePolygonOptions);
|
| 590 |
+
|
| 591 |
+
// Tool selection
|
| 592 |
+
selectTool.addEventListener('click', () => setActiveTool('select'));
|
| 593 |
+
textTool.addEventListener('click', () => setActiveTool('text'));
|
| 594 |
+
drawTool.addEventListener('click', () => setActiveTool('draw'));
|
| 595 |
+
shapeTool.addEventListener('click', () => setActiveTool('shape'));
|
| 596 |
+
blurTool.addEventListener('click', () => setActiveTool('blur'));
|
| 597 |
+
aiRemoveBg.addEventListener('click', () => setActiveTool('aiRemoveBg'));
|
| 598 |
+
aiEnhance.addEventListener('click', () => setActiveTool('aiEnhance'));
|
| 599 |
+
aiTextRecognition.addEventListener('click', () => setActiveTool('aiTextRecognition'));
|
| 600 |
+
runAi.addEventListener('click', runAiProcessing);
|
| 601 |
+
|
| 602 |
+
// Canvas events
|
| 603 |
+
canvas.on('object:added', saveHistory);
|
| 604 |
+
canvas.on('object:modified', saveHistory);
|
| 605 |
+
canvas.on('object:removed', saveHistory);
|
| 606 |
+
canvas.on('selection:created', handleSelection);
|
| 607 |
+
canvas.on('selection:updated', handleSelection);
|
| 608 |
+
canvas.on('selection:cleared', clearSelection);
|
| 609 |
+
canvas.on('path:created', function(e) {
|
| 610 |
+
if (e.path && e.path.selectable) {
|
| 611 |
+
canvas.setActiveObject(e.path);
|
| 612 |
+
}
|
| 613 |
+
});
|
| 614 |
+
|
| 615 |
+
// Property changes
|
| 616 |
+
canvasBg.addEventListener('change', updateCanvasBackground);
|
| 617 |
+
canvasOpacity.addEventListener('input', updateCanvasOpacity);
|
| 618 |
+
textContent.addEventListener('input', updateTextContent);
|
| 619 |
+
textFont.addEventListener('change', updateTextFont);
|
| 620 |
+
textSize.addEventListener('input', updateTextSize);
|
| 621 |
+
textColor.addEventListener('change', updateTextColor);
|
| 622 |
+
textBgColor.addEventListener('change', updateTextBgColor);
|
| 623 |
+
brushSize.addEventListener('input', updateBrushSize);
|
| 624 |
+
brushColor.addEventListener('change', updateBrushColor);
|
| 625 |
+
brushOpacity.addEventListener('input', updateBrushOpacity);
|
| 626 |
+
brushType.addEventListener('change', updateBrushType);
|
| 627 |
+
shapeType.addEventListener('change', updateShapeType);
|
| 628 |
+
shapeFill.addEventListener('change', updateShapeFill);
|
| 629 |
+
shapeStroke.addEventListener('change', updateShapeStroke);
|
| 630 |
+
shapeStrokeWidth.addEventListener('input', updateShapeStrokeWidth);
|
| 631 |
+
polygonSides.addEventListener('change', updatePolygonSides);
|
| 632 |
+
blurIntensity.addEventListener('input', updateBlurIntensity);
|
| 633 |
+
blurRadius.addEventListener('input', updateBlurRadius);
|
| 634 |
+
blurType.addEventListener('change', updateBlurType);
|
| 635 |
+
|
| 636 |
+
// Keyboard shortcuts
|
| 637 |
+
document.addEventListener('keydown', function(e) {
|
| 638 |
+
// Undo: Ctrl+Z
|
| 639 |
+
if (e.ctrlKey && e.key === 'z') {
|
| 640 |
+
undo();
|
| 641 |
+
e.preventDefault();
|
| 642 |
+
}
|
| 643 |
+
// Redo: Ctrl+Y or Ctrl+Shift+Z
|
| 644 |
+
if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {
|
| 645 |
+
redo();
|
| 646 |
+
e.preventDefault();
|
| 647 |
+
}
|
| 648 |
+
// Delete key
|
| 649 |
+
if (e.key === 'Delete' && canvas.getActiveObject()) {
|
| 650 |
+
canvas.remove(canvas.getActiveObject());
|
| 651 |
+
saveHistory();
|
| 652 |
+
}
|
| 653 |
+
});
|
| 654 |
+
|
| 655 |
+
// Functions
|
| 656 |
+
function setActiveTool(tool) {
|
| 657 |
+
currentTool = tool;
|
| 658 |
+
resetCanvasEvents();
|
| 659 |
+
|
| 660 |
+
// Update UI
|
| 661 |
+
document.querySelectorAll('.sidebar button').forEach(btn => {
|
| 662 |
+
btn.classList.remove('bg-indigo-100', 'text-indigo-600');
|
| 663 |
+
btn.classList.add('text-gray-700', 'hover:bg-gray-100');
|
| 664 |
+
});
|
| 665 |
+
|
| 666 |
+
// Hide all property panels
|
| 667 |
+
generalProps.classList.add('hidden');
|
| 668 |
+
textProps.classList.add('hidden');
|
| 669 |
+
drawProps.classList.add('hidden');
|
| 670 |
+
shapeProps.classList.add('hidden');
|
| 671 |
+
blurProps.classList.add('hidden');
|
| 672 |
+
aiProps.classList.add('hidden');
|
| 673 |
+
|
| 674 |
+
switch(tool) {
|
| 675 |
+
case 'select':
|
| 676 |
+
document.getElementById('selectTool').classList.add('bg-indigo-100', 'text-indigo-600');
|
| 677 |
+
generalProps.classList.remove('hidden');
|
| 678 |
+
canvas.selection = true;
|
| 679 |
+
canvas.defaultCursor = 'default';
|
| 680 |
+
break;
|
| 681 |
+
case 'text':
|
| 682 |
+
document.getElementById('textTool').classList.add('bg-indigo-100', 'text-indigo-600');
|
| 683 |
+
textProps.classList.remove('hidden');
|
| 684 |
+
canvas.selection = false;
|
| 685 |
+
canvas.defaultCursor = 'text';
|
| 686 |
+
setupTextTool();
|
| 687 |
+
break;
|
| 688 |
+
case 'draw':
|
| 689 |
+
document.getElementById('drawTool').classList.add('bg-indigo-100', 'text-indigo-600');
|
| 690 |
+
drawProps.classList.remove('hidden');
|
| 691 |
+
canvas.selection = false;
|
| 692 |
+
canvas.defaultCursor = 'crosshair';
|
| 693 |
+
setupDrawTool();
|
| 694 |
+
break;
|
| 695 |
+
case 'shape':
|
| 696 |
+
document.getElementById('shapeTool').classList.add('bg-indigo-100', 'text-indigo-600');
|
| 697 |
+
shapeProps.classList.remove('hidden');
|
| 698 |
+
canvas.selection = false;
|
| 699 |
+
canvas.defaultCursor = 'crosshair';
|
| 700 |
+
setupShapeTool();
|
| 701 |
+
break;
|
| 702 |
+
case 'blur':
|
| 703 |
+
document.getElementById('blurTool').classList.add('bg-indigo-100', 'text-indigo-600');
|
| 704 |
+
blurProps.classList.remove('hidden');
|
| 705 |
+
canvas.selection = false;
|
| 706 |
+
canvas.defaultCursor = 'crosshair';
|
| 707 |
+
setupBlurTool();
|
| 708 |
+
break;
|
| 709 |
+
case 'aiRemoveBg':
|
| 710 |
+
case 'aiEnhance':
|
| 711 |
+
case 'aiTextRecognition':
|
| 712 |
+
document.getElementById(tool).classList.add('bg-indigo-100', 'text-indigo-600');
|
| 713 |
+
aiProps.classList.remove('hidden');
|
| 714 |
+
canvas.selection = true;
|
| 715 |
+
canvas.defaultCursor = 'default';
|
| 716 |
+
break;
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
function resetCanvasEvents() {
|
| 721 |
+
canvas.off('mouse:down');
|
| 722 |
+
canvas.off('mouse:move');
|
| 723 |
+
canvas.off('mouse:up');
|
| 724 |
+
canvas.off('object:added');
|
| 725 |
+
isDrawing = false;
|
| 726 |
+
drawingPath = null;
|
| 727 |
+
blurArea = null;
|
| 728 |
+
polygonPoints = [];
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
function setupTextTool() {
|
| 732 |
+
canvas.on('mouse:down', function(options) {
|
| 733 |
+
if (options.target) return;
|
| 734 |
+
|
| 735 |
+
const text = new fabric.IText('Double click to edit', {
|
| 736 |
+
left: options.e.clientX - canvas.upperCanvasEl.getBoundingClientRect().left,
|
| 737 |
+
top: options.e.clientY - canvas.upperCanvasEl.getBoundingClientRect().top,
|
| 738 |
+
fontFamily: textFont.value,
|
| 739 |
+
fontSize: parseInt(textSize.value),
|
| 740 |
+
fill: textColor.value,
|
| 741 |
+
backgroundColor: textBgColor.value === 'transparent' ? '' : textBgColor.value,
|
| 742 |
+
padding: 5,
|
| 743 |
+
selectable: true
|
| 744 |
+
});
|
| 745 |
+
|
| 746 |
+
canvas.add(text);
|
| 747 |
+
canvas.setActiveObject(text);
|
| 748 |
+
text.enterEditing();
|
| 749 |
+
text.selectAll();
|
| 750 |
+
});
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
function setupDrawTool() {
|
| 754 |
+
canvas.on('mouse:down', function(options) {
|
| 755 |
+
isDrawing = true;
|
| 756 |
+
|
| 757 |
+
const pointer = canvas.getPointer(options.e);
|
| 758 |
+
drawingPath = new fabric.Path(`M ${pointer.x} ${pointer.y}`, {
|
| 759 |
+
stroke: brushSettings.color,
|
| 760 |
+
strokeWidth: brushSettings.size,
|
| 761 |
+
fill: 'transparent',
|
| 762 |
+
opacity: brushSettings.opacity,
|
| 763 |
+
selectable: true
|
| 764 |
+
});
|
| 765 |
+
|
| 766 |
+
// Apply brush type
|
| 767 |
+
if (brushSettings.type === 'spray') {
|
| 768 |
+
drawingPath.strokeLineCap = 'round';
|
| 769 |
+
drawingPath.strokeLineJoin = 'round';
|
| 770 |
+
drawingPath.strokeDashArray = [1, brushSettings.size];
|
| 771 |
+
} else if (brushSettings.type === 'pattern') {
|
| 772 |
+
drawingPath.strokeDashArray = [brushSettings.size, brushSettings.size/2];
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
canvas.add(drawingPath);
|
| 776 |
+
});
|
| 777 |
+
|
| 778 |
+
canvas.on('mouse:move', function(options) {
|
| 779 |
+
if (!isDrawing || !drawingPath) return;
|
| 780 |
+
|
| 781 |
+
const pointer = canvas.getPointer(options.e);
|
| 782 |
+
const pathData = drawingPath.path;
|
| 783 |
+
|
| 784 |
+
pathData.push([
|
| 785 |
+
'L',
|
| 786 |
+
pointer.x,
|
| 787 |
+
pointer.y
|
| 788 |
+
]);
|
| 789 |
+
|
| 790 |
+
drawingPath.set({ path: pathData });
|
| 791 |
+
canvas.renderAll();
|
| 792 |
+
});
|
| 793 |
+
|
| 794 |
+
canvas.on('mouse:up', function() {
|
| 795 |
+
isDrawing = false;
|
| 796 |
+
drawingPath = null;
|
| 797 |
+
});
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
function setupShapeTool() {
|
| 801 |
+
let startX, startY, shape;
|
| 802 |
+
|
| 803 |
+
canvas.on('mouse:down', function(options) {
|
| 804 |
+
const pointer = canvas.getPointer(options.e);
|
| 805 |
+
startX = pointer.x;
|
| 806 |
+
startY = pointer.y;
|
| 807 |
+
|
| 808 |
+
if (shapeType.value === 'polygon') {
|
| 809 |
+
if (polygonPoints.length === 0) {
|
| 810 |
+
// First point
|
| 811 |
+
polygonPoints.push({ x: startX, y: startY });
|
| 812 |
+
|
| 813 |
+
// Create a line that will follow the mouse
|
| 814 |
+
shape = new fabric.Line([startX, startY, startX, startY], {
|
| 815 |
+
stroke: shapeStroke.value,
|
| 816 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 817 |
+
fill: shapeFill.value,
|
| 818 |
+
selectable: false
|
| 819 |
+
});
|
| 820 |
+
canvas.add(shape);
|
| 821 |
+
} else {
|
| 822 |
+
// Add new point
|
| 823 |
+
polygonPoints.push({ x: pointer.x, y: pointer.y });
|
| 824 |
+
|
| 825 |
+
// Update the polygon
|
| 826 |
+
updatePolygonShape();
|
| 827 |
+
}
|
| 828 |
+
return;
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
switch(shapeType.value) {
|
| 832 |
+
case 'rect':
|
| 833 |
+
shape = new fabric.Rect({
|
| 834 |
+
left: startX,
|
| 835 |
+
top: startY,
|
| 836 |
+
width: 0,
|
| 837 |
+
height: 0,
|
| 838 |
+
fill: shapeFill.value,
|
| 839 |
+
stroke: shapeStroke.value,
|
| 840 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 841 |
+
selectable: true
|
| 842 |
+
});
|
| 843 |
+
break;
|
| 844 |
+
case 'circle':
|
| 845 |
+
shape = new fabric.Circle({
|
| 846 |
+
left: startX,
|
| 847 |
+
top: startY,
|
| 848 |
+
radius: 0,
|
| 849 |
+
fill: shapeFill.value,
|
| 850 |
+
stroke: shapeStroke.value,
|
| 851 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 852 |
+
selectable: true
|
| 853 |
+
});
|
| 854 |
+
break;
|
| 855 |
+
case 'triangle':
|
| 856 |
+
shape = new fabric.Triangle({
|
| 857 |
+
left: startX,
|
| 858 |
+
top: startY,
|
| 859 |
+
width: 0,
|
| 860 |
+
height: 0,
|
| 861 |
+
fill: shapeFill.value,
|
| 862 |
+
stroke: shapeStroke.value,
|
| 863 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 864 |
+
selectable: true
|
| 865 |
+
});
|
| 866 |
+
break;
|
| 867 |
+
case 'line':
|
| 868 |
+
shape = new fabric.Line([startX, startY, startX, startY], {
|
| 869 |
+
stroke: shapeStroke.value,
|
| 870 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 871 |
+
selectable: true
|
| 872 |
+
});
|
| 873 |
+
break;
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
if (shape) {
|
| 877 |
+
canvas.add(shape);
|
| 878 |
+
activeObject = shape;
|
| 879 |
+
}
|
| 880 |
+
});
|
| 881 |
+
|
| 882 |
+
canvas.on('mouse:move', function(options) {
|
| 883 |
+
if (!activeObject && polygonPoints.length === 0) return;
|
| 884 |
+
|
| 885 |
+
const pointer = canvas.getPointer(options.e);
|
| 886 |
+
|
| 887 |
+
if (polygonPoints.length > 0) {
|
| 888 |
+
// Update the temporary line for polygon creation
|
| 889 |
+
const lastPoint = polygonPoints[polygonPoints.length - 1];
|
| 890 |
+
activeObject.set({
|
| 891 |
+
x2: pointer.x,
|
| 892 |
+
y2: pointer.y
|
| 893 |
+
});
|
| 894 |
+
canvas.renderAll();
|
| 895 |
+
return;
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
switch(shapeType.value) {
|
| 899 |
+
case 'rect':
|
| 900 |
+
activeObject.set({
|
| 901 |
+
width: pointer.x - startX,
|
| 902 |
+
height: pointer.y - startY,
|
| 903 |
+
absolutePositioned: true
|
| 904 |
+
});
|
| 905 |
+
break;
|
| 906 |
+
case 'circle':
|
| 907 |
+
const radius = Math.sqrt(
|
| 908 |
+
Math.pow(pointer.x - startX, 2) +
|
| 909 |
+
Math.pow(pointer.y - startY, 2)
|
| 910 |
+
) / 2;
|
| 911 |
+
activeObject.set({
|
| 912 |
+
radius: radius,
|
| 913 |
+
left: startX - radius,
|
| 914 |
+
top: startY - radius
|
| 915 |
+
});
|
| 916 |
+
break;
|
| 917 |
+
case 'triangle':
|
| 918 |
+
activeObject.set({
|
| 919 |
+
width: pointer.x - startX,
|
| 920 |
+
height: pointer.y - startY
|
| 921 |
+
});
|
| 922 |
+
break;
|
| 923 |
+
case 'line':
|
| 924 |
+
activeObject.set({
|
| 925 |
+
x2: pointer.x,
|
| 926 |
+
y2: pointer.y
|
| 927 |
+
});
|
| 928 |
+
break;
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
canvas.renderAll();
|
| 932 |
+
});
|
| 933 |
+
|
| 934 |
+
canvas.on('mouse:up', function(options) {
|
| 935 |
+
if (shapeType.value === 'polygon') {
|
| 936 |
+
// For polygon, we don't finish until double click
|
| 937 |
+
return;
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
if (activeObject) {
|
| 941 |
+
// For other shapes, finalize the shape
|
| 942 |
+
if (Math.abs(activeObject.width) < 5 || Math.abs(activeObject.height) < 5) {
|
| 943 |
+
// Too small, probably accidental click
|
| 944 |
+
canvas.remove(activeObject);
|
| 945 |
+
} else {
|
| 946 |
+
// Make sure it's selectable
|
| 947 |
+
activeObject.set({ selectable: true });
|
| 948 |
+
canvas.setActiveObject(activeObject);
|
| 949 |
+
}
|
| 950 |
+
activeObject = null;
|
| 951 |
+
}
|
| 952 |
+
});
|
| 953 |
+
|
| 954 |
+
canvas.on('mouse:dblclick', function() {
|
| 955 |
+
if (shapeType.value === 'polygon' && polygonPoints.length >= 2) {
|
| 956 |
+
// Finalize the polygon
|
| 957 |
+
updatePolygonShape(true);
|
| 958 |
+
polygonPoints = [];
|
| 959 |
+
}
|
| 960 |
+
});
|
| 961 |
+
}
|
| 962 |
+
|
| 963 |
+
function updatePolygonShape(finalize = false) {
|
| 964 |
+
if (polygonPoints.length < 2) return;
|
| 965 |
+
|
| 966 |
+
// Remove the temporary line
|
| 967 |
+
if (activeObject) {
|
| 968 |
+
canvas.remove(activeObject);
|
| 969 |
+
}
|
| 970 |
+
|
| 971 |
+
// Create polygon path
|
| 972 |
+
let path = `M ${polygonPoints[0].x} ${polygonPoints[0].y}`;
|
| 973 |
+
for (let i = 1; i < polygonPoints.length; i++) {
|
| 974 |
+
path += ` L ${polygonPoints[i].x} ${polygonPoints[i].y}`;
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
if (finalize) {
|
| 978 |
+
// Close the path
|
| 979 |
+
path += ' Z';
|
| 980 |
+
|
| 981 |
+
const polygon = new fabric.Path(path, {
|
| 982 |
+
fill: shapeFill.value,
|
| 983 |
+
stroke: shapeStroke.value,
|
| 984 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 985 |
+
selectable: true
|
| 986 |
+
});
|
| 987 |
+
|
| 988 |
+
canvas.add(polygon);
|
| 989 |
+
canvas.setActiveObject(polygon);
|
| 990 |
+
activeObject = null;
|
| 991 |
+
} else {
|
| 992 |
+
// Create a new temporary line
|
| 993 |
+
const lastPoint = polygonPoints[polygonPoints.length - 1];
|
| 994 |
+
activeObject = new fabric.Line([
|
| 995 |
+
lastPoint.x, lastPoint.y,
|
| 996 |
+
lastPoint.x, lastPoint.y
|
| 997 |
+
], {
|
| 998 |
+
stroke: shapeStroke.value,
|
| 999 |
+
strokeWidth: parseInt(shapeStrokeWidth.value),
|
| 1000 |
+
selectable: false
|
| 1001 |
+
});
|
| 1002 |
+
|
| 1003 |
+
canvas.add(activeObject);
|
| 1004 |
+
}
|
| 1005 |
+
|
| 1006 |
+
canvas.renderAll();
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
function setupBlurTool() {
|
| 1010 |
+
canvas.on('mouse:down', function(options) {
|
| 1011 |
+
if (options.target) return;
|
| 1012 |
+
|
| 1013 |
+
const pointer = canvas.getPointer(options.e);
|
| 1014 |
+
blurArea = new fabric.Rect({
|
| 1015 |
+
left: pointer.x,
|
| 1016 |
+
top: pointer.y,
|
| 1017 |
+
width: 0,
|
| 1018 |
+
height: 0,
|
| 1019 |
+
fill: 'rgba(0,0,0,0.3)',
|
| 1020 |
+
stroke: '#666',
|
| 1021 |
+
strokeWidth: 1,
|
| 1022 |
+
strokeDashArray: [5, 5],
|
| 1023 |
+
selectable: false,
|
| 1024 |
+
hasControls: false,
|
| 1025 |
+
lockMovementX: true,
|
| 1026 |
+
lockMovementY: true,
|
| 1027 |
+
lockRotation: true,
|
| 1028 |
+
lockScalingX: true,
|
| 1029 |
+
lockScalingY: true,
|
| 1030 |
+
lockUniScaling: true
|
| 1031 |
+
});
|
| 1032 |
+
|
| 1033 |
+
canvas.add(blurArea);
|
| 1034 |
+
});
|
| 1035 |
+
|
| 1036 |
+
canvas.on('mouse:move', function(options) {
|
| 1037 |
+
if (!blurArea) return;
|
| 1038 |
+
|
| 1039 |
+
const pointer = canvas.getPointer(options.e);
|
| 1040 |
+
blurArea.set({
|
| 1041 |
+
width: pointer.x - blurArea.left,
|
| 1042 |
+
height: pointer.y - blurArea.top
|
| 1043 |
+
});
|
| 1044 |
+
|
| 1045 |
+
canvas.renderAll();
|
| 1046 |
+
});
|
| 1047 |
+
|
| 1048 |
+
canvas.on('mouse:up', function() {
|
| 1049 |
+
if (!blurArea) return;
|
| 1050 |
+
|
| 1051 |
+
// Apply blur effect to the selected area
|
| 1052 |
+
applyBlurEffect(blurArea);
|
| 1053 |
+
canvas.remove(blurArea);
|
| 1054 |
+
blurArea = null;
|
| 1055 |
+
});
|
| 1056 |
+
}
|
| 1057 |
+
|
| 1058 |
+
function applyBlurEffect(area) {
|
| 1059 |
+
// Create a rectangle that represents the blurred area
|
| 1060 |
+
const blurRect = new fabric.Rect({
|
| 1061 |
+
left: area.left,
|
| 1062 |
+
top: area.top,
|
| 1063 |
+
width: area.width,
|
| 1064 |
+
height: area.height,
|
| 1065 |
+
fill: 'rgba(255,255,255,0.7)',
|
| 1066 |
+
selectable: true,
|
| 1067 |
+
type: 'blur',
|
| 1068 |
+
blurIntensity: parseInt(blurIntensity.value),
|
| 1069 |
+
blurRadius: parseInt(blurRadius.value),
|
| 1070 |
+
blurType: blurType.value
|
| 1071 |
+
});
|
| 1072 |
+
|
| 1073 |
+
// Apply SVG filter for blur effect
|
| 1074 |
+
if (blurType.value === 'gaussian') {
|
| 1075 |
+
blurRect.set({
|
| 1076 |
+
filters: [
|
| 1077 |
+
new fabric.Image.filters.Blur({
|
| 1078 |
+
blur: blurRect.blurIntensity / 5
|
| 1079 |
+
})
|
| 1080 |
+
]
|
| 1081 |
+
});
|
| 1082 |
+
} else if (blurType.value === 'pixelate') {
|
| 1083 |
+
blurRect.set({
|
| 1084 |
+
filters: [
|
| 1085 |
+
new fabric.Image.filters.Pixelate({
|
| 1086 |
+
blocksize: Math.max(2, 10 - blurRect.blurIntensity / 2)
|
| 1087 |
+
})
|
| 1088 |
+
]
|
| 1089 |
+
});
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
canvas.add(blurRect);
|
| 1093 |
+
canvas.renderAll();
|
| 1094 |
+
}
|
| 1095 |
+
|
| 1096 |
+
function runAiProcessing() {
|
| 1097 |
+
if (!apiKey) {
|
| 1098 |
+
showNotification('Please set your DeepSeek API key first', 'error');
|
| 1099 |
+
apiKeyModal.classList.remove('hidden');
|
| 1100 |
+
return;
|
| 1101 |
+
}
|
| 1102 |
+
|
| 1103 |
+
aiProcessing.classList.remove('hidden');
|
| 1104 |
+
|
| 1105 |
+
// Simulate AI processing with DeepSeek API
|
| 1106 |
+
// In a real implementation, you would make actual API calls here
|
| 1107 |
+
setTimeout(() => {
|
| 1108 |
+
aiProcessing.classList.add('hidden');
|
| 1109 |
+
|
| 1110 |
+
// Simulate different AI effects based on tool
|
| 1111 |
+
if (currentTool === 'aiRemoveBg') {
|
| 1112 |
+
// Simulate background removal
|
| 1113 |
+
const bgRect = new fabric.Rect({
|
| 1114 |
+
left: 100,
|
| 1115 |
+
top: 100,
|
| 1116 |
+
width: 200,
|
| 1117 |
+
height: 200,
|
| 1118 |
+
fill: 'transparent',
|
| 1119 |
+
stroke: '#6366f1',
|
| 1120 |
+
strokeWidth: 2,
|
| 1121 |
+
strokeDashArray: [5, 5],
|
| 1122 |
+
selectable: true
|
| 1123 |
+
});
|
| 1124 |
+
|
| 1125 |
+
canvas.add(bgRect);
|
| 1126 |
+
canvas.setActiveObject(bgRect);
|
| 1127 |
+
|
| 1128 |
+
showNotification('Background removed successfully!', 'success');
|
| 1129 |
+
}
|
| 1130 |
+
else if (currentTool === 'aiEnhance') {
|
| 1131 |
+
// Simulate image enhancement
|
| 1132 |
+
const bgImage = canvas.backgroundImage;
|
| 1133 |
+
if (bgImage) {
|
| 1134 |
+
bgImage.set({
|
| 1135 |
+
filters: [
|
| 1136 |
+
new fabric.Image.filters.Contrast({
|
| 1137 |
+
contrast: 0.1 * parseInt(aiEnhanceMode.value === 'contrast' ? 5 : 2)
|
| 1138 |
+
}),
|
| 1139 |
+
new fabric.Image.filters.Brightness({
|
| 1140 |
+
brightness: 0.05
|
| 1141 |
+
})
|
| 1142 |
+
]
|
| 1143 |
+
});
|
| 1144 |
+
canvas.renderAll();
|
| 1145 |
+
showNotification('Image enhanced successfully!', 'success');
|
| 1146 |
+
}
|
| 1147 |
+
}
|
| 1148 |
+
else if (currentTool === 'aiTextRecognition') {
|
| 1149 |
+
// Simulate OCR text recognition
|
| 1150 |
+
const text = new fabric.IText('Recognized text: Lorem ipsum dolor sit amet', {
|
| 1151 |
+
left: 50,
|
| 1152 |
+
top: 50,
|
| 1153 |
+
fontFamily: 'Arial',
|
| 1154 |
+
fontSize: 16,
|
| 1155 |
+
fill: '#6366f1',
|
| 1156 |
+
selectable: true
|
| 1157 |
+
});
|
| 1158 |
+
|
| 1159 |
+
canvas.add(text);
|
| 1160 |
+
canvas.setActiveObject(text);
|
| 1161 |
+
|
| 1162 |
+
showNotification('Text recognized successfully!', 'success');
|
| 1163 |
+
}
|
| 1164 |
+
}, 2000);
|
| 1165 |
+
}
|
| 1166 |
+
|
| 1167 |
+
function handleFileUpload(e) {
|
| 1168 |
+
const file = e.target.files[0];
|
| 1169 |
+
if (!file) return;
|
| 1170 |
+
|
| 1171 |
+
if (!file.type.match('image.*')) {
|
| 1172 |
+
showNotification('Please select an image file', 'error');
|
| 1173 |
+
return;
|
| 1174 |
+
}
|
| 1175 |
+
|
| 1176 |
+
if (file.size > 10 * 1024 * 1024) {
|
| 1177 |
+
showNotification('Image size should be less than 10MB', 'error');
|
| 1178 |
+
return;
|
| 1179 |
+
}
|
| 1180 |
+
|
| 1181 |
+
const reader = new FileReader();
|
| 1182 |
+
reader.onload = function(f) {
|
| 1183 |
+
fabric.Image.fromURL(f.target.result, function(img) {
|
| 1184 |
+
// Clear canvas first
|
| 1185 |
+
canvas.clear();
|
| 1186 |
+
canvas.setBackgroundColor('#ffffff');
|
| 1187 |
+
|
| 1188 |
+
// Scale image to fit canvas if it's too large
|
| 1189 |
+
const scale = Math.min(
|
| 1190 |
+
canvas.width / img.width,
|
| 1191 |
+
canvas.height / img.height
|
| 1192 |
+
);
|
| 1193 |
+
|
| 1194 |
+
// Apply scaling if needed
|
| 1195 |
+
if (scale < 1) {
|
| 1196 |
+
img.scale(scale);
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
|
| 1200 |
+
originX: 'left',
|
| 1201 |
+
originY: 'top',
|
| 1202 |
+
left: (canvas.width - img.width * img.scaleX) / 2,
|
| 1203 |
+
top: (canvas.height - img.height * img.scaleY) / 2
|
| 1204 |
+
});
|
| 1205 |
+
|
| 1206 |
+
// Update image info
|
| 1207 |
+
imageInfo.textContent = `${file.name} (${Math.round(file.size/1024)}KB, ${img.width}x${img.height})`;
|
| 1208 |
+
|
| 1209 |
+
// Close modal
|
| 1210 |
+
uploadModal.classList.add('hidden');
|
| 1211 |
+
|
| 1212 |
+
// Save initial state
|
| 1213 |
+
saveHistory();
|
| 1214 |
+
}, {
|
| 1215 |
+
crossOrigin: 'anonymous'
|
| 1216 |
+
});
|
| 1217 |
+
};
|
| 1218 |
+
reader.readAsDataURL(file);
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
function saveCanvasState() {
|
| 1222 |
+
const dataURL = canvas.toDataURL({
|
| 1223 |
+
format: 'png',
|
| 1224 |
+
quality: 1
|
| 1225 |
+
});
|
| 1226 |
+
|
| 1227 |
+
// In a real app, you might save to localStorage or server
|
| 1228 |
+
localStorage.setItem('savedCanvas', dataURL);
|
| 1229 |
+
|
| 1230 |
+
showNotification('Project saved successfully!', 'success');
|
| 1231 |
+
}
|
| 1232 |
+
|
| 1233 |
+
function downloadCanvas() {
|
| 1234 |
+
const format = exportFormat.value;
|
| 1235 |
+
const quality = exportFormat.value === 'jpg' || exportFormat.value === 'webp'
|
| 1236 |
+
? parseInt(exportQuality.value) / 100
|
| 1237 |
+
: 1;
|
| 1238 |
+
|
| 1239 |
+
// Handle size option
|
| 1240 |
+
let width = canvas.width;
|
| 1241 |
+
let height = canvas.height;
|
| 1242 |
+
|
| 1243 |
+
if (exportSize.value !== 'original') {
|
| 1244 |
+
const size = parseInt(exportSize.value);
|
| 1245 |
+
const scale = size / canvas.width;
|
| 1246 |
+
width = size;
|
| 1247 |
+
height = canvas.height * scale;
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
// Create temporary canvas for resizing
|
| 1251 |
+
const tempCanvas = document.createElement('canvas');
|
| 1252 |
+
const tempCtx = tempCanvas.getContext('2d');
|
| 1253 |
+
tempCanvas.width = width;
|
| 1254 |
+
tempCanvas.height = height;
|
| 1255 |
+
|
| 1256 |
+
// Draw the canvas content to the temporary canvas
|
| 1257 |
+
tempCtx.drawImage(canvas.lowerCanvasEl, 0, 0, width, height);
|
| 1258 |
+
|
| 1259 |
+
const link = document.createElement('a');
|
| 1260 |
+
link.download = `snapedit-${new Date().getTime()}.${format}`;
|
| 1261 |
+
link.href = tempCanvas.toDataURL({
|
| 1262 |
+
format: format,
|
| 1263 |
+
quality: quality
|
| 1264 |
+
});
|
| 1265 |
+
link.click();
|
| 1266 |
+
|
| 1267 |
+
exportModal.classList.add('hidden');
|
| 1268 |
+
}
|
| 1269 |
+
|
| 1270 |
+
function copyToClipboard() {
|
| 1271 |
+
const format = exportFormat.value;
|
| 1272 |
+
const quality = exportFormat.value === 'jpg' || exportFormat.value === 'webp'
|
| 1273 |
+
? parseInt(exportQuality.value) / 100
|
| 1274 |
+
: 1;
|
| 1275 |
+
|
| 1276 |
+
canvas.toBlob(function(blob) {
|
| 1277 |
+
navigator.clipboard.write([
|
| 1278 |
+
new ClipboardItem({
|
| 1279 |
+
[blob.type]: blob
|
| 1280 |
+
})
|
| 1281 |
+
]).then(() => {
|
| 1282 |
+
showNotification('Image copied to clipboard!', 'success');
|
| 1283 |
+
exportModal.classList.add('hidden');
|
| 1284 |
+
}).catch(err => {
|
| 1285 |
+
console.error('Failed to copy:', err);
|
| 1286 |
+
showNotification('Failed to copy image to clipboard', 'error');
|
| 1287 |
+
});
|
| 1288 |
+
}, `image/${format}`, quality);
|
| 1289 |
+
}
|
| 1290 |
+
|
| 1291 |
+
function toggleQualityControl() {
|
| 1292 |
+
if (exportFormat.value === 'jpg' || exportFormat.value === 'webp') {
|
| 1293 |
+
qualityContainer.classList.remove('hidden');
|
| 1294 |
+
} else {
|
| 1295 |
+
qualityContainer.classList.add('hidden');
|
| 1296 |
+
}
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
+
function togglePolygonOptions() {
|
| 1300 |
+
if (shapeType.value === 'polygon') {
|
| 1301 |
+
polygonSidesContainer.classList.remove('hidden');
|
| 1302 |
+
} else {
|
| 1303 |
+
polygonSidesContainer.classList.add('hidden');
|
| 1304 |
+
}
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
+
function saveHistory() {
|
| 1308 |
+
// Trim history if we've undone some actions
|
| 1309 |
+
if (historyIndex < history.length - 1) {
|
| 1310 |
+
history = history.slice(0, historyIndex + 1);
|
| 1311 |
+
}
|
| 1312 |
+
|
| 1313 |
+
// Save current canvas state
|
| 1314 |
+
history.push(JSON.stringify(canvas));
|
| 1315 |
+
historyIndex++;
|
| 1316 |
+
|
| 1317 |
+
// Limit history size
|
| 1318 |
+
if (history.length > 50) {
|
| 1319 |
+
history.shift();
|
| 1320 |
+
historyIndex--;
|
| 1321 |
+
}
|
| 1322 |
+
|
| 1323 |
+
// Update undo/redo buttons
|
| 1324 |
+
updateUndoRedoButtons();
|
| 1325 |
+
}
|
| 1326 |
+
|
| 1327 |
+
function undo() {
|
| 1328 |
+
if (historyIndex <= 0) return;
|
| 1329 |
+
|
| 1330 |
+
historyIndex--;
|
| 1331 |
+
loadFromHistory();
|
| 1332 |
+
}
|
| 1333 |
+
|
| 1334 |
+
function redo() {
|
| 1335 |
+
if (historyIndex >= history.length - 1) return;
|
| 1336 |
+
|
| 1337 |
+
historyIndex++;
|
| 1338 |
+
loadFromHistory();
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
function loadFromHistory() {
|
| 1342 |
+
if (historyIndex < 0 || historyIndex >= history.length) return;
|
| 1343 |
+
|
| 1344 |
+
canvas.loadFromJSON(history[historyIndex], function() {
|
| 1345 |
+
canvas.renderAll();
|
| 1346 |
+
});
|
| 1347 |
+
|
| 1348 |
+
updateUndoRedoButtons();
|
| 1349 |
+
}
|
| 1350 |
+
|
| 1351 |
+
function updateUndoRedoButtons() {
|
| 1352 |
+
// In a real app, you might enable/disable buttons here
|
| 1353 |
+
}
|
| 1354 |
+
|
| 1355 |
+
function handleSelection(e) {
|
| 1356 |
+
activeObject = e.selected[0];
|
| 1357 |
+
|
| 1358 |
+
// Show relevant properties based on object type
|
| 1359 |
+
generalProps.classList.add('hidden');
|
| 1360 |
+
textProps.classList.add('hidden');
|
| 1361 |
+
drawProps.classList.add('hidden');
|
| 1362 |
+
shapeProps.classList.add('hidden');
|
| 1363 |
+
blurProps.classList.add('hidden');
|
| 1364 |
+
aiProps.classList.add('hidden');
|
| 1365 |
+
|
| 1366 |
+
if (!activeObject) {
|
| 1367 |
+
generalProps.classList.remove('hidden');
|
| 1368 |
+
return;
|
| 1369 |
+
}
|
| 1370 |
+
|
| 1371 |
+
if (activeObject.type === 'i-text') {
|
| 1372 |
+
textProps.classList.remove('hidden');
|
| 1373 |
+
textContent.value = activeObject.text;
|
| 1374 |
+
textFont.value = activeObject.fontFamily || 'Arial';
|
| 1375 |
+
textSize.value = activeObject.fontSize;
|
| 1376 |
+
textColor.value = activeObject.fill || '#000000';
|
| 1377 |
+
textBgColor.value = activeObject.backgroundColor || 'transparent';
|
| 1378 |
+
} else if (activeObject.type === 'path') {
|
| 1379 |
+
drawProps.classList.remove('hidden');
|
| 1380 |
+
brushSize.value = activeObject.strokeWidth;
|
| 1381 |
+
brushColor.value = activeObject.stroke;
|
| 1382 |
+
brushOpacity.value = (activeObject.opacity || 1) * 100;
|
| 1383 |
+
} else if (['rect', 'circle', 'triangle', 'line', 'polygon'].includes(activeObject.type)) {
|
| 1384 |
+
shapeProps.classList.remove('hidden');
|
| 1385 |
+
shapeFill.value = activeObject.fill || '#000000';
|
| 1386 |
+
shapeStroke.value = activeObject.stroke || '#000000';
|
| 1387 |
+
shapeStrokeWidth.value = activeObject.strokeWidth || 1;
|
| 1388 |
+
} else if (activeObject.type === 'blur') {
|
| 1389 |
+
blurProps.classList.remove('hidden');
|
| 1390 |
+
blurIntensity.value = activeObject.blurIntensity || 5;
|
| 1391 |
+
blurRadius.value = activeObject.blurRadius || 50;
|
| 1392 |
+
blurType.value = activeObject.blurType || 'gaussian';
|
| 1393 |
+
} else {
|
| 1394 |
+
generalProps.classList.remove('hidden');
|
| 1395 |
+
}
|
| 1396 |
+
}
|
| 1397 |
+
|
| 1398 |
+
function clearSelection() {
|
| 1399 |
+
activeObject = null;
|
| 1400 |
+
generalProps.classList.remove('hidden');
|
| 1401 |
+
textProps.classList.add('hidden');
|
| 1402 |
+
drawProps.classList.add('hidden');
|
| 1403 |
+
shapeProps.classList.add('hidden');
|
| 1404 |
+
blurProps.classList.add('hidden');
|
| 1405 |
+
aiProps.classList.add('hidden');
|
| 1406 |
+
}
|
| 1407 |
+
|
| 1408 |
+
function updateCanvasBackground() {
|
| 1409 |
+
canvas.setBackgroundColor(canvasBg.value, canvas.renderAll.bind(canvas));
|
| 1410 |
+
saveHistory();
|
| 1411 |
+
}
|
| 1412 |
+
|
| 1413 |
+
function updateCanvasOpacity() {
|
| 1414 |
+
canvas.setBackgroundColor(canvas.backgroundColor, canvas.renderAll.bind(canvas), {
|
| 1415 |
+
opacity: parseInt(canvasOpacity.value) / 100
|
| 1416 |
+
});
|
| 1417 |
+
saveHistory();
|
| 1418 |
+
}
|
| 1419 |
+
|
| 1420 |
+
function updateTextContent() {
|
| 1421 |
+
if (activeObject && activeObject.type === 'i-text') {
|
| 1422 |
+
activeObject.set('text', textContent.value);
|
| 1423 |
+
canvas.renderAll();
|
| 1424 |
+
saveHistory();
|
| 1425 |
+
}
|
| 1426 |
+
}
|
| 1427 |
+
|
| 1428 |
+
function updateTextFont() {
|
| 1429 |
+
if (activeObject && activeObject.type === 'i-text') {
|
| 1430 |
+
activeObject.set('fontFamily', textFont.value);
|
| 1431 |
+
canvas.renderAll();
|
| 1432 |
+
saveHistory();
|
| 1433 |
+
}
|
| 1434 |
+
}
|
| 1435 |
+
|
| 1436 |
+
function updateTextSize() {
|
| 1437 |
+
if (activeObject && activeObject.type === 'i-text') {
|
| 1438 |
+
activeObject.set('fontSize', parseInt(textSize.value));
|
| 1439 |
+
canvas.renderAll();
|
| 1440 |
+
saveHistory();
|
| 1441 |
+
}
|
| 1442 |
+
}
|
| 1443 |
+
|
| 1444 |
+
function updateTextColor() {
|
| 1445 |
+
if (activeObject && activeObject.type === 'i-text') {
|
| 1446 |
+
activeObject.set('fill', textColor.value);
|
| 1447 |
+
canvas.renderAll();
|
| 1448 |
+
saveHistory();
|
| 1449 |
+
}
|
| 1450 |
+
}
|
| 1451 |
+
|
| 1452 |
+
function updateTextBgColor() {
|
| 1453 |
+
if (activeObject && activeObject.type === 'i-text') {
|
| 1454 |
+
activeObject.set('backgroundColor', textBgColor.value === 'transparent' ? '' : textBgColor.value);
|
| 1455 |
+
canvas.renderAll();
|
| 1456 |
+
saveHistory();
|
| 1457 |
+
}
|
| 1458 |
+
}
|
| 1459 |
+
|
| 1460 |
+
function updateBrushSize() {
|
| 1461 |
+
brushSettings.size = parseInt(brushSize.value);
|
| 1462 |
+
}
|
| 1463 |
+
|
| 1464 |
+
function updateBrushColor() {
|
| 1465 |
+
brushSettings.color = brushColor.value;
|
| 1466 |
+
}
|
| 1467 |
+
|
| 1468 |
+
function updateBrushOpacity() {
|
| 1469 |
+
brushSettings.opacity = parseInt(brushOpacity.value) / 100;
|
| 1470 |
+
}
|
| 1471 |
+
|
| 1472 |
+
function updateBrushType() {
|
| 1473 |
+
brushSettings.type = brushType.value;
|
| 1474 |
+
}
|
| 1475 |
+
|
| 1476 |
+
function updateShapeType() {
|
| 1477 |
+
// This would change the shape type for future shapes
|
| 1478 |
+
}
|
| 1479 |
+
|
| 1480 |
+
function updateShapeFill() {
|
| 1481 |
+
if (activeObject && ['rect', 'circle', 'triangle', 'polygon'].includes(activeObject.type)) {
|
| 1482 |
+
activeObject.set('fill', shapeFill.value);
|
| 1483 |
+
canvas.renderAll();
|
| 1484 |
+
saveHistory();
|
| 1485 |
+
}
|
| 1486 |
+
}
|
| 1487 |
+
|
| 1488 |
+
function updateShapeStroke() {
|
| 1489 |
+
if (activeObject && ['rect', 'circle', 'triangle', 'line', 'polygon'].includes(activeObject.type)) {
|
| 1490 |
+
activeObject.set('stroke', shapeStroke.value);
|
| 1491 |
+
canvas.renderAll();
|
| 1492 |
+
saveHistory();
|
| 1493 |
+
}
|
| 1494 |
+
}
|
| 1495 |
+
|
| 1496 |
+
function updateShapeStrokeWidth() {
|
| 1497 |
+
if (activeObject && ['rect', 'circle', 'triangle', 'line', 'polygon'].includes(activeObject.type)) {
|
| 1498 |
+
activeObject.set('strokeWidth', parseInt(shapeStrokeWidth.value));
|
| 1499 |
+
canvas.renderAll();
|
| 1500 |
+
saveHistory();
|
| 1501 |
+
}
|
| 1502 |
+
}
|
| 1503 |
+
|
| 1504 |
+
function updatePolygonSides() {
|
| 1505 |
+
// This would update the number of sides for future polygons
|
| 1506 |
+
}
|
| 1507 |
+
|
| 1508 |
+
function updateBlurIntensity() {
|
| 1509 |
+
if (activeObject && activeObject.type === 'blur') {
|
| 1510 |
+
activeObject.set('blurIntensity', parseInt(blurIntensity.value));
|
| 1511 |
+
|
| 1512 |
+
// Update filter
|
| 1513 |
+
if (activeObject.blurType === 'gaussian') {
|
| 1514 |
+
activeObject.set({
|
| 1515 |
+
filters: [
|
| 1516 |
+
new fabric.Image.filters.Blur({
|
| 1517 |
+
blur: activeObject.blurIntensity / 5
|
| 1518 |
+
})
|
| 1519 |
+
]
|
| 1520 |
+
});
|
| 1521 |
+
} else if (activeObject.blurType === 'pixelate') {
|
| 1522 |
+
activeObject.set({
|
| 1523 |
+
filters: [
|
| 1524 |
+
new fabric.Image.filters.Pixelate({
|
| 1525 |
+
blocksize: Math.max(2, 10 - activeObject.blurIntensity / 2)
|
| 1526 |
+
})
|
| 1527 |
+
]
|
| 1528 |
+
});
|
| 1529 |
+
}
|
| 1530 |
+
|
| 1531 |
+
canvas.renderAll();
|
| 1532 |
+
saveHistory();
|
| 1533 |
+
}
|
| 1534 |
+
}
|
| 1535 |
+
|
| 1536 |
+
function updateBlurRadius() {
|
| 1537 |
+
if (activeObject && activeObject.type === 'blur') {
|
| 1538 |
+
activeObject.set('blurRadius', parseInt(blurRadius.value));
|
| 1539 |
+
canvas.renderAll();
|
| 1540 |
+
saveHistory();
|
| 1541 |
+
}
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
function updateBlurType() {
|
| 1545 |
+
if (activeObject && activeObject.type === 'blur') {
|
| 1546 |
+
activeObject.set('blurType', blurType.value);
|
| 1547 |
+
|
| 1548 |
+
// Update filter based on type
|
| 1549 |
+
if (blurType.value === 'gaussian') {
|
| 1550 |
+
activeObject.set({
|
| 1551 |
+
filters: [
|
| 1552 |
+
new fabric.Image.filters.Blur({
|
| 1553 |
+
blur: activeObject.blurIntensity / 5
|
| 1554 |
+
})
|
| 1555 |
+
]
|
| 1556 |
+
});
|
| 1557 |
+
} else if (blurType.value === 'pixelate') {
|
| 1558 |
+
activeObject.set({
|
| 1559 |
+
filters: [
|
| 1560 |
+
new fabric.Image.filters.Pixelate({
|
| 1561 |
+
blocksize: Math.max(2, 10 - activeObject.blurIntensity / 2)
|
| 1562 |
+
})
|
| 1563 |
+
]
|
| 1564 |
+
});
|
| 1565 |
+
} else {
|
| 1566 |
+
// Mask type - no filter, just a semi-transparent rectangle
|
| 1567 |
+
activeObject.set({ filters: [] });
|
| 1568 |
+
}
|
| 1569 |
+
|
| 1570 |
+
canvas.renderAll();
|
| 1571 |
+
saveHistory();
|
| 1572 |
+
}
|
| 1573 |
+
}
|
| 1574 |
+
|
| 1575 |
+
function loadApiKey() {
|
| 1576 |
+
const encryptedKey = localStorage.getItem('deepseek_api_key');
|
| 1577 |
+
if (encryptedKey) {
|
| 1578 |
+
try {
|
| 1579 |
+
// In a real app, you would use a more secure decryption method
|
| 1580 |
+
apiKey = CryptoJS.AES.decrypt(encryptedKey, 'secure_salt').toString(CryptoJS.enc.Utf8);
|
| 1581 |
+
apiKeyStatus.innerHTML = '<i class="fas fa-circle text-green-500 mr-1"></i> API Key Loaded';
|
| 1582 |
+
apiKeyStatus.classList.remove('bg-gray-100');
|
| 1583 |
+
apiKeyStatus.classList.add('bg-green-100', 'text-green-600');
|
| 1584 |
+
} catch (e) {
|
| 1585 |
+
console.error('Failed to decrypt API key:', e);
|
| 1586 |
+
apiKey = null;
|
| 1587 |
+
}
|
| 1588 |
+
}
|
| 1589 |
+
}
|
| 1590 |
+
|
| 1591 |
+
function saveApiKey() {
|
| 1592 |
+
const key = apiKeyInput.value.trim();
|
| 1593 |
+
if (!key) {
|
| 1594 |
+
showNotification('Please enter a valid API key', 'error');
|
| 1595 |
+
return;
|
| 1596 |
+
}
|
| 1597 |
+
|
| 1598 |
+
try {
|
| 1599 |
+
// Encrypt the API key before storing
|
| 1600 |
+
const encryptedKey = CryptoJS.AES.encrypt(key, 'secure_salt').toString();
|
| 1601 |
+
localStorage.setItem('deepseek_api_key', encryptedKey);
|
| 1602 |
+
apiKey = key;
|
| 1603 |
+
|
| 1604 |
+
apiKeyStatus.innerHTML = '<i class="fas fa-circle text-green-500 mr-1"></i> API Key Loaded';
|
| 1605 |
+
apiKeyStatus.classList.remove('bg-gray-100');
|
| 1606 |
+
apiKeyStatus.classList.add('bg-green-100', 'text-green-600');
|
| 1607 |
+
|
| 1608 |
+
showNotification('API key saved successfully', 'success');
|
| 1609 |
+
apiKeyModal.classList.add('hidden');
|
| 1610 |
+
} catch (e) {
|
| 1611 |
+
console.error('Failed to encrypt API key:', e);
|
| 1612 |
+
showNotification('Failed to save API key', 'error');
|
| 1613 |
+
}
|
| 1614 |
+
}
|
| 1615 |
+
|
| 1616 |
+
function clearApiKey() {
|
| 1617 |
+
localStorage.removeItem('deepseek_api_key');
|
| 1618 |
+
apiKey = null;
|
| 1619 |
+
apiKeyInput.value = '';
|
| 1620 |
+
|
| 1621 |
+
apiKeyStatus.innerHTML = '<i class="fas fa-circle text-red-500 mr-1"></i> No API Key';
|
| 1622 |
+
apiKeyStatus.classList.remove('bg-green-100', 'text-green-600');
|
| 1623 |
+
apiKeyStatus.classList.add('bg-gray-100', 'text-gray-600');
|
| 1624 |
+
|
| 1625 |
+
showNotification('API key cleared', 'info');
|
| 1626 |
+
}
|
| 1627 |
+
|
| 1628 |
+
function showNotification(message, type = 'info') {
|
| 1629 |
+
const colors = {
|
| 1630 |
+
info: 'bg-blue-500',
|
| 1631 |
+
success: 'bg-green-500',
|
| 1632 |
+
warning: 'bg-yellow-500',
|
| 1633 |
+
error: 'bg-red-500'
|
| 1634 |
+
};
|
| 1635 |
+
|
| 1636 |
+
const icons = {
|
| 1637 |
+
info: 'fa-info-circle',
|
| 1638 |
+
success: 'fa-check-circle',
|
| 1639 |
+
warning: 'fa-exclamation-triangle',
|
| 1640 |
+
error: 'fa-exclamation-circle'
|
| 1641 |
+
};
|
| 1642 |
+
|
| 1643 |
+
const notification = document.createElement('div');
|
| 1644 |
+
notification.className = `fixed bottom-4 right-4 ${colors[type]} text-white px-4 py-2 rounded shadow-lg flex items-center`;
|
| 1645 |
+
notification.innerHTML = `<i class="fas ${icons[type]} mr-2"></i> ${message}`;
|
| 1646 |
+
document.body.appendChild(notification);
|
| 1647 |
+
|
| 1648 |
+
setTimeout(() => {
|
| 1649 |
+
notification.classList.add('opacity-0', 'transition-opacity', 'duration-300');
|
| 1650 |
+
setTimeout(() => {
|
| 1651 |
+
notification.remove();
|
| 1652 |
+
}, 300);
|
| 1653 |
+
}, 3000);
|
| 1654 |
+
}
|
| 1655 |
+
|
| 1656 |
+
// Initialize
|
| 1657 |
+
setActiveTool('select');
|
| 1658 |
+
|
| 1659 |
+
// User menu toggle
|
| 1660 |
+
document.getElementById('userMenuBtn').addEventListener('click', function() {
|
| 1661 |
+
document.getElementById('userMenu').classList.toggle('hidden');
|
| 1662 |
+
});
|
| 1663 |
+
|
| 1664 |
+
// Close user menu when clicking outside
|
| 1665 |
+
document.addEventListener('click', function(e) {
|
| 1666 |
+
if (!e.target.closest('#userMenu') && !e.target.closest('#userMenuBtn')) {
|
| 1667 |
+
document.getElementById('userMenu').classList.add('hidden');
|
| 1668 |
+
}
|
| 1669 |
+
});
|
| 1670 |
+
});
|
| 1671 |
+
</script>
|
| 1672 |
+
<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=sqibhe/snapeidit" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 1673 |
+
</html>
|
prompts.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
**Objective**: Develop a cross-platform screenshot editing tool where users can upload, annotate, enhance, and export screenshots with AI-powered features. **Technical Requirements**: 1. **Frontend**: React.js/Electron for desktop or React Native for mobile. Use HTML5 Canvas or Fabric.js for image manipulation. 2. **Backend**: Node.js/Django for API, user auth, and cloud sync. 3. **AI Integration**: - Use OpenCV/TensorFlow.js for object removal and background detection. - Integrate OCR (Tesseract.js/Azure Cognitive Services) for text extraction. 4. **Collaboration**: Implement WebSockets/Socket.io for real-time editing. 5. **Security**: End-to-end encryption (AES-256), GDPR compliance, and role-based access. **Features to Implement**: - All features listed above, prioritized as: Core Editing > AI Tools > Collaboration > Advanced Tools. - Ensure mobile-responsive design with offline-first capabilities. **Testing**: - Cross-browser testing (Chrome, Firefox, Safari). - Performance optimization for large images (10MB+). - User testing via beta releases with feedback loops. **Deployment**: - Dockerize the app for scalable cloud deployment (AWS/Google Cloud). - Provide detailed docs (user guides + API references). **Deliverables**: - MVP within 6 weeks, full release in 12 weeks. - Open-source core with premium features (e.g., AI tools) as paid add-ons.
|
| 2 |
+
Okay make sure you add Deepseek ai In this, The User Who wants to use this Tool canadd their own API key on the top of the tool to use it, also make sure that API Key is encrypted, Also this tool all features are not working fine you should enhance all tool feature to make sure its working accurately
|