abdullahalioo commited on
Commit
ce23758
·
verified ·
1 Parent(s): cab5ea3

Upload 22 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ 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
+ attached_assets/image_1743188683006.png filter=lfs diff=lfs merge=lfs -text
37
+ generated-icon.png filter=lfs diff=lfs merge=lfs -text
.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <body>
3
+ <script src="https://js.puter.com/v2/"></script>
4
+ <script>
5
+ (async () => {
6
+ const resp = await puter.ai.chat('good', {model: 'grok-beta', stream: true });
7
+ for await ( const part of resp ) puter.print(part?.text?.replaceAll('\n', '<br>'));
8
+ })();
9
+ </script>
10
+ </body>
11
+ </html>
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
+
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.DEBUG)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Create Flask app
12
+ app = Flask(__name__)
13
+ app.secret_key = os.environ.get("SESSION_SECRET", "default_secret_key")
14
+
15
+ # Initialize g4f client
16
+ client = Client()
17
+
18
+
19
+ @app.route('/')
20
+ def index():
21
+ return render_template('index.html')
22
+
23
+
24
+ @app.route('/api/chat', methods=['POST'])
25
+ def chat():
26
+ try:
27
+ data = request.json
28
+ messages = data.get('messages', [])
29
+ model = data.get('model', 'gpt-4o-mini')
30
+
31
+ # Add system prompt
32
+ system_prompt = {
33
+ "role":
34
+ "system",
35
+ "content":
36
+ "You are orion helpful AI assistant. You provide accurate, informative, and friendly responses while keeping them concise and relevant."
37
+ }
38
+
39
+ # Insert system prompt at the beginning if not already present
40
+ if not messages or messages[0].get('role') != 'system':
41
+ messages.insert(0, system_prompt)
42
+
43
+ logger.debug(
44
+ f"Sending request to g4f with model: {model} and messages: {messages}"
45
+ )
46
+
47
+ # Call the g4f API
48
+ response = client.chat.completions.create(model=model,
49
+ messages=messages,
50
+ web_search=False)
51
+
52
+ ai_response = response.choices[0].message.content
53
+ logger.debug(f"Received response from g4f: {ai_response}")
54
+
55
+ return jsonify({'status': 'success', 'message': ai_response})
56
+ except Exception as e:
57
+ logger.error(f"Error in chat endpoint: {str(e)}")
58
+ return jsonify({
59
+ 'status': 'error',
60
+ 'message': f"An error occurred: {str(e)}"
61
+ }), 500
62
+
63
+
64
+ @app.route('/api/conversations/<conversation_id>', methods=['DELETE'])
65
+ def delete_conversation(conversation_id):
66
+ try:
67
+ return jsonify({'status': 'success', 'message': f'Conversation {conversation_id} deleted'})
68
+ except Exception as e:
69
+ logger.error(f"Error deleting conversation: {str(e)}")
70
+ return jsonify({
71
+ 'status': 'error',
72
+ 'message': f"An error occurred: {str(e)}"
73
+ }), 500
74
+
75
+ @app.route('/api/models', methods=['GET'])
76
+ def get_models():
77
+ try:
78
+ # Return a list of available models
79
+ # You can customize this list based on what g4f supports
80
+ models = [{
81
+ "id": "gpt-4o-mini",
82
+ "name": "GPT-4o Mini"
83
+ }, {
84
+ "id": "gpt-3.5-turbo",
85
+ "name": "GPT-3.5 Turbo"
86
+ }, {
87
+ "id": "claude-instant",
88
+ "name": "Claude Instant"
89
+ }, {
90
+ "id": "gemini-pro",
91
+ "name": "Gemini Pro"
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)
attached_assets/image_1742498963362.png ADDED
attached_assets/image_1742500787258.png ADDED
attached_assets/image_1742500896102.png ADDED
attached_assets/image_1742501019996.png ADDED
attached_assets/image_1742501148334.png ADDED
attached_assets/image_1742501607975.png ADDED
attached_assets/image_1742501775050.png ADDED
attached_assets/image_1742501895659.png ADDED
attached_assets/image_1742504124361.png ADDED
attached_assets/image_1743188683006.png ADDED

Git LFS Details

  • SHA256: 66e4f8c11280d194ee98c2aaf2ee1b69d1c2d183241012337f809b12c3ff48b6
  • Pointer size: 131 Bytes
  • Size of remote file: 758 kB
attached_assets/image_1743190220399.png ADDED
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 | G4F</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/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-mini" selected>GPT-4o Mini</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 g4f</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 Mini</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 g4f 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/[email protected]/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,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }