Create index.html
Browse files- index.html +1343 -0
index.html
ADDED
|
@@ -0,0 +1,1343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ar" dir="rtl" data-theme="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
| 6 |
+
<meta name="mobile-web-app-capable" content="yes">
|
| 7 |
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 8 |
+
<title>g4fpro</title>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
| 12 |
+
<style>
|
| 13 |
+
:root{
|
| 14 |
+
--bg:#0c0c12;--bg1:#141420;--bg2:#1b1b28;--bg3:#222232;--bg4:#2a2a3e;--bgh:#313148;
|
| 15 |
+
--ln:#1e1e2e;--ln2:#2c2c42;--ln3:#404055;
|
| 16 |
+
--t0:#e8e8f5;--t1:#b0b0cc;--t2:#6868a0;--t3:#404058;
|
| 17 |
+
--ac:#7c6fff;--ach:#8f84ff;--acbg:#7c6fff14;--acln:#7c6fff44;
|
| 18 |
+
--tl:#00d4ff;--tlbg:#00d4ff0e;
|
| 19 |
+
--gn:#22d48a;--gnbg:#22d48a0e;
|
| 20 |
+
--am:#f5a623;--ambg:#f5a6230e;
|
| 21 |
+
--rd:#ff5f6d;--rdbg:#ff5f6d0e;
|
| 22 |
+
--font:'Cairo',sans-serif;--mono:'JetBrains Mono',monospace;
|
| 23 |
+
--r1:5px;--r2:10px;--r3:14px;--r4:20px;
|
| 24 |
+
--s1:0 1px 4px #0009;--s2:0 4px 18px #000b;--s3:0 12px 40px #000e;
|
| 25 |
+
}
|
| 26 |
+
[data-theme=light]{
|
| 27 |
+
--bg:#f2f2fa;--bg1:#fff;--bg2:#f5f5fd;--bg3:#ebebf8;--bg4:#e2e2f0;--bgh:#d6d6ee;
|
| 28 |
+
--ln:#e8e8f6;--ln2:#d8d8ee;--ln3:#c0c0dc;
|
| 29 |
+
--t0:#0d0d1a;--t1:#2a2a44;--t2:#5a5a88;--t3:#9898b8;
|
| 30 |
+
--s1:0 1px 4px #0002;--s2:0 4px 18px #0003;--s3:0 12px 40px #0004;
|
| 31 |
+
}
|
| 32 |
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
|
| 33 |
+
html,body{height:100%;overflow:hidden;font-family:var(--font);background:var(--bg);color:var(--t0);-webkit-font-smoothing:antialiased}
|
| 34 |
+
button{cursor:pointer;font-family:var(--font);border:none;background:none;color:inherit}
|
| 35 |
+
input,textarea,select{font-family:var(--font);-webkit-appearance:none;appearance:none}
|
| 36 |
+
a{color:var(--tl)}
|
| 37 |
+
::-webkit-scrollbar{width:3px;height:3px}
|
| 38 |
+
::-webkit-scrollbar-thumb{background:var(--ln2);border-radius:2px}
|
| 39 |
+
::-webkit-scrollbar-track{background:transparent}
|
| 40 |
+
#app{display:flex;height:100dvh;overflow:hidden}
|
| 41 |
+
#sb{width:268px;min-width:268px;background:var(--bg1);border-left:1px solid var(--ln);display:flex;flex-direction:column;height:100dvh;overflow:hidden;flex-shrink:0;transition:width .22s ease,min-width .22s ease}
|
| 42 |
+
#sb.desk-hide{width:0;min-width:0;border-left:none}
|
| 43 |
+
@media(max-width:680px){
|
| 44 |
+
#sb{position:fixed;right:0;top:0;bottom:0;width:282px!important;min-width:0;transform:translateX(110%);box-shadow:var(--s3);transition:transform .22s ease;z-index:50}
|
| 45 |
+
#sb.mob-open{transform:translateX(0)}
|
| 46 |
+
}
|
| 47 |
+
.sb-head{padding:10px;border-bottom:1px solid var(--ln);flex-shrink:0}
|
| 48 |
+
.new-btn{width:100%;display:flex;align-items:center;gap:7px;padding:9px 12px;background:var(--ac);color:#fff;border-radius:var(--r2);font-size:.87rem;font-weight:700;transition:.14s}
|
| 49 |
+
.new-btn:hover{background:var(--ach)}.new-btn:active{transform:scale(.97)}
|
| 50 |
+
.cl{flex:1;overflow-y:auto;padding:4px}
|
| 51 |
+
.grp{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--t3);padding:8px 8px 3px}
|
| 52 |
+
.ci{display:flex;align-items:center;gap:7px;padding:7px 9px;border-radius:var(--r2);cursor:pointer;transition:.13s;position:relative}
|
| 53 |
+
.ci:hover{background:var(--bgh)}.ci.on{background:var(--acbg);border:1px solid var(--acln)}
|
| 54 |
+
.ci-dot{width:6px;height:6px;border-radius:50%;background:var(--t3);flex-shrink:0;transition:.13s}
|
| 55 |
+
.ci.on .ci-dot{background:var(--ac)}
|
| 56 |
+
.ci-info{flex:1;min-width:0}
|
| 57 |
+
.ci-title{font-size:.79rem;font-weight:600;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 58 |
+
.ci.on .ci-title{color:var(--ac)}
|
| 59 |
+
.ci-sub{font-size:.64rem;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}
|
| 60 |
+
.ci-acts{display:none;gap:1px;flex-shrink:0}.ci:hover .ci-acts{display:flex}
|
| 61 |
+
.cia{width:22px;height:22px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--t2);transition:.13s}
|
| 62 |
+
.cia:hover{background:var(--bg4);color:var(--t0)}.cia.del:hover{color:var(--rd)}
|
| 63 |
+
.sb-bot{border-top:1px solid var(--ln);padding:10px;flex-shrink:0;display:flex;flex-direction:column;gap:7px;overflow-y:auto;max-height:58vh}
|
| 64 |
+
.lbl{font-size:.63rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--t3);margin-bottom:3px;display:flex;align-items:center;gap:4px}
|
| 65 |
+
.lbl i{color:var(--ac);font-size:.61rem}
|
| 66 |
+
.sel{width:100%;padding:7px 24px 7px 7px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.8rem;font-weight:600;cursor:pointer;direction:rtl;transition:.13s;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%23668' d='M5 6L0 0h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:left 7px center;background-size:8px}
|
| 67 |
+
.sel:focus{border-color:var(--ac);outline:none}
|
| 68 |
+
.tog-row{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1)}
|
| 69 |
+
.tog-lbl{font-size:.78rem;font-weight:600;color:var(--t1);display:flex;align-items:center;gap:5px}
|
| 70 |
+
.tog-lbl i{color:var(--tl);font-size:.7rem}
|
| 71 |
+
.tog{position:relative;width:34px;height:18px;flex-shrink:0}
|
| 72 |
+
.tog input{opacity:0;width:0;height:0;position:absolute}
|
| 73 |
+
.tog-t{position:absolute;inset:0;background:var(--bg4);border:1px solid var(--ln2);border-radius:9px;cursor:pointer;transition:.15s}
|
| 74 |
+
.tog-t::after{content:'';position:absolute;width:12px;height:12px;right:2px;top:2px;background:var(--t2);border-radius:50%;transition:.15s}
|
| 75 |
+
.tog input:checked~.tog-t{background:var(--tl);border-color:var(--tl)}
|
| 76 |
+
.tog input:checked~.tog-t::after{transform:translateX(-16px);background:#fff}
|
| 77 |
+
.sys-ta{width:100%;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.78rem;line-height:1.5;resize:none;min-height:50px;max-height:86px;direction:rtl;transition:.13s}
|
| 78 |
+
.sys-ta:focus{border-color:var(--ac);outline:none}.sys-ta::placeholder{color:var(--t3)}
|
| 79 |
+
#main{flex:1;min-width:0;display:flex;flex-direction:column;height:100dvh;background:var(--bg)}
|
| 80 |
+
#hdr{height:50px;background:var(--bg1);border-bottom:1px solid var(--ln);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0}
|
| 81 |
+
.hb{width:32px;height:32px;border-radius:var(--r1);background:var(--bg3);border:1px solid var(--ln2);color:var(--t1);display:flex;align-items:center;justify-content:center;font-size:.82rem;transition:.13s;flex-shrink:0}
|
| 82 |
+
.hb:hover{background:var(--bgh);color:var(--t0)}.hb:active{transform:scale(.91)}
|
| 83 |
+
.logo{display:flex;align-items:center;gap:6px;font-weight:900;font-size:.96rem;letter-spacing:-.03em;white-space:nowrap}
|
| 84 |
+
.logo-mk{width:27px;height:27px;background:linear-gradient(135deg,var(--ac),var(--tl));border-radius:7px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.72rem}
|
| 85 |
+
.logo-ac{color:var(--ac)}
|
| 86 |
+
.hdr-m{flex:1;min-width:0;text-align:center}
|
| 87 |
+
.hdr-t{font-size:.81rem;font-weight:700;color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 88 |
+
.hdr-r{display:flex;align-items:center;gap:5px;flex-shrink:0}
|
| 89 |
+
.hst{display:flex;align-items:center;gap:4px;font-size:.67rem;color:var(--gn);font-weight:600;white-space:nowrap}
|
| 90 |
+
.hst::before{content:'';width:5px;height:5px;background:var(--gn);border-radius:50%;animation:pu 2s ease infinite}
|
| 91 |
+
@keyframes pu{0%,100%{opacity:1}50%{opacity:.3}}
|
| 92 |
+
.ppill{display:flex;align-items:center;gap:4px;padding:3px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:14px;font-size:.67rem;font-weight:700;color:var(--t2);white-space:nowrap;max-width:116px;overflow:hidden;text-overflow:ellipsis}
|
| 93 |
+
.pd{width:5px;height:5px;border-radius:50%;background:var(--ac);flex-shrink:0}
|
| 94 |
+
@media(max-width:480px){.ppill{display:none}}
|
| 95 |
+
#mwrap{flex:1;overflow-y:auto;position:relative;-webkit-overflow-scrolling:touch}
|
| 96 |
+
#msgs{max-width:780px;margin:0 auto;padding:18px 10px 14px;display:flex;flex-direction:column;gap:2px}
|
| 97 |
+
@media(max-width:680px){#msgs{padding:12px 8px 10px}}
|
| 98 |
+
#wlc{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:calc(100dvh - 140px);gap:18px;text-align:center;padding:20px;animation:fup .4s ease}
|
| 99 |
+
@keyframes fup{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
| 100 |
+
.wico{width:66px;height:66px;background:linear-gradient(135deg,var(--ac),var(--tl));border-radius:18px;display:flex;align-items:center;justify-content:center;font-size:1.6rem;color:#fff;animation:fl 3.5s ease-in-out infinite}
|
| 101 |
+
@keyframes fl{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
|
| 102 |
+
.wt{font-size:1.35rem;font-weight:900}.wt span{color:var(--ac)}
|
| 103 |
+
.ws{font-size:.85rem;color:var(--t2);max-width:340px;line-height:1.65}
|
| 104 |
+
.sg{display:grid;grid-template-columns:1fr 1fr;gap:6px;width:100%;max-width:420px}
|
| 105 |
+
@media(max-width:420px){.sg{grid-template-columns:1fr}}
|
| 106 |
+
.sc{padding:10px 11px;background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r2);text-align:right;cursor:pointer;transition:.13s}
|
| 107 |
+
.sc:hover{background:var(--bgh);border-color:var(--acln);transform:translateY(-2px)}.sc:active{transform:scale(.97)}
|
| 108 |
+
.sc-i{font-size:.95rem;display:block;margin-bottom:3px}
|
| 109 |
+
.sc-t{font-size:.76rem;color:var(--t1);font-weight:600;line-height:1.3}
|
| 110 |
+
.sc-s{font-size:.65rem;color:var(--t3);margin-top:1px}
|
| 111 |
+
.mr{display:flex;flex-direction:column;gap:2px;animation:ms .22s cubic-bezier(.34,1.4,.64,1)}
|
| 112 |
+
@keyframes ms{from{opacity:0;transform:translateY(5px)scale(.98)}to{opacity:1;transform:none}}
|
| 113 |
+
.mr.user{align-items:flex-end}.mr.assistant{align-items:flex-start}.mr.err{align-items:flex-start}
|
| 114 |
+
.mhdr{display:flex;align-items:center;gap:5px;padding:0 3px;font-size:.65rem;color:var(--t3)}
|
| 115 |
+
.mr.user .mhdr{flex-direction:row-reverse}
|
| 116 |
+
.mav{width:20px;height:20px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:.6rem;color:#fff;flex-shrink:0}
|
| 117 |
+
.mav.u{background:var(--ac)}.mav.b{background:linear-gradient(135deg,var(--ac),var(--tl))}
|
| 118 |
+
.mtag{background:var(--bg3);border:1px solid var(--ln2);padding:1px 5px;border-radius:8px;font-size:.6rem;font-weight:700;color:var(--t2)}
|
| 119 |
+
.pinbdg{color:var(--am);font-size:.6rem}
|
| 120 |
+
.mb{max-width:80%;padding:10px 14px;border-radius:var(--r3);font-size:.89rem;line-height:1.78;word-wrap:break-word;overflow-wrap:break-word;overflow:hidden}
|
| 121 |
+
@media(max-width:680px){.mb{max-width:91%;font-size:.87rem;padding:9px 11px}}
|
| 122 |
+
.mr.user .mb{background:var(--ac);color:#fff;border-bottom-left-radius:3px}
|
| 123 |
+
.mr.assistant .mb{background:var(--bg2);border:1px solid var(--ln2);color:var(--t0);border-bottom-right-radius:3px}
|
| 124 |
+
.mr.err .mb{background:var(--rdbg);border:1px solid #ff5f6d28;color:#ff9faa;border-radius:var(--r2)}
|
| 125 |
+
/* thinking */
|
| 126 |
+
.thinking-block{max-width:80%;background:var(--ambg);border:1px solid #f5a62330;border-radius:var(--r2);overflow:hidden;animation:ms .22s ease}
|
| 127 |
+
@media(max-width:680px){.thinking-block{max-width:91%}}
|
| 128 |
+
.thinking-header{display:flex;align-items:center;gap:6px;padding:7px 11px;cursor:pointer;user-select:none;transition:.13s}
|
| 129 |
+
.thinking-header:hover{background:#f5a62310}
|
| 130 |
+
.thinking-icon{color:var(--am);font-size:.75rem;flex-shrink:0}
|
| 131 |
+
.thinking-label{font-size:.73rem;font-weight:700;color:var(--am);flex:1}
|
| 132 |
+
.thinking-summary{font-size:.67rem;color:var(--t3);margin-right:4px;font-style:italic;max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 133 |
+
.thinking-toggle{font-size:.65rem;color:var(--t3);transition:transform .2s}
|
| 134 |
+
.thinking-block.open .thinking-toggle{transform:rotate(180deg)}
|
| 135 |
+
.thinking-body{display:none;padding:6px 11px 9px;border-top:1px solid #f5a62320}
|
| 136 |
+
.thinking-block.open .thinking-body{display:block}
|
| 137 |
+
.thinking-line{font-size:.74rem;color:var(--t2);line-height:1.65;padding:2px 0}
|
| 138 |
+
.thinking-line::before{content:'› ';color:var(--am);opacity:.6;font-family:var(--mono)}
|
| 139 |
+
.thinking-block.streaming .thinking-icon{animation:spin 1.4s linear infinite}
|
| 140 |
+
@keyframes spin{to{transform:rotate(360deg)}}
|
| 141 |
+
.cur{display:inline-block;width:2px;height:.88em;background:var(--ac);border-radius:1px;margin-right:1px;vertical-align:middle;animation:cr .7s step-end infinite}
|
| 142 |
+
@keyframes cr{0%,100%{opacity:1}50%{opacity:0}}
|
| 143 |
+
.macts{display:flex;gap:2px;padding:1px 3px;opacity:0;transition:opacity .13s;flex-wrap:wrap}
|
| 144 |
+
.mr:hover .macts,.mr:focus-within .macts{opacity:1}
|
| 145 |
+
.mr.user .macts{flex-direction:row-reverse}
|
| 146 |
+
.ma{display:flex;align-items:center;gap:3px;padding:3px 7px;border-radius:5px;font-size:.65rem;font-weight:600;color:var(--t2);background:var(--bg2);border:1px solid var(--ln2);transition:.13s;white-space:nowrap}
|
| 147 |
+
.ma:hover{background:var(--bgh);color:var(--t0)}.ma i{font-size:.6rem}
|
| 148 |
+
.ma.rg{color:var(--ac);border-color:var(--acln)}.ma.rg:hover{background:var(--acbg)}
|
| 149 |
+
.ma.pn.on{color:var(--am);border-color:#f5a62335}
|
| 150 |
+
.etx{width:100%;background:var(--bg3);border:2px solid var(--ac);border-radius:var(--r2);padding:9px 10px;color:var(--t0);font-family:var(--font);font-size:.88rem;line-height:1.55;resize:none;direction:rtl;min-height:52px}
|
| 151 |
+
.etx:focus{outline:none}
|
| 152 |
+
.ebtns{display:flex;gap:6px;margin-top:5px;justify-content:flex-end}
|
| 153 |
+
.esv{padding:5px 13px;background:var(--ac);color:#fff;border-radius:5px;font-size:.77rem;font-weight:700;transition:.13s}
|
| 154 |
+
.esv:hover{background:var(--ach)}
|
| 155 |
+
.ecn{padding:5px 13px;background:var(--bg4);color:var(--t1);border-radius:5px;font-size:.77rem;font-weight:600;border:1px solid var(--ln2);transition:.13s}
|
| 156 |
+
.ecn:hover{background:var(--bgh)}
|
| 157 |
+
.fups{display:flex;flex-wrap:wrap;gap:5px;padding:1px 3px;margin-top:2px;animation:fup .3s ease}
|
| 158 |
+
.fup{padding:4px 10px;background:var(--bg2);border:1px solid var(--ln2);border-radius:14px;font-size:.72rem;font-weight:600;color:var(--tl);transition:.13s}
|
| 159 |
+
.fup:hover{background:var(--tlbg);border-color:#00d4ff35;transform:translateY(-1px)}
|
| 160 |
+
.mb h1,.mb h2,.mb h3{font-weight:800;margin:10px 0 4px;line-height:1.3}
|
| 161 |
+
.mb h1{font-size:1.1em}.mb h2{font-size:1em}.mb h3{font-size:.95em}
|
| 162 |
+
.mb p{margin:4px 0}
|
| 163 |
+
.mb ul,.mb ol{padding-right:16px;margin:5px 0}.mb li{margin:3px 0;line-height:1.65}
|
| 164 |
+
.mb blockquote{border-right:3px solid var(--ac);padding-right:9px;margin:5px 0;color:var(--t2);font-style:italic}
|
| 165 |
+
.mb strong{font-weight:700;color:var(--tl)}.mb em{font-style:italic;color:var(--am)}
|
| 166 |
+
.mb table{width:100%;border-collapse:collapse;margin:7px 0;font-size:.82em;display:block;overflow-x:auto}
|
| 167 |
+
.mb th{background:var(--bg4);padding:5px 9px;border:1px solid var(--ln2);font-weight:700;text-align:right;white-space:nowrap}
|
| 168 |
+
.mb td{padding:5px 9px;border:1px solid var(--ln)}.mb tr:nth-child(even){background:var(--bg3)}
|
| 169 |
+
.mb a{color:var(--tl)}.mb hr{border:none;border-top:1px solid var(--ln2);margin:9px 0}
|
| 170 |
+
.mb code:not(pre code){background:#fff1;padding:2px 5px;border-radius:4px;font-family:var(--mono);font-size:.8em;border:1px solid var(--ln2)}
|
| 171 |
+
.mr.user .mb code:not(pre code){background:#fff2;border-color:#fff2}
|
| 172 |
+
.cbw{position:relative;margin:8px 0;border-radius:var(--r2);overflow:hidden;border:1px solid #30363d;max-width:100%}
|
| 173 |
+
.cbh{display:flex;align-items:center;justify-content:space-between;padding:7px 12px;background:#161b22;border-bottom:1px solid #30363d;gap:8px}
|
| 174 |
+
.cb-lang{font-family:var(--mono);font-size:.68rem;color:#58a6ff;font-weight:500;display:flex;align-items:center;gap:5px}
|
| 175 |
+
.cb-lang::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor;opacity:.65;flex-shrink:0}
|
| 176 |
+
.cb-lang.l-html::before{background:#e44d26}.cb-lang.l-css::before{background:#264de4}
|
| 177 |
+
.cb-lang.l-js::before,.cb-lang.l-javascript::before{background:#f7df1e}
|
| 178 |
+
.cb-lang.l-python::before{background:#3572A5}
|
| 179 |
+
.cb-lang.l-bash::before,.cb-lang.l-sh::before{background:#4EAA25}
|
| 180 |
+
.cb-btns{display:flex;gap:4px}
|
| 181 |
+
.cb-copy{display:flex;align-items:center;gap:3px;padding:3px 8px;background:#21262d;border:1px solid #30363d;border-radius:5px;color:#8b949e;font-size:.66rem;font-family:var(--font);transition:.13s;cursor:pointer}
|
| 182 |
+
.cb-copy:hover{background:#30363d;color:#e6edf3;border-color:#6e7681}
|
| 183 |
+
.cb-run{display:flex;align-items:center;gap:3px;padding:3px 9px;background:#1f6feb18;border:1px solid #1f6feb50;border-radius:5px;color:#58a6ff;font-size:.66rem;font-family:var(--font);transition:.13s;cursor:pointer}
|
| 184 |
+
.cb-run:hover{background:#1f6feb30;border-color:#1f6feb}
|
| 185 |
+
.cbw pre{margin:0!important;padding:14px!important;background:#0d1117!important;font-size:.79rem!important;line-height:1.65!important;overflow-x:auto}
|
| 186 |
+
.cbw pre code{font-family:var(--mono)!important;white-space:pre;word-break:normal}
|
| 187 |
+
.artifact{display:flex;align-items:center;gap:10px;padding:10px 13px;background:var(--bg2);border:1px solid var(--ln2);border-right:3px solid var(--ac);border-radius:var(--r2);margin-top:8px;max-width:80%;animation:fup .3s ease}
|
| 188 |
+
@media(max-width:680px){.artifact{max-width:91%}}
|
| 189 |
+
.art-icon{width:34px;height:34px;border-radius:8px;background:var(--acbg);border:1px solid var(--acln);display:flex;align-items:center;justify-content:center;font-size:.85rem;color:var(--ac);flex-shrink:0}
|
| 190 |
+
.art-info{flex:1;min-width:0}
|
| 191 |
+
.art-name{font-size:.8rem;font-weight:700;color:var(--t0);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 192 |
+
.art-meta{font-size:.64rem;color:var(--t3);margin-top:2px}
|
| 193 |
+
.art-btns{display:flex;gap:4px;flex-shrink:0;flex-wrap:wrap}
|
| 194 |
+
.ab{display:flex;align-items:center;gap:3px;padding:4px 8px;border-radius:5px;font-size:.66rem;font-weight:600;transition:.13s;white-space:nowrap}
|
| 195 |
+
.ab i{font-size:.6rem}
|
| 196 |
+
.ab.view{background:var(--acbg);border:1px solid var(--acln);color:var(--ac)}.ab.view:hover{background:var(--ac);color:#fff}
|
| 197 |
+
.ab.copy{background:var(--bg3);border:1px solid var(--ln2);color:var(--t2)}.ab.copy:hover{background:var(--bgh);color:var(--t0)}
|
| 198 |
+
.ab.run{background:#1f6feb18;border:1px solid #1f6feb50;color:#58a6ff}.ab.run:hover{background:#1f6feb;color:#fff}
|
| 199 |
+
.ab.dl{background:var(--gnbg);border:1px solid #22d48a35;color:var(--gn)}.ab.dl:hover{background:var(--gn);color:#000}
|
| 200 |
+
/* typing indicator */
|
| 201 |
+
.typing-ind{display:flex;align-items:center;gap:8px;padding:9px 13px;background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r3);border-bottom-right-radius:3px;width:fit-content}
|
| 202 |
+
.typing-dots{display:flex;gap:4px}
|
| 203 |
+
.typing-dot{width:7px;height:7px;border-radius:50%;background:var(--ac);animation:td 1.4s ease infinite}
|
| 204 |
+
.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}
|
| 205 |
+
@keyframes td{0%,80%,100%{transform:scale(.6);opacity:.3}40%{transform:scale(1.2);opacity:1}}
|
| 206 |
+
/* scroll button */
|
| 207 |
+
#scrbtn{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);display:none;align-items:center;gap:4px;padding:6px 14px;background:var(--bg3);border:1px solid var(--ln3);border-radius:16px;font-size:.73rem;font-weight:700;color:var(--t1);box-shadow:var(--s2);transition:.13s;z-index:5;white-space:nowrap}
|
| 208 |
+
#scrbtn:hover{background:var(--bgh)}
|
| 209 |
+
#scrbtn.on{display:flex}
|
| 210 |
+
#ia{background:var(--bg1);border-top:1px solid var(--ln);padding:9px 10px 11px;padding-bottom:calc(11px + env(safe-area-inset-bottom));flex-shrink:0}
|
| 211 |
+
.ia-in{max-width:780px;margin:0 auto}
|
| 212 |
+
.ib{display:flex;align-items:flex-end;gap:6px;background:var(--bg2);border:1.5px solid var(--ln2);border-radius:var(--r4);padding:7px 8px;transition:.13s}
|
| 213 |
+
.ib:focus-within{border-color:var(--ac);box-shadow:0 0 0 3px #7c6fff16}
|
| 214 |
+
#ci{flex:1;background:none;border:none;color:var(--t0);font-size:16px;line-height:1.55;resize:none;min-height:24px;max-height:150px;direction:rtl;padding:2px 0;scrollbar-width:none;font-family:var(--font)}
|
| 215 |
+
@media(min-width:480px){#ci{font-size:.92rem}}
|
| 216 |
+
#ci::-webkit-scrollbar{display:none}
|
| 217 |
+
#ci::placeholder{color:var(--t3)}
|
| 218 |
+
#ci:disabled{opacity:.4}
|
| 219 |
+
.ibtn{width:36px;height:36px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:.85rem;transition:.13s;flex-shrink:0}
|
| 220 |
+
#sndbtn{background:var(--ac);color:#fff;box-shadow:0 2px 9px #7c6fff40}
|
| 221 |
+
#sndbtn:hover:not(:disabled){background:var(--ach)}
|
| 222 |
+
#sndbtn:active:not(:disabled){transform:scale(.9)}
|
| 223 |
+
#sndbtn:disabled{opacity:.3;cursor:default;box-shadow:none}
|
| 224 |
+
#stopbtn{background:var(--rdbg);color:var(--rd);border:1px solid #ff5f6d28;display:none}
|
| 225 |
+
#stopbtn.on{display:flex}
|
| 226 |
+
#stopbtn:hover{background:#ff5f6d1e}
|
| 227 |
+
.ifoot{display:flex;align-items:center;justify-content:space-between;margin-top:5px;padding:0 1px;font-size:.66rem;color:var(--t3)}
|
| 228 |
+
.ihnts{display:flex;align-items:center;gap:7px}
|
| 229 |
+
@media(max-width:480px){.ihnts{display:none}}
|
| 230 |
+
kbd{background:var(--bg3);border:1px solid var(--ln2);border-radius:3px;padding:1px 4px;font-family:var(--mono);font-size:.59rem}
|
| 231 |
+
.tkc span{color:var(--ac);font-weight:700}
|
| 232 |
+
#ov{display:none;position:fixed;inset:0;background:#0009;z-index:40}
|
| 233 |
+
#ov.on{display:block}
|
| 234 |
+
/* sandbox modal */
|
| 235 |
+
#sbmod{position:fixed;inset:0;background:#000c;z-index:200;display:none;align-items:center;justify-content:center;padding:12px}
|
| 236 |
+
#sbmod.open{display:flex}
|
| 237 |
+
.sbw{background:var(--bg1);border:1px solid var(--ln2);border-radius:var(--r3);width:100%;max-width:1000px;height:90vh;max-height:90vh;display:flex;flex-direction:column;box-shadow:var(--s3);overflow:hidden}
|
| 238 |
+
.sbw-head{display:flex;align-items:center;gap:8px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--ln2);flex-shrink:0}
|
| 239 |
+
.sbw-title{flex:1;font-size:.85rem;font-weight:700;display:flex;align-items:center;gap:7px}
|
| 240 |
+
.sbw-title i{color:var(--ac)}
|
| 241 |
+
.sb-tabs{display:flex;gap:3px}
|
| 242 |
+
.sb-tab{padding:5px 12px;border-radius:5px;font-size:.74rem;font-weight:600;color:var(--t2);transition:.13s;cursor:pointer}
|
| 243 |
+
.sb-tab.on{background:var(--acbg);color:var(--ac);border:1px solid var(--acln)}
|
| 244 |
+
.sb-tab:hover:not(.on){background:var(--bgh);color:var(--t1)}
|
| 245 |
+
.sbw-close{width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:.8rem;color:var(--t2);transition:.13s}
|
| 246 |
+
.sbw-close:hover{background:var(--rdbg);color:var(--rd)}
|
| 247 |
+
.sbw-body{flex:1;display:flex;overflow:hidden}
|
| 248 |
+
@media(max-width:600px){.sbw-body{flex-direction:column}}
|
| 249 |
+
.sb-code-pane{flex:1;min-width:0;display:flex;flex-direction:column;border-left:1px solid var(--ln2)}
|
| 250 |
+
.sb-prev-pane{flex:1;min-width:0;display:flex;flex-direction:column}
|
| 251 |
+
.sb-pane-lbl{padding:6px 12px;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--t3);background:var(--bg2);border-bottom:1px solid var(--ln);flex-shrink:0}
|
| 252 |
+
.sb-pane-lbl i{margin-left:4px}
|
| 253 |
+
.sb-editor{flex:1;resize:none;background:#0d1117;border:none;color:#e6edf3;font-family:var(--mono);font-size:.8rem;line-height:1.65;padding:14px;overflow:auto;direction:ltr;text-align:left}
|
| 254 |
+
.sb-editor:focus{outline:none}
|
| 255 |
+
.sb-frame{flex:1;border:none;background:#fff}
|
| 256 |
+
.sbw-foot{display:flex;align-items:center;gap:8px;padding:8px 14px;background:var(--bg2);border-top:1px solid var(--ln2);flex-shrink:0}
|
| 257 |
+
.sb-run{display:flex;align-items:center;gap:5px;padding:6px 14px;background:var(--gn);color:#000;border-radius:var(--r1);font-size:.8rem;font-weight:700;transition:.13s}
|
| 258 |
+
.sb-run:hover{background:#2fea9e}.sb-run:active{transform:scale(.95)}
|
| 259 |
+
.sb-stat{font-size:.7rem;color:var(--t3);flex:1}
|
| 260 |
+
/* rename modal */
|
| 261 |
+
.modal{position:fixed;inset:0;background:#0009;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px}
|
| 262 |
+
.mbox{background:var(--bg2);border:1px solid var(--ln2);border-radius:var(--r3);padding:20px;width:100%;max-width:340px;box-shadow:var(--s3)}
|
| 263 |
+
.mttl{font-size:.94rem;font-weight:800;margin-bottom:12px}
|
| 264 |
+
.mi{width:100%;padding:8px 10px;background:var(--bg3);border:1.5px solid var(--ln2);border-radius:var(--r1);color:var(--t0);font-size:.88rem;direction:rtl;transition:.13s}
|
| 265 |
+
.mi:focus{border-color:var(--ac);outline:none}
|
| 266 |
+
.mbtns{display:flex;gap:7px;margin-top:12px;justify-content:flex-end}
|
| 267 |
+
.mok{padding:7px 15px;background:var(--ac);color:#fff;border-radius:var(--r1);font-weight:700;font-size:.82rem;transition:.13s}
|
| 268 |
+
.mok:hover{background:var(--ach)}
|
| 269 |
+
.mcc{padding:7px 15px;background:var(--bg4);color:var(--t1);border-radius:var(--r1);font-weight:600;font-size:.82rem;border:1px solid var(--ln2);transition:.13s}
|
| 270 |
+
.mcc:hover{background:var(--bgh)}
|
| 271 |
+
/* toast */
|
| 272 |
+
#tw{position:fixed;bottom:72px;left:50%;transform:translateX(-50%);z-index:300;display:flex;flex-direction:column;align-items:center;gap:5px;pointer-events:none}
|
| 273 |
+
.toast{padding:7px 15px;background:var(--bg3);border:1px solid var(--ln2);border-radius:16px;font-size:.77rem;font-weight:600;color:var(--t1);box-shadow:var(--s2);white-space:nowrap;animation:tp .2s ease}
|
| 274 |
+
.toast.ok{color:var(--gn);border-color:#22d48a35}.toast.er{color:var(--rd);border-color:#ff5f6d35}
|
| 275 |
+
@keyframes tp{from{opacity:0;transform:translateY(7px)scale(.87)}to{opacity:1;transform:none}}
|
| 276 |
+
/* show-thinking toggle in sidebar */
|
| 277 |
+
.think-tog-row{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:var(--bg3);border:1px solid var(--ln2);border-radius:var(--r1)}
|
| 278 |
+
</style>
|
| 279 |
+
</head>
|
| 280 |
+
<body>
|
| 281 |
+
<div id="app">
|
| 282 |
+
<aside id="sb">
|
| 283 |
+
<div class="sb-head">
|
| 284 |
+
<button class="new-btn" id="newConvBtn"><i class="fa-solid fa-plus"></i> محادثة جديدة</button>
|
| 285 |
+
</div>
|
| 286 |
+
<div class="cl" id="cl"></div>
|
| 287 |
+
<div class="sb-bot">
|
| 288 |
+
<div><div class="lbl"><i class="fa-solid fa-plug"></i>المزود</div>
|
| 289 |
+
<select class="sel" id="psel"><option value="">⏳ جاري التحميل...</option></select></div>
|
| 290 |
+
<div><div class="lbl"><i class="fa-solid fa-brain"></i>النموذج</div>
|
| 291 |
+
<select class="sel" id="msel"><option value="">اختر مزوداً</option></select></div>
|
| 292 |
+
<div>
|
| 293 |
+
<div class="think-tog-row">
|
| 294 |
+
<span class="tog-lbl"><i class="fa-solid fa-brain" style="color:var(--am)"></i>عرض التفكير</span>
|
| 295 |
+
<label class="tog"><input type="checkbox" id="showThinkTog" checked><div class="tog-t"></div></label>
|
| 296 |
+
</div>
|
| 297 |
+
</div>
|
| 298 |
+
<div class="tog-row">
|
| 299 |
+
<span class="tog-lbl"><i class="fa-solid fa-bolt"></i>البث المباشر</span>
|
| 300 |
+
<label class="tog"><input type="checkbox" id="stog" checked><div class="tog-t"></div></label>
|
| 301 |
+
</div>
|
| 302 |
+
<div class="tog-row">
|
| 303 |
+
<span class="tog-lbl"><i class="fa-solid fa-lightbulb"></i>اقتراحات المتابعة</span>
|
| 304 |
+
<label class="tog"><input type="checkbox" id="fupTog" checked><div class="tog-t"></div></label>
|
| 305 |
+
</div>
|
| 306 |
+
<div class="tog-row">
|
| 307 |
+
<span class="tog-lbl"><i class="fa-solid fa-circle-half-stroke"></i>الوضع الفاتح</span>
|
| 308 |
+
<label class="tog"><input type="checkbox" id="themeTog"><div class="tog-t"></div></label>
|
| 309 |
+
</div>
|
| 310 |
+
<div><div class="lbl"><i class="fa-solid fa-scroll"></i>تعليمات النظام</div>
|
| 311 |
+
<textarea class="sys-ta" id="sysp" placeholder="أنت مساعد ذكاء اصطناعي مفيد..."></textarea></div>
|
| 312 |
+
</div>
|
| 313 |
+
</aside>
|
| 314 |
+
<div id="ov"></div>
|
| 315 |
+
<div id="main">
|
| 316 |
+
<header id="hdr">
|
| 317 |
+
<button class="hb" id="sbToggleBtn"><i class="fa-solid fa-bars"></i></button>
|
| 318 |
+
<div class="logo"><div class="logo-mk"><i class="fa-solid fa-robot"></i></div>g4f<span class="logo-ac">pro</span></div>
|
| 319 |
+
<div class="hdr-m"><div class="hdr-t" id="htitle">محادثة جديدة</div></div>
|
| 320 |
+
<div class="hdr-r">
|
| 321 |
+
<div class="hst">متصل</div>
|
| 322 |
+
<div class="ppill"><div class="pd"></div><span id="ptxt">Auto</span></div>
|
| 323 |
+
<button class="hb" id="thbtn"><i class="fa-solid fa-moon"></i></button>
|
| 324 |
+
<button class="hb" id="reloadBtn"><i class="fa-solid fa-rotate" id="rlic"></i></button>
|
| 325 |
+
</div>
|
| 326 |
+
</header>
|
| 327 |
+
<div id="mwrap">
|
| 328 |
+
<div id="msgs"></div>
|
| 329 |
+
<button id="scrbtn"><i class="fa-solid fa-arrow-down"></i> للأسفل</button>
|
| 330 |
+
</div>
|
| 331 |
+
<div id="ia">
|
| 332 |
+
<div class="ia-in">
|
| 333 |
+
<div class="ib">
|
| 334 |
+
<textarea id="ci" placeholder="اكتب رسالتك..." rows="1" disabled></textarea>
|
| 335 |
+
<button class="ibtn" id="stopbtn"><i class="fa-solid fa-stop"></i></button>
|
| 336 |
+
<button class="ibtn" id="sndbtn" disabled><i class="fa-solid fa-paper-plane"></i></button>
|
| 337 |
+
</div>
|
| 338 |
+
<div class="ifoot">
|
| 339 |
+
<div class="ihnts"><span><kbd>Enter</kbd> إرسال</span><span><kbd>Shift+Enter</kbd> سطر</span></div>
|
| 340 |
+
<div class="tkc">~<span id="tkc">0</span> توكن</div>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
</div>
|
| 345 |
+
</div>
|
| 346 |
+
<!-- Rename modal -->
|
| 347 |
+
<div class="modal" id="rmod" style="display:none">
|
| 348 |
+
<div class="mbox">
|
| 349 |
+
<div class="mttl">إعادة تسمية المحادثة</div>
|
| 350 |
+
<input class="mi" id="rmi" type="text" placeholder="اسم المحادثة...">
|
| 351 |
+
<div class="mbtns">
|
| 352 |
+
<button class="mcc" id="renCancelBtn">إلغاء</button>
|
| 353 |
+
<button class="mok" id="renSaveBtn">حفظ</button>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
<!-- Sandbox modal -->
|
| 358 |
+
<div id="sbmod">
|
| 359 |
+
<div class="sbw">
|
| 360 |
+
<div class="sbw-head">
|
| 361 |
+
<div class="sbw-title"><i class="fa-solid fa-code"></i><span id="sb-fn">index.html</span></div>
|
| 362 |
+
<div class="sb-tabs">
|
| 363 |
+
<div class="sb-tab on" data-tab="split">تقسيم</div>
|
| 364 |
+
<div class="sb-tab" data-tab="code">كود</div>
|
| 365 |
+
<div class="sb-tab" data-tab="preview">معاينة</div>
|
| 366 |
+
</div>
|
| 367 |
+
<button class="sbw-close" id="sbCloseBtn"><i class="fa-solid fa-xmark"></i></button>
|
| 368 |
+
</div>
|
| 369 |
+
<div class="sbw-body">
|
| 370 |
+
<div class="sb-code-pane" id="sb-cpane">
|
| 371 |
+
<div class="sb-pane-lbl"><i class="fa-solid fa-code" style="color:var(--ac)"></i>محرر الكود</div>
|
| 372 |
+
<textarea class="sb-editor" id="sb-ed" spellcheck="false" dir="ltr"></textarea>
|
| 373 |
+
</div>
|
| 374 |
+
<div class="sb-prev-pane" id="sb-ppane">
|
| 375 |
+
<div class="sb-pane-lbl"><i class="fa-solid fa-eye" style="color:var(--tl)"></i>معاينة حية</div>
|
| 376 |
+
<iframe class="sb-frame" id="sb-iframe" sandbox="allow-scripts allow-same-origin" title="preview"></iframe>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
+
<div class="sbw-foot">
|
| 380 |
+
<button class="sb-run" id="sbRunBtn"><i class="fa-solid fa-play"></i> تشغيل</button>
|
| 381 |
+
<span class="sb-stat" id="sb-stat">جاهز</span>
|
| 382 |
+
</div>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
<div id="tw"></div>
|
| 386 |
+
|
| 387 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/11.1.1/marked.min.js"></script>
|
| 388 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
| 389 |
+
<script>
|
| 390 |
+
'use strict';
|
| 391 |
+
|
| 392 |
+
/* ═══════════════════════════════════════════════════════
|
| 393 |
+
MODULE: Utils
|
| 394 |
+
════════════���══════════════════════════════════════════ */
|
| 395 |
+
const Utils = (() => {
|
| 396 |
+
function esc(s) {
|
| 397 |
+
return String(s)
|
| 398 |
+
.replace(/&/g,'&').replace(/</g,'<')
|
| 399 |
+
.replace(/>/g,'>').replace(/"/g,'"');
|
| 400 |
+
}
|
| 401 |
+
function fmtBytes(n) { return n>1024?(n/1024).toFixed(1)+'KB':n+'B'; }
|
| 402 |
+
function uid() { return 'c-'+Date.now()+'-'+Math.random().toString(36).slice(2,8); }
|
| 403 |
+
function groupByDate(convs) {
|
| 404 |
+
const now=Date.now();
|
| 405 |
+
const groups={'اليوم':[],'أمس':[],'هذا الأسبوع':[],'أقدم':[]};
|
| 406 |
+
for(const c of convs){
|
| 407 |
+
const d=now-c.created;
|
| 408 |
+
if(d<86400000) groups['اليوم'].push(c);
|
| 409 |
+
else if(d<172800000) groups['أمس'].push(c);
|
| 410 |
+
else if(d<604800000) groups['هذا الأسبوع'].push(c);
|
| 411 |
+
else groups['أقدم'].push(c);
|
| 412 |
+
}
|
| 413 |
+
return groups;
|
| 414 |
+
}
|
| 415 |
+
function suggestFollowUps(text) {
|
| 416 |
+
const l=text.toLowerCase();
|
| 417 |
+
if(l.includes('html')||l.includes('css')||l.includes('كود')||l.includes('python')||l.includes('javascript'))
|
| 418 |
+
return ['اشرح هذا الكود','كيف أحسّنه؟','أضف ميزة جديدة'];
|
| 419 |
+
if(l.includes('شرح')||l.includes('مفهوم'))
|
| 420 |
+
return ['أعطني مثالاً عملياً','ما الفرق مع البدائل؟'];
|
| 421 |
+
return ['أخبرني أكثر','اشرح بمثال'];
|
| 422 |
+
}
|
| 423 |
+
function wrapForPreview(code,lang) {
|
| 424 |
+
if(lang==='css') return `<!DOCTYPE html><html><head><style>${code}</style></head><body></body></html>`;
|
| 425 |
+
if(lang==='js'||lang==='javascript') return `<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><div id="out"></div><script>${code}<\/script></body></html>`;
|
| 426 |
+
return code;
|
| 427 |
+
}
|
| 428 |
+
function extractArtifacts(text) {
|
| 429 |
+
const WEB = new Set(['html', 'css', 'js', 'javascript']);
|
| 430 |
+
const results = [];
|
| 431 |
+
// هذا التعبير يضمن أن الكود لا يتمدد ويخرب الزوايا
|
| 432 |
+
const re = /```(\w+)?\n([\s\S]*?)```/g;
|
| 433 |
+
let m;
|
| 434 |
+
while ((m = re.exec(text)) !== null) {
|
| 435 |
+
const lang = (m[1] || 'text').toLowerCase();
|
| 436 |
+
const code = m[2].trim();
|
| 437 |
+
// منع الانهيار إذا كان الكود طويلاً جداً وعرضياً
|
| 438 |
+
if (code.length < 30) continue;
|
| 439 |
+
results.push({
|
| 440 |
+
lang,
|
| 441 |
+
code,
|
| 442 |
+
filename: `code.${lang === 'javascript' ? 'js' : lang}`
|
| 443 |
+
});
|
| 444 |
+
}
|
| 445 |
+
return results;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
return {esc,fmtBytes,uid,groupByDate,suggestFollowUps,wrapForPreview,extractArtifacts};
|
| 449 |
+
})();
|
| 450 |
+
|
| 451 |
+
/* ═══════════════════════════════════════════════════════
|
| 452 |
+
MODULE: Store
|
| 453 |
+
═══════════════════════════════════════════════════════ */
|
| 454 |
+
const Store = (() => {
|
| 455 |
+
const get=(k,d=null)=>{try{const v=localStorage.getItem(k);return v===null?d:JSON.parse(v);}catch{return d;}};
|
| 456 |
+
const set=(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v));}catch{}};
|
| 457 |
+
return {
|
| 458 |
+
getConvs: () => get('g4c',[]),
|
| 459 |
+
setConvs: (v) => set('g4c',v),
|
| 460 |
+
getActiveId: () => get('g4a',''),
|
| 461 |
+
setActiveId: (v) => set('g4a',v),
|
| 462 |
+
getTheme: () => get('g4t','dark'),
|
| 463 |
+
setTheme: (v) => set('g4t',v),
|
| 464 |
+
getProvider: () => get('g4p',''),
|
| 465 |
+
setProvider: (v) => set('g4p',v),
|
| 466 |
+
getModel: () => get('g4m',''),
|
| 467 |
+
setModel: (v) => set('g4m',v),
|
| 468 |
+
getStream: () => get('g4s','1')==='1',
|
| 469 |
+
setStream: (v) => set('g4s',v?'1':'0'),
|
| 470 |
+
getShowThink: () => get('g4th','1')==='1',
|
| 471 |
+
setShowThink: (v) => set('g4th',v?'1':'0'),
|
| 472 |
+
getShowFup: () => get('g4fu','1')==='1',
|
| 473 |
+
setShowFup: (v) => set('g4fu',v?'1':'0'),
|
| 474 |
+
getDraft: () => get('g4d',''),
|
| 475 |
+
setDraft: (v) => set('g4d',v),
|
| 476 |
+
};
|
| 477 |
+
})();
|
| 478 |
+
|
| 479 |
+
/* ═══════════════════════════════════════════════════════
|
| 480 |
+
MODULE: API — все вызовы к серверу здесь
|
| 481 |
+
Сервер: Flask g4fpro v2.0
|
| 482 |
+
Эндпоинты:
|
| 483 |
+
GET /api/providers → {ok, providers:{name:{models,type,needs_auth}}, total}
|
| 484 |
+
GET /api/models/<provider> → {ok, models:[...]}
|
| 485 |
+
POST /api/reload → {ok, providers:N}
|
| 486 |
+
POST /chat → {ok, reply, thinking:[], model, provider, time, conversation_id}
|
| 487 |
+
POST /chat/stream → SSE: start | thinking | chunk | done | error
|
| 488 |
+
═══════════════════════════════════════════════════════ */
|
| 489 |
+
const API = (() => {
|
| 490 |
+
const post=(url,body)=>fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
| 491 |
+
async function getProviders(){const r=await fetch('/api/providers');return r.json();}
|
| 492 |
+
async function getModels(provider){const r=await fetch(`/api/models/${provider}`);return r.json();}
|
| 493 |
+
async function reload(){return post('/api/reload',{});}
|
| 494 |
+
async function chat(payload){const r=await post('/chat',payload);return r.json();}
|
| 495 |
+
function streamChat(payload){return post('/chat/stream',payload);}
|
| 496 |
+
return {getProviders,getModels,reload,chat,streamChat};
|
| 497 |
+
})();
|
| 498 |
+
|
| 499 |
+
/* ═══════════════════════════════════════════════════════
|
| 500 |
+
MODULE: MD — markdown renderer
|
| 501 |
+
═══════════════════════════════════════════════════════ */
|
| 502 |
+
const MD = (() => {
|
| 503 |
+
const RUNNABLE=new Set(['html','css','js','javascript','jsx','tsx','svg']);
|
| 504 |
+
function _hi(code,lang){
|
| 505 |
+
try{
|
| 506 |
+
if(lang&&lang!=='text'&&hljs.getLanguage(lang)) return hljs.highlight(code,{language:lang}).value;
|
| 507 |
+
return hljs.highlightAuto(code).value;
|
| 508 |
+
}catch{return Utils.esc(code);}
|
| 509 |
+
}
|
| 510 |
+
function _buildBlock(lang,raw){
|
| 511 |
+
const hi=_hi(raw,lang);
|
| 512 |
+
const runBtn=RUNNABLE.has(lang)
|
| 513 |
+
?`<button class="cb-run" data-action="run"><i class="fa-solid fa-play"></i> تشغيل</button>`:''
|
| 514 |
+
return `<div class="cbw">
|
| 515 |
+
<div class="cbh">
|
| 516 |
+
<span class="cb-lang l-${Utils.esc(lang)}" data-lang="${Utils.esc(lang)}">${Utils.esc(lang)||'text'}</span>
|
| 517 |
+
<div class="cb-btns">${runBtn}<button class="cb-copy" data-action="copy"><i class="fa-regular fa-copy"></i> نسخ</button></div>
|
| 518 |
+
</div>
|
| 519 |
+
<pre><code class="hljs language-${Utils.esc(lang)}">${hi}</code></pre>
|
| 520 |
+
</div>`;
|
| 521 |
+
}
|
| 522 |
+
function render(text){
|
| 523 |
+
if(!text)return'';
|
| 524 |
+
const blocks=[];
|
| 525 |
+
const ph=(lang,code)=>{const i=blocks.length;blocks.push({lang:(lang||'text').toLowerCase(),code:code.trim()});return`%%CB${i}%%`;};
|
| 526 |
+
const withPH = text.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (_, lang, code) => ph(lang, code));
|
| 527 |
+
|
| 528 |
+
let html;
|
| 529 |
+
try{html=marked.parse(withPH);}catch{html=Utils.esc(withPH);}
|
| 530 |
+
html=html.replace(/%%CB(\d+)%%/g,(_,i)=>{const{lang,code}=blocks[parseInt(i)];return _buildBlock(lang,code);});
|
| 531 |
+
return html;
|
| 532 |
+
}
|
| 533 |
+
return {render};
|
| 534 |
+
})();
|
| 535 |
+
|
| 536 |
+
/* ═══════════════════════════════════════════════════════
|
| 537 |
+
MODULE: Sandbox
|
| 538 |
+
═══════════════════════════════════════════════════════ */
|
| 539 |
+
const Sandbox = (() => {
|
| 540 |
+
let _deb=null;
|
| 541 |
+
const el={
|
| 542 |
+
modal:()=>document.getElementById('sbmod'),
|
| 543 |
+
fname:()=>document.getElementById('sb-fn'),
|
| 544 |
+
editor:()=>document.getElementById('sb-ed'),
|
| 545 |
+
frame:()=>document.getElementById('sb-iframe'),
|
| 546 |
+
status:()=>document.getElementById('sb-stat'),
|
| 547 |
+
cpane:()=>document.getElementById('sb-cpane'),
|
| 548 |
+
ppane:()=>document.getElementById('sb-ppane'),
|
| 549 |
+
};
|
| 550 |
+
function _write(code){
|
| 551 |
+
try{
|
| 552 |
+
const doc=el.frame().contentDocument||el.frame().contentWindow.document;
|
| 553 |
+
doc.open();doc.write(code);doc.close();
|
| 554 |
+
el.status().textContent='✅ تم التشغيل';el.status().style.color='var(--gn)';
|
| 555 |
+
}catch(e){el.status().textContent='❌ '+e.message.slice(0,60);el.status().style.color='var(--rd)';}
|
| 556 |
+
}
|
| 557 |
+
function open(code,filename){
|
| 558 |
+
el.fname().textContent=filename||'code';
|
| 559 |
+
el.editor().value=code||'';
|
| 560 |
+
el.modal().classList.add('open');
|
| 561 |
+
setTab('split');_write(code);
|
| 562 |
+
el.editor().oninput=()=>{clearTimeout(_deb);_deb=setTimeout(()=>_write(el.editor().value),400);};
|
| 563 |
+
}
|
| 564 |
+
function close(){el.modal().classList.remove('open');el.editor().oninput=null;clearTimeout(_deb);}
|
| 565 |
+
function run(){_write(el.editor().value);}
|
| 566 |
+
function setTab(tab){
|
| 567 |
+
document.querySelectorAll('.sb-tab').forEach(t=>t.classList.toggle('on',t.dataset.tab===tab));
|
| 568 |
+
const show=(pane,vis)=>{pane.style.display=vis?'flex':'none';};
|
| 569 |
+
show(el.cpane(),tab!=='preview');show(el.ppane(),tab!=='code');
|
| 570 |
+
}
|
| 571 |
+
return {open,close,run,setTab};
|
| 572 |
+
})();
|
| 573 |
+
|
| 574 |
+
/* ═══════════════════════════════════════════════════════
|
| 575 |
+
MODULE: UI — DOM builders
|
| 576 |
+
═══════════════════════════════════════════════════════ */
|
| 577 |
+
const UI = (() => {
|
| 578 |
+
function msgHeader(role,model,time,pinned){
|
| 579 |
+
const hdr=document.createElement('div');hdr.className='mhdr';
|
| 580 |
+
const av=document.createElement('div');av.className=role==='user'?'mav u':'mav b';
|
| 581 |
+
av.innerHTML=role==='user'?'<i class="fa-solid fa-user"></i>':'<i class="fa-solid fa-robot"></i>';
|
| 582 |
+
const ts=document.createElement('span');ts.textContent=time;
|
| 583 |
+
if(role==='user'){
|
| 584 |
+
if(pinned)hdr.appendChild(_pin());hdr.appendChild(ts);hdr.appendChild(av);
|
| 585 |
+
} else {
|
| 586 |
+
hdr.appendChild(av);
|
| 587 |
+
if(model){const tag=document.createElement('span');tag.className='mtag';tag.textContent=model;hdr.appendChild(tag);}
|
| 588 |
+
hdr.appendChild(ts);if(pinned)hdr.appendChild(_pin());
|
| 589 |
+
}
|
| 590 |
+
return hdr;
|
| 591 |
+
}
|
| 592 |
+
function _pin(){const s=document.createElement('span');s.className='pinbdg';s.innerHTML='<i class="fa-solid fa-thumbtack"></i>';return s;}
|
| 593 |
+
function msgActions(role,idx,pinned){
|
| 594 |
+
const div=document.createElement('div');div.className='macts';
|
| 595 |
+
if(role==='user'){
|
| 596 |
+
div.innerHTML=`
|
| 597 |
+
<button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
|
| 598 |
+
<button class="ma" data-action="edit" data-idx="${idx}"><i class="fa-solid fa-pen"></i> تعديل</button>
|
| 599 |
+
<button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
|
| 600 |
+
} else {
|
| 601 |
+
div.innerHTML=`
|
| 602 |
+
<button class="ma" data-action="copy" data-idx="${idx}"><i class="fa-regular fa-copy"></i> نسخ</button>
|
| 603 |
+
<button class="ma rg" data-action="regen" data-idx="${idx}"><i class="fa-solid fa-rotate-right"></i> إعادة</button>
|
| 604 |
+
<button class="ma pn${pinned?' on':''}" data-action="pin" data-idx="${idx}"><i class="fa-solid fa-thumbtack"></i></button>`;
|
| 605 |
+
}
|
| 606 |
+
return div;
|
| 607 |
+
}
|
| 608 |
+
function thinkingBlock(lines,summary){
|
| 609 |
+
const div=document.createElement('div');div.className='thinking-block';
|
| 610 |
+
const sumText=summary||(lines.length?lines[0].slice(0,60)+'…':'');
|
| 611 |
+
const linesHTML=lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
|
| 612 |
+
div.innerHTML=`
|
| 613 |
+
<div class="thinking-header">
|
| 614 |
+
<i class="thinking-icon fa-solid fa-brain"></i>
|
| 615 |
+
<span class="thinking-label">تفكير النموذج (${lines.length} سطر)</span>
|
| 616 |
+
<span class="thinking-summary">${Utils.esc(sumText)}</span>
|
| 617 |
+
<i class="thinking-toggle fa-solid fa-chevron-down"></i>
|
| 618 |
+
</div>
|
| 619 |
+
<div class="thinking-body">${linesHTML}</div>`;
|
| 620 |
+
div.querySelector('.thinking-header').addEventListener('click',()=>div.classList.toggle('open'));
|
| 621 |
+
return div;
|
| 622 |
+
}
|
| 623 |
+
function artifactCard(art){
|
| 624 |
+
const ICONS={html:'fa-file-code',css:'fa-palette',js:'fa-square-js',javascript:'fa-square-js',jsx:'fa-atom',tsx:'fa-atom',svg:'fa-image'};
|
| 625 |
+
const icon=ICONS[art.lang]||'fa-code';
|
| 626 |
+
const div=document.createElement('div');div.className='artifact';div._artData=art;
|
| 627 |
+
div.innerHTML=`
|
| 628 |
+
<div class="art-icon"><i class="fa-solid ${icon}"></i></div>
|
| 629 |
+
<div class="art-info">
|
| 630 |
+
<div class="art-name">${Utils.esc(art.filename)}</div>
|
| 631 |
+
<div class="art-meta">${art.lang.toUpperCase()} · ${Utils.fmtBytes(art.bytes)}</div>
|
| 632 |
+
</div>
|
| 633 |
+
<div class="art-btns">
|
| 634 |
+
<button class="ab view" data-action="art-view"><i class="fa-solid fa-eye"></i> عرض</button>
|
| 635 |
+
<button class="ab copy" data-action="art-copy"><i class="fa-regular fa-copy"></i> نسخ</button>
|
| 636 |
+
<button class="ab run" data-action="art-run"><i class="fa-solid fa-play"></i> تشغيل</button>
|
| 637 |
+
<button class="ab dl" data-action="art-dl"><i class="fa-solid fa-download"></i></button>
|
| 638 |
+
</div>`;
|
| 639 |
+
return div;
|
| 640 |
+
}
|
| 641 |
+
function typingRow(id,label){
|
| 642 |
+
const row=document.createElement('div');row.className='mr assistant';row.id=id;
|
| 643 |
+
const lbl=label||'يكتب...';
|
| 644 |
+
row.innerHTML=`
|
| 645 |
+
<div class="mhdr"><div class="mav b"><i class="fa-solid fa-robot"></i></div></div>
|
| 646 |
+
<div class="typing-ind">
|
| 647 |
+
<div class="typing-dots"><div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div></div>
|
| 648 |
+
<span style="font-size:.73rem;color:var(--t2)">${Utils.esc(lbl)}</span>
|
| 649 |
+
</div>`;
|
| 650 |
+
return row;
|
| 651 |
+
}
|
| 652 |
+
function welcomeScreen(){
|
| 653 |
+
const div=document.createElement('div');div.id='wlc';
|
| 654 |
+
div.innerHTML=`
|
| 655 |
+
<div class="wico"><i class="fa-solid fa-robot"></i></div>
|
| 656 |
+
<div class="wt">مرحباً في <span>g4fpro</span></div>
|
| 657 |
+
<div class="ws">واجهة AI تدعم جميع مزودي g4f تلقائياً — بدون مفاتيح API</div>
|
| 658 |
+
<div class="sg">
|
| 659 |
+
<div class="sc" data-prompt="اشرح الذكاء الاصطناعي بأسلوب بسيط"><span class="sc-i">🤖</span><div class="sc-t">اشرح الذكاء الاصطناعي</div><div class="sc-s">تفسير مبسط</div></div>
|
| 660 |
+
<div class="sc" data-prompt="اكتب صفحة HTML احترافية مع CSS"><span class="sc-i">💻</span><div class="sc-t">صفحة HTML + CSS</div><div class="sc-s">مع معاينة مباشرة</div></div>
|
| 661 |
+
<div class="sc" data-prompt="اكتب رسالة بريد إلكتروني احترافية لطلب اجتماع"><span class="sc-i">✉️</span><div class="sc-t">رسالة احترافية</div><div class="sc-s">بريد عمل</div></div>
|
| 662 |
+
<div class="sc" data-prompt="ما أفضل 5 مكتبات Python لعام 2025 مع أمثلة؟"><span class="sc-i">📊</span><div class="sc-t">أفضل مكتبات 2025</div><div class="sc-s">Python</div></div>
|
| 663 |
+
</div>`;
|
| 664 |
+
return div;
|
| 665 |
+
}
|
| 666 |
+
function followUpRow(suggestions){
|
| 667 |
+
const div=document.createElement('div');div.className='fups';
|
| 668 |
+
for(const s of suggestions){
|
| 669 |
+
const btn=document.createElement('button');
|
| 670 |
+
btn.className='fup';btn.textContent=s;btn.dataset.prompt=s;
|
| 671 |
+
div.appendChild(btn);
|
| 672 |
+
}
|
| 673 |
+
return div;
|
| 674 |
+
}
|
| 675 |
+
function toast(container,msg,type=''){
|
| 676 |
+
const el=document.createElement('div');el.className=`toast ${type}`.trim();el.textContent=msg;
|
| 677 |
+
container.appendChild(el);
|
| 678 |
+
setTimeout(()=>{el.style.transition='opacity .3s';el.style.opacity='0';setTimeout(()=>el.remove(),300);},2400);
|
| 679 |
+
}
|
| 680 |
+
return {msgHeader,msgActions,thinkingBlock,artifactCard,typingRow,welcomeScreen,followUpRow,toast};
|
| 681 |
+
})();
|
| 682 |
+
|
| 683 |
+
/* ═══════════════════════════════════════════════════════
|
| 684 |
+
MODULE: App
|
| 685 |
+
═══════════════════════════════════════════════════════ */
|
| 686 |
+
const App = (() => {
|
| 687 |
+
// ── State ──────────────────────────────────────
|
| 688 |
+
const state = {
|
| 689 |
+
convs: Store.getConvs(),
|
| 690 |
+
activeId: Store.getActiveId(),
|
| 691 |
+
provs: {},
|
| 692 |
+
sbOpen: window.innerWidth > 680,
|
| 693 |
+
busy: false,
|
| 694 |
+
stopReq: false,
|
| 695 |
+
renId: null,
|
| 696 |
+
userScrolled:false,
|
| 697 |
+
};
|
| 698 |
+
const active=()=>state.convs.find(c=>c.id===state.activeId)||null;
|
| 699 |
+
function persist(){Store.setConvs(state.convs);Store.setActiveId(state.activeId);}
|
| 700 |
+
function getServerId(conv){if(!conv.serverId){conv.serverId=Utils.uid();persist();}return conv.serverId;}
|
| 701 |
+
|
| 702 |
+
// ── DOM refs ───────────────────────────────────
|
| 703 |
+
const $=id=>document.getElementById(id);
|
| 704 |
+
const DOM={
|
| 705 |
+
msgs: ()=>$('msgs'), mwrap: ()=>$('mwrap'),
|
| 706 |
+
ci: ()=>$('ci'), sndbtn: ()=>$('sndbtn'),
|
| 707 |
+
stopbtn:()=>$('stopbtn'),psel: ()=>$('psel'),
|
| 708 |
+
msel: ()=>$('msel'), stog: ()=>$('stog'),
|
| 709 |
+
sysp: ()=>$('sysp'), htitle: ()=>$('htitle'),
|
| 710 |
+
tw: ()=>$('tw'), cl: ()=>$('cl'),
|
| 711 |
+
ptxt: ()=>$('ptxt'), scrbtn: ()=>$('scrbtn'),
|
| 712 |
+
rmod: ()=>$('rmod'), rmi: ()=>$('rmi'),
|
| 713 |
+
rlic: ()=>$('rlic'), sb: ()=>$('sb'),
|
| 714 |
+
ov: ()=>$('ov'), thbtn: ()=>$('thbtn'),
|
| 715 |
+
showThinkTog: ()=>$('showThinkTog'),
|
| 716 |
+
fupTog: ()=>$('fupTog'),
|
| 717 |
+
themeTog: ()=>$('themeTog'),
|
| 718 |
+
};
|
| 719 |
+
|
| 720 |
+
// ── Scroll ─────────────────────────────────────
|
| 721 |
+
function scrollBot(force=false){
|
| 722 |
+
if(!force&&state.userScrolled)return;
|
| 723 |
+
const w=DOM.mwrap();
|
| 724 |
+
w.scrollTo({top:w.scrollHeight,behavior:'smooth'});
|
| 725 |
+
}
|
| 726 |
+
function _onScroll(){
|
| 727 |
+
const w=DOM.mwrap();
|
| 728 |
+
const atBottom=w.scrollHeight-w.scrollTop-w.clientHeight<120;
|
| 729 |
+
DOM.scrbtn().classList.toggle('on',!atBottom);
|
| 730 |
+
state.userScrolled=!atBottom;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
// ── Toast ──────────────────────────────────────
|
| 734 |
+
function toast(msg,type=''){UI.toast(DOM.tw(),msg,type);}
|
| 735 |
+
|
| 736 |
+
// ── Busy ───────────────────────────────────────
|
| 737 |
+
function setBusy(on){
|
| 738 |
+
state.busy=on;
|
| 739 |
+
DOM.sndbtn().disabled=on;DOM.ci().disabled=on;
|
| 740 |
+
DOM.stopbtn().classList.toggle('on',on);
|
| 741 |
+
if(!on){state.stopReq=false;DOM.ci().focus();}
|
| 742 |
+
}
|
| 743 |
+
function enableInput(){
|
| 744 |
+
DOM.ci().disabled=false;DOM.sndbtn().disabled=false;DOM.ci().focus();
|
| 745 |
+
const draft=Store.getDraft();
|
| 746 |
+
if(draft){DOM.ci().value=draft;_resizeTa(DOM.ci());_updTok(draft);}
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
// ── Input helpers ──────────────────────────────
|
| 750 |
+
function _resizeTa(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,150)+'px';}
|
| 751 |
+
function _updTok(text){$('tkc').textContent=Math.round(text.length/4);}
|
| 752 |
+
function _onInput(){const ci=DOM.ci();_resizeTa(ci);_updTok(ci.value);Store.setDraft(ci.value);}
|
| 753 |
+
|
| 754 |
+
// ── Conversations ──────────────────────────────
|
| 755 |
+
function newConv(){
|
| 756 |
+
const id='c'+Date.now();
|
| 757 |
+
const conv={id,title:'محادثة جديدة',msgs:[],created:Date.now(),pins:[]};
|
| 758 |
+
state.convs.unshift(conv);setActive(id);persist();renderConvList();
|
| 759 |
+
toast('✨ محادثة جديدة','ok');
|
| 760 |
+
if(window.innerWidth<=680)closeSidebar();
|
| 761 |
+
}
|
| 762 |
+
function setActive(id){
|
| 763 |
+
state.activeId=id;persist();renderConvList();renderMessages();
|
| 764 |
+
const c=state.convs.find(x=>x.id===id);
|
| 765 |
+
DOM.htitle().textContent=c?c.title:'محادثة';
|
| 766 |
+
}
|
| 767 |
+
function deleteConv(id){
|
| 768 |
+
if(!confirm('حذف هذه المحادثة نهائياً؟'))return;
|
| 769 |
+
state.convs=state.convs.filter(c=>c.id!==id);
|
| 770 |
+
if(state.activeId===id)state.activeId=state.convs[0]?.id||'';
|
| 771 |
+
persist();renderConvList();renderMessages();
|
| 772 |
+
}
|
| 773 |
+
function startRename(id){
|
| 774 |
+
state.renId=id;const c=state.convs.find(x=>x.id===id);
|
| 775 |
+
DOM.rmi().value=c?.title||'';DOM.rmod().style.display='flex';
|
| 776 |
+
setTimeout(()=>DOM.rmi().focus(),40);
|
| 777 |
+
}
|
| 778 |
+
function confirmRename(){
|
| 779 |
+
const v=DOM.rmi().value.trim();if(!v||!state.renId){closeModal();return;}
|
| 780 |
+
const c=state.convs.find(x=>x.id===state.renId);if(c)c.title=v;
|
| 781 |
+
persist();renderConvList();
|
| 782 |
+
if(state.activeId===state.renId)DOM.htitle().textContent=v;
|
| 783 |
+
closeModal();
|
| 784 |
+
}
|
| 785 |
+
function closeModal(){DOM.rmod().style.display='none';state.renId=null;}
|
| 786 |
+
|
| 787 |
+
// ── Render conv list ───────────────────────────
|
| 788 |
+
function renderConvList(){
|
| 789 |
+
const el=DOM.cl();
|
| 790 |
+
if(!state.convs.length){
|
| 791 |
+
el.innerHTML='<div style="padding:22px 10px;text-align:center;color:var(--t3);font-size:.77rem"><i class="fa-regular fa-comment" style="font-size:1.3rem;display:block;margin-bottom:8px;opacity:.28"></i>لا توجد محادثات</div>';
|
| 792 |
+
return;
|
| 793 |
+
}
|
| 794 |
+
const frag=document.createDocumentFragment();
|
| 795 |
+
const groups=Utils.groupByDate(state.convs);
|
| 796 |
+
for(const [label,list] of Object.entries(groups)){
|
| 797 |
+
if(!list.length)continue;
|
| 798 |
+
const grpEl=document.createElement('div');grpEl.className='grp';grpEl.textContent=label;frag.appendChild(grpEl);
|
| 799 |
+
for(const conv of list){
|
| 800 |
+
const lm=conv.msgs[conv.msgs.length-1];
|
| 801 |
+
const prev=lm?lm.content.slice(0,32)+(lm.content.length>32?'…':''):'لا توجد رسائل';
|
| 802 |
+
const item=document.createElement('div');
|
| 803 |
+
item.className=`ci${conv.id===state.activeId?' on':''}`;item.dataset.cid=conv.id;
|
| 804 |
+
item.innerHTML=`
|
| 805 |
+
<div class="ci-dot"></div>
|
| 806 |
+
<div class="ci-info">
|
| 807 |
+
<div class="ci-title">${Utils.esc(conv.title)}</div>
|
| 808 |
+
<div class="ci-sub">${Utils.esc(prev)}</div>
|
| 809 |
+
</div>
|
| 810 |
+
<div class="ci-acts">
|
| 811 |
+
<button class="cia" data-action="rename" data-cid="${conv.id}" title="تسمية"><i class="fa-solid fa-pen"></i></button>
|
| 812 |
+
<button class="cia del" data-action="delete" data-cid="${conv.id}" title="حذف"><i class="fa-solid fa-trash"></i></button>
|
| 813 |
+
</div>`;
|
| 814 |
+
frag.appendChild(item);
|
| 815 |
+
}
|
| 816 |
+
}
|
| 817 |
+
el.innerHTML='';el.appendChild(frag);
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
// ── Render messages ────────────────────────────
|
| 821 |
+
function renderMessages(){
|
| 822 |
+
const conv=active();const wrap=DOM.msgs();
|
| 823 |
+
if(!conv||!conv.msgs.length){wrap.innerHTML='';wrap.appendChild(UI.welcomeScreen());return;}
|
| 824 |
+
const frag=document.createDocumentFragment();
|
| 825 |
+
conv.msgs.forEach((m,i)=>frag.appendChild(buildMsgRow(m,i)));
|
| 826 |
+
wrap.innerHTML='';wrap.appendChild(frag);
|
| 827 |
+
state.userScrolled=false;scrollBot(true);
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
function buildMsgRow(msg,idx){
|
| 831 |
+
const conv=active();
|
| 832 |
+
const pinned=conv?.pins?.includes(idx)||false;
|
| 833 |
+
const t=new Date(msg.ts||Date.now()).toLocaleTimeString('ar-EG',{hour:'2-digit',minute:'2-digit'});
|
| 834 |
+
const row=document.createElement('div');row.className=`mr ${msg.role}`;row.dataset.i=idx;
|
| 835 |
+
if(msg.role!=='err') row.appendChild(UI.msgHeader(msg.role,msg.model||'',t,pinned));
|
| 836 |
+
|
| 837 |
+
// Thinking block — only if showThinkTog is on and thinking exists
|
| 838 |
+
const showThink=DOM.showThinkTog().checked;
|
| 839 |
+
if(showThink&&msg.thinking&&msg.thinking.length){
|
| 840 |
+
row.appendChild(UI.thinkingBlock(msg.thinking));
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
// Bubble
|
| 844 |
+
const bub=document.createElement('div');bub.className='mb';
|
| 845 |
+
if(msg.role==='assistant'){
|
| 846 |
+
bub.innerHTML=MD.render(msg.content);
|
| 847 |
+
} else {
|
| 848 |
+
const span=document.createElement('span');
|
| 849 |
+
const lines=msg.content.split('\n');
|
| 850 |
+
lines.forEach((line,i)=>{
|
| 851 |
+
span.appendChild(document.createTextNode(line));
|
| 852 |
+
if(i<lines.length-1)span.appendChild(document.createElement('br'));
|
| 853 |
+
});
|
| 854 |
+
bub.appendChild(span);
|
| 855 |
+
}
|
| 856 |
+
row.appendChild(bub);
|
| 857 |
+
if(msg.role==='user'||msg.role==='assistant') row.appendChild(UI.msgActions(msg.role,idx,pinned));
|
| 858 |
+
if(msg.role==='assistant') Utils.extractArtifacts(msg.content).forEach(art=>row.appendChild(UI.artifactCard(art)));
|
| 859 |
+
return row;
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
// ── Append user msg without full re-render ─────
|
| 863 |
+
function appendUserMsg(msg,idx){
|
| 864 |
+
$('wlc')?.remove();
|
| 865 |
+
DOM.msgs().appendChild(buildMsgRow(msg,idx));
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
// ── Send ───────────────────────────────────────
|
| 869 |
+
async function send(text){
|
| 870 |
+
text=(text||DOM.ci().value).trim();
|
| 871 |
+
if(!text||state.busy)return;
|
| 872 |
+
const prov=DOM.psel().value;const model=DOM.msel().value;
|
| 873 |
+
if(!prov){toast('⚠️ اختر مزوداً أولاً','er');return;}
|
| 874 |
+
if(!state.activeId||!active())newConv();
|
| 875 |
+
const conv=active();if(!conv)return;
|
| 876 |
+
$('wlc')?.remove();
|
| 877 |
+
const um={role:'user',content:text,ts:Date.now()};
|
| 878 |
+
conv.msgs.push(um);
|
| 879 |
+
if(conv.msgs.length===1){conv.title=text.slice(0,40)+(text.length>40?'…':'');DOM.htitle().textContent=conv.title;}
|
| 880 |
+
persist();renderConvList();
|
| 881 |
+
appendUserMsg(um,conv.msgs.length-1);
|
| 882 |
+
DOM.ci().value='';Store.setDraft('');_resizeTa(DOM.ci());_updTok('');
|
| 883 |
+
setBusy(true);state.userScrolled=false;scrollBot(true);
|
| 884 |
+
const lid='tp'+Date.now();
|
| 885 |
+
const typRow=UI.typingRow(lid,'يفكر...');
|
| 886 |
+
DOM.msgs().appendChild(typRow);scrollBot();
|
| 887 |
+
const payload={
|
| 888 |
+
message:text,model,provider:prov,
|
| 889 |
+
system_prompt:DOM.sysp().value.trim(),
|
| 890 |
+
conversation_id:getServerId(conv),
|
| 891 |
+
};
|
| 892 |
+
try{
|
| 893 |
+
if(DOM.stog().checked) await _doStream(payload,lid,conv,model);
|
| 894 |
+
else await _doSync(payload,lid,conv,model);
|
| 895 |
+
}catch(e){
|
| 896 |
+
document.getElementById(lid)?.remove();
|
| 897 |
+
const errRow=document.createElement('div');errRow.className='mr err';
|
| 898 |
+
errRow.innerHTML=`<div class="mb">❌ ${Utils.esc(e.message)}</div>`;
|
| 899 |
+
DOM.msgs().appendChild(errRow);setBusy(false);
|
| 900 |
+
}
|
| 901 |
+
}
|
| 902 |
+
|
| 903 |
+
// ── Sync call ──────────────────────────────────
|
| 904 |
+
async function _doSync(payload,lid,conv,model){
|
| 905 |
+
const data=await API.chat(payload);
|
| 906 |
+
document.getElementById(lid)?.remove();
|
| 907 |
+
if(data.conversation_id)conv.serverId=data.conversation_id;
|
| 908 |
+
const content=data.ok?data.reply:'❌ '+(data.error||'خطأ');
|
| 909 |
+
// Server returns thinking as array of strings
|
| 910 |
+
const thinking=data.ok?(data.thinking||[]):[];
|
| 911 |
+
const bm={role:'assistant',content,thinking,model,ts:Date.now()};
|
| 912 |
+
conv.msgs.push(bm);persist();
|
| 913 |
+
DOM.msgs().appendChild(buildMsgRow(bm,conv.msgs.length-1));
|
| 914 |
+
if(DOM.fupTog().checked) _appendFollowUps(content);
|
| 915 |
+
setBusy(false);scrollBot();
|
| 916 |
+
}
|
| 917 |
+
|
| 918 |
+
// ── Stream call ────────────────────────────────
|
| 919 |
+
// SSE events from server:
|
| 920 |
+
// {type:"start", conversation_id:"...", model:"...", provider:"..."}
|
| 921 |
+
// {type:"thinking", lines:["...","..."]}
|
| 922 |
+
// {type:"chunk", content:"..."}
|
| 923 |
+
// {type:"done"}
|
| 924 |
+
// {type:"error", content:"..."}
|
| 925 |
+
async function _doStream(payload,lid,conv,model){
|
| 926 |
+
state.stopReq=false;
|
| 927 |
+
const res=await API.streamChat(payload);
|
| 928 |
+
document.getElementById(lid)?.remove();
|
| 929 |
+
|
| 930 |
+
const bm={role:'assistant',content:'',thinking:[],model,ts:Date.now()};
|
| 931 |
+
conv.msgs.push(bm);
|
| 932 |
+
const idx=conv.msgs.length-1;
|
| 933 |
+
|
| 934 |
+
const row=document.createElement('div');row.className='mr assistant';row.dataset.i=idx;
|
| 935 |
+
const hdr=document.createElement('div');hdr.className='mhdr';
|
| 936 |
+
hdr.innerHTML=`<div class="mav b"><i class="fa-solid fa-robot"></i></div>${model?`<span class="mtag">${Utils.esc(model)}</span>`:''}`;
|
| 937 |
+
row.appendChild(hdr);
|
| 938 |
+
|
| 939 |
+
// Thinking placeholder (hidden until server sends thinking event)
|
| 940 |
+
let thinkBlockEl=null;
|
| 941 |
+
let thinkBodyEl=null;
|
| 942 |
+
|
| 943 |
+
// Live bubble
|
| 944 |
+
const bub=document.createElement('div');bub.className='mb';
|
| 945 |
+
bub.innerHTML='<span class="cur"></span>';
|
| 946 |
+
row.appendChild(bub);
|
| 947 |
+
DOM.msgs().appendChild(row);scrollBot();
|
| 948 |
+
|
| 949 |
+
const reader=res.body.getReader();const dec=new TextDecoder();
|
| 950 |
+
let full='',sseBuffer='',raf=null;
|
| 951 |
+
const schedRender=()=>{
|
| 952 |
+
if(raf)return;
|
| 953 |
+
raf=requestAnimationFrame(()=>{
|
| 954 |
+
bub.innerHTML=MD.render(full)+'<span class="cur"></span>';
|
| 955 |
+
scrollBot();raf=null;
|
| 956 |
+
});
|
| 957 |
+
};
|
| 958 |
+
|
| 959 |
+
try{
|
| 960 |
+
while(true){
|
| 961 |
+
if(state.stopReq){reader.cancel();break;}
|
| 962 |
+
const{value,done}=await reader.read();if(done)break;
|
| 963 |
+
sseBuffer+=dec.decode(value,{stream:true});
|
| 964 |
+
const lines=sseBuffer.split('\n');sseBuffer=lines.pop();
|
| 965 |
+
for(const line of lines){
|
| 966 |
+
if(!line.startsWith('data: '))continue;
|
| 967 |
+
let ev;try{ev=JSON.parse(line.slice(6));}catch{continue;}
|
| 968 |
+
|
| 969 |
+
if(ev.type==='start'){
|
| 970 |
+
if(ev.conversation_id)conv.serverId=ev.conversation_id;
|
| 971 |
+
}
|
| 972 |
+
else if(ev.type==='thinking'&&ev.lines?.length){
|
| 973 |
+
// Server extracted thinking block — show if toggle is on
|
| 974 |
+
bm.thinking=ev.lines;
|
| 975 |
+
const showThink=DOM.showThinkTog().checked;
|
| 976 |
+
if(showThink&&!thinkBlockEl){
|
| 977 |
+
thinkBlockEl=UI.thinkingBlock(ev.lines);
|
| 978 |
+
thinkBlockEl.classList.add('streaming');
|
| 979 |
+
row.insertBefore(thinkBlockEl,bub);
|
| 980 |
+
thinkBodyEl=thinkBlockEl.querySelector('.thinking-body');
|
| 981 |
+
} else if(showThink&&thinkBlockEl){
|
| 982 |
+
// Update lines
|
| 983 |
+
if(thinkBodyEl){
|
| 984 |
+
thinkBodyEl.innerHTML=ev.lines.map(l=>`<div class="thinking-line">${Utils.esc(l)}</div>`).join('');
|
| 985 |
+
}
|
| 986 |
+
}
|
| 987 |
+
}
|
| 988 |
+
else if(ev.type==='chunk'&&ev.content){
|
| 989 |
+
// Once chunks arrive, thinking is done streaming
|
| 990 |
+
if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
|
| 991 |
+
full+=ev.content;schedRender();
|
| 992 |
+
}
|
| 993 |
+
else if(ev.type==='error'){
|
| 994 |
+
full+='\n❌ '+ev.content;schedRender();
|
| 995 |
+
}
|
| 996 |
+
else if(ev.type==='done'){
|
| 997 |
+
break;
|
| 998 |
+
}
|
| 999 |
+
}
|
| 1000 |
+
}
|
| 1001 |
+
}catch(e){/* stream interrupted */}
|
| 1002 |
+
|
| 1003 |
+
// Final render without cursor
|
| 1004 |
+
cancelAnimationFrame(raf);
|
| 1005 |
+
bub.innerHTML=MD.render(full||'(لا يوجد رد)');
|
| 1006 |
+
if(thinkBlockEl)thinkBlockEl.classList.remove('streaming');
|
| 1007 |
+
|
| 1008 |
+
bm.content=full;persist();
|
| 1009 |
+
|
| 1010 |
+
// Replace streaming row with full interactive row
|
| 1011 |
+
const finalRow=buildMsgRow(bm,idx);
|
| 1012 |
+
row.replaceWith(finalRow);
|
| 1013 |
+
if(DOM.fupTog().checked) _appendFollowUps(full);
|
| 1014 |
+
setBusy(false);scrollBot();
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
+
function _appendFollowUps(text){
|
| 1018 |
+
if(!text||text.startsWith('❌'))return;
|
| 1019 |
+
const row=UI.followUpRow(Utils.suggestFollowUps(text));
|
| 1020 |
+
DOM.msgs().appendChild(row);scrollBot();
|
| 1021 |
+
}
|
| 1022 |
+
|
| 1023 |
+
// ── Message actions ────────────────────────────
|
| 1024 |
+
function copyMsg(idx){
|
| 1025 |
+
const conv=active();
|
| 1026 |
+
navigator.clipboard?.writeText(conv?.msgs[idx]?.content||'');
|
| 1027 |
+
toast('✅ تم النسخ','ok');
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
function copyCode(btn){
|
| 1031 |
+
const code=btn.closest('.cbw')?.querySelector('code');
|
| 1032 |
+
if(!code)return;
|
| 1033 |
+
navigator.clipboard?.writeText(code.textContent);
|
| 1034 |
+
const orig=btn.innerHTML;
|
| 1035 |
+
btn.innerHTML='<i class="fa-solid fa-check"></i> تم';
|
| 1036 |
+
setTimeout(()=>{btn.innerHTML=orig;},1500);
|
| 1037 |
+
toast('✅ تم نسخ الكود','ok');
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
function runCodeBlock(btn){
|
| 1041 |
+
const cbw=btn.closest('.cbw');
|
| 1042 |
+
const code=cbw?.querySelector('code')?.textContent||'';
|
| 1043 |
+
const lang=cbw?.querySelector('.cb-lang')?.dataset.lang||'html';
|
| 1044 |
+
Sandbox.open(Utils.wrapForPreview(code,lang),`code.${lang==='javascript'?'js':lang}`);
|
| 1045 |
+
}
|
| 1046 |
+
|
| 1047 |
+
function editMsg(idx){
|
| 1048 |
+
const conv=active();const msg=conv?.msgs[idx];if(!msg)return;
|
| 1049 |
+
const row=DOM.msgs().querySelector(`.mr[data-i="${idx}"]`);if(!row)return;
|
| 1050 |
+
const bub=row.querySelector('.mb');const orig=msg.content;
|
| 1051 |
+
bub.innerHTML='';
|
| 1052 |
+
const ta=document.createElement('textarea');ta.className='etx';ta.value=orig;bub.appendChild(ta);
|
| 1053 |
+
const btns=document.createElement('div');btns.className='ebtns';
|
| 1054 |
+
const cancelBtn=document.createElement('button');cancelBtn.className='ecn';cancelBtn.textContent='إلغاء';
|
| 1055 |
+
cancelBtn.addEventListener('click',()=>cancelEdit(idx,orig,bub));
|
| 1056 |
+
const saveBtn=document.createElement('button');saveBtn.className='esv';saveBtn.textContent='إرسال';
|
| 1057 |
+
saveBtn.addEventListener('click',()=>saveEdit(idx,ta.value.trim()));
|
| 1058 |
+
btns.appendChild(cancelBtn);btns.appendChild(saveBtn);bub.appendChild(btns);
|
| 1059 |
+
ta.focus();ta.selectionStart=ta.value.length;
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
function cancelEdit(idx,orig,bub){
|
| 1063 |
+
bub.innerHTML='';
|
| 1064 |
+
const span=document.createElement('span');
|
| 1065 |
+
const lines=orig.split('\n');
|
| 1066 |
+
lines.forEach((line,i)=>{
|
| 1067 |
+
span.appendChild(document.createTextNode(line));
|
| 1068 |
+
if(i<lines.length-1)span.appendChild(document.createElement('br'));
|
| 1069 |
+
});
|
| 1070 |
+
bub.appendChild(span);
|
| 1071 |
+
}
|
| 1072 |
+
|
| 1073 |
+
async function saveEdit(idx,newText){
|
| 1074 |
+
if(!newText)return;
|
| 1075 |
+
const conv=active();if(!conv)return;
|
| 1076 |
+
conv.msgs[idx].content=newText;conv.msgs[idx].ts=Date.now();
|
| 1077 |
+
conv.msgs=conv.msgs.slice(0,idx+1);
|
| 1078 |
+
persist();renderMessages();await _resend(newText);
|
| 1079 |
+
}
|
| 1080 |
+
|
| 1081 |
+
async function regen(idx){
|
| 1082 |
+
const conv=active();if(!conv)return;
|
| 1083 |
+
let ui=idx-1;
|
| 1084 |
+
while(ui>=0&&conv.msgs[ui].role!=='user')ui--;
|
| 1085 |
+
if(ui<0)return;
|
| 1086 |
+
const text=conv.msgs[ui].content;
|
| 1087 |
+
conv.msgs=conv.msgs.slice(0,idx);
|
| 1088 |
+
persist();renderMessages();await _resend(text);
|
| 1089 |
+
}
|
| 1090 |
+
|
| 1091 |
+
async function _resend(text){
|
| 1092 |
+
const conv=active();if(!conv)return;
|
| 1093 |
+
const payload={
|
| 1094 |
+
message:text,
|
| 1095 |
+
model:DOM.msel().value,
|
| 1096 |
+
provider:DOM.psel().value,
|
| 1097 |
+
system_prompt:DOM.sysp().value.trim(),
|
| 1098 |
+
conversation_id:getServerId(conv),
|
| 1099 |
+
};
|
| 1100 |
+
setBusy(true);
|
| 1101 |
+
const lid='tp'+Date.now();
|
| 1102 |
+
const typRow=UI.typingRow(lid,'يعيد التوليد...');
|
| 1103 |
+
DOM.msgs().appendChild(typRow);scrollBot();
|
| 1104 |
+
try{
|
| 1105 |
+
if(DOM.stog().checked) await _doStream(payload,lid,conv,DOM.msel().value);
|
| 1106 |
+
else await _doSync(payload,lid,conv,DOM.msel().value);
|
| 1107 |
+
}catch{document.getElementById(lid)?.remove();setBusy(false);}
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
function togglePin(idx){
|
| 1111 |
+
const conv=active();if(!conv)return;
|
| 1112 |
+
if(!conv.pins)conv.pins=[];
|
| 1113 |
+
const i=conv.pins.indexOf(idx);
|
| 1114 |
+
if(i===-1){conv.pins.push(idx);toast('📌 تم التثبيت','ok');}
|
| 1115 |
+
else{conv.pins.splice(i,1);toast('تم إلغاء التثبيت','');}
|
| 1116 |
+
persist();renderMessages();
|
| 1117 |
+
}
|
| 1118 |
+
|
| 1119 |
+
// ── Artifact actions ───────────────────────────
|
| 1120 |
+
function artAction(type,el){
|
| 1121 |
+
const art=el.closest('.artifact')?._artData;if(!art)return;
|
| 1122 |
+
if(type==='art-view'||type==='art-run'){
|
| 1123 |
+
Sandbox.open(Utils.wrapForPreview(art.code,art.lang),art.filename);
|
| 1124 |
+
} else if(type==='art-copy'){
|
| 1125 |
+
navigator.clipboard?.writeText(art.code);
|
| 1126 |
+
const orig=el.innerHTML;el.innerHTML='<i class="fa-solid fa-check"></i> تم';
|
| 1127 |
+
setTimeout(()=>{el.innerHTML=orig;},1500);toast('✅ تم النسخ','ok');
|
| 1128 |
+
} else if(type==='art-dl'){
|
| 1129 |
+
const a=document.createElement('a');
|
| 1130 |
+
a.href=URL.createObjectURL(new Blob([art.code],{type:'text/plain'}));
|
| 1131 |
+
a.download=art.filename;a.click();URL.revokeObjectURL(a.href);
|
| 1132 |
+
toast('⬇️ جاري التحميل','ok');
|
| 1133 |
+
}
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
// ── Providers ──────────────────────────────────
|
| 1137 |
+
async function loadProviders(){
|
| 1138 |
+
try{
|
| 1139 |
+
const data=await API.getProviders();
|
| 1140 |
+
if(!data.ok)throw new Error();
|
| 1141 |
+
state.provs=data.providers;
|
| 1142 |
+
_buildProviderSelect(data.providers);
|
| 1143 |
+
enableInput();
|
| 1144 |
+
toast(`✅ ${data.total} مزود`,'ok');
|
| 1145 |
+
_restoreProvider();
|
| 1146 |
+
}catch{
|
| 1147 |
+
toast('❌ فشل الاتصال','er');
|
| 1148 |
+
enableInput();
|
| 1149 |
+
}
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
async function reloadProviders(){
|
| 1153 |
+
const ic=DOM.rlic();ic.classList.add('fa-spin');
|
| 1154 |
+
try{await API.reload();await loadProviders();}catch{}
|
| 1155 |
+
ic.classList.remove('fa-spin');
|
| 1156 |
+
}
|
| 1157 |
+
|
| 1158 |
+
function _buildProviderSelect(providers){
|
| 1159 |
+
const sel=DOM.psel();const frag=document.createDocumentFragment();
|
| 1160 |
+
// Auto first
|
| 1161 |
+
if(providers['Auto']){
|
| 1162 |
+
const o=document.createElement('option');o.value='Auto';o.textContent='⚡ Auto — تلقائي';frag.appendChild(o);
|
| 1163 |
+
}
|
| 1164 |
+
const tg=document.createElement('optgroup');tg.label='── بدون مصادقة ──';
|
| 1165 |
+
const ag=document.createElement('optgroup');ag.label='── تحتاج تسجيل ──';
|
| 1166 |
+
for(const[k,p] of Object.entries(providers)){
|
| 1167 |
+
if(k==='Auto')continue;
|
| 1168 |
+
const o=document.createElement('option');o.value=k;o.textContent=k;
|
| 1169 |
+
(p.needs_auth?ag:tg).appendChild(o);
|
| 1170 |
+
}
|
| 1171 |
+
if(tg.children.length)frag.appendChild(tg);
|
| 1172 |
+
if(ag.children.length)frag.appendChild(ag);
|
| 1173 |
+
sel.innerHTML='';sel.appendChild(frag);
|
| 1174 |
+
}
|
| 1175 |
+
|
| 1176 |
+
async function onProviderChange(){
|
| 1177 |
+
const pv=DOM.psel().value;
|
| 1178 |
+
Store.setProvider(pv);
|
| 1179 |
+
DOM.ptxt().textContent=pv||'—';
|
| 1180 |
+
const ms=DOM.msel();ms.innerHTML='<option>⏳ جاري...</option>';
|
| 1181 |
+
try{
|
| 1182 |
+
const data=await API.getModels(pv);
|
| 1183 |
+
const frag=document.createDocumentFragment();
|
| 1184 |
+
(data.models||['gpt-4o']).forEach(m=>{
|
| 1185 |
+
const o=document.createElement('option');o.value=m;o.textContent=m;frag.appendChild(o);
|
| 1186 |
+
});
|
| 1187 |
+
ms.innerHTML='';ms.appendChild(frag);
|
| 1188 |
+
_restoreModel();
|
| 1189 |
+
}catch{ms.innerHTML='<option value="gpt-4o">gpt-4o</option>';}
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
function _restoreProvider(){
|
| 1193 |
+
const p=Store.getProvider();
|
| 1194 |
+
if(p&&[...DOM.psel().options].some(o=>o.value===p)){DOM.psel().value=p;onProviderChange();}
|
| 1195 |
+
else onProviderChange();
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
+
function _restoreModel(){
|
| 1199 |
+
const m=Store.getModel();
|
| 1200 |
+
if(m&&[...DOM.msel().options].some(o=>o.value===m))DOM.msel().value=m;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
// ── Sidebar ────────────────────────────────────
|
| 1204 |
+
function toggleSidebar(){
|
| 1205 |
+
state.sbOpen=!state.sbOpen;
|
| 1206 |
+
const sb=DOM.sb();
|
| 1207 |
+
if(window.innerWidth>680){sb.classList.toggle('desk-hide',!state.sbOpen);}
|
| 1208 |
+
else{sb.classList.toggle('mob-open',state.sbOpen);DOM.ov().classList.toggle('on',state.sbOpen);}
|
| 1209 |
+
}
|
| 1210 |
+
function closeSidebar(){
|
| 1211 |
+
state.sbOpen=false;DOM.sb().classList.remove('mob-open');DOM.ov().classList.remove('on');
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
// ── Theme ──────────────────────────────────────
|
| 1215 |
+
function toggleTheme(){
|
| 1216 |
+
const next=Store.getTheme()==='dark'?'light':'dark';
|
| 1217 |
+
Store.setTheme(next);
|
| 1218 |
+
document.documentElement.dataset.theme=next;
|
| 1219 |
+
_updateThemeIcon(next);
|
| 1220 |
+
DOM.themeTog().checked=next==='light';
|
| 1221 |
+
}
|
| 1222 |
+
function _updateThemeIcon(theme){
|
| 1223 |
+
DOM.thbtn().querySelector('i').className=theme==='dark'?'fa-solid fa-sun':'fa-solid fa-moon';
|
| 1224 |
+
}
|
| 1225 |
+
|
| 1226 |
+
// ── Event delegation ───────────────────────────
|
| 1227 |
+
function _delegateMsgs(e){
|
| 1228 |
+
const btn=e.target.closest('[data-action]');if(!btn)return;
|
| 1229 |
+
const action=btn.dataset.action;
|
| 1230 |
+
const idx=parseInt(btn.dataset.idx??'-1');
|
| 1231 |
+
if(action==='copy'&&btn.classList.contains('cb-copy')){copyCode(btn);return;}
|
| 1232 |
+
if(action==='run'&&btn.classList.contains('cb-run')){runCodeBlock(btn);return;}
|
| 1233 |
+
switch(action){
|
| 1234 |
+
case 'copy': copyMsg(idx);break;
|
| 1235 |
+
case 'edit': editMsg(idx);break;
|
| 1236 |
+
case 'pin': togglePin(idx);break;
|
| 1237 |
+
case 'regen': regen(idx);break;
|
| 1238 |
+
case 'art-view':case 'art-copy':case 'art-run':case 'art-dl': artAction(action,btn);break;
|
| 1239 |
+
}
|
| 1240 |
+
}
|
| 1241 |
+
|
| 1242 |
+
function _delegateConvList(e){
|
| 1243 |
+
const btn=e.target.closest('[data-action]');
|
| 1244 |
+
if(btn){
|
| 1245 |
+
e.stopPropagation();
|
| 1246 |
+
const action=btn.dataset.action;const cid=btn.dataset.cid;
|
| 1247 |
+
if(action==='rename')startRename(cid);
|
| 1248 |
+
if(action==='delete')deleteConv(cid);
|
| 1249 |
+
return;
|
| 1250 |
+
}
|
| 1251 |
+
const item=e.target.closest('.ci[data-cid]');
|
| 1252 |
+
if(item)setActive(item.dataset.cid);
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
function _delegateWelcome(e){
|
| 1256 |
+
const card=e.target.closest('[data-prompt]');if(card)send(card.dataset.prompt);
|
| 1257 |
+
}
|
| 1258 |
+
|
| 1259 |
+
function _delegateFollowUps(e){
|
| 1260 |
+
const btn=e.target.closest('.fup[data-prompt]');if(btn)send(btn.dataset.prompt);
|
| 1261 |
+
}
|
| 1262 |
+
|
| 1263 |
+
// ── Init ───────────────────────────────────────
|
| 1264 |
+
function init(){
|
| 1265 |
+
if(window.marked)marked.setOptions({breaks:true,gfm:true});
|
| 1266 |
+
|
| 1267 |
+
// Theme
|
| 1268 |
+
const theme=Store.getTheme();
|
| 1269 |
+
document.documentElement.dataset.theme=theme;
|
| 1270 |
+
_updateThemeIcon(theme);
|
| 1271 |
+
DOM.themeTog().checked=theme==='light';
|
| 1272 |
+
|
| 1273 |
+
// Toggles restore
|
| 1274 |
+
DOM.showThinkTog().checked=Store.getShowThink();
|
| 1275 |
+
DOM.fupTog().checked=Store.getShowFup();
|
| 1276 |
+
DOM.stog().checked=Store.getStream();
|
| 1277 |
+
|
| 1278 |
+
// Sidebar initial state
|
| 1279 |
+
if(!state.sbOpen)DOM.sb().classList.add('desk-hide');
|
| 1280 |
+
|
| 1281 |
+
// Input events
|
| 1282 |
+
DOM.ci().addEventListener('input',_onInput);
|
| 1283 |
+
DOM.ci().addEventListener('keydown',e=>{
|
| 1284 |
+
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}
|
| 1285 |
+
});
|
| 1286 |
+
|
| 1287 |
+
// Scroll
|
| 1288 |
+
DOM.mwrap().addEventListener('scroll',_onScroll,{passive:true});
|
| 1289 |
+
|
| 1290 |
+
// Settings persist
|
| 1291 |
+
DOM.msel().addEventListener('change',()=>Store.setModel(DOM.msel().value));
|
| 1292 |
+
DOM.stog().addEventListener('change',()=>Store.setStream(DOM.stog().checked));
|
| 1293 |
+
DOM.showThinkTog().addEventListener('change',()=>{Store.setShowThink(DOM.showThinkTog().checked);renderMessages();});
|
| 1294 |
+
DOM.fupTog().addEventListener('change',()=>Store.setShowFup(DOM.fupTog().checked));
|
| 1295 |
+
DOM.themeTog().addEventListener('change',toggleTheme);
|
| 1296 |
+
|
| 1297 |
+
// Rename modal
|
| 1298 |
+
DOM.rmi().addEventListener('keydown',e=>{if(e.key==='Enter')confirmRename();if(e.key==='Escape')closeModal();});
|
| 1299 |
+
DOM.rmod().addEventListener('click',e=>{if(e.target===DOM.rmod())closeModal();});
|
| 1300 |
+
|
| 1301 |
+
// Header buttons
|
| 1302 |
+
$('newConvBtn').addEventListener('click',newConv);
|
| 1303 |
+
$('sbToggleBtn').addEventListener('click',toggleSidebar);
|
| 1304 |
+
DOM.ov().addEventListener('click',closeSidebar);
|
| 1305 |
+
DOM.thbtn().addEventListener('click',toggleTheme);
|
| 1306 |
+
$('reloadBtn').addEventListener('click',reloadProviders);
|
| 1307 |
+
DOM.sndbtn().addEventListener('click',()=>send());
|
| 1308 |
+
$('stopbtn').addEventListener('click',()=>{state.stopReq=true;setBusy(false);toast('تم إيقاف الرد','');});
|
| 1309 |
+
$('scrbtn').addEventListener('click',()=>{state.userScrolled=false;scrollBot(true);});
|
| 1310 |
+
|
| 1311 |
+
// Rename modal buttons
|
| 1312 |
+
$('renCancelBtn').addEventListener('click',closeModal);
|
| 1313 |
+
$('renSaveBtn').addEventListener('click',confirmRename);
|
| 1314 |
+
|
| 1315 |
+
// Sandbox buttons
|
| 1316 |
+
$('sbCloseBtn').addEventListener('click',Sandbox.close);
|
| 1317 |
+
$('sbRunBtn').addEventListener('click',Sandbox.run);
|
| 1318 |
+
document.querySelectorAll('.sb-tab').forEach(tab=>tab.addEventListener('click',()=>Sandbox.setTab(tab.dataset.tab)));
|
| 1319 |
+
document.addEventListener('keydown',e=>{if(e.key==='Escape')Sandbox.close();});
|
| 1320 |
+
|
| 1321 |
+
// Delegated events
|
| 1322 |
+
DOM.msgs().addEventListener('click',e=>{_delegateMsgs(e);_delegateWelcome(e);_delegateFollowUps(e);});
|
| 1323 |
+
DOM.cl().addEventListener('click',_delegateConvList);
|
| 1324 |
+
|
| 1325 |
+
// Provider select
|
| 1326 |
+
DOM.psel().addEventListener('change',onProviderChange);
|
| 1327 |
+
|
| 1328 |
+
// Load providers from server
|
| 1329 |
+
loadProviders();
|
| 1330 |
+
renderConvList();
|
| 1331 |
+
|
| 1332 |
+
if(state.activeId&&active()){renderMessages();}
|
| 1333 |
+
else if(state.convs.length){setActive(state.convs[0].id);}
|
| 1334 |
+
else{DOM.msgs().innerHTML='';DOM.msgs().appendChild(UI.welcomeScreen());}
|
| 1335 |
+
}
|
| 1336 |
+
|
| 1337 |
+
return {init};
|
| 1338 |
+
})();
|
| 1339 |
+
|
| 1340 |
+
App.init();
|
| 1341 |
+
</script>
|
| 1342 |
+
</body>
|
| 1343 |
+
</html
|