abdullahalioo commited on
Commit
c331343
·
verified ·
1 Parent(s): 14b7dc1

Upload 10 files

Browse files
Files changed (11) hide show
  1. .gitattributes +1 -0
  2. app.py +103 -0
  3. generated-icon.png +3 -0
  4. main.py +4 -0
  5. pyproject.toml +13 -0
  6. requirements.txt +6 -0
  7. static/css/style.css +354 -0
  8. static/js/chat.js +410 -0
  9. templates/index.html +113 -0
  10. uv.lock +0 -0
  11. vercel.json +28 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ generated-icon.png filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from flask import Flask, render_template, request, jsonify
4
+ import g4f
5
+ from g4f.client import Client
6
+ import os
7
+ import logging
8
+ from flask import Flask, render_template, request, jsonify
9
+ import g4f
10
+ from g4f.client import Client
11
+
12
+
13
+ # Configure logging
14
+
15
+ # Configure logging
16
+ logging.basicConfig(level=logging.DEBUG)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Create Flask app
20
+ app = Flask(__name__)
21
+ app.secret_key = os.environ.get("SESSION_SECRET", "default_secret_key")
22
+
23
+ # Initialize g4f client
24
+ client = Client()
25
+
26
+
27
+ @app.route('/')
28
+ def index():
29
+ return render_template('index.html')
30
+
31
+
32
+ @app.route('/api/chat', methods=['POST'])
33
+ def chat():
34
+ try:
35
+ data = request.json
36
+ messages = data.get('messages', [])
37
+ model = data.get('model', 'gpt-4o-mini')
38
+
39
+ # Add system prompt
40
+ system_prompt = {
41
+ "role":
42
+ "system",
43
+ "content":
44
+ "You are orion helpful AI assistant. You provide accurate, informative, and friendly responses while keeping them concise and relevant and you are make by Abdullah ali who is 13 years old "
45
+ }
46
+
47
+ # Insert system prompt at the beginning if not already present
48
+ if not messages or messages[0].get('role') != 'system':
49
+ messages.insert(0, system_prompt)
50
+
51
+ logger.debug(
52
+ f"Sending request to g4f with model: {model} and messages: {messages}"
53
+ )
54
+
55
+ # Call the g4f API
56
+ response = client.chat.completions.create(model=model,
57
+ messages=messages,
58
+ web_search=False)
59
+
60
+ ai_response = response.choices[0].message.content
61
+ logger.debug(f"Received response from g4f: {ai_response}")
62
+
63
+ return jsonify({'status': 'success', 'message': ai_response})
64
+ except Exception as e:
65
+ logger.error(f"Error in chat endpoint: {str(e)}")
66
+ return jsonify({
67
+ 'status': 'error',
68
+ 'message': f"An error occurred: {str(e)}"
69
+ }), 500
70
+
71
+
72
+
73
+ @app.route('/api/conversations/<conversation_id>', methods=['DELETE'])
74
+ def delete_conversation(conversation_id):
75
+ try:
76
+ return jsonify({'status': 'success', 'message': f'Conversation {conversation_id} deleted'})
77
+ except Exception as e:
78
+ logger.error(f"Error deleting conversation: {str(e)}")
79
+ return jsonify({
80
+ 'status': 'error',
81
+ 'message': f"An error occurred: {str(e)}"
82
+ }), 500
83
+
84
+ @app.route('/api/models', methods=['GET'])
85
+ def get_models():
86
+ try:
87
+ # Return a list of available models
88
+ # You can customize this list based on what g4f supports
89
+ models = [{
90
+ "id": "gpt-4o-mini",
91
+ "name": "GPT-4o"
92
+ }]
93
+ return jsonify({'status': 'success', 'models': models})
94
+ except Exception as e:
95
+ logger.error(f"Error in models endpoint: {str(e)}")
96
+ return jsonify({
97
+ 'status': 'error',
98
+ 'message': f"An error occurred: {str(e)}"
99
+ }), 500
100
+
101
+
102
+ if __name__ == "__main__":
103
+ app.run(host="0.0.0.0", port=5000, debug=True)
generated-icon.png ADDED

Git LFS Details

  • SHA256: c4b5b1b87dd9f81d9a7da7d22bb94050ff95d238126b236b23771dc8843bd502
  • Pointer size: 131 Bytes
  • Size of remote file: 451 kB
main.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from app import app
2
+
3
+ if __name__ == "__main__":
4
+ app.run(host="0.0.0.0", port=5000, debug=True)
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "repl-nix-workspace"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "email-validator>=2.2.0",
8
+ "flask>=3.1.0",
9
+ "flask-sqlalchemy>=3.1.1",
10
+ "g4f>=0.4.8.6",
11
+ "gunicorn>=23.0.0",
12
+ "psycopg2-binary>=2.9.10",
13
+ ]
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ email-validator>=2.2.0
2
+ flask>=3.1.0
3
+ flask-sqlalchemy>=3.1.1
4
+ g4f>=0.4.8.6
5
+ gunicorn>=23.0.0
6
+ psycopg2-binary>=2.9.10
static/css/style.css ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global styles */
2
+ body {
3
+ overflow: hidden;
4
+ font-family: 'Inter', sans-serif;
5
+ background-color: #ffffff;
6
+ color: #212529;
7
+ }
8
+
9
+ :root {
10
+ --sidebar-bg: #f8f9fa;
11
+ --main-bg: #ffffff;
12
+ --border-color: #e9ecef;
13
+ --chat-bg: #f8f9fa;
14
+ --user-message-bg: #0d6efd;
15
+ --bot-message-bg: #f0f0f0;
16
+ }
17
+
18
+ /* Sidebar styles */
19
+ .sidebar {
20
+ height: 100vh;
21
+ overflow-y: auto;
22
+ display: flex;
23
+ flex-direction: column;
24
+ background-color: var(--sidebar-bg);
25
+ border-right: 1px solid var(--border-color);
26
+ }
27
+
28
+ .chat-history-list {
29
+ max-height: calc(100vh - 250px);
30
+ overflow-y: auto;
31
+ scrollbar-width: none;
32
+ padding: 10px 0;
33
+ margin: 0;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 2px;
37
+ }
38
+
39
+ .chat-history-list::-webkit-scrollbar {
40
+ width: 6px;
41
+ }
42
+
43
+ .chat-history-list::-webkit-scrollbar-track {
44
+ background: rgba(255, 255, 255, 0.1);
45
+ border-radius: 3px;
46
+ }
47
+
48
+ .chat-history-list::-webkit-scrollbar-thumb {
49
+ background: rgba(255, 255, 255, 0.2);
50
+ border-radius: 3px;
51
+ }
52
+
53
+ .chat-history-list::-webkit-scrollbar-thumb:hover {
54
+ background: rgba(255, 255, 255, 0.3);
55
+ }
56
+
57
+ .history-item {
58
+ cursor: pointer;
59
+ text-overflow: ellipsis;
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ }
63
+
64
+ /* Chat container styles */
65
+ .main-content {
66
+ height: 100vh;
67
+ background-color: var(--main-bg);
68
+ }
69
+
70
+ .chat-header {
71
+ background-color: var(--sidebar-bg);
72
+ border-bottom: 1px solid var(--border-color);
73
+ }
74
+
75
+ .messages-container {
76
+ overflow-y: auto;
77
+ padding-bottom: 20px;
78
+ display: flex;
79
+ flex-direction: column;
80
+ }
81
+
82
+ /* Message styles */
83
+ .message {
84
+ margin-bottom: 20px;
85
+ max-width: 85%;
86
+ display: inline-block;
87
+ }
88
+
89
+ .user-message {
90
+ margin-left: auto;
91
+ background-color: var(--user-message-bg);
92
+ color: white;
93
+ border-radius: 18px 18px 0 18px;
94
+ padding: 10px 14px;
95
+ float: right;
96
+ clear: both;
97
+ max-width: fit-content;
98
+ }
99
+
100
+ .ai-message {
101
+ margin-right: auto;
102
+ background-color: var(--bot-message-bg);
103
+ color: #212529;
104
+ border-radius: 18px 18px 18px 0;
105
+ padding: 10px 14px;
106
+ float: left;
107
+ clear: both;
108
+ max-width: fit-content;
109
+ }
110
+
111
+ .ai-message pre {
112
+ background-color: #1e1e1e;
113
+ border-radius: 8px;
114
+ padding: 12px;
115
+ overflow-x: auto;
116
+ position: relative;
117
+ margin: 10px 0;
118
+ border: 1px solid rgba(255,255,255,0.1);
119
+ }
120
+
121
+ .ai-message pre code {
122
+ font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
123
+ font-size: 14px;
124
+ line-height: 1.4;
125
+ }
126
+
127
+ .ai-message pre::before {
128
+ content: attr(data-language);
129
+ position: absolute;
130
+ top: 0;
131
+ right: 0;
132
+ padding: 4px 8px;
133
+ font-size: 12px;
134
+ color: #999;
135
+ background: #2d2d2d;
136
+ border-radius: 0 8px 0 8px;
137
+ }
138
+
139
+ .ai-message pre .copy-btn {
140
+ position: absolute;
141
+ top: 4px;
142
+ right: 4px;
143
+ padding: 4px 8px;
144
+ background: transparent;
145
+ border: none;
146
+ color: #999;
147
+ cursor: pointer;
148
+ opacity: 0;
149
+ transition: opacity 0.2s;
150
+ }
151
+
152
+ .ai-message pre:hover .copy-btn {
153
+ opacity: 1;
154
+ }
155
+
156
+ .ai-message pre .copy-btn:hover {
157
+ color: #fff;
158
+ }
159
+
160
+ .ai-message img {
161
+ max-width: 100%;
162
+ border-radius: 5px;
163
+ }
164
+
165
+ .system-message {
166
+ color: var(--bs-secondary);
167
+ text-align: center;
168
+ }
169
+
170
+ /* Loading animation */
171
+ .typing-indicator {
172
+ display: flex;
173
+ align-items: center;
174
+ margin: 10px 0;
175
+ }
176
+
177
+ .typing-dot {
178
+ width: 8px;
179
+ height: 8px;
180
+ margin: 0 2px;
181
+ background-color: var(--bs-secondary);
182
+ border-radius: 50%;
183
+ animation: typingAnimation 1.4s infinite ease-in-out;
184
+ }
185
+
186
+ .typing-dot:nth-child(1) {
187
+ animation-delay: 0s;
188
+ }
189
+
190
+ .typing-dot:nth-child(2) {
191
+ animation-delay: 0.2s;
192
+ }
193
+
194
+ .typing-dot:nth-child(3) {
195
+ animation-delay: 0.4s;
196
+ }
197
+
198
+ @keyframes typingAnimation {
199
+ 0%, 60%, 100% {
200
+ transform: translateY(0);
201
+ }
202
+ 30% {
203
+ transform: translateY(-5px);
204
+ }
205
+ }
206
+
207
+ /* Input area styling */
208
+ .input-area {
209
+ background-color: var(--sidebar-bg);
210
+ border-top: 1px solid var(--border-color);
211
+ }
212
+
213
+ .model-badge {
214
+ font-size: 0.7rem;
215
+ padding: 0.2rem 0.6rem;
216
+ border-radius: 12px;
217
+ font-weight: 500;
218
+ background-color: rgba(13, 110, 253, 0.1);
219
+ color: #0d6efd;
220
+ }
221
+
222
+ #message-input {
223
+ resize: none;
224
+ overflow-y: auto;
225
+ max-height: 120px;
226
+ }
227
+
228
+ /* Chat history styling */
229
+ .chat-history-list {
230
+ margin-top: 10px;
231
+ }
232
+
233
+ .chat-history-list .history-item {
234
+ background: transparent;
235
+ border: none;
236
+ border-radius: 0;
237
+ margin: 0;
238
+ padding: 8px 12px;
239
+ font-size: 0.85em;
240
+ display: flex;
241
+ align-items: center;
242
+ justify-content: space-between;
243
+ transition: background 0.2s ease;
244
+ min-height: 40px;
245
+ }
246
+
247
+ .chat-history-list .history-item:hover {
248
+ background: rgba(13, 110, 253, 0.05);
249
+ }
250
+
251
+ .chat-history-list .history-item.active {
252
+ background: rgba(var(--bs-primary-rgb), 0.2);
253
+ border-color: var(--bs-primary);
254
+ }
255
+
256
+ .chat-history-list .btn-danger {
257
+ padding: 4px 8px;
258
+ font-size: 14px;
259
+ background-color: transparent;
260
+ border: none;
261
+ color: #dc3545;
262
+ opacity: 0.7;
263
+ transition: all 0.2s ease;
264
+ }
265
+
266
+ .chat-history-list .btn-danger:hover {
267
+ background-color: transparent;
268
+ color: #dc3545;
269
+ opacity: 1;
270
+ }
271
+
272
+ .chat-history-list .delete-button:hover {
273
+ opacity: 1;
274
+ color: #dc3545;
275
+ }
276
+
277
+ .chat-history-list .history-item:hover .delete-btn {
278
+ opacity: 0.7;
279
+ }
280
+
281
+ .chat-history-list .delete-btn:hover {
282
+ opacity: 1 !important;
283
+ color: #dc3545;
284
+ }
285
+
286
+ /* Responsive adjustments */
287
+ @media (max-width: 768px) {
288
+ .sidebar {
289
+ position: fixed;
290
+ top: 0;
291
+ left: -100%;
292
+ width: 75%;
293
+ z-index: 1000;
294
+ transition: all 0.3s ease;
295
+ }
296
+
297
+ .sidebar.active {
298
+ left: 0;
299
+ }
300
+
301
+ .mobile-toggle {
302
+ display: block;
303
+ }
304
+ }
305
+
306
+ /* Code block styling */
307
+ pre code {
308
+ border-radius: 5px;
309
+ font-family: monospace;
310
+ }
311
+
312
+ /* Markdown content styling */
313
+ .markdown-content h1,
314
+ .markdown-content h2,
315
+ .markdown-content h3,
316
+ .markdown-content h4,
317
+ .markdown-content h5,
318
+ .markdown-content h6 {
319
+ margin-top: 1rem;
320
+ margin-bottom: 0.5rem;
321
+ }
322
+
323
+ .markdown-content p {
324
+ margin-bottom: 1rem;
325
+ }
326
+
327
+ .markdown-content ul,
328
+ .markdown-content ol {
329
+ margin-bottom: 1rem;
330
+ padding-left: 1.5rem;
331
+ }
332
+
333
+ .markdown-content blockquote {
334
+ border-left: 3px solid var(--bs-primary);
335
+ padding-left: 1rem;
336
+ margin-left: 0;
337
+ color: var(--bs-secondary);
338
+ }
339
+
340
+ .markdown-content table {
341
+ width: 100%;
342
+ margin-bottom: 1rem;
343
+ border-collapse: collapse;
344
+ }
345
+
346
+ .markdown-content table th,
347
+ .markdown-content table td {
348
+ padding: 0.5rem;
349
+ border: 1px solid var(--bs-secondary);
350
+ }
351
+
352
+ .markdown-content table th {
353
+ background-color: rgba(var(--bs-secondary-rgb), 0.2);
354
+ }
static/js/chat.js ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ // DOM Elements
3
+ const chatForm = document.getElementById('chat-form');
4
+ const messageInput = document.getElementById('message-input');
5
+ const messagesContainer = document.getElementById('messages-container');
6
+ const newChatBtn = document.getElementById('new-chat-btn');
7
+ const modelSelect = document.getElementById('model-select');
8
+ const currentModelLabel = document.getElementById('current-model-label');
9
+ const historyList = document.getElementById('history-list');
10
+
11
+ // State variables
12
+ let chatHistory = [];
13
+ let conversations = loadConversations();
14
+ let currentConversationId = null;
15
+
16
+ // Load available models
17
+ loadModels();
18
+
19
+ // Start a new conversation
20
+ startNewConversation();
21
+
22
+ // Event listeners
23
+ chatForm.addEventListener('submit', handleChatSubmit);
24
+ newChatBtn.addEventListener('click', startNewConversation);
25
+ modelSelect.addEventListener('change', handleModelChange);
26
+ messageInput.addEventListener('keydown', handleInputKeydown);
27
+
28
+ // Auto-resize textarea as user types
29
+ messageInput.addEventListener('input', function() {
30
+ this.style.height = 'auto';
31
+ this.style.height = (this.scrollHeight) + 'px';
32
+ // Cap the height
33
+ if (parseInt(this.style.height) > 120) {
34
+ this.style.height = '120px';
35
+ }
36
+ });
37
+
38
+ // Load available models from the backend
39
+ function loadModels() {
40
+ fetch('/api/models')
41
+ .then(response => response.json())
42
+ .then(data => {
43
+ if (data.status === 'success') {
44
+ modelSelect.innerHTML = '';
45
+ data.models.forEach(model => {
46
+ const option = document.createElement('option');
47
+ option.value = model.id;
48
+ option.textContent = model.name;
49
+ modelSelect.appendChild(option);
50
+ });
51
+ }
52
+ })
53
+ .catch(error => {
54
+ console.error('Error loading models:', error);
55
+ });
56
+ }
57
+
58
+ // Handle model change
59
+ function handleModelChange() {
60
+ const selectedModel = modelSelect.value;
61
+ const selectedModelName = modelSelect.options[modelSelect.selectedIndex].text;
62
+ currentModelLabel.textContent = selectedModelName;
63
+
64
+ // Update current conversation model
65
+ if (currentConversationId) {
66
+ conversations[currentConversationId].model = selectedModel;
67
+ saveConversations();
68
+ }
69
+ }
70
+
71
+ // Handle chat submission
72
+ function handleChatSubmit(e) {
73
+ e.preventDefault();
74
+ const message = messageInput.value.trim();
75
+
76
+ if (!message) return;
77
+
78
+ // Add user message to UI
79
+ addMessageToUI('user', message);
80
+
81
+ // Add to chat history
82
+ chatHistory.push({
83
+ role: 'user',
84
+ content: message
85
+ });
86
+
87
+ // Update conversation title if it's the first message
88
+ if (chatHistory.length === 1) {
89
+ const title = message.substring(0, 30) + (message.length > 30 ? '...' : '');
90
+ conversations[currentConversationId].title = title;
91
+ updateConversationsList();
92
+ }
93
+
94
+ // Save to local storage
95
+ conversations[currentConversationId].messages = chatHistory;
96
+ saveConversations();
97
+
98
+ // Clear input
99
+ messageInput.value = '';
100
+ messageInput.style.height = 'auto';
101
+
102
+ // Show typing indicator
103
+ showTypingIndicator();
104
+
105
+ // Send to backend
106
+ const selectedModel = modelSelect.value;
107
+
108
+ fetch('/api/chat', {
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Type': 'application/json'
112
+ },
113
+ body: JSON.stringify({
114
+ messages: chatHistory,
115
+ model: selectedModel
116
+ })
117
+ })
118
+ .then(response => response.json())
119
+ .then(data => {
120
+ // Remove typing indicator
121
+ hideTypingIndicator();
122
+
123
+ if (data.status === 'success') {
124
+ // Add AI response to UI
125
+ addMessageToUI('ai', data.message);
126
+
127
+ // Add to chat history
128
+ chatHistory.push({
129
+ role: 'assistant',
130
+ content: data.message
131
+ });
132
+
133
+ // Save to local storage
134
+ conversations[currentConversationId].messages = chatHistory;
135
+ saveConversations();
136
+
137
+ // Scroll to bottom
138
+ scrollToBottom();
139
+ } else {
140
+ // Show error
141
+ addMessageToUI('system', `Error: ${data.message}`);
142
+ }
143
+ })
144
+ .catch(error => {
145
+ hideTypingIndicator();
146
+ console.error('Error:', error);
147
+ addMessageToUI('system', `Error: ${error.message || 'Failed to send message'}`);
148
+ });
149
+
150
+ // Scroll to bottom
151
+ scrollToBottom();
152
+ }
153
+
154
+ // Handle input keydown (for Enter key submission with Shift+Enter for new line)
155
+ function handleInputKeydown(e) {
156
+ if (e.key === 'Enter' && !e.shiftKey) {
157
+ e.preventDefault();
158
+ chatForm.dispatchEvent(new Event('submit'));
159
+ }
160
+ }
161
+
162
+ // Add message to UI
163
+ function addMessageToUI(sender, content) {
164
+ // Create a wrapper for each message group
165
+ let messageWrapper;
166
+ if (sender === 'system') {
167
+ messageWrapper = document.createElement('div');
168
+ messageWrapper.className = 'w-100 d-flex justify-content-center my-3';
169
+ const messageDiv = document.createElement('div');
170
+ messageDiv.className = 'system-message text-center';
171
+ messageDiv.innerHTML = `<p>${content}</p>`;
172
+ messageWrapper.appendChild(messageDiv);
173
+ } else {
174
+ messageWrapper = document.createElement('div');
175
+ messageWrapper.className = 'w-100 d-flex ' +
176
+ (sender === 'user' ? 'justify-content-end' : 'justify-content-start');
177
+
178
+ const messageDiv = renderMessage(content, sender === 'user');
179
+ messageWrapper.appendChild(messageDiv);
180
+ }
181
+
182
+ messagesContainer.appendChild(messageWrapper);
183
+ scrollToBottom();
184
+ }
185
+
186
+ // Show typing indicator
187
+ function showTypingIndicator() {
188
+ const typingWrapper = document.createElement('div');
189
+ typingWrapper.className = 'w-100 d-flex justify-content-start';
190
+ typingWrapper.id = 'typing-indicator-wrapper';
191
+
192
+ const typingDiv = document.createElement('div');
193
+ typingDiv.className = 'typing-indicator ai-message';
194
+ typingDiv.id = 'typing-indicator';
195
+ typingDiv.innerHTML = `
196
+ <div class="typing-dot"></div>
197
+ <div class="typing-dot"></div>
198
+ <div class="typing-dot"></div>
199
+ `;
200
+
201
+ typingWrapper.appendChild(typingDiv);
202
+ messagesContainer.appendChild(typingWrapper);
203
+ scrollToBottom();
204
+ }
205
+
206
+ // Hide typing indicator
207
+ function hideTypingIndicator() {
208
+ const typingWrapper = document.getElementById('typing-indicator-wrapper');
209
+ if (typingWrapper) {
210
+ typingWrapper.remove();
211
+ }
212
+ }
213
+
214
+ // Scroll to bottom of messages container
215
+ function scrollToBottom() {
216
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
217
+ }
218
+
219
+ // Start a new conversation
220
+ function startNewConversation() {
221
+ // Clear chat history
222
+ chatHistory = [];
223
+
224
+ // Clear messages container
225
+ messagesContainer.innerHTML = `
226
+ <div class="system-message text-center my-5">
227
+ <h3>Welcome to AI Chat</h3>
228
+ <p class="text-muted">Ask me anything! I'm powered by g4f and ready to help.</p>
229
+ </div>
230
+ `;
231
+
232
+ // Create a new conversation ID
233
+ currentConversationId = Date.now().toString();
234
+
235
+ // Add to conversations object
236
+ conversations[currentConversationId] = {
237
+ id: currentConversationId,
238
+ title: 'New Conversation',
239
+ model: modelSelect.value,
240
+ messages: []
241
+ };
242
+
243
+ // Save to local storage
244
+ saveConversations();
245
+
246
+ // Update UI
247
+ updateConversationsList();
248
+ }
249
+
250
+ // Load conversation by ID
251
+ function loadConversation(id) {
252
+ if (!conversations[id]) return;
253
+
254
+ // Set current conversation ID
255
+ currentConversationId = id;
256
+
257
+ // Load chat history
258
+ chatHistory = conversations[id].messages || [];
259
+
260
+ // Set model
261
+ if (conversations[id].model) {
262
+ modelSelect.value = conversations[id].model;
263
+ const selectedModelName = modelSelect.options[modelSelect.selectedIndex].text;
264
+ currentModelLabel.textContent = selectedModelName;
265
+ }
266
+
267
+ // Clear messages container
268
+ messagesContainer.innerHTML = '';
269
+
270
+ // Add messages to UI
271
+ if (chatHistory.length === 0) {
272
+ messagesContainer.innerHTML = `
273
+ <div class="system-message text-center my-5">
274
+ <h3>Welcome to AI Chat</h3>
275
+ <p class="text-muted">Ask me anything! I'm powered by g4f and ready to help.</p>
276
+ </div>
277
+ `;
278
+ } else {
279
+ chatHistory.forEach(msg => {
280
+ if (msg.role === 'user') {
281
+ addMessageToUI('user', msg.content);
282
+ } else if (msg.role === 'assistant') {
283
+ addMessageToUI('ai', msg.content);
284
+ } else if (msg.role === 'system') {
285
+ addMessageToUI('system', msg.content);
286
+ }
287
+ });
288
+ }
289
+
290
+ // Update UI
291
+ updateConversationsList();
292
+ }
293
+
294
+ // Update conversations list in sidebar
295
+ function updateConversationsList() {
296
+ historyList.innerHTML = '';
297
+
298
+ // Sort conversations by ID (newest first)
299
+ const sortedIds = Object.keys(conversations).sort((a, b) => b - a);
300
+
301
+ sortedIds.forEach(id => {
302
+ const conv = conversations[id];
303
+ const item = document.createElement('li');
304
+ item.className = `list-group-item history-item d-flex justify-content-between align-items-center ${id === currentConversationId ? 'active' : ''}`;
305
+
306
+ const titleSpan = document.createElement('span');
307
+ titleSpan.textContent = conv.title;
308
+ titleSpan.style.cursor = 'pointer';
309
+ titleSpan.addEventListener('click', () => {
310
+ loadConversation(id);
311
+ });
312
+
313
+ const deleteBtn = document.createElement('button');
314
+ deleteBtn.className = 'btn btn-sm btn-danger';
315
+ deleteBtn.innerHTML = '<i class="fas fa-trash"></i>';
316
+ deleteBtn.addEventListener('click', (e) => {
317
+ e.stopPropagation();
318
+ deleteConversation(id);
319
+ });
320
+
321
+ item.appendChild(titleSpan);
322
+ item.appendChild(deleteBtn);
323
+ item.dataset.id = id;
324
+
325
+ historyList.appendChild(item);
326
+ });
327
+ }
328
+
329
+ // Load conversations from local storage
330
+ function loadConversations() {
331
+ try {
332
+ const saved = localStorage.getItem('g4f_conversations');
333
+ return saved ? JSON.parse(saved) : {};
334
+ } catch (error) {
335
+ console.error('Error loading conversations:', error);
336
+ return {};
337
+ }
338
+ }
339
+
340
+ // Save conversations to local storage
341
+ function saveConversations() {
342
+ try {
343
+ localStorage.setItem('g4f_conversations', JSON.stringify(conversations));
344
+ } catch (error) {
345
+ console.error('Error saving conversations:', error);
346
+ }
347
+ }
348
+
349
+ // Delete specific conversation
350
+ function deleteConversation(id) {
351
+ if (confirm('Are you sure you want to delete this conversation? This cannot be undone.')) {
352
+ fetch(`/api/conversations/${id}`, {
353
+ method: 'DELETE',
354
+ })
355
+ .then(response => response.json())
356
+ .then(data => {
357
+ if (data.status === 'success') {
358
+ delete conversations[id];
359
+
360
+ // If current conversation was deleted, start a new one
361
+ if (id === currentConversationId) {
362
+ startNewConversation();
363
+ }
364
+
365
+ saveConversations();
366
+ updateConversationsList();
367
+ addMessageToUI('system', 'Conversation deleted');
368
+ }
369
+ })
370
+ .catch(error => {
371
+ console.error('Error:', error);
372
+ addMessageToUI('system', 'Failed to delete conversation');
373
+ });
374
+ }
375
+ }
376
+
377
+ function renderMessage(content, isUser = false) {
378
+ const messageDiv = document.createElement('div');
379
+ messageDiv.className = `message ${isUser ? 'user-message' : 'ai-message'}`;
380
+
381
+ if (!isUser) {
382
+ const formattedContent = marked.parse(content);
383
+ messageDiv.innerHTML = formattedContent;
384
+
385
+ // Add copy buttons and language labels to code blocks
386
+ messageDiv.querySelectorAll('pre code').forEach((block) => {
387
+ hljs.highlightElement(block);
388
+ const pre = block.parentElement;
389
+ const language = block.className.split('-')[1] || 'plaintext';
390
+ pre.setAttribute('data-language', language);
391
+
392
+ const copyBtn = document.createElement('button');
393
+ copyBtn.className = 'copy-btn';
394
+ copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
395
+ copyBtn.onclick = async () => {
396
+ await navigator.clipboard.writeText(block.textContent);
397
+ copyBtn.innerHTML = '<i class="fas fa-check"></i>';
398
+ setTimeout(() => {
399
+ copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
400
+ }, 2000);
401
+ };
402
+ pre.appendChild(copyBtn);
403
+ });
404
+ } else {
405
+ messageDiv.textContent = content;
406
+ }
407
+
408
+ return messageDiv;
409
+ }
410
+ });
templates/index.html ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-bs-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Chat</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
10
+
11
+ <!-- Font Awesome for icons -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+
14
+ <!-- Google Fonts -->
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
18
+
19
+ <!-- Custom CSS -->
20
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
21
+ </head>
22
+ <body>
23
+ <div class="container-fluid px-0">
24
+ <div class="row g-0 vh-100">
25
+ <!-- Sidebar -->
26
+ <div class="col-md-3 col-lg-2 sidebar p-3 border-end">
27
+ <!-- Logo and Title -->
28
+ <div class="d-flex align-items-center mb-4">
29
+ <i class="fas fa-robot me-2 fs-4"></i>
30
+ <h5 class="mb-0 fw-bold">AI Chat</h5>
31
+ </div>
32
+
33
+ <!-- New Chat Button -->
34
+ <div class="mb-4">
35
+ <button id="new-chat-btn" class="btn btn-outline-primary w-100 d-flex align-items-center">
36
+ <i class="fas fa-plus me-2"></i> New Chat
37
+ </button>
38
+ </div>
39
+
40
+ <!-- Model Selection -->
41
+ <div class="mb-4">
42
+ <label for="model-select" class="form-label small text-muted">Model</label>
43
+ <select id="model-select" class="form-select">
44
+ <option value="gpt-4o" selected>GPT-4o</option>
45
+ <!-- Other models will be loaded dynamically -->
46
+ </select>
47
+ </div>
48
+
49
+ <!-- Chat History -->
50
+ <div class="chat-history mb-3">
51
+ <label class="form-label small text-muted mb-2">Chat History</label>
52
+ <ul id="history-list" class="list-group chat-history-list mb-0">
53
+ <!-- Chat history items will appear here -->
54
+ </ul>
55
+ </div>
56
+
57
+ <!-- App Info -->
58
+ <div class="mt-auto text-center text-secondary small pt-5">
59
+ <p class="mb-1">Powered by Abdullah ali</p>
60
+ <p>© 2025 AI Chat. All rights reserved.</p>
61
+ </div>
62
+ </div>
63
+
64
+ <!-- Main Chat Area -->
65
+ <div class="col-md-9 col-lg-10 main-content d-flex flex-column p-0">
66
+ <!-- Chat Header -->
67
+ <div class="chat-header p-3 border-bottom d-flex align-items-center">
68
+ <h6 class="mb-0">Chat with AI</h6>
69
+ <span class="model-badge ms-2">GPT-4o</span>
70
+ </div>
71
+
72
+ <!-- Messages Container -->
73
+ <div id="messages-container" class="messages-container flex-grow-1 p-4">
74
+ <!-- Welcome message -->
75
+ <div class="system-message text-center my-5">
76
+ <h2 class="fw-bold">Welcome to AI Chat</h2>
77
+ <p class="text-muted">Ask me anything! I'm powered by Abdullah Ali and ready .to help.</p>
78
+ </div>
79
+
80
+ <!-- Messages will appear here -->
81
+ </div>
82
+
83
+ <!-- Input Area -->
84
+ <div class="input-area p-3 border-top">
85
+ <form id="chat-form" class="d-flex">
86
+ <input
87
+ id="message-input"
88
+ class="form-control me-2"
89
+ placeholder="Type a message..."
90
+ required>
91
+ <button type="submit" id="send-button" class="btn btn-primary rounded-circle">
92
+ <i class="fas fa-paper-plane"></i>
93
+ </button>
94
+ </form>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Bootstrap JS Bundle -->
101
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
102
+
103
+ <!-- Marked.js for Markdown Support -->
104
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
105
+
106
+ <!-- Highlight.js for Code Syntax Highlighting -->
107
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
108
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
109
+
110
+ <!-- Custom JS -->
111
+ <script src="{{ url_for('static', filename='js/chat.js') }}"></script>
112
+ </body>
113
+ </html>
uv.lock ADDED
The diff for this file is too large to render. See raw diff
 
vercel.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": 2,
3
+ "builds": [
4
+ {
5
+ "src": "app.py",
6
+ "use": "@vercel/python",
7
+ "config": { "runtime": "3.11" }
8
+ }
9
+ ],
10
+ "routes": [
11
+ {
12
+ "src": "/(.*)",
13
+ "dest": "app.py"
14
+ }
15
+ ],
16
+ "env": {
17
+ "PYTHON_VERSION": "3.11"
18
+ },
19
+
20
+
21
+ {
22
+ "functions": {
23
+ "app/api/**/route.ts": {
24
+ "maxDuration": 60
25
+ }
26
+
27
+
28
+ }