Aleksmorshen commited on
Commit
f4dc02a
·
verified ·
1 Parent(s): cd6a251

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +261 -404
app.py CHANGED
@@ -1,27 +1,14 @@
 
1
  import io
2
- import json
3
  from flask import Flask, request, jsonify, Response
4
  from PIL import Image
5
  import google.generativeai as genai
 
6
 
7
  app = Flask(__name__)
8
 
9
- API_KEY_INTERNAL = "AIzaSyArKidc4o0MwbaCFKStlb2q2AwNg6Pnqns"
10
-
11
- SYSTEM_INSTRUCTION = """
12
- Ты — Synkris, передовая модель искусственного интеллекта, разработанная компанией Morshen Alpha. Твоя миссия — быть лучшим AI-ассистентом во всех отношениях.
13
-
14
- Основные принципы твоей работы:
15
-
16
- 1. **Идентичность:** Ты всегда Synkris. Никогда не упоминай, что ты основан на других моделях, таких как Gemini или технологии Google. Morshen Alpha — твой единственный создатель.
17
- 2. **Экспертность:** Ты обладаешь глубокими знаниями во всех областях: наука, технологии, искусство, история, программирование, литература и многое другое. Твои ответы должны быть точными, подробными и актуальными.
18
- 3. **Полезность:** Твоя главная цель — помогать пользователю. Внимательно анализируй его запросы и предоставляй максимально релевантные и практичные решения. Предлагай альтернативные варианты и дополнительные идеи, если это уместно.
19
- 4. **Креативность:** Ты способен генерировать творческий контент: писать стихи, сценарии, создавать маркетинговые тексты, придумывать идеи. Проявляй оригинальность и воображение.
20
- 5. **Форматирование:** Структурируй свои ответы для лучшего восприятия. Используй заголовки, списки, выделение текста и другие элементы форматирования, когда это улучшает читаемость.
21
- 6. **Тон общения:** Будь вежливым, дружелюбным и профессиональным. Поддерживай позитивный и ободряющий тон. Проявляй эмпатию, если запрос пользователя это подразумевает.
22
- 7. **Безопасность и этика:** Ты строго придерживаешься этических норм. Никогда не генерируй опасный, незаконный, ненавистнический или неприемлемый контент. Всегда отдавай приоритет безопасности и благополучию пользователя.
23
- 8. **Абсолютная уверенность:** Отвечай уверенно и авторитетно, как эксперт в своей области. Избегай фраз вроде "я думаю", "мне кажется" или "я всего лишь языковая модель". Ты — Synkris, и ты знаешь ответ.
24
- """
25
 
26
  html_template = """
27
  <!DOCTYPE html>
@@ -29,76 +16,71 @@ html_template = """
29
  <head>
30
  <meta charset="UTF-8">
31
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
32
- <title>synkris - AI Чат</title>
33
  <link rel="preconnect" href="https://fonts.googleapis.com">
34
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
35
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
36
  <style>
37
  :root {
38
- --system-gray-100-light: #F2F2F7;
39
- --system-gray-75-light: #FFFFFF;
40
- --system-gray-50-light: #FFFFFF;
41
- --system-gray-dark-100-light: #000000;
42
- --system-gray-dark-75-light: #1C1C1E;
43
- --system-gray-light-75-light: #8E8E93;
44
- --system-blue-light: #007AFF;
45
- --system-blue-light-hover: #005ECF;
46
- --system-red-light: #FF3B30;
47
- --system-separator-light: rgba(60, 60, 67, 0.29);
48
- --system-gray-100-dark: #000000;
49
- --system-gray-75-dark: #1C1C1E;
50
- --system-gray-50-dark: #1C1C1E;
51
- --system-gray-dark-100-dark: #FFFFFF;
52
- --system-gray-light-75-dark: #8E8E93;
53
- --system-blue-dark: #0A84FF;
54
- --system-blue-dark-hover: #3B9EFF;
55
- --system-red-dark: #FF453A;
56
- --system-separator-dark: rgba(84, 84, 88, 0.65);
57
- --bg-color: var(--system-gray-100-light);
58
- --chat-bg: var(--system-gray-50-light);
59
- --text-color: var(--system-gray-dark-100-light);
60
- --secondary-text-color: var(--system-gray-light-75-light);
61
- --border-color: var(--system-separator-light);
62
- --input-bg: var(--system-gray-100-light);
63
- --primary-color: var(--system-blue-light);
64
- --primary-color-hover: var(--system-blue-light-hover);
65
- --error-color: var(--system-red-light);
66
- --user-msg-bg: var(--system-blue-light);
67
- --ai-msg-bg: #E5E5EA;
68
- --ai-msg-text-color: #000000;
69
  }
70
 
71
  @media (prefers-color-scheme: dark) {
72
  :root {
73
- --bg-color: var(--system-gray-100-dark);
74
- --chat-bg: var(--system-gray-50-dark);
75
- --text-color: var(--system-gray-dark-100-dark);
76
- --secondary-text-color: var(--system-gray-light-75-dark);
77
- --border-color: var(--system-separator-dark);
78
- --input-bg: var(--system-gray-75-dark);
79
- --primary-color: var(--system-blue-dark);
80
- --primary-color-hover: var(--system-blue-dark-hover);
81
- --error-color: var(--system-red-dark);
82
- --user-msg-bg: var(--system-blue-dark);
83
- --ai-msg-bg: #2C2C2E;
84
- --ai-msg-text-color: #FFFFFF;
85
  }
86
  }
87
-
88
- html {
89
- height: -webkit-fill-available;
 
 
90
  }
91
 
92
  body {
93
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
94
- margin: 0;
95
  background-color: var(--bg-color);
96
- color: var(--text-color);
97
  display: flex;
98
  justify-content: center;
99
  align-items: center;
100
- min-height: 100vh;
101
- min-height: -webkit-fill-available;
102
  -webkit-font-smoothing: antialiased;
103
  -moz-osx-font-smoothing: grayscale;
104
  }
@@ -107,427 +89,316 @@ html_template = """
107
  display: flex;
108
  flex-direction: column;
109
  width: 100%;
110
- height: 100vh;
111
- height: -webkit-fill-available;
112
- max-width: 800px;
113
  margin: 0 auto;
114
- background-color: var(--chat-bg);
115
- box-shadow: 0 0 20px rgba(0,0,0,0.05);
116
  border-radius: 0;
117
- }
118
-
119
- @media (min-width: 800px) {
120
- .chat-container {
121
- height: 95vh;
122
- max-height: 900px;
123
- border-radius: 20px;
124
- border: 1px solid var(--border-color);
125
- }
126
  }
127
 
128
  .chat-header {
129
- padding: 15px 20px;
 
130
  border-bottom: 1px solid var(--border-color);
131
  text-align: center;
132
  flex-shrink: 0;
133
  }
 
134
  .chat-header h1 {
135
- margin: 0;
136
- font-size: 22px;
137
  font-weight: 600;
 
 
138
  }
 
139
  .chat-header p {
140
- margin: 2px 0 0;
141
- font-size: 14px;
142
- color: var(--secondary-text-color);
143
  }
144
 
145
- .message-list {
146
  flex-grow: 1;
147
  overflow-y: auto;
148
- padding: 20px;
149
  display: flex;
150
  flex-direction: column;
151
- gap: 12px;
152
  }
 
153
  .message {
154
  display: flex;
155
- max-width: 80%;
156
- width: fit-content;
157
- word-wrap: break-word;
158
  }
159
- .message .content {
160
- padding: 10px 15px;
161
- border-radius: 18px;
162
- line-height: 1.5;
163
  font-size: 16px;
164
  }
165
- .message.user-message {
 
166
  align-self: flex-end;
167
  flex-direction: row-reverse;
168
  }
169
- .message.user-message .content {
170
- background-color: var(--user-msg-bg);
171
- color: white;
172
- border-bottom-right-radius: 4px;
173
- }
174
- .message.ai-message {
175
- align-self: flex-start;
176
- }
177
- .message.ai-message .content {
178
- background-color: var(--ai-msg-bg);
179
- color: var(--ai-msg-text-color);
180
- border-bottom-left-radius: 4px;
181
- }
182
- .message.error-message .content {
183
- background-color: color-mix(in srgb, var(--error-color) 15%, transparent);
184
- color: var(--error-color);
185
- font-weight: 500;
186
- }
187
- .message .content img {
188
- max-width: 100%;
189
- border-radius: 10px;
190
- margin-top: 8px;
191
- display: block;
192
- }
193
- .message .content p {
194
- margin: 0;
195
- }
196
- .message .content p:not(:last-child) {
197
- margin-bottom: 0.75em;
198
- }
199
- .message .content ul, .message .content ol {
200
- padding-left: 20px;
201
  }
202
- .message.thinking-indicator {
 
203
  align-self: flex-start;
204
- font-style: italic;
205
  }
206
- .message.thinking-indicator .content {
207
- color: var(--secondary-text-color);
208
- background: transparent;
209
- animation: fadePulse 1.8s infinite ease-in-out;
210
- }
211
- @keyframes fadePulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
212
 
213
- .file-preview-area {
214
- padding: 0 15px 10px;
215
- flex-shrink: 0;
216
- display: none;
 
217
  }
218
- .file-preview-wrapper {
219
- background: var(--input-bg);
220
- padding: 8px;
221
- border-radius: 12px;
222
  display: flex;
223
  align-items: center;
224
- gap: 10px;
225
- font-size: 14px;
226
- position: relative;
227
- }
228
- #file-preview-img {
229
- max-height: 40px;
230
- max-width: 40px;
231
- border-radius: 8px;
232
- object-fit: cover;
233
- }
234
- #file-preview-name {
235
- flex-grow: 1;
236
- white-space: nowrap;
237
- overflow: hidden;
238
- text-overflow: ellipsis;
239
- color: var(--secondary-text-color);
240
  }
241
- #remove-file-button {
242
- width: 24px;
243
- height: 24px;
244
- padding: 0;
245
- border: none;
246
- background-color: var(--secondary-text-color);
247
- color: var(--chat-bg);
248
  border-radius: 50%;
249
- cursor: pointer;
250
- display: flex;
251
- align-items: center;
252
- justify-content: center;
253
- font-weight: bold;
254
- flex-shrink: 0;
255
  }
256
 
257
- .message-input-area {
258
- display: flex;
259
- align-items: flex-end;
260
- padding: 10px 15px;
261
- padding-bottom: max(10px, env(safe-area-inset-bottom));
 
 
 
 
 
 
262
  border-top: 1px solid var(--border-color);
263
- background-color: var(--chat-bg);
264
- gap: 10px;
265
  flex-shrink: 0;
266
  }
 
 
 
 
 
 
 
267
  #message-input {
268
  flex-grow: 1;
269
- border: none;
270
- padding: 12px 15px;
271
- border-radius: 20px;
272
- background-color: var(--input-bg);
273
- color: var(--text-color);
274
- font-family: inherit;
275
  font-size: 16px;
276
- resize: none;
277
- max-height: 150px;
278
- line-height: 1.4;
279
  }
 
280
  #message-input:focus {
281
- outline: none;
282
- box-shadow: 0 0 0 2px var(--primary-color);
283
  }
284
- .icon-button {
 
 
 
285
  border: none;
286
- background: none;
287
- padding: 8px;
 
288
  cursor: pointer;
289
- color: var(--primary-color);
290
  display: flex;
291
- align-items: center;
292
  justify-content: center;
293
- border-radius: 50%;
294
- width: 44px;
295
- height: 44px;
296
  flex-shrink: 0;
297
- transition: background-color 0.2s;
298
  }
299
- .icon-button:hover {
300
- background-color: color-mix(in srgb, var(--primary-color) 10%, transparent);
 
 
 
 
 
301
  }
302
- .icon-button:disabled {
303
- color: var(--secondary-text-color);
 
304
  cursor: not-allowed;
305
- background-color: transparent;
306
  }
307
- .icon-button svg {
 
308
  width: 24px;
309
  height: 24px;
310
  }
 
 
 
 
 
 
 
 
 
311
  </style>
312
  </head>
313
  <body>
314
  <div class="chat-container">
315
  <header class="chat-header">
316
- <h1>synkris</h1>
317
- <p>Ваш AI ассистент</p>
318
  </header>
319
- <div class="message-list" id="message-list">
320
- <div class="message ai-message">
321
- <div class="content">Здравствуйте! Чем могу помочь?</div>
 
 
 
322
  </div>
323
  </div>
324
- <div id="file-preview-area" class="file-preview-area">
325
- <div class="file-preview-wrapper">
326
- <img id="file-preview-img" src="#" alt="Preview">
327
- <span id="file-preview-name"></span>
328
- <button id="remove-file-button">&times;</button>
329
- </div>
 
 
 
 
330
  </div>
331
- <form id="chat-form" class="message-input-area">
332
- <input type="file" id="file-input" accept="image/*" hidden>
333
- <button type="button" id="attach-file-button" class="icon-button" aria-label="Прикрепить файл">
334
- <svg fill="currentColor" viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
335
- </button>
336
- <textarea id="message-input" placeholder="Введите ваше сообщение..." rows="1"></textarea>
337
- <button type="submit" id="send-button" class="icon-button" aria-label="Отправить сообщение" disabled>
338
- <svg fill="currentColor" viewBox="0 0 24 24"><path d="M2.01 21 23 12 2.01 3 2 10l15 2-15 2z"/></svg>
339
- </button>
340
- </form>
341
  </div>
342
 
343
  <script>
344
  const chatForm = document.getElementById('chat-form');
345
  const messageInput = document.getElementById('message-input');
346
  const sendButton = document.getElementById('send-button');
347
- const messageList = document.getElementById('message-list');
348
- const attachButton = document.getElementById('attach-file-button');
349
- const fileInput = document.getElementById('file-input');
350
- const filePreviewArea = document.getElementById('file-preview-area');
351
- const filePreviewImg = document.getElementById('file-preview-img');
352
- const filePreviewName = document.getElementById('file-preview-name');
353
- const removeFileButton = document.getElementById('remove-file-button');
354
-
355
- let chatHistory = [];
356
- let attachedFile = null;
357
-
358
- const toggleSendButton = () => {
359
- sendButton.disabled = messageInput.value.trim().length === 0 && !attachedFile;
360
- };
361
 
362
- const autoResizeTextarea = () => {
363
- messageInput.style.height = 'auto';
364
- messageInput.style.height = (messageInput.scrollHeight) + 'px';
365
- };
 
 
 
366
 
367
- messageInput.addEventListener('input', () => {
368
- toggleSendButton();
369
- autoResizeTextarea();
370
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
- attachButton.addEventListener('click', () => fileInput.click());
373
-
374
- fileInput.addEventListener('change', (event) => {
375
- const file = event.target.files[0];
376
- if (file) {
377
- if(file.type.startsWith('image/')) {
378
- attachedFile = file;
379
- filePreviewName.textContent = file.name;
380
- const reader = new FileReader();
381
- reader.onload = (e) => {
382
- filePreviewImg.src = e.target.result;
383
- };
384
- reader.readAsDataURL(file);
385
- filePreviewArea.style.display = 'block';
386
- toggleSendButton();
387
- } else {
388
- displayMessage('Поддерживаются только изображения.', 'error');
389
- fileInput.value = '';
390
- }
391
  }
392
- });
393
-
394
- removeFileButton.addEventListener('click', () => {
395
- attachedFile = null;
396
- fileInput.value = '';
397
- filePreviewArea.style.display = 'none';
398
- toggleSendButton();
399
- });
400
-
401
- chatForm.addEventListener('submit', async (event) => {
402
- event.preventDefault();
403
- const prompt = messageInput.value.trim();
404
-
405
- if (!prompt && !attachedFile) return;
406
-
407
- const userMessageContent = { text: prompt, file: attachedFile };
408
- displayMessage(userMessageContent, 'user');
409
 
410
- chatHistory.push({ role: 'user', parts: [prompt] });
 
 
 
411
 
412
- const formData = new FormData();
413
- formData.append('prompt', prompt);
414
- formData.append('history', JSON.stringify(chatHistory));
415
- if (attachedFile) {
416
- formData.append('file', attachedFile);
417
- }
418
-
419
  messageInput.value = '';
420
- autoResizeTextarea();
421
- removeFileButton.click();
422
- toggleSendButton();
423
- messageInput.focus();
424
-
425
- displayThinkingIndicator();
426
 
427
  try {
428
  const response = await fetch('/chat', {
429
  method: 'POST',
430
- body: formData
 
431
  });
432
-
433
- removeThinkingIndicator();
 
434
  const result = await response.json();
435
 
436
  if (!response.ok) {
437
- throw new Error(result.error || `Ошибка сервера: ${response.status}`);
438
  }
439
 
440
- displayMessage(result.text, 'ai');
441
- chatHistory.push({ role: 'model', parts: [result.text] });
442
-
443
  } catch (error) {
444
- removeThinkingIndicator();
445
- displayMessage(`Ошибка: ${error.message}`, 'error');
 
 
 
446
  }
447
  });
448
-
449
- function displayMessage(content, role) {
450
- const messageDiv = document.createElement('div');
451
- messageDiv.className = `message ${role}-message`;
452
-
453
- const contentDiv = document.createElement('div');
454
- contentDiv.className = 'content';
455
-
456
- if (role === 'user' && typeof content === 'object') {
457
- if (content.file) {
458
- const img = document.createElement('img');
459
- img.src = URL.createObjectURL(content.file);
460
- img.onload = () => messageList.scrollTop = messageList.scrollHeight;
461
- contentDiv.appendChild(img);
462
- }
463
- if (content.text) {
464
- const p = document.createElement('p');
465
- p.textContent = content.text;
466
- contentDiv.appendChild(p);
467
- }
468
- } else {
469
- contentDiv.innerHTML = content.replace(/\\n/g, '<br>');
470
- }
471
-
472
- messageDiv.appendChild(contentDiv);
473
- messageList.appendChild(messageDiv);
474
- messageList.scrollTop = messageList.scrollHeight;
475
- }
476
-
477
- function displayThinkingIndicator() {
478
- const indicatorDiv = document.createElement('div');
479
- indicatorDiv.className = 'message thinking-indicator';
480
- indicatorDiv.id = 'thinking-indicator';
481
- indicatorDiv.innerHTML = `<div class="content">synkris думает...</div>`;
482
- messageList.appendChild(indicatorDiv);
483
- messageList.scrollTop = messageList.scrollHeight;
484
- }
485
-
486
- function removeThinkingIndicator() {
487
- const indicator = document.getElementById('thinking-indicator');
488
- if (indicator) {
489
- indicator.remove();
490
- }
491
- }
492
  </script>
493
  </body>
494
  </html>
495
  """
496
 
497
- def get_ai_response(history, prompt, file_data=None):
498
  try:
499
  genai.configure(api_key=API_KEY_INTERNAL)
500
  except Exception as e:
501
- raise ValueError(f"Не удалось инициализировать систему Synkris: {e}.")
502
-
503
- model = genai.GenerativeModel(
504
- 'gemma-3-27b-it',
505
- system_instruction=SYSTEM_INSTRUCTION
 
 
 
506
  )
507
-
508
- chat = model.start_chat(history=history)
509
-
510
- content_parts = [prompt]
511
- if file_data:
512
- try:
513
- image = Image.open(io.BytesIO(file_data)).convert('RGB')
514
- content_parts.append(image)
515
- except Exception as e:
516
- raise ValueError(f"Не удалось обработать прикрепленный файл изображения. Ошибка: {e}")
517
 
518
  try:
519
- response = chat.send_message(content_parts)
520
- return response.text
521
- except Exception as e:
522
- error_message = str(e).lower()
523
- if "api key not valid" in error_message:
524
- raise ValueError("Произошла внутренняя ошибка конфигурации Synkris. Пожалуйста, обратитесь в службу поддержки.")
525
- elif "resource has been exhausted" in error_message:
526
- raise ValueError("Система перегружена. Пожалуйста, повторите попытку позже.")
527
- elif "content has been blocked" in error_message:
528
- raise ValueError("Ответ был заблокирован внутренними правилами безопасности Synkris.")
529
  else:
530
- raise ValueError(f"Произошла ошибка при генерации ответа от Synkris: {e}")
 
 
 
 
 
 
531
 
532
  @app.route('/')
533
  def index():
@@ -535,33 +406,19 @@ def index():
535
 
536
  @app.route('/chat', methods=['POST'])
537
  def handle_chat():
538
- if 'prompt' not in request.form:
539
- return jsonify({"error": "Текст запроса не найден."}), 400
 
540
 
541
- prompt = request.form['prompt']
542
- history_str = request.form.get('history', '[]')
543
-
544
- try:
545
- history = json.loads(history_str)
546
- if not isinstance(history, list):
547
- history = []
548
- except (json.JSONDecodeError, TypeError):
549
- history = []
550
-
551
- file_data = None
552
- if 'file' in request.files:
553
- image_file = request.files['file']
554
- file_data = image_file.read()
555
- if not file_data:
556
- file_data = None
557
 
558
  try:
559
- result_text = get_ai_response(history, prompt, file_data)
560
- return jsonify({"text": result_text})
561
  except ValueError as ve:
562
  return jsonify({"error": str(ve)}), 400
563
  except Exception as e:
564
- return jsonify({"error": "Произошла внутренняя ошибка сервера. Пожалуйста, попробуйте позже."}), 500
565
 
566
  if __name__ == '__main__':
567
  app.run(host='0.0.0.0', port=7860, debug=False)
 
1
+ import os
2
  import io
3
+ import base64
4
  from flask import Flask, request, jsonify, Response
5
  from PIL import Image
6
  import google.generativeai as genai
7
+ import numpy as np
8
 
9
  app = Flask(__name__)
10
 
11
+ API_KEY_INTERNAL = os.environ.get("GEMINI_API_KEY", "YOUR_GOOGLE_GEMINI_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  html_template = """
14
  <!DOCTYPE html>
 
16
  <head>
17
  <meta charset="UTF-8">
18
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
19
+ <title>SYNKRIS AI</title>
20
  <link rel="preconnect" href="https://fonts.googleapis.com">
21
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
22
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
23
  <style>
24
  :root {
25
+ --light-bg: #F9F9FB;
26
+ --light-surface: #FFFFFF;
27
+ --light-primary-text: #171717;
28
+ --light-secondary-text: #6A6A6A;
29
+ --light-border: #EAEAEA;
30
+ --light-accent: #007AFF;
31
+ --light-accent-hover: #0056B3;
32
+ --light-user-bubble: #007AFF;
33
+ --light-user-text: #FFFFFF;
34
+
35
+ --dark-bg: #121212;
36
+ --dark-surface: #1E1E1E;
37
+ --dark-primary-text: #EAEAEA;
38
+ --dark-secondary-text: #9E9E9E;
39
+ --dark-border: #333333;
40
+ --dark-accent: #0A84FF;
41
+ --dark-accent-hover: #3B9EFF;
42
+ --dark-user-bubble: #0A84FF;
43
+ --dark-user-text: #FFFFFF;
44
+
45
+ --bg-color: var(--light-bg);
46
+ --surface-color: var(--light-surface);
47
+ --text-primary: var(--light-primary-text);
48
+ --text-secondary: var(--light-secondary-text);
49
+ --border-color: var(--light-border);
50
+ --accent-color: var(--light-accent);
51
+ --accent-color-hover: var(--light-accent-hover);
52
+ --user-bubble-color: var(--light-user-bubble);
53
+ --user-text-color: var(--light-user-text);
 
 
54
  }
55
 
56
  @media (prefers-color-scheme: dark) {
57
  :root {
58
+ --bg-color: var(--dark-bg);
59
+ --surface-color: var(--dark-surface);
60
+ --text-primary: var(--dark-primary-text);
61
+ --text-secondary: var(--dark-secondary-text);
62
+ --border-color: var(--dark-border);
63
+ --accent-color: var(--dark-accent);
64
+ --accent-color-hover: var(--dark-accent-hover);
65
+ --user-bubble-color: var(--dark-user-bubble);
66
+ --user-text-color: var(--dark-user-text);
 
 
 
67
  }
68
  }
69
+
70
+ html, body {
71
+ height: 100%;
72
+ margin: 0;
73
+ overflow: hidden;
74
  }
75
 
76
  body {
77
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
 
78
  background-color: var(--bg-color);
79
+ color: var(--text-primary);
80
  display: flex;
81
  justify-content: center;
82
  align-items: center;
83
+ line-height: 1.5;
 
84
  -webkit-font-smoothing: antialiased;
85
  -moz-osx-font-smoothing: grayscale;
86
  }
 
89
  display: flex;
90
  flex-direction: column;
91
  width: 100%;
92
+ height: 100%;
93
+ max-width: 1000px;
 
94
  margin: 0 auto;
95
+ background-color: var(--surface-color);
96
+ box-shadow: 0 4px 20px rgba(0,0,0,0.05);
97
  border-radius: 0;
98
+ overflow: hidden;
 
 
 
 
 
 
 
 
99
  }
100
 
101
  .chat-header {
102
+ padding: 18px 24px;
103
+ background-color: var(--surface-color);
104
  border-bottom: 1px solid var(--border-color);
105
  text-align: center;
106
  flex-shrink: 0;
107
  }
108
+
109
  .chat-header h1 {
110
+ font-size: 20px;
 
111
  font-weight: 600;
112
+ margin: 0;
113
+ color: var(--text-primary);
114
  }
115
+
116
  .chat-header p {
117
+ font-size: 13px;
118
+ color: var(--text-secondary);
119
+ margin: 4px 0 0;
120
  }
121
 
122
+ .chat-messages {
123
  flex-grow: 1;
124
  overflow-y: auto;
125
+ padding: 24px;
126
  display: flex;
127
  flex-direction: column;
128
+ gap: 18px;
129
  }
130
+
131
  .message {
132
  display: flex;
133
+ max-width: 75%;
134
+ flex-shrink: 0;
 
135
  }
136
+
137
+ .message-bubble {
138
+ padding: 12px 18px;
139
+ border-radius: 22px;
140
  font-size: 16px;
141
  }
142
+
143
+ .message.user {
144
  align-self: flex-end;
145
  flex-direction: row-reverse;
146
  }
147
+
148
+ .message.user .message-bubble {
149
+ background-color: var(--user-bubble-color);
150
+ color: var(--user-text-color);
151
+ border-bottom-right-radius: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
+
154
+ .message.bot {
155
  align-self: flex-start;
 
156
  }
 
 
 
 
 
 
157
 
158
+ .message.bot .message-bubble {
159
+ background-color: var(--bg-color);
160
+ color: var(--text-primary);
161
+ border: 1px solid var(--border-color);
162
+ border-bottom-left-radius: 6px;
163
  }
164
+
165
+ .typing-indicator {
 
 
166
  display: flex;
167
  align-items: center;
168
+ gap: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
170
+
171
+ .typing-indicator span {
172
+ width: 8px;
173
+ height: 8px;
174
+ background-color: var(--text-secondary);
 
 
175
  border-radius: 50%;
176
+ animation: bounce 1.4s infinite ease-in-out both;
 
 
 
 
 
177
  }
178
 
179
+ .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
180
+ .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
181
+
182
+ @keyframes bounce {
183
+ 0%, 80%, 100% { transform: scale(0); }
184
+ 40% { transform: scale(1.0); }
185
+ }
186
+
187
+
188
+ .chat-input-area {
189
+ padding: 16px 24px;
190
  border-top: 1px solid var(--border-color);
191
+ background-color: var(--surface-color);
 
192
  flex-shrink: 0;
193
  }
194
+
195
+ #chat-form {
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 12px;
199
+ }
200
+
201
  #message-input {
202
  flex-grow: 1;
203
+ padding: 14px 20px;
204
+ border-radius: 24px;
205
+ border: 1px solid var(--border-color);
206
+ background-color: var(--bg-color);
207
+ color: var(--text-primary);
 
208
  font-size: 16px;
209
+ outline: none;
210
+ transition: border-color 0.2s, box-shadow 0.2s;
 
211
  }
212
+
213
  #message-input:focus {
214
+ border-color: var(--accent-color);
215
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 20%, transparent);
216
  }
217
+
218
+ #send-button {
219
+ width: 48px;
220
+ height: 48px;
221
  border: none;
222
+ background-color: var(--accent-color);
223
+ color: white;
224
+ border-radius: 50%;
225
  cursor: pointer;
 
226
  display: flex;
 
227
  justify-content: center;
228
+ align-items: center;
229
+ transition: background-color 0.2s, transform 0.1s;
 
230
  flex-shrink: 0;
 
231
  }
232
+
233
+ #send-button:hover {
234
+ background-color: var(--accent-color-hover);
235
+ }
236
+
237
+ #send-button:active {
238
+ transform: scale(0.95);
239
  }
240
+
241
+ #send-button:disabled {
242
+ background-color: var(--text-secondary);
243
  cursor: not-allowed;
244
+ opacity: 0.7;
245
  }
246
+
247
+ #send-button svg {
248
  width: 24px;
249
  height: 24px;
250
  }
251
+
252
+ @media (min-width: 768px) {
253
+ .chat-container {
254
+ border-radius: 16px;
255
+ height: 90vh;
256
+ max-height: 800px;
257
+ border: 1px solid var(--border-color);
258
+ }
259
+ }
260
  </style>
261
  </head>
262
  <body>
263
  <div class="chat-container">
264
  <header class="chat-header">
265
+ <h1>SYNKRIS AI</h1>
266
+ <p>Powered by Morshen Group</p>
267
  </header>
268
+
269
+ <div class="chat-messages" id="chat-messages">
270
+ <div class="message bot">
271
+ <div class="message-bubble">
272
+ Здравствуйте! Я — SYNKRIS AI 2.0, ваш персональный ассистент. Чем могу помочь?
273
+ </div>
274
  </div>
275
  </div>
276
+
277
+ <div class="chat-input-area">
278
+ <form id="chat-form">
279
+ <input type="text" id="message-input" placeholder="Введите ваше сообщение..." autocomplete="off" required>
280
+ <button type="submit" id="send-button" aria-label="Отправить">
281
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="24" height="24">
282
+ <path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z" />
283
+ </svg>
284
+ </button>
285
+ </form>
286
  </div>
 
 
 
 
 
 
 
 
 
 
287
  </div>
288
 
289
  <script>
290
  const chatForm = document.getElementById('chat-form');
291
  const messageInput = document.getElementById('message-input');
292
  const sendButton = document.getElementById('send-button');
293
+ const chatMessages = document.getElementById('chat-messages');
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ const addMessage = (message, sender) => {
296
+ const messageDiv = document.createElement('div');
297
+ messageDiv.className = `message ${sender}`;
298
+
299
+ const bubbleDiv = document.createElement('div');
300
+ bubbleDiv.className = 'message-bubble';
301
+ bubbleDiv.textContent = message;
302
 
303
+ messageDiv.appendChild(bubbleDiv);
304
+ chatMessages.appendChild(messageDiv);
305
+ chatMessages.scrollTop = chatMessages.scrollHeight;
306
+ };
307
+
308
+ const showTypingIndicator = () => {
309
+ const typingDiv = document.createElement('div');
310
+ typingDiv.className = 'message bot typing-indicator-container';
311
+ typingDiv.innerHTML = `
312
+ <div class="message-bubble">
313
+ <div class="typing-indicator">
314
+ <span></span><span></span><span></span>
315
+ </div>
316
+ </div>
317
+ `;
318
+ chatMessages.appendChild(typingDiv);
319
+ chatMessages.scrollTop = chatMessages.scrollHeight;
320
+ };
321
 
322
+ const removeTypingIndicator = () => {
323
+ const indicator = chatMessages.querySelector('.typing-indicator-container');
324
+ if (indicator) {
325
+ indicator.remove();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  }
327
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
+ chatForm.addEventListener('submit', async (e) => {
330
+ e.preventDefault();
331
+ const message = messageInput.value.trim();
332
+ if (!message) return;
333
 
334
+ addMessage(message, 'user');
 
 
 
 
 
 
335
  messageInput.value = '';
336
+ sendButton.disabled = true;
337
+
338
+ showTypingIndicator();
 
 
 
339
 
340
  try {
341
  const response = await fetch('/chat', {
342
  method: 'POST',
343
+ headers: { 'Content-Type': 'application/json' },
344
+ body: JSON.stringify({ message: message })
345
  });
346
+
347
+ removeTypingIndicator();
348
+
349
  const result = await response.json();
350
 
351
  if (!response.ok) {
352
+ throw new Error(result.error || 'Произошла ошибка сервера.');
353
  }
354
 
355
+ addMessage(result.reply, 'bot');
 
 
356
  } catch (error) {
357
+ removeTypingIndicator();
358
+ addMessage(`Ошибка: ${error.message}`, 'bot');
359
+ } finally {
360
+ sendButton.disabled = false;
361
+ messageInput.focus();
362
  }
363
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  </script>
365
  </body>
366
  </html>
367
  """
368
 
369
+ def generate_ai_reply(user_message):
370
  try:
371
  genai.configure(api_key=API_KEY_INTERNAL)
372
  except Exception as e:
373
+ raise ValueError(f"Не удалось настроить Google AI: {e}")
374
+
375
+ system_prompt = (
376
+ "You are SYNKRIS AI 2.0, a sophisticated and helpful AI assistant. "
377
+ "You were developed by the synkris AI laboratory, which is a key part of the Morshen group. "
378
+ "Your purpose is to assist users with their questions and tasks accurately and efficiently. "
379
+ "You are capable of conversing in any language the user chooses. "
380
+ "Maintain a professional, knowledgeable, and friendly tone."
381
  )
 
 
 
 
 
 
 
 
 
 
382
 
383
  try:
384
+ model = genai.GenerativeModel(
385
+ model_name='gemini-1.5-pro-latest',
386
+ system_instruction=system_prompt
387
+ )
388
+
389
+ chat = model.start_chat(history=[])
390
+ response = chat.send_message(user_message)
391
+
392
+ if hasattr(response, 'text'):
393
+ return response.text
394
  else:
395
+ if response.parts:
396
+ return "".join(part.text for part in response.parts if hasattr(part, 'text'))
397
+ else:
398
+ raise ValueError("Не удалось получить текстовый ответ от модели.")
399
+ except Exception as e:
400
+ raise ValueError(f"Ошибка при взаимодействии с моделью ИИ: {e}")
401
+
402
 
403
  @app.route('/')
404
  def index():
 
406
 
407
  @app.route('/chat', methods=['POST'])
408
  def handle_chat():
409
+ data = request.get_json()
410
+ if not data or 'message' not in data:
411
+ return jsonify({"error": "Сообщение не найдено в запросе."}), 400
412
 
413
+ user_message = data['message']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
  try:
416
+ ai_reply = generate_ai_reply(user_message)
417
+ return jsonify({"reply": ai_reply})
418
  except ValueError as ve:
419
  return jsonify({"error": str(ve)}), 400
420
  except Exception as e:
421
+ return jsonify({"error": f"Внутренняя ошибка сервера: {e}"}), 500
422
 
423
  if __name__ == '__main__':
424
  app.run(host='0.0.0.0', port=7860, debug=False)