Starchik1 commited on
Commit
fb91b72
·
verified ·
1 Parent(s): 73bdbfa

Upload 9 files

Browse files
Files changed (9) hide show
  1. .env +11 -0
  2. .gitignore +35 -0
  3. api_mode.py +86 -0
  4. app.py +119 -0
  5. requirements.txt +7 -0
  6. run.py +60 -0
  7. static/css/style.css +582 -0
  8. static/js/script.js +363 -0
  9. templates/index.html +87 -0
.env ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Конфигурационные переменные
2
+ FLASK_ENV=development
3
+ FLASK_DEBUG=True
4
+
5
+ # Настройки модели
6
+ # Если вы используете API вместо локальной модели, раскомментируйте и заполните следующие строки
7
+ # MISTRAL_API_KEY=your_api_key_here
8
+ # MISTRAL_API_URL=https://api.mistral.ai/v1/
9
+
10
+ # Настройки приложения
11
+ APP_NAME=Mistral AI Assistant
.gitignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Виртуальное окружение
2
+ venv/
3
+ env/
4
+ .env
5
+
6
+ # Кэш Python
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+ .pytest_cache/
11
+
12
+ # Дистрибутивы
13
+ dist/
14
+ build/
15
+ *.egg-info/
16
+
17
+ # Локальные файлы
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+ # Файлы IDE
22
+ .idea/
23
+ .vscode/
24
+ *.swp
25
+ *.swo
26
+
27
+ # Кэш модели
28
+ .cache/
29
+ .huggingface/
30
+
31
+ # Логи
32
+ *.log
33
+
34
+ # Локальное хранилище моделей
35
+ models/
api_mode.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from flask import Flask, render_template, request, jsonify
4
+ from flask_cors import CORS
5
+ import requests
6
+ from dotenv import load_dotenv
7
+
8
+ # Загружаем переменные окружения
9
+ load_dotenv()
10
+
11
+ app = Flask(__name__)
12
+ CORS(app)
13
+
14
+ # Проверяем наличие API ключа
15
+ API_KEY = os.getenv("MISTRAL_API_KEY")
16
+ API_URL = os.getenv("MISTRAL_API_URL", "https://api.mistral.ai/v1/")
17
+
18
+ # Функция для генерации ответа через API
19
+ def generate_response_api(prompt, max_length=1024):
20
+ if not API_KEY:
21
+ return "Ошибка: API ключ не найден. Пожалуйста, добавьте MISTRAL_API_KEY в файл .env"
22
+
23
+ headers = {
24
+ "Authorization": f"Bearer {API_KEY}",
25
+ "Content-Type": "application/json"
26
+ }
27
+
28
+ data = {
29
+ "model": "mistral-medium", # или другая доступная модель
30
+ "messages": [
31
+ {"role": "user", "content": prompt}
32
+ ],
33
+ "max_tokens": max_length,
34
+ "temperature": 0.7,
35
+ "top_p": 0.9
36
+ }
37
+
38
+ try:
39
+ response = requests.post(f"{API_URL}chat/completions", headers=headers, json=data)
40
+ response.raise_for_status()
41
+ result = response.json()
42
+ return result["choices"][0]["message"]["content"]
43
+ except Exception as e:
44
+ return f"Ошибка при обращении к API: {str(e)}"
45
+
46
+ # Маршруты
47
+ @app.route('/')
48
+ def index():
49
+ return render_template('index.html')
50
+
51
+ @app.route('/api/chat', methods=['POST'])
52
+ def chat():
53
+ data = request.json
54
+ prompt = data.get('prompt', '')
55
+
56
+ if not prompt:
57
+ return jsonify({"error": "Пустой запрос"}), 400
58
+
59
+ try:
60
+ response = generate_response_api(prompt)
61
+ return jsonify({"response": response})
62
+ except Exception as e:
63
+ return jsonify({"error": str(e)}), 500
64
+
65
+ @app.route('/api/code', methods=['POST'])
66
+ def code():
67
+ data = request.json
68
+ prompt = data.get('prompt', '')
69
+ language = data.get('language', 'python')
70
+
71
+ if not prompt:
72
+ return jsonify({"error": "Пустой запрос"}), 400
73
+
74
+ # Добавляем контекст для генерации кода
75
+ code_prompt = f"Напиши код на языке {language} для решения следующей задачи: {prompt}"
76
+
77
+ try:
78
+ response = generate_response_api(code_prompt)
79
+ return jsonify({"code": response})
80
+ except Exception as e:
81
+ return jsonify({"error": str(e)}), 500
82
+
83
+ if __name__ == '__main__':
84
+ print("🚀 Запуск Mistral AI Assistant в режиме API")
85
+ print("⚠️ Убедитесь, что вы добавили MISTRAL_API_KEY в файл .env")
86
+ app.run(debug=True, port=5000)
app.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from flask import Flask, render_template, request, jsonify
4
+ from flask_cors import CORS
5
+ import torch
6
+ from transformers import AutoModelForCausalLM, AutoTokenizer
7
+
8
+ app = Flask(__name__)
9
+ CORS(app)
10
+
11
+ # Глобальные переменные для модели
12
+ model = None
13
+ tokenizer = None
14
+
15
+ # Загрузка модели Mistral (локально)
16
+ def load_model():
17
+ global model, tokenizer
18
+ try:
19
+ model_name = "mistralai/Mistral-7B-Instruct-v0.3"
20
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
21
+
22
+ # Проверяем наличие GPU
23
+ if torch.cuda.is_available():
24
+ print("Загрузка модели на GPU...")
25
+ model = AutoModelForCausalLM.from_pretrained(
26
+ model_name,
27
+ torch_dtype=torch.float16,
28
+ device_map="auto",
29
+ load_in_8bit=True # Для оптимизации памяти
30
+ )
31
+ else:
32
+ print("GPU не обнаружен. Загрузка модели на CPU (это может быть медленно)...")
33
+ # Загрузка облегченной версии для CPU
34
+ model = AutoModelForCausalLM.from_pretrained(
35
+ model_name,
36
+ torch_dtype=torch.float32, # Используем float32 для CPU
37
+ low_cpu_mem_usage=True,
38
+ device_map="auto"
39
+ )
40
+ print("Модель успешно загружена!")
41
+ except Exception as e:
42
+ print(f"Ошибка при загрузке модели: {e}")
43
+ model = None
44
+ tokenizer = None
45
+
46
+ # Инициализация приложения
47
+ @app.before_request
48
+ def before_request():
49
+ global model, tokenizer
50
+ if model is None or tokenizer is None:
51
+ load_model()
52
+
53
+ # Функция для генерации ответа от модели
54
+ def generate_response(prompt, max_length=1024):
55
+ if model is None or tokenizer is None:
56
+ return "Ошибка: Модель не загружена"
57
+
58
+ # Форматирование запроса в формате Mistral Instruct
59
+ formatted_prompt = f"<s>[INST] {prompt} [/INST]"
60
+
61
+ inputs = tokenizer(formatted_prompt, return_tensors="pt").to(model.device)
62
+
63
+ # Генерация ответа
64
+ with torch.no_grad():
65
+ outputs = model.generate(
66
+ inputs["input_ids"],
67
+ max_new_tokens=max_length,
68
+ temperature=0.7,
69
+ top_p=0.9,
70
+ do_sample=True,
71
+ pad_token_id=tokenizer.eos_token_id
72
+ )
73
+
74
+ # Декодирование ответа
75
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
76
+ # Извлечение только ответа модели (после [/INST])
77
+ response = response.split("[/INST]")[-1].strip()
78
+
79
+ return response
80
+
81
+ # Маршруты
82
+ @app.route('/')
83
+ def index():
84
+ return render_template('index.html')
85
+
86
+ @app.route('/api/chat', methods=['POST'])
87
+ def chat():
88
+ data = request.json
89
+ prompt = data.get('prompt', '')
90
+
91
+ if not prompt:
92
+ return jsonify({"error": "Пустой запрос"}), 400
93
+
94
+ try:
95
+ response = generate_response(prompt)
96
+ return jsonify({"response": response})
97
+ except Exception as e:
98
+ return jsonify({"error": str(e)}), 500
99
+
100
+ @app.route('/api/code', methods=['POST'])
101
+ def code():
102
+ data = request.json
103
+ prompt = data.get('prompt', '')
104
+ language = data.get('language', 'python')
105
+
106
+ if not prompt:
107
+ return jsonify({"error": "Пустой запрос"}), 400
108
+
109
+ # Добавляем контекст для генерации кода
110
+ code_prompt = f"Напиши код на языке {language} для решения следующей задачи: {prompt}"
111
+
112
+ try:
113
+ response = generate_response(code_prompt)
114
+ return jsonify({"code": response})
115
+ except Exception as e:
116
+ return jsonify({"error": str(e)}), 500
117
+
118
+ if __name__ == '__main__':
119
+ app.run(debug=True, port=5000)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ flask==2.3.3
2
+ requests==2.31.0
3
+ python-dotenv==1.0.0
4
+ flask-cors==4.0.0
5
+ transformers==4.36.2
6
+ torch==2.1.2
7
+ sentencepiece==0.1.99
run.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+
5
+ def check_dependencies():
6
+ """Проверяет, установлены ли все необходимые зависимости"""
7
+ try:
8
+ import flask
9
+ import torch
10
+ import transformers
11
+ print("✅ Все необходимые зависимости установлены.")
12
+ return True
13
+ except ImportError as e:
14
+ print(f"❌ Отсутствуют зависимости: {e}")
15
+ return False
16
+
17
+ def install_dependencies():
18
+ """Устанавливает необходимые зависимости из requirements.txt"""
19
+ print("📦 Установка зависимостей...")
20
+ try:
21
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
22
+ print("✅ Зависимости успешно установлены!")
23
+ return True
24
+ except subprocess.CalledProcessError as e:
25
+ print(f"❌ Ошибка при установке зависимостей: {e}")
26
+ return False
27
+
28
+ def run_app():
29
+ """Запускает Flask-приложение"""
30
+ print("🚀 Запуск Mistral AI Assistant...")
31
+ print("⚠️ Первый запуск может занять некоторое время, пока загружается модель.")
32
+ try:
33
+ from app import app
34
+ app.run(debug=True, port=5000)
35
+ except Exception as e:
36
+ print(f"❌ Ошибка при запуске приложения: {e}")
37
+
38
+ def main():
39
+ print("="*50)
40
+ print("🤖 Mistral AI Assistant - Установка и запуск")
41
+ print("="*50)
42
+
43
+ # Проверяем зависимости
44
+ if not check_dependencies():
45
+ print("\nНеобходимо установить зависимости.")
46
+ choice = input("Установить зависимости сейчас? (y/n): ").lower()
47
+ if choice == 'y':
48
+ if not install_dependencies():
49
+ print("❌ Не удалось установить зависимости. Пожалуйста, установите их вручную.")
50
+ return
51
+ else:
52
+ print("⚠️ Для работы приложения необходимо установить зависимости.")
53
+ print("Выполните: pip install -r requirements.txt")
54
+ return
55
+
56
+ # Запускаем приложение
57
+ run_app()
58
+
59
+ if __name__ == "__main__":
60
+ main()
static/css/style.css ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Основные стили */
2
+ :root {
3
+ --primary-color: #6366f1;
4
+ --secondary-color: #4f46e5;
5
+ --text-color: #1f2937;
6
+ --text-secondary: #6b7280;
7
+ --bg-color: #ffffff;
8
+ --bg-secondary: #f3f4f6;
9
+ --border-color: #e5e7eb;
10
+ --shadow-color: rgba(0, 0, 0, 0.1);
11
+ --message-user-bg: #f3f4f6;
12
+ --message-ai-bg: #ffffff;
13
+ --code-bg: #282c34;
14
+ }
15
+
16
+ /* Темная тема */
17
+ body.dark-mode {
18
+ --primary-color: #818cf8;
19
+ --secondary-color: #6366f1;
20
+ --text-color: #f9fafb;
21
+ --text-secondary: #d1d5db;
22
+ --bg-color: #111827;
23
+ --bg-secondary: #1f2937;
24
+ --border-color: #374151;
25
+ --shadow-color: rgba(0, 0, 0, 0.3);
26
+ --message-user-bg: #1f2937;
27
+ --message-ai-bg: #111827;
28
+ --code-bg: #1a1d23;
29
+ }
30
+
31
+ * {
32
+ margin: 0;
33
+ padding: 0;
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ body {
38
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
39
+ color: var(--text-color);
40
+ background-color: var(--bg-color);
41
+ transition: background-color 0.3s, color 0.3s;
42
+ }
43
+
44
+ .app-container {
45
+ display: flex;
46
+ height: 100vh;
47
+ overflow: hidden;
48
+ }
49
+
50
+ /* Боковая панель */
51
+ .sidebar {
52
+ width: 260px;
53
+ background-color: var(--bg-secondary);
54
+ border-right: 1px solid var(--border-color);
55
+ display: flex;
56
+ flex-direction: column;
57
+ padding: 16px;
58
+ transition: background-color 0.3s;
59
+ }
60
+
61
+ .logo {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 10px;
65
+ margin-bottom: 20px;
66
+ padding: 10px;
67
+ }
68
+
69
+ .logo i {
70
+ font-size: 24px;
71
+ color: var(--primary-color);
72
+ }
73
+
74
+ .logo h1 {
75
+ font-size: 18px;
76
+ font-weight: 600;
77
+ }
78
+
79
+ .new-chat-btn {
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ gap: 8px;
84
+ background-color: var(--primary-color);
85
+ color: white;
86
+ border: none;
87
+ border-radius: 8px;
88
+ padding: 10px 16px;
89
+ font-size: 14px;
90
+ font-weight: 500;
91
+ cursor: pointer;
92
+ transition: background-color 0.2s;
93
+ margin-bottom: 20px;
94
+ }
95
+
96
+ .new-chat-btn:hover {
97
+ background-color: var(--secondary-color);
98
+ }
99
+
100
+ .chat-history {
101
+ flex: 1;
102
+ overflow-y: auto;
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 8px;
106
+ }
107
+
108
+ .chat-item {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 8px;
112
+ padding: 8px 12px;
113
+ border-radius: 8px;
114
+ cursor: pointer;
115
+ transition: background-color 0.2s;
116
+ color: var(--text-secondary);
117
+ }
118
+
119
+ .chat-item:hover {
120
+ background-color: rgba(99, 102, 241, 0.1);
121
+ }
122
+
123
+ .chat-item.active {
124
+ background-color: rgba(99, 102, 241, 0.15);
125
+ color: var(--primary-color);
126
+ }
127
+
128
+ .chat-item i {
129
+ font-size: 14px;
130
+ }
131
+
132
+ .chat-item span {
133
+ font-size: 14px;
134
+ white-space: nowrap;
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ }
138
+
139
+ .sidebar-footer {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: space-between;
143
+ padding-top: 16px;
144
+ border-top: 1px solid var(--border-color);
145
+ margin-top: 16px;
146
+ }
147
+
148
+ .toggle-mode-btn {
149
+ background: none;
150
+ border: none;
151
+ color: var(--text-secondary);
152
+ cursor: pointer;
153
+ font-size: 16px;
154
+ padding: 8px;
155
+ border-radius: 8px;
156
+ transition: background-color 0.2s;
157
+ }
158
+
159
+ .toggle-mode-btn:hover {
160
+ background-color: rgba(99, 102, 241, 0.1);
161
+ }
162
+
163
+ .model-info {
164
+ font-size: 12px;
165
+ color: var(--text-secondary);
166
+ }
167
+
168
+ /* Основной контент */
169
+ .main-content {
170
+ flex: 1;
171
+ display: flex;
172
+ flex-direction: column;
173
+ height: 100%;
174
+ overflow: hidden;
175
+ }
176
+
177
+ .chat-container {
178
+ flex: 1;
179
+ overflow-y: auto;
180
+ padding: 20px;
181
+ display: flex;
182
+ flex-direction: column;
183
+ }
184
+
185
+ /* Экран приветствия */
186
+ .welcome-screen {
187
+ display: flex;
188
+ flex-direction: column;
189
+ align-items: center;
190
+ justify-content: center;
191
+ text-align: center;
192
+ height: 100%;
193
+ padding: 20px;
194
+ max-width: 800px;
195
+ margin: 0 auto;
196
+ }
197
+
198
+ .welcome-logo {
199
+ font-size: 48px;
200
+ color: var(--primary-color);
201
+ margin-bottom: 20px;
202
+ background-color: rgba(99, 102, 241, 0.1);
203
+ width: 100px;
204
+ height: 100px;
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ border-radius: 20px;
209
+ }
210
+
211
+ .welcome-screen h1 {
212
+ font-size: 28px;
213
+ margin-bottom: 10px;
214
+ }
215
+
216
+ .welcome-screen p {
217
+ font-size: 16px;
218
+ color: var(--text-secondary);
219
+ margin-bottom: 30px;
220
+ }
221
+
222
+ .examples {
223
+ width: 100%;
224
+ }
225
+
226
+ .examples h3 {
227
+ font-size: 16px;
228
+ margin-bottom: 16px;
229
+ }
230
+
231
+ .example-grid {
232
+ display: grid;
233
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
234
+ gap: 16px;
235
+ }
236
+
237
+ .example {
238
+ background-color: var(--bg-secondary);
239
+ border: 1px solid var(--border-color);
240
+ border-radius: 8px;
241
+ padding: 16px;
242
+ cursor: pointer;
243
+ transition: border-color 0.2s, transform 0.2s;
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 12px;
247
+ }
248
+
249
+ .example:hover {
250
+ border-color: var(--primary-color);
251
+ transform: translateY(-2px);
252
+ }
253
+
254
+ .example i {
255
+ font-size: 18px;
256
+ color: var(--primary-color);
257
+ }
258
+
259
+ .example span {
260
+ font-size: 14px;
261
+ }
262
+
263
+ /* Сообщения */
264
+ .messages {
265
+ display: flex;
266
+ flex-direction: column;
267
+ gap: 24px;
268
+ }
269
+
270
+ .message {
271
+ display: flex;
272
+ gap: 16px;
273
+ padding: 16px;
274
+ border-radius: 8px;
275
+ animation: fadeIn 0.3s ease-out;
276
+ }
277
+
278
+ @keyframes fadeIn {
279
+ from { opacity: 0; transform: translateY(10px); }
280
+ to { opacity: 1; transform: translateY(0); }
281
+ }
282
+
283
+ .message.user {
284
+ background-color: var(--message-user-bg);
285
+ align-self: flex-end;
286
+ max-width: 85%;
287
+ }
288
+
289
+ .message.ai {
290
+ background-color: var(--message-ai-bg);
291
+ border: 1px solid var(--border-color);
292
+ align-self: flex-start;
293
+ max-width: 85%;
294
+ }
295
+
296
+ .message-avatar {
297
+ width: 36px;
298
+ height: 36px;
299
+ border-radius: 50%;
300
+ background-color: var(--primary-color);
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ color: white;
305
+ font-size: 16px;
306
+ flex-shrink: 0;
307
+ }
308
+
309
+ .message-avatar.user {
310
+ background-color: var(--text-secondary);
311
+ }
312
+
313
+ .message-content {
314
+ flex: 1;
315
+ overflow-wrap: break-word;
316
+ }
317
+
318
+ .message-sender {
319
+ font-weight: 600;
320
+ margin-bottom: 4px;
321
+ }
322
+
323
+ .message-text {
324
+ line-height: 1.5;
325
+ font-size: 15px;
326
+ }
327
+
328
+ .message-text p {
329
+ margin-bottom: 12px;
330
+ }
331
+
332
+ .message-text p:last-child {
333
+ margin-bottom: 0;
334
+ }
335
+
336
+ .message-text pre {
337
+ background-color: var(--code-bg);
338
+ border-radius: 6px;
339
+ padding: 12px;
340
+ overflow-x: auto;
341
+ margin: 12px 0;
342
+ }
343
+
344
+ .message-text code {
345
+ font-family: 'Fira Code', monospace;
346
+ font-size: 14px;
347
+ }
348
+
349
+ .message-text code:not(pre code) {
350
+ background-color: rgba(99, 102, 241, 0.1);
351
+ padding: 2px 4px;
352
+ border-radius: 4px;
353
+ font-size: 14px;
354
+ }
355
+
356
+ .message-text ul, .message-text ol {
357
+ margin: 12px 0;
358
+ padding-left: 24px;
359
+ }
360
+
361
+ .message-text li {
362
+ margin-bottom: 6px;
363
+ }
364
+
365
+ .message-text a {
366
+ color: var(--primary-color);
367
+ text-decoration: none;
368
+ }
369
+
370
+ .message-text a:hover {
371
+ text-decoration: underline;
372
+ }
373
+
374
+ /* Контейнер ввода */
375
+ .input-container {
376
+ padding: 16px;
377
+ border-top: 1px solid var(--border-color);
378
+ background-color: var(--bg-color);
379
+ }
380
+
381
+ .input-wrapper {
382
+ display: flex;
383
+ gap: 12px;
384
+ background-color: var(--bg-secondary);
385
+ border: 1px solid var(--border-color);
386
+ border-radius: 8px;
387
+ padding: 8px 16px;
388
+ transition: border-color 0.2s;
389
+ }
390
+
391
+ .input-wrapper:focus-within {
392
+ border-color: var(--primary-color);
393
+ }
394
+
395
+ textarea {
396
+ flex: 1;
397
+ border: none;
398
+ background: none;
399
+ resize: none;
400
+ height: 24px;
401
+ max-height: 200px;
402
+ font-family: inherit;
403
+ font-size: 15px;
404
+ color: var(--text-color);
405
+ outline: none;
406
+ padding: 4px 0;
407
+ line-height: 1.5;
408
+ }
409
+
410
+ textarea::placeholder {
411
+ color: var(--text-secondary);
412
+ }
413
+
414
+ .send-btn {
415
+ background: none;
416
+ border: none;
417
+ color: var(--primary-color);
418
+ cursor: pointer;
419
+ font-size: 16px;
420
+ padding: 8px;
421
+ border-radius: 8px;
422
+ transition: background-color 0.2s;
423
+ align-self: flex-end;
424
+ }
425
+
426
+ .send-btn:hover {
427
+ background-color: rgba(99, 102, 241, 0.1);
428
+ }
429
+
430
+ .send-btn:disabled {
431
+ color: var(--text-secondary);
432
+ cursor: not-allowed;
433
+ }
434
+
435
+ .input-footer {
436
+ display: flex;
437
+ justify-content: center;
438
+ margin-top: 8px;
439
+ }
440
+
441
+ .disclaimer {
442
+ font-size: 12px;
443
+ color: var(--text-secondary);
444
+ }
445
+
446
+ /* Адаптивность */
447
+ @media (max-width: 768px) {
448
+ .app-container {
449
+ flex-direction: column;
450
+ }
451
+
452
+ .sidebar {
453
+ width: 100%;
454
+ height: auto;
455
+ border-right: none;
456
+ border-bottom: 1px solid var(--border-color);
457
+ padding: 12px;
458
+ }
459
+
460
+ .chat-history {
461
+ display: none;
462
+ }
463
+
464
+ .logo {
465
+ margin-bottom: 12px;
466
+ }
467
+
468
+ .new-chat-btn {
469
+ margin-bottom: 0;
470
+ }
471
+
472
+ .sidebar-footer {
473
+ display: none;
474
+ }
475
+
476
+ .main-content {
477
+ height: 0;
478
+ flex: 1;
479
+ }
480
+
481
+ .example-grid {
482
+ grid-template-columns: 1fr;
483
+ }
484
+
485
+ .message.user, .message.ai {
486
+ max-width: 100%;
487
+ }
488
+ }
489
+
490
+ /* Анимации загрузки */
491
+ .typing-indicator {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 6px;
495
+ padding: 8px 12px;
496
+ border-radius: 16px;
497
+ background-color: var(--bg-secondary);
498
+ width: fit-content;
499
+ margin-top: 8px;
500
+ }
501
+
502
+ .typing-dot {
503
+ width: 8px;
504
+ height: 8px;
505
+ background-color: var(--text-secondary);
506
+ border-radius: 50%;
507
+ animation: typingAnimation 1.4s infinite ease-in-out;
508
+ }
509
+
510
+ .typing-dot:nth-child(1) {
511
+ animation-delay: 0s;
512
+ }
513
+
514
+ .typing-dot:nth-child(2) {
515
+ animation-delay: 0.2s;
516
+ }
517
+
518
+ .typing-dot:nth-child(3) {
519
+ animation-delay: 0.4s;
520
+ }
521
+
522
+ @keyframes typingAnimation {
523
+ 0%, 60%, 100% {
524
+ transform: translateY(0);
525
+ opacity: 0.6;
526
+ }
527
+ 30% {
528
+ transform: translateY(-4px);
529
+ opacity: 1;
530
+ }
531
+ }
532
+
533
+ /* Кнопки действий для сообщений */
534
+ .message-actions {
535
+ display: flex;
536
+ gap: 8px;
537
+ margin-top: 8px;
538
+ }
539
+
540
+ .action-btn {
541
+ background: none;
542
+ border: none;
543
+ color: var(--text-secondary);
544
+ cursor: pointer;
545
+ font-size: 14px;
546
+ padding: 4px 8px;
547
+ border-radius: 4px;
548
+ transition: background-color 0.2s, color 0.2s;
549
+ display: flex;
550
+ align-items: center;
551
+ gap: 4px;
552
+ }
553
+
554
+ .action-btn:hover {
555
+ background-color: rgba(99, 102, 241, 0.1);
556
+ color: var(--primary-color);
557
+ }
558
+
559
+ /* Стили для кода */
560
+ .hljs {
561
+ background: var(--code-bg) !important;
562
+ border-radius: 6px;
563
+ }
564
+
565
+ /* Стили для скроллбара */
566
+ ::-webkit-scrollbar {
567
+ width: 8px;
568
+ height: 8px;
569
+ }
570
+
571
+ ::-webkit-scrollbar-track {
572
+ background: transparent;
573
+ }
574
+
575
+ ::-webkit-scrollbar-thumb {
576
+ background-color: var(--border-color);
577
+ border-radius: 4px;
578
+ }
579
+
580
+ ::-webkit-scrollbar-thumb:hover {
581
+ background-color: var(--text-secondary);
582
+ }
static/js/script.js ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ // Элементы DOM
3
+ const userInput = document.getElementById('user-input');
4
+ const sendBtn = document.getElementById('send-btn');
5
+ const messagesContainer = document.getElementById('messages');
6
+ const chatContainer = document.getElementById('chat-container');
7
+ const welcomeScreen = document.getElementById('welcome-screen');
8
+ const newChatBtn = document.getElementById('new-chat');
9
+ const chatHistory = document.getElementById('chat-history');
10
+ const toggleModeBtn = document.getElementById('toggle-mode');
11
+ const examples = document.querySelectorAll('.example');
12
+
13
+ // Состояние приложения
14
+ let currentChatId = generateId();
15
+ let chats = {};
16
+ let isWaitingForResponse = false;
17
+
18
+ // Инициализация
19
+ initTheme();
20
+ loadChats();
21
+
22
+ // Обработчики событий
23
+ sendBtn.addEventListener('click', sendMessage);
24
+ userInput.addEventListener('keydown', handleKeyDown);
25
+ newChatBtn.addEventListener('click', createNewChat);
26
+ toggleModeBtn.addEventListener('click', toggleDarkMode);
27
+
28
+ // Обработка примеров запросов
29
+ examples.forEach(example => {
30
+ example.addEventListener('click', () => {
31
+ const prompt = example.getAttribute('data-prompt');
32
+ if (prompt) {
33
+ hideWelcomeScreen();
34
+ userInput.value = prompt;
35
+ sendMessage();
36
+ }
37
+ });
38
+ });
39
+
40
+ // Автоматическое изменение высоты текстового поля
41
+ userInput.addEventListener('input', function() {
42
+ this.style.height = 'auto';
43
+ this.style.height = (this.scrollHeight) + 'px';
44
+
45
+ // Активация/деактивация кнопки отправки
46
+ sendBtn.disabled = this.value.trim() === '' || isWaitingForResponse;
47
+ });
48
+
49
+ // Функции
50
+ function sendMessage() {
51
+ const message = userInput.value.trim();
52
+ if (message === '' || isWaitingForResponse) return;
53
+
54
+ // Добавляем сообщение пользователя
55
+ addMessage('user', message);
56
+
57
+ // Очищаем ввод
58
+ userInput.value = '';
59
+ userInput.style.height = 'auto';
60
+ sendBtn.disabled = true;
61
+
62
+ // Показываем индикатор набора текста
63
+ showTypingIndicator();
64
+
65
+ // Устанавливаем флаг ожидания ответа
66
+ isWaitingForResponse = true;
67
+
68
+ // Сохраняем чат
69
+ saveChat(message);
70
+
71
+ // Отправляем запрос на сервер
72
+ fetch('/api/chat', {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json'
76
+ },
77
+ body: JSON.stringify({ prompt: message })
78
+ })
79
+ .then(response => response.json())
80
+ .then(data => {
81
+ // Удаляем индикатор набора текста
82
+ removeTypingIndicator();
83
+
84
+ // Добавляем ответ от AI
85
+ if (data.error) {
86
+ addMessage('ai', `Ошибка: ${data.error}`);
87
+ } else {
88
+ addMessage('ai', data.response);
89
+ }
90
+
91
+ // Сохраняем ответ в чат
92
+ saveChat(null, data.response || `Ошибка: ${data.error}`);
93
+
94
+ // Сбрасываем флаг ожидания ответа
95
+ isWaitingForResponse = false;
96
+
97
+ // Активируем кнопку отправки, если есть текст
98
+ sendBtn.disabled = userInput.value.trim() === '';
99
+ })
100
+ .catch(error => {
101
+ console.error('Ошибка:', error);
102
+ removeTypingIndicator();
103
+ addMessage('ai', 'Произошла ошибка при обработке запроса. Пожалуйста, попробуйте еще раз.');
104
+ isWaitingForResponse = false;
105
+ sendBtn.disabled = userInput.value.trim() === '';
106
+ });
107
+ }
108
+
109
+ function handleKeyDown(e) {
110
+ if (e.key === 'Enter' && !e.shiftKey) {
111
+ e.preventDefault();
112
+ sendMessage();
113
+ }
114
+ }
115
+
116
+ function addMessage(sender, text) {
117
+ // Скрываем экран приветствия, если он отображается
118
+ hideWelcomeScreen();
119
+
120
+ const messageDiv = document.createElement('div');
121
+ messageDiv.className = `message ${sender}`;
122
+
123
+ const avatarDiv = document.createElement('div');
124
+ avatarDiv.className = `message-avatar ${sender}`;
125
+ avatarDiv.innerHTML = sender === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';
126
+
127
+ const contentDiv = document.createElement('div');
128
+ contentDiv.className = 'message-content';
129
+
130
+ const senderDiv = document.createElement('div');
131
+ senderDiv.className = 'message-sender';
132
+ senderDiv.textContent = sender === 'user' ? 'Вы' : 'Mistral AI';
133
+
134
+ const textDiv = document.createElement('div');
135
+ textDiv.className = 'message-text';
136
+
137
+ // Обрабатываем текст с поддержкой Markdown
138
+ if (sender === 'ai') {
139
+ textDiv.innerHTML = marked.parse(text);
140
+
141
+ // Подсветка синтаксиса кода
142
+ textDiv.querySelectorAll('pre code').forEach((block) => {
143
+ hljs.highlightElement(block);
144
+ });
145
+
146
+ // Добавляем кнопки действий
147
+ const actionsDiv = document.createElement('div');
148
+ actionsDiv.className = 'message-actions';
149
+
150
+ const copyBtn = document.createElement('button');
151
+ copyBtn.className = 'action-btn';
152
+ copyBtn.innerHTML = '<i class="fas fa-copy"></i> Копировать';
153
+ copyBtn.addEventListener('click', () => copyToClipboard(text));
154
+
155
+ actionsDiv.appendChild(copyBtn);
156
+ contentDiv.appendChild(actionsDiv);
157
+ } else {
158
+ textDiv.textContent = text;
159
+ }
160
+
161
+ contentDiv.appendChild(senderDiv);
162
+ contentDiv.appendChild(textDiv);
163
+
164
+ messageDiv.appendChild(avatarDiv);
165
+ messageDiv.appendChild(contentDiv);
166
+
167
+ messagesContainer.appendChild(messageDiv);
168
+
169
+ // Прокручиваем к последнему сообщению
170
+ messageDiv.scrollIntoView({ behavior: 'smooth' });
171
+ }
172
+
173
+ function showTypingIndicator() {
174
+ const indicatorDiv = document.createElement('div');
175
+ indicatorDiv.className = 'message ai typing';
176
+ indicatorDiv.innerHTML = `
177
+ <div class="message-avatar ai">
178
+ <i class="fas fa-robot"></i>
179
+ </div>
180
+ <div class="message-content">
181
+ <div class="message-sender">Mistral AI</div>
182
+ <div class="typing-indicator">
183
+ <div class="typing-dot"></div>
184
+ <div class="typing-dot"></div>
185
+ <div class="typing-dot"></div>
186
+ </div>
187
+ </div>
188
+ `;
189
+
190
+ messagesContainer.appendChild(indicatorDiv);
191
+ indicatorDiv.scrollIntoView({ behavior: 'smooth' });
192
+ }
193
+
194
+ function removeTypingIndicator() {
195
+ const typingIndicator = document.querySelector('.message.typing');
196
+ if (typingIndicator) {
197
+ typingIndicator.remove();
198
+ }
199
+ }
200
+
201
+ function hideWelcomeScreen() {
202
+ if (welcomeScreen.style.display !== 'none') {
203
+ welcomeScreen.style.display = 'none';
204
+ }
205
+ }
206
+
207
+ function createNewChat() {
208
+ // Сохраняем текущий чат перед созданием нового
209
+ saveChats();
210
+
211
+ // Создаем новый чат
212
+ currentChatId = generateId();
213
+ chats[currentChatId] = {
214
+ id: currentChatId,
215
+ title: 'Новый чат',
216
+ messages: [],
217
+ timestamp: Date.now()
218
+ };
219
+
220
+ // Очищаем сообщения
221
+ messagesContainer.innerHTML = '';
222
+
223
+ // Показываем экран приветствия
224
+ welcomeScreen.style.display = 'flex';
225
+
226
+ // Обновляем историю чатов
227
+ updateChatHistory();
228
+ }
229
+
230
+ function saveChat(userMessage, aiResponse) {
231
+ if (!chats[currentChatId]) {
232
+ chats[currentChatId] = {
233
+ id: currentChatId,
234
+ title: userMessage ? userMessage.substring(0, 30) : 'Новый чат',
235
+ messages: [],
236
+ timestamp: Date.now()
237
+ };
238
+ }
239
+
240
+ if (userMessage) {
241
+ chats[currentChatId].messages.push({
242
+ sender: 'user',
243
+ text: userMessage,
244
+ timestamp: Date.now()
245
+ });
246
+
247
+ // Обновляем заголовок чата, если это первое сообщение
248
+ if (chats[currentChatId].messages.length === 1) {
249
+ chats[currentChatId].title = userMessage.substring(0, 30) + (userMessage.length > 30 ? '...' : '');
250
+ }
251
+ }
252
+
253
+ if (aiResponse) {
254
+ chats[currentChatId].messages.push({
255
+ sender: 'ai',
256
+ text: aiResponse,
257
+ timestamp: Date.now()
258
+ });
259
+ }
260
+
261
+ // Сохраняем чаты в localStorage
262
+ saveChats();
263
+
264
+ // Обновляем историю чатов
265
+ updateChatHistory();
266
+ }
267
+
268
+ function saveChats() {
269
+ localStorage.setItem('mistral_chats', JSON.stringify(chats));
270
+ }
271
+
272
+ function loadChats() {
273
+ const savedChats = localStorage.getItem('mistral_chats');
274
+ if (savedChats) {
275
+ chats = JSON.parse(savedChats);
276
+ updateChatHistory();
277
+
278
+ // Если есть чаты, загружаем последний активный
279
+ const chatIds = Object.keys(chats);
280
+ if (chatIds.length > 0) {
281
+ // Сортируем по времени и берем самый последний
282
+ const sortedIds = chatIds.sort((a, b) => chats[b].timestamp - chats[a].timestamp);
283
+ loadChat(sortedIds[0]);
284
+ }
285
+ }
286
+ }
287
+
288
+ function updateChatHistory() {
289
+ chatHistory.innerHTML = '';
290
+
291
+ // Сортируем чаты по времени (сначала новые)
292
+ const sortedChats = Object.values(chats).sort((a, b) => b.timestamp - a.timestamp);
293
+
294
+ sortedChats.forEach(chat => {
295
+ const chatItem = document.createElement('div');
296
+ chatItem.className = `chat-item ${chat.id === currentChatId ? 'active' : ''}`;
297
+ chatItem.setAttribute('data-id', chat.id);
298
+
299
+ chatItem.innerHTML = `
300
+ <i class="fas fa-comment"></i>
301
+ <span>${chat.title}</span>
302
+ `;
303
+
304
+ chatItem.addEventListener('click', () => loadChat(chat.id));
305
+
306
+ chatHistory.appendChild(chatItem);
307
+ });
308
+ }
309
+
310
+ function loadChat(chatId) {
311
+ if (!chats[chatId]) return;
312
+
313
+ currentChatId = chatId;
314
+ messagesContainer.innerHTML = '';
315
+ welcomeScreen.style.display = 'none';
316
+
317
+ // Загружаем сообщения
318
+ chats[chatId].messages.forEach(msg => {
319
+ addMessage(msg.sender, msg.text);
320
+ });
321
+
322
+ // Обновляем активный чат в истории
323
+ updateChatHistory();
324
+ }
325
+
326
+ function toggleDarkMode() {
327
+ const isDarkMode = document.body.classList.toggle('dark-mode');
328
+ localStorage.setItem('dark_mode', isDarkMode ? 'true' : 'false');
329
+
330
+ // Обновляем иконку
331
+ toggleModeBtn.innerHTML = isDarkMode ?
332
+ '<i class="fas fa-sun"></i>' :
333
+ '<i class="fas fa-moon"></i>';
334
+ }
335
+
336
+ function initTheme() {
337
+ const savedTheme = localStorage.getItem('dark_mode');
338
+ if (savedTheme === 'true') {
339
+ document.body.classList.add('dark-mode');
340
+ toggleModeBtn.innerHTML = '<i class="fas fa-sun"></i>';
341
+ }
342
+ }
343
+
344
+ function copyToClipboard(text) {
345
+ navigator.clipboard.writeText(text).then(() => {
346
+ // Показываем уведомление об успешном копировании
347
+ const notification = document.createElement('div');
348
+ notification.className = 'copy-notification';
349
+ notification.textContent = 'Скопировано в буфер обмена';
350
+ document.body.appendChild(notification);
351
+
352
+ setTimeout(() => {
353
+ notification.remove();
354
+ }, 2000);
355
+ }).catch(err => {
356
+ console.error('Ошибка при копировании: ', err);
357
+ });
358
+ }
359
+
360
+ function generateId() {
361
+ return Date.now().toString(36) + Math.random().toString(36).substring(2);
362
+ }
363
+ });
templates/index.html ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mistral AI Assistant</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
12
+ </head>
13
+ <body>
14
+ <div class="app-container">
15
+ <div class="sidebar">
16
+ <div class="logo">
17
+ <i class="fas fa-robot"></i>
18
+ <h1>Mistral AI</h1>
19
+ </div>
20
+ <button id="new-chat" class="new-chat-btn">
21
+ <i class="fas fa-plus"></i> Новый чат
22
+ </button>
23
+ <div class="chat-history" id="chat-history">
24
+ <!-- История чатов будет добавляться здесь -->
25
+ </div>
26
+ <div class="sidebar-footer">
27
+ <button id="toggle-mode" class="toggle-mode-btn">
28
+ <i class="fas fa-moon"></i>
29
+ </button>
30
+ <div class="model-info">
31
+ <span>Mistral-7B-Instruct-v0.3</span>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="main-content">
37
+ <div class="chat-container" id="chat-container">
38
+ <div class="welcome-screen" id="welcome-screen">
39
+ <div class="welcome-logo">
40
+ <i class="fas fa-robot"></i>
41
+ </div>
42
+ <h1>Mistral AI Assistant</h1>
43
+ <p>Ваш помощник на базе модели Mistral-7B-Instruct-v0.3</p>
44
+ <div class="examples">
45
+ <h3>Примеры запросов:</h3>
46
+ <div class="example-grid">
47
+ <div class="example" data-prompt="Объясни, как работает рекурсия в программировании и приведи пример на Python.">
48
+ <i class="fas fa-code"></i>
49
+ <span>Объяснить рекурсию с примером</span>
50
+ </div>
51
+ <div class="example" data-prompt="Напиши функцию на Python для сортировки списка объектов по нескольким атрибутам.">
52
+ <i class="fas fa-sort"></i>
53
+ <span>Сортировка по нескольким атрибутам</span>
54
+ </div>
55
+ <div class="example" data-prompt="Создай класс для работы с бинарным деревом поиска на JavaScript.">
56
+ <i class="fas fa-tree"></i>
57
+ <span>Бинарное дерево поиска</span>
58
+ </div>
59
+ <div class="example" data-prompt="Объясни паттерн проектирования 'Наблюдатель' и приведи пример реализации.">
60
+ <i class="fas fa-eye"></i>
61
+ <span>Паттерн 'Наблюдатель'</span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <div class="messages" id="messages">
67
+ <!-- Сообщения будут добавляться здесь -->
68
+ </div>
69
+ </div>
70
+
71
+ <div class="input-container">
72
+ <div class="input-wrapper">
73
+ <textarea id="user-input" placeholder="Введите ваш запрос..."></textarea>
74
+ <button id="send-btn" class="send-btn">
75
+ <i class="fas fa-paper-plane"></i>
76
+ </button>
77
+ </div>
78
+ <div class="input-footer">
79
+ <span class="disclaimer">Модель может выдавать неточную информацию. Проверяйте важные данные.</span>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <script src="{{ url_for('static', filename='js/script.js') }}"></script>
86
+ </body>
87
+ </html>