Arghet6 commited on
Commit
e662673
·
verified ·
1 Parent(s): c0703c6

Upload 25 files

Browse files
admin.py CHANGED
@@ -83,7 +83,7 @@ def view_reports():
83
  if emotion_filter:
84
  query = query.filter(AnalysisReport.emotion == emotion_filter)
85
 
86
- reports = query.paginate(page=page, per_page=50, error_out=False)
87
 
88
  # Получаем список всех эмоций для фильтра
89
  emotions = db.session.query(
 
83
  if emotion_filter:
84
  query = query.filter(AnalysisReport.emotion == emotion_filter)
85
 
86
+ reports = query.paginate(page=page, per_page=20, error_out=False)
87
 
88
  # Получаем список всех эмоций для фильтра
89
  emotions = db.session.query(
app.py CHANGED
@@ -1,7 +1,8 @@
1
- from flask import Flask, request, jsonify, render_template, flash, redirect, url_for
2
  from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
3
  from werkzeug.security import generate_password_hash, check_password_hash
4
  from transformers import pipeline
 
5
  import torch
6
  from pydub import AudioSegment
7
  import os
@@ -16,6 +17,8 @@ import json
16
  from admin import admin_bp
17
  from flask_migrate import Migrate
18
  from models import User
 
 
19
 
20
  instance_path = Path(__file__).parent / 'instance'
21
  instance_path.mkdir(exist_ok=True, mode=0o755)
@@ -29,8 +32,10 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
29
  # Инициализация Flask-Login
30
  db.init_app(app)
31
  login_manager.init_app(app)
32
- login_manager.login_view = 'auth_bp.login'
33
  migrate = Migrate(app, db)
 
 
34
  # Инициализация моделей
35
  def init_models():
36
  try:
@@ -62,6 +67,7 @@ def init_models():
62
  print(f"Ошибка загрузки моделей: {e}")
63
  return None
64
 
 
65
  models = init_models()
66
  if not models:
67
  raise RuntimeError("Не удалось загрузить модели")
@@ -73,6 +79,7 @@ def datetimeformat(value, format='%d.%m.%Y %H:%M'):
73
  return ""
74
  return value.strftime(format)
75
 
 
76
  @app.context_processor
77
  def utility_processor():
78
  return {
@@ -91,6 +98,8 @@ def utility_processor():
91
  'surprise': '#fdcb6e'
92
  }.get(emotion, '#4a4ae8')
93
  }
 
 
94
  # Импорт Blueprint
95
  from auth import auth_bp
96
  from profile import profile_bp
@@ -105,6 +114,7 @@ speech_to_text_model = models['speech_to_text_model']
105
  text_classifier = models['text_classifier']
106
  audio_classifier = models['audio_classifier']
107
 
 
108
  @app.cli.command('create-admin')
109
  def create_admin():
110
  """Создание администратора"""
@@ -121,6 +131,7 @@ def create_admin():
121
  db.session.commit()
122
  print(f"Администратор {email} создан")
123
 
 
124
  def transcribe_audio(audio_path):
125
  """Преобразование аудио в текст с помощью Whisper"""
126
  if not speech_to_text_model:
@@ -135,9 +146,9 @@ def transcribe_audio(audio_path):
135
 
136
  # Инициализация Flask-Login
137
  login_manager = LoginManager(app)
138
- login_manager.login_view = 'login'
139
-
140
-
141
 
142
 
143
  @login_manager.user_loader
@@ -224,9 +235,10 @@ def login():
224
  user_obj = User(id=user['id'], username=user['username'],
225
  email=user['email'], password_hash=user['password_hash'])
226
  login_user(user_obj)
 
227
  return redirect(url_for('index'))
228
-
229
- flash('Неверный email или пароль', 'danger')
230
 
231
  return render_template('auth/login.html')
232
 
@@ -261,26 +273,71 @@ def register():
261
  return render_template('auth/register.html')
262
 
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  @app.route('/logout')
265
  @login_required
266
  def logout():
 
267
  logout_user()
268
- return redirect(url_for('login'))
269
 
270
 
271
- # Основные маршруты
272
  @app.route("/")
273
- @login_required
274
  def index():
275
- conn = get_db_connection()
276
- try:
277
- chats = conn.execute(
278
- "SELECT chat_id, title FROM chats WHERE user_id = ? ORDER BY created_at DESC",
279
- (current_user.id,)
280
- ).fetchall()
281
- return render_template("index.html", chats=chats)
282
- finally:
283
- conn.close()
 
 
284
 
285
 
286
  @app.route('/profile')
@@ -323,6 +380,7 @@ def profile():
323
  finally:
324
  conn.close()
325
 
 
326
  @app.route("/analyze", methods=["POST"])
327
  @login_required
328
  def analyze_text():
@@ -423,9 +481,6 @@ def analyze_audio():
423
  return jsonify({'error': str(e)}), 500
424
 
425
 
426
-
427
-
428
-
429
  @app.route('/analyze_telegram_chat', methods=['POST'])
430
  @login_required
431
  def analyze_telegram_chat():
@@ -536,8 +591,19 @@ def delete_chat(chat_id):
536
  try:
537
  # Удаляем связанные сообщения
538
  conn.execute("DELETE FROM messages WHERE chat_id = ?", (chat_id,))
 
 
 
 
 
 
 
 
 
 
539
  # Удаляем сам чат
540
- conn.execute("DELETE FROM chats WHERE chat_id = ? AND user_id = ?", (chat_id, current_user.id))
 
541
  conn.commit()
542
  return jsonify({"success": True})
543
  except Exception as e:
@@ -561,6 +627,7 @@ def get_telegram_analysis():
561
  finally:
562
  conn.close()
563
 
 
564
  @app.route('/load_chat/<chat_id>')
565
  @login_required
566
  def load_chat(chat_id):
@@ -590,7 +657,6 @@ def load_chat(chat_id):
590
  conn.close()
591
 
592
 
593
-
594
  @app.route('/save_message', methods=['POST'])
595
  @login_required
596
  def save_message():
@@ -646,7 +712,4 @@ def save_message():
646
 
647
 
648
  if __name__ == "__main__":
649
- from waitress import serve
650
- serve(app, host="0.0.0.0", port=8080)
651
-
652
-
 
1
+ from flask import Flask, request, jsonify, render_template, flash, redirect, url_for, current_app
2
  from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
3
  from werkzeug.security import generate_password_hash, check_password_hash
4
  from transformers import pipeline
5
+ from flask import session
6
  import torch
7
  from pydub import AudioSegment
8
  import os
 
17
  from admin import admin_bp
18
  from flask_migrate import Migrate
19
  from models import User
20
+ from werkzeug.utils import secure_filename
21
+ from forms import EditProfileForm
22
 
23
  instance_path = Path(__file__).parent / 'instance'
24
  instance_path.mkdir(exist_ok=True, mode=0o755)
 
32
  # Инициализация Flask-Login
33
  db.init_app(app)
34
  login_manager.init_app(app)
35
+ login_manager.login_view = 'welcome'
36
  migrate = Migrate(app, db)
37
+
38
+
39
  # Инициализация моделей
40
  def init_models():
41
  try:
 
67
  print(f"Ошибка загрузки моделей: {e}")
68
  return None
69
 
70
+
71
  models = init_models()
72
  if not models:
73
  raise RuntimeError("Не удалось загрузить модели")
 
79
  return ""
80
  return value.strftime(format)
81
 
82
+
83
  @app.context_processor
84
  def utility_processor():
85
  return {
 
98
  'surprise': '#fdcb6e'
99
  }.get(emotion, '#4a4ae8')
100
  }
101
+
102
+
103
  # Импорт Blueprint
104
  from auth import auth_bp
105
  from profile import profile_bp
 
114
  text_classifier = models['text_classifier']
115
  audio_classifier = models['audio_classifier']
116
 
117
+
118
  @app.cli.command('create-admin')
119
  def create_admin():
120
  """Создание администратора"""
 
131
  db.session.commit()
132
  print(f"Администратор {email} создан")
133
 
134
+
135
  def transcribe_audio(audio_path):
136
  """Преобразование аудио в текст с помощью Whisper"""
137
  if not speech_to_text_model:
 
146
 
147
  # Инициализация Flask-Login
148
  login_manager = LoginManager(app)
149
+ login_manager.login_view = 'auth_bp.login'
150
+ login_manager.login_message = "Для доступа к этой странице необходимо авторизоваться"
151
+ login_manager.login_message_category = "info"
152
 
153
 
154
  @login_manager.user_loader
 
235
  user_obj = User(id=user['id'], username=user['username'],
236
  email=user['email'], password_hash=user['password_hash'])
237
  login_user(user_obj)
238
+ session.pop('_flashes', None)
239
  return redirect(url_for('index'))
240
+ else:
241
+ flash('Неверный email или пароль', 'danger') # <-- теперь здесь
242
 
243
  return render_template('auth/login.html')
244
 
 
273
  return render_template('auth/register.html')
274
 
275
 
276
+ from werkzeug.security import check_password_hash, generate_password_hash
277
+
278
+ @app.route('/edit_profile', methods=['GET', 'POST'])
279
+ @login_required
280
+ def edit_profile():
281
+ form = EditProfileForm(obj=current_user)
282
+ if form.validate_on_submit():
283
+ # Обновляем имя и почту
284
+ current_user.username = form.username.data
285
+ current_user.email = form.email.data
286
+
287
+ # Обновляем аватар
288
+ if form.avatar.data:
289
+ filename = secure_filename(form.avatar.data.filename)
290
+ unique_filename = f"{uuid.uuid4().hex}_{filename}"
291
+ avatar_path = os.path.join(current_app.root_path, 'static/avatars', unique_filename)
292
+ form.avatar.data.save(avatar_path)
293
+ current_user.avatar = unique_filename
294
+
295
+ # Обработка смены пароля
296
+ if form.current_password.data:
297
+ # Проверяем текущий пароль
298
+ if check_password_hash(current_user.password_hash, form.current_password.data):
299
+ # Меняем пароль на новый
300
+ current_user.password_hash = generate_password_hash(form.new_password.data)
301
+ flash('Пароль успешно изменён', 'success')
302
+ else:
303
+ flash('Текущий пароль неверный', 'danger')
304
+ return redirect(url_for('edit_profile'))
305
+
306
+ db.session.commit()
307
+ flash('Профиль обновлён', 'success')
308
+ return redirect(url_for('profile'))
309
+
310
+ return render_template('edit_profile.html', form=form)
311
+
312
+
313
+
314
+ # Основные маршруты
315
+ @app.route("/welcome")
316
+ def welcome():
317
+ return render_template("welcome.html")
318
+
319
+
320
  @app.route('/logout')
321
  @login_required
322
  def logout():
323
+ session.clear()
324
  logout_user()
325
+ return redirect(url_for('welcome'))
326
 
327
 
 
328
  @app.route("/")
 
329
  def index():
330
+ if current_user.is_authenticated:
331
+ conn = get_db_connection()
332
+ try:
333
+ chats = conn.execute(
334
+ "SELECT chat_id, title FROM chats WHERE user_id = ? ORDER BY created_at DESC",
335
+ (current_user.id,)
336
+ ).fetchall()
337
+ return render_template("index.html", chats=chats)
338
+ finally:
339
+ conn.close()
340
+ return redirect(url_for('welcome'))
341
 
342
 
343
  @app.route('/profile')
 
380
  finally:
381
  conn.close()
382
 
383
+
384
  @app.route("/analyze", methods=["POST"])
385
  @login_required
386
  def analyze_text():
 
481
  return jsonify({'error': str(e)}), 500
482
 
483
 
 
 
 
484
  @app.route('/analyze_telegram_chat', methods=['POST'])
485
  @login_required
486
  def analyze_telegram_chat():
 
591
  try:
592
  # Удаляем связанные сообщения
593
  conn.execute("DELETE FROM messages WHERE chat_id = ?", (chat_id,))
594
+
595
+ # Удаляем анализы эмоций, связанные с сообщениями этого чата
596
+ # (если у вас есть связь между analysis_reports и chat_id)
597
+ conn.execute("""
598
+ DELETE FROM analysis_reports
599
+ WHERE content IN (
600
+ SELECT content FROM messages WHERE chat_id = ?
601
+ ) AND user_id = ?
602
+ """, (chat_id, current_user.id))
603
+
604
  # Удаляем сам чат
605
+ conn.execute("DELETE FROM chats WHERE chat_id = ? AND user_id = ?",
606
+ (chat_id, current_user.id))
607
  conn.commit()
608
  return jsonify({"success": True})
609
  except Exception as e:
 
627
  finally:
628
  conn.close()
629
 
630
+
631
  @app.route('/load_chat/<chat_id>')
632
  @login_required
633
  def load_chat(chat_id):
 
657
  conn.close()
658
 
659
 
 
660
  @app.route('/save_message', methods=['POST'])
661
  @login_required
662
  def save_message():
 
712
 
713
 
714
  if __name__ == "__main__":
715
+ app.run(debug=True)
 
 
 
forms.py CHANGED
@@ -2,21 +2,69 @@ from flask_wtf import FlaskForm
2
  from wtforms import StringField, PasswordField, SubmitField
3
  from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
4
  from models import User
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  class LoginForm(FlaskForm):
7
  email = StringField('Email', validators=[DataRequired(), Email()])
8
  password = PasswordField('Пароль', validators=[DataRequired()])
9
  submit = SubmitField('Войти')
10
 
 
11
  class RegistrationForm(FlaskForm):
12
- username = StringField('Имя пользователя', validators=[DataRequired(), Length(min=4, max=25)])
13
- email = StringField('Email', validators=[DataRequired(), Email()])
14
- password = PasswordField('Пароль', validators=[DataRequired(), Length(min=6)])
15
- confirm_password = PasswordField('Подтвердите пароль',
16
- validators=[DataRequired(), EqualTo('password')])
17
- submit = SubmitField('Зарегистрироваться')
 
 
 
 
 
 
 
 
 
18
 
19
- def validate_email(self, email):
20
- user = User.query.filter_by(email=email.data).first()
21
- if user:
22
- raise ValidationError('Этот email уже используется')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from wtforms import StringField, PasswordField, SubmitField
3
  from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
4
  from models import User
5
+ from wtforms import FileField
6
+ from wtforms.validators import Email, Optional
7
+ from flask_wtf.file import FileAllowed
8
+
9
+
10
+ class EditProfileForm(FlaskForm):
11
+ username = StringField('Имя пользователя', validators=[Optional()])
12
+ email = StringField('Email', validators=[Optional(), Email()])
13
+ avatar = FileField('Аватарка', validators=[FileAllowed(['jpg', 'png', 'jpeg'], 'Только изображения')])
14
+
15
+ current_password = PasswordField('Текущий пароль', validators=[Optional()])
16
+ new_password = PasswordField('Новый пароль', validators=[Optional()])
17
+ confirm_password = PasswordField('Подтвердите новый пароль', validators=[
18
+ Optional(),
19
+ EqualTo('new_password', message='Пароли должны совпадать')
20
+ ])
21
+
22
+ submit = SubmitField('Сохранить')
23
+
24
+
25
+ def validate_email(self, email):
26
+ user = User.query.filter_by(email=email.data).first()
27
+ if user:
28
+ raise ValidationError('Этот email уже используется')
29
+
30
 
31
  class LoginForm(FlaskForm):
32
  email = StringField('Email', validators=[DataRequired(), Email()])
33
  password = PasswordField('Пароль', validators=[DataRequired()])
34
  submit = SubmitField('Войти')
35
 
36
+
37
  class RegistrationForm(FlaskForm):
38
+ username = StringField(
39
+ 'Имя пользователя',
40
+ validators=[
41
+ DataRequired(message="Пожалуйста, введите имя пользователя."),
42
+ Length(min=4, max=25, message="Имя пользователя должно быть от 4 до 25 символов.")
43
+ ]
44
+ )
45
+
46
+ email = StringField(
47
+ 'Email',
48
+ validators=[
49
+ DataRequired(message="Пожалуйста, введите email."),
50
+ Email(message="Пожалуйста, введите корректный email.")
51
+ ]
52
+ )
53
 
54
+ password = PasswordField(
55
+ 'Пароль',
56
+ validators=[
57
+ DataRequired(message="Пожалуйста, введите пароль."),
58
+ Length(min=6, message="Пароль должен содержать не менее 6 символов.")
59
+ ]
60
+ )
61
+
62
+ confirm_password = PasswordField(
63
+ 'Подтвердите пароль',
64
+ validators=[
65
+ DataRequired(message="Пожалуйста, подтвердите пароль."),
66
+ EqualTo('password', message="Пароли не совпадают.")
67
+ ]
68
+ )
69
+
70
+ submit = SubmitField('Зарегистрироваться')
models.py CHANGED
@@ -3,6 +3,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
3
  from extensions import db
4
  from datetime import datetime
5
 
 
6
  class User(UserMixin, db.Model):
7
  __tablename__ = 'users'
8
  id = db.Column(db.Integer, primary_key=True)
@@ -13,6 +14,7 @@ class User(UserMixin, db.Model):
13
  created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
14
  chats = db.relationship('Chat', backref='user', lazy=True)
15
  reports = db.relationship('AnalysisReport', backref='user', lazy=True)
 
16
 
17
  def set_password(self, password):
18
  self.password_hash = generate_password_hash(password)
@@ -20,6 +22,7 @@ class User(UserMixin, db.Model):
20
  def check_password(self, password):
21
  return check_password_hash(self.password_hash, password)
22
 
 
23
  class Chat(db.Model):
24
  __tablename__ = 'chats'
25
  chat_id = db.Column(db.String(36), primary_key=True)
@@ -28,6 +31,7 @@ class Chat(db.Model):
28
  title = db.Column(db.String(100))
29
  messages = db.relationship('Message', backref='chat', lazy=True)
30
 
 
31
  class Message(db.Model):
32
  __tablename__ = 'messages'
33
  id = db.Column(db.Integer, primary_key=True)
@@ -36,6 +40,7 @@ class Message(db.Model):
36
  content = db.Column(db.Text)
37
  timestamp = db.Column(db.DateTime, default=db.func.current_timestamp())
38
 
 
39
  class AnalysisReport(db.Model):
40
  __tablename__ = 'analysis_reports'
41
  id = db.Column(db.Integer, primary_key=True)
@@ -43,4 +48,4 @@ class AnalysisReport(db.Model):
43
  content = db.Column(db.Text)
44
  emotion = db.Column(db.String(50))
45
  confidence = db.Column(db.Float)
46
- created_at = db.Column(db.DateTime, default=datetime.utcnow)
 
3
  from extensions import db
4
  from datetime import datetime
5
 
6
+
7
  class User(UserMixin, db.Model):
8
  __tablename__ = 'users'
9
  id = db.Column(db.Integer, primary_key=True)
 
14
  created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
15
  chats = db.relationship('Chat', backref='user', lazy=True)
16
  reports = db.relationship('AnalysisReport', backref='user', lazy=True)
17
+ avatar = db.Column(db.String(128), default='default.png')
18
 
19
  def set_password(self, password):
20
  self.password_hash = generate_password_hash(password)
 
22
  def check_password(self, password):
23
  return check_password_hash(self.password_hash, password)
24
 
25
+
26
  class Chat(db.Model):
27
  __tablename__ = 'chats'
28
  chat_id = db.Column(db.String(36), primary_key=True)
 
31
  title = db.Column(db.String(100))
32
  messages = db.relationship('Message', backref='chat', lazy=True)
33
 
34
+
35
  class Message(db.Model):
36
  __tablename__ = 'messages'
37
  id = db.Column(db.Integer, primary_key=True)
 
40
  content = db.Column(db.Text)
41
  timestamp = db.Column(db.DateTime, default=db.func.current_timestamp())
42
 
43
+
44
  class AnalysisReport(db.Model):
45
  __tablename__ = 'analysis_reports'
46
  id = db.Column(db.Integer, primary_key=True)
 
48
  content = db.Column(db.Text)
49
  emotion = db.Column(db.String(50))
50
  confidence = db.Column(db.Float)
51
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
profile.py CHANGED
@@ -9,13 +9,14 @@ from models import AnalysisReport
9
 
10
  profile_bp = Blueprint('profile', __name__)
11
 
 
12
  @profile_bp.route('/profile')
13
  @login_required
14
  def profile():
15
  # Получаем отчеты
16
- reports = AnalysisReport.query.filter_by(user_id=current_user.id)\
17
- .order_by(AnalysisReport.created_at.desc())\
18
- .all()
19
 
20
  # Преобразуем в список словарей с правильными датами
21
  formatted_reports = []
@@ -37,21 +38,24 @@ def profile():
37
  most_common_emotion = max(emotion_stats, key=lambda x: x.count).emotion if emotion_stats else None
38
  total_reports = len(formatted_reports)
39
 
40
- # Определяем emotion_map
41
  emotion_map = {
42
  'joy': '😊 Радость',
 
43
  'neutral': '😐 Нейтрально',
 
44
  'anger': '😠 Злость',
 
45
  'sadness': '😢 Грусть',
46
- 'surprise': '😲 Удивление',
47
- 'happy': '😊 Радость',
48
  'sad': '😢 Грусть',
49
- 'angry': '😠 Злость'
 
 
50
  }
51
 
52
  return render_template('profile.html',
53
- reports=formatted_reports,
54
- most_common_emotion=most_common_emotion,
55
- total_reports=total_reports,
56
- emotion_map=emotion_map,
57
- datetime=datetime)
 
9
 
10
  profile_bp = Blueprint('profile', __name__)
11
 
12
+
13
  @profile_bp.route('/profile')
14
  @login_required
15
  def profile():
16
  # Получаем отчеты
17
+ reports = AnalysisReport.query.filter_by(user_id=current_user.id) \
18
+ .order_by(AnalysisReport.created_at.desc()) \
19
+ .all()
20
 
21
  # Преобразуем в список словарей с правильными датами
22
  formatted_reports = []
 
38
  most_common_emotion = max(emotion_stats, key=lambda x: x.count).emotion if emotion_stats else None
39
  total_reports = len(formatted_reports)
40
 
41
+ # Полный словарь соответствий эмоций
42
  emotion_map = {
43
  'joy': '😊 Радость',
44
+ 'happy': '😊 Радость',
45
  'neutral': '😐 Нейтрально',
46
+ 'no_emotion': '😐 Нейтрально',
47
  'anger': '😠 Злость',
48
+ 'angry': '😠 Злость',
49
  'sadness': '😢 Грусть',
 
 
50
  'sad': '😢 Грусть',
51
+ 'surprise': '😲 Удивление',
52
+ 'fear': '😨 Страх',
53
+ 'disgust': '🤢 Отвращение'
54
  }
55
 
56
  return render_template('profile.html',
57
+ reports=formatted_reports,
58
+ most_common_emotion=most_common_emotion,
59
+ total_reports=total_reports,
60
+ emotion_map=emotion_map,
61
+ datetime=datetime)
requirements.txt CHANGED
@@ -1,5 +1,4 @@
1
  flask==2.3.2
2
- waitress>=2.1.2
3
  werkzeug==2.3.7
4
  flask-login==0.6.2
5
  flask-sqlalchemy==2.5.1
 
1
  flask==2.3.2
 
2
  werkzeug==2.3.7
3
  flask-login==0.6.2
4
  flask-sqlalchemy==2.5.1
templates/admin/base.html ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>{% block title %}Админ-панель{% endblock %}</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='admin.css') }}">
10
+ </head>
11
+ <body>
12
+ <div class="admin-wrapper">
13
+ <!-- Боковая панель -->
14
+ <div class="admin-sidebar">
15
+ <div class="admin-brand">
16
+ <i class="fas fa-lock"></i> Админ-панель
17
+ </div>
18
+ <nav class="admin-nav">
19
+ <a href="{{ url_for('admin_bp.dashboard') }}" class="nav-item">
20
+ <i class="fas fa-tachometer-alt"></i> Дашборд
21
+ </a>
22
+ <a href="{{ url_for('admin_bp.manage_users') }}" class="nav-item">
23
+ <i class="fas fa-users"></i> Пользователи
24
+ </a>
25
+ <a href="{{ url_for('admin_bp.view_reports') }}" class="nav-item">
26
+ <i class="fas fa-chart-bar"></i> Отчеты
27
+ </a>
28
+ <div class="nav-divider"></div>
29
+ <a href="{{ url_for('index') }}" class="nav-item">
30
+ <i class="fas fa-arrow-left"></i> На сайт
31
+ </a>
32
+ </nav>
33
+ <div class="admin-user">
34
+ <i class="fas fa-user-circle"></i>
35
+ <span>{{ current_user.username }}</span>
36
+ </div>
37
+ </div>
38
+
39
+ <!-- Основное содержимое -->
40
+ <main class="admin-main">
41
+ <div class="admin-container">
42
+ {% with messages = get_flashed_messages(with_categories=true) %}
43
+ {% if messages %}
44
+ <div class="flashes">
45
+ {% for category, message in messages %}
46
+ <div class="alert alert-{{ category }} alert-dismissible fade show">
47
+ {{ message }}
48
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
49
+ </div>
50
+ {% endfor %}
51
+ </div>
52
+ {% endif %}
53
+ {% endwith %}
54
+
55
+ {% block content %}{% endblock %}
56
+ </div>
57
+ </main>
58
+ </div>
59
+
60
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
61
+ <script src="{{ url_for('static', filename='admin.js') }}"></script>
62
+ {% block scripts %}{% endblock %}
63
+ </body>
64
+ </html>
templates/admin/dashboard.html ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "admin/base.html" %}
2
+
3
+ {% block title %}Дашборд{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="admin-header">
7
+ <h1><i class="fas fa-tachometer-alt"></i> Дашборд</h1>
8
+ </div>
9
+
10
+ <div class="row mb-4">
11
+ <div class="col-md-3">
12
+ <div class="stat-card bg-primary">
13
+ <div class="stat-icon">
14
+ <i class="fas fa-users"></i>
15
+ </div>
16
+ <div class="stat-info">
17
+ <h3>{{ users_count }}</h3>
18
+ <p>Пользователей</p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ <div class="col-md-3">
23
+ <div class="stat-card bg-success">
24
+ <div class="stat-icon">
25
+ <i class="fas fa-user-plus"></i>
26
+ </div>
27
+ <div class="stat-info">
28
+ <h3>{{ new_users }}</h3>
29
+ <p>Новых за 30 дней</p>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="col-md-3">
34
+ <div class="stat-card bg-info">
35
+ <div class="stat-icon">
36
+ <i class="fas fa-chart-bar"></i>
37
+ </div>
38
+ <div class="stat-info">
39
+ <h3>{{ reports_count }}</h3>
40
+ <p>Анализов</p>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ <div class="col-md-3">
45
+ <div class="stat-card bg-warning">
46
+ <div class="stat-icon">
47
+ <i class="fas fa-comments"></i>
48
+ </div>
49
+ <div class="stat-info">
50
+ <h3>{{ active_users }}</h3>
51
+ <p>Активных</p>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <div class="row">
58
+ <div class="col-md-6">
59
+ <div class="card mb-4">
60
+ <div class="card-header">
61
+ <h5><i class="fas fa-chart-pie"></i> Распределение эмоций</h5>
62
+ </div>
63
+ <div class="card-body">
64
+ <div class="table-responsive">
65
+ <table class="table">
66
+ <thead>
67
+ <tr>
68
+ <th>Эмоция</th>
69
+ <th>Количество</th>
70
+ <th>Процент</th>
71
+ </tr>
72
+ </thead>
73
+ <tbody>
74
+ {% for emotion, count in emotion_stats %}
75
+ <tr>
76
+ <td>{{ emotion_map.get(emotion, emotion) }}</td>
77
+ <td>{{ count }}</td>
78
+ <td>
79
+ <div class="progress">
80
+ <div class="progress-bar"
81
+ style="width: {{ (count / reports_count * 100) if reports_count > 0 else 0 }}%">
82
+ {{ (count / reports_count * 100)|round(1) if reports_count > 0 else 0 }}%
83
+ </div>
84
+ </div>
85
+ </td>
86
+ </tr>
87
+ {% endfor %}
88
+ </tbody>
89
+ </table>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ <div class="col-md-6">
95
+ <div class="card">
96
+ <div class="card-header">
97
+ <h5><i class="fas fa-user-chart"></i> Топ пользователей</h5>
98
+ </div>
99
+ <div class="card-body">
100
+ <div class="table-responsive">
101
+ <table class="table">
102
+ <thead>
103
+ <tr>
104
+ <th>Пользователь</th>
105
+ <th>Анализов</th>
106
+ </tr>
107
+ </thead>
108
+ <tbody>
109
+ {% for user, count in user_activity %}
110
+ <tr>
111
+ <td>{{ user }}</td>
112
+ <td>{{ count }}</td>
113
+ </tr>
114
+ {% endfor %}
115
+ </tbody>
116
+ </table>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ {% endblock %}
templates/admin/reports.html ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "admin/base.html" %}
2
+
3
+ {% block title %}Отчеты анализа{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="admin-header">
7
+ <h1><i class="fas fa-chart-bar"></i> Отчеты анализа</h1>
8
+ <div class="admin-actions">
9
+ <form class="filter-form" method="get" action="{{ url_for('admin_bp.view_reports') }}">
10
+ <select class="form-select" name="emotion" onchange="this.form.submit()">
11
+ <option value="">Все эмоции</option>
12
+ {% for emotion in emotions %}
13
+ <option value="{{ emotion.emotion }}" {% if emotion.emotion == current_emotion %}selected{% endif %}>
14
+ {{ emotion_map.get(emotion.emotion, emotion.emotion) }}
15
+ </option>
16
+ {% endfor %}
17
+ </select>
18
+ </form>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="card">
23
+ <div class="card-body">
24
+ <div class="table-responsive">
25
+ <table class="table table-hover">
26
+ <thead>
27
+ <tr>
28
+ <th>ID</th>
29
+ <th>Пользователь</th>
30
+ <th>Текст</th>
31
+ <th>Эмоция</th>
32
+ <th>Уверенность</th>
33
+ <th>Дата</th>
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ {% for report in reports.items %}
38
+ <tr>
39
+ <td>{{ report.id }}</td>
40
+ <td>{{ report.user.username }}</td>
41
+ <td class="text-truncate" style="max-width: 200px;" title="{{ report.content }}">
42
+ {{ report.content }}
43
+ </td>
44
+ <td>
45
+ <span class="badge" style="background: {{ get_emotion_color(report.emotion) }}">
46
+ {{ emotion_map.get(report.emotion, report.emotion) }}
47
+ </span>
48
+ </td>
49
+ <td>{{ (report.confidence * 100)|round(1) }}%</td>
50
+ <td>{{ report.created_at|datetimeformat }}</td>
51
+ </tr>
52
+ {% endfor %}
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+
57
+ <!-- Пагинация -->
58
+ <nav aria-label="Page navigation">
59
+ <ul class="pagination justify-content-center">
60
+ {% if reports.has_prev %}
61
+ <li class="page-item">
62
+ <a class="page-link" href="{{ url_for('admin_bp.view_reports', page=reports.prev_num, emotion=current_emotion) }}">
63
+ &laquo;
64
+ </a>
65
+ </li>
66
+ {% endif %}
67
+
68
+ {% for page_num in reports.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=3) %}
69
+ {% if page_num %}
70
+ <li class="page-item {% if page_num == reports.page %}active{% endif %}">
71
+ <a class="page-link" href="{{ url_for('admin_bp.view_reports', page=page_num, emotion=current_emotion) }}">
72
+ {{ page_num }}
73
+ </a>
74
+ </li>
75
+ {% else %}
76
+ <li class="page-item disabled"><span class="page-link">...</span></li>
77
+ {% endif %}
78
+ {% endfor %}
79
+
80
+ {% if reports.has_next %}
81
+ <li class="page-item">
82
+ <a class="page-link" href="{{ url_for('admin_bp.view_reports', page=reports.next_num, emotion=current_emotion) }}">
83
+ &raquo;
84
+ </a>
85
+ </li>
86
+ {% endif %}
87
+ </ul>
88
+ </nav>
89
+ </div>
90
+ </div>
91
+ {% endblock %}
templates/admin/users.html ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "admin/base.html" %}
2
+
3
+ {% block title %}Управление пользователями{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="admin-header">
7
+ <h1><i class="fas fa-users"></i> Управление пользователями</h1>
8
+ <div class="admin-actions">
9
+ <form class="search-form" method="get" action="{{ url_for('admin_bp.manage_users') }}">
10
+ <div class="input-group">
11
+ <input type="text" class="form-control" name="search" placeholder="Поиск..."
12
+ value="{{ search_query }}">
13
+ <button class="btn btn-outline-secondary" type="submit">
14
+ <i class="fas fa-search"></i>
15
+ </button>
16
+ </div>
17
+ </form>
18
+ </div>
19
+ </div>
20
+
21
+ <div class="card">
22
+ <div class="card-body">
23
+ <div class="table-responsive">
24
+ <table class="table table-hover">
25
+ <thead>
26
+ <tr>
27
+ <th>ID</th>
28
+ <th>Имя</th>
29
+ <th>Email</th>
30
+ <th>Дата регистрации</th>
31
+ <th>Статус</th>
32
+ <th>Действия</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {% for user in users.items %}
37
+ <tr>
38
+ <td>{{ user.id }}</td>
39
+ <td>{{ user.username }}</td>
40
+ <td>{{ user.email }}</td>
41
+ <td>{{ user.created_at|datetimeformat }}</td>
42
+ <td>
43
+ {% if user.is_admin %}
44
+ <span class="badge bg-danger">Админ</span>
45
+ {% else %}
46
+ <span class="badge bg-secondary">Пользователь</span>
47
+ {% endif %}
48
+ </td>
49
+ <td>
50
+ <div class="btn-group">
51
+ <button class="btn btn-sm btn-{{ 'danger' if user.is_admin else 'success' }} toggle-admin"
52
+ data-user-id="{{ user.id }}">
53
+ {{ 'Убрать админа' if user.is_admin else 'Сделать админом' }}
54
+ </button>
55
+ {% if user.id != current_user.id %}
56
+ <button class="btn btn-sm btn-outline-danger delete-user"
57
+ data-user-id="{{ user.id }}">
58
+ <i class="fas fa-trash"></i>
59
+ </button>
60
+ {% endif %}
61
+ </div>
62
+ </td>
63
+ </tr>
64
+ {% endfor %}
65
+ </tbody>
66
+ </table>
67
+ </div>
68
+
69
+ <!-- Пагинация -->
70
+ <nav aria-label="Page navigation">
71
+ <ul class="pagination justify-content-center">
72
+ {% if users.has_prev %}
73
+ <li class="page-item">
74
+ <a class="page-link" href="{{ url_for('admin_bp.manage_users', page=users.prev_num, search=search_query) }}">
75
+ &laquo;
76
+ </a>
77
+ </li>
78
+ {% endif %}
79
+
80
+ {% for page_num in users.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=3) %}
81
+ {% if page_num %}
82
+ <li class="page-item {% if page_num == users.page %}active{% endif %}">
83
+ <a class="page-link" href="{{ url_for('admin_bp.manage_users', page=page_num, search=search_query) }}">
84
+ {{ page_num }}
85
+ </a>
86
+ </li>
87
+ {% else %}
88
+ <li class="page-item disabled"><span class="page-link">...</span></li>
89
+ {% endif %}
90
+ {% endfor %}
91
+
92
+ {% if users.has_next %}
93
+ <li class="page-item">
94
+ <a class="page-link" href="{{ url_for('admin_bp.manage_users', page=users.next_num, search=search_query) }}">
95
+ &raquo;
96
+ </a>
97
+ </li>
98
+ {% endif %}
99
+ </ul>
100
+ </nav>
101
+ </div>
102
+ </div>
103
+ {% endblock %}
104
+
105
+ {% block scripts %}
106
+ {{ super() }}
107
+ <script>
108
+ document.addEventListener('DOMContentLoaded', function() {
109
+ // Функция для получения CSRF-токена из cookies
110
+ function getCookie(name) {
111
+ const cookieValue = document.cookie
112
+ .split('; ')
113
+ .find(row => row.startsWith(name + '='))
114
+ ?.split('=')[1];
115
+ return cookieValue ? decodeURIComponent(cookieValue) : null;
116
+ }
117
+
118
+ // Обработка переключения админа
119
+ document.querySelectorAll('.toggle-admin').forEach(btn => {
120
+ btn.addEventListener('click', function() {
121
+ const userId = this.dataset.userId;
122
+ fetch(`/admin/toggle_admin/${userId}`, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ 'X-CSRFToken': getCookie('csrf_token')
127
+ }
128
+ })
129
+ .then(response => response.json())
130
+ .then(data => {
131
+ if (data.status === 'success') {
132
+ location.reload();
133
+ }
134
+ });
135
+ });
136
+ });
137
+
138
+ // Обработка удаления пользователя
139
+ document.querySelectorAll('.delete-user').forEach(btn => {
140
+ btn.addEventListener('click', function() {
141
+ if (!confirm('Вы уверены, что хотите удалить этого пользователя?')) return;
142
+
143
+ const userId = this.dataset.userId;
144
+ fetch(`/admin/delete_user/${userId}`, {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ 'X-CSRFToken': getCookie('csrf_token')
149
+ }
150
+ })
151
+ .then(response => response.json())
152
+ .then(data => {
153
+ if (data.status === 'success') {
154
+ location.reload();
155
+ } else {
156
+ alert(data.message || 'Ошибка при удалении');
157
+ }
158
+ });
159
+ });
160
+ });
161
+ });
162
+ </script>
163
+ {% endblock %}
templates/auth/login.html CHANGED
@@ -1,40 +1,44 @@
1
- {% extends "base.html" %}
2
 
3
  {% block title %}Вход{% endblock %}
4
 
5
  {% block content %}
6
- <div class="row justify-content-center">
7
- <div class="col-md-6">
8
- <div class="card">
9
- <div class="card-header">
10
- <h4 class="mb-0">Вход в систему</h4>
11
- </div>
12
- <div class="card-body">
13
- <form method="POST" action="{{ url_for('auth_bp.login') }}">
14
- {{ form.hidden_tag() }}
15
- <div class="mb-3">
16
- {{ form.email.label(class="form-label") }}
17
- {{ form.email(class="form-control") }}
18
- {% for error in form.email.errors %}
19
- <div class="text-danger">{{ error }}</div>
20
- {% endfor %}
21
- </div>
22
- <div class="mb-3">
23
- {{ form.password.label(class="form-label") }}
24
- {{ form.password(class="form-control") }}
25
- {% for error in form.password.errors %}
26
- <div class="text-danger">{{ error }}</div>
27
- {% endfor %}
28
- </div>
29
- <div class="d-grid gap-2">
30
- {{ form.submit(class="btn btn-primary") }}
31
- </div>
32
- </form>
33
- <div class="mt-3 text-center">
34
- Нет аккаунта? <a href="{{ url_for('auth_bp.register') }}">Зарегистрируйтесь</a>
35
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
  </div>
 
38
  </div>
39
  </div>
40
  {% endblock %}
 
1
+ {% extends "base_public.html" %}
2
 
3
  {% block title %}Вход{% endblock %}
4
 
5
  {% block content %}
6
+ <div class="auth-container">
7
+ <div class="auth-card">
8
+ <div class="auth-header">
9
+ <h3><i class="fas fa-sign-in-alt"></i> Вход в систему</h3>
10
+ </div>
11
+ <div class="auth-body">
12
+
13
+ <form method="POST" action="{{ url_for('auth_bp.login') }}">
14
+ {{ form.hidden_tag() }}
15
+ <div class="form-group">
16
+ <label for="email" class="form-label">
17
+ <i class="fas fa-envelope"></i> Email
18
+ </label>
19
+ {{ form.email(class="form-input", placeholder="Введите ваш email") }}
20
+ {% for error in form.email.errors %}
21
+ <div class="form-error">{{ error }}</div>
22
+ {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
23
  </div>
24
+ <div class="form-group">
25
+ <label for="password" class="form-label">
26
+ <i class="fas fa-lock"></i> Пароль
27
+ </label>
28
+ {{ form.password(class="form-input", placeholder="Введите пароль") }}
29
+ {% for error in form.password.errors %}
30
+ <div class="form-error">{{ error }}</div>
31
+ {% endfor %}
32
+ </div>
33
+ <button type="submit" class="btn-auth">
34
+ <i class="fas fa-sign-in-alt"></i> Войти
35
+ </button>
36
+ </form>
37
+ <div class="auth-footer">
38
+ Нет аккаунта? <a href="{{ url_for('auth_bp.register') }}" class="auth-link">Зарегистрируйтесь</a>
39
  </div>
40
  </div>
41
+
42
  </div>
43
  </div>
44
  {% endblock %}
templates/auth/register.html CHANGED
@@ -1,52 +1,58 @@
1
- {% extends "base.html" %}
2
 
3
  {% block title %}Регистрация{% endblock %}
4
 
5
  {% block content %}
6
- <div class="row justify-content-center">
7
- <div class="col-md-6">
8
- <div class="card">
9
- <div class="card-header">
10
- <h4 class="mb-0">Регистрация</h4>
11
- </div>
12
- <div class="card-body">
13
- <form method="POST" action="{{ url_for('.register') }}">
14
- {{ form.hidden_tag() }}
15
- <div class="mb-3">
16
- {{ form.username.label(class="form-label") }}
17
- {{ form.username(class="form-control") }}
18
- {% for error in form.username.errors %}
19
- <div class="text-danger">{{ error }}</div>
20
- {% endfor %}
21
- </div>
22
- <div class="mb-3">
23
- {{ form.email.label(class="form-label") }}
24
- {{ form.email(class="form-control") }}
25
- {% for error in form.email.errors %}
26
- <div class="text-danger">{{ error }}</div>
27
- {% endfor %}
28
- </div>
29
- <div class="mb-3">
30
- {{ form.password.label(class="form-label") }}
31
- {{ form.password(class="form-control") }}
32
- {% for error in form.password.errors %}
33
- <div class="text-danger">{{ error }}</div>
34
- {% endfor %}
35
- </div>
36
- <div class="mb-3">
37
- {{ form.confirm_password.label(class="form-label") }}
38
- {{ form.confirm_password(class="form-control") }}
39
- {% for error in form.confirm_password.errors %}
40
- <div class="text-danger">{{ error }}</div>
41
- {% endfor %}
42
- </div>
43
- <div class="d-grid gap-2">
44
- {{ form.submit(class="btn btn-success") }}
45
- </div>
46
- </form>
47
- <div class="mt-3 text-center">
48
- Уже есть аккаунт? <a href="{{ url_for('auth_bp.login') }}">Войдите</a>
49
  </div>
 
 
 
 
 
 
50
  </div>
51
  </div>
52
  </div>
 
1
+ {% extends "base_public.html" %}
2
 
3
  {% block title %}Регистрация{% endblock %}
4
 
5
  {% block content %}
6
+ <div class="auth-container">
7
+ <div class="auth-card">
8
+ <div class="auth-header">
9
+ <h3><i class="fas fa-user-plus"></i> Регистрация</h3>
10
+ </div>
11
+ <div class="auth-body">
12
+ <form method="POST" action="{{ url_for('.register') }}">
13
+ {{ form.hidden_tag() }}
14
+ <div class="form-group">
15
+ <label for="username" class="form-label">
16
+ <i class="fas fa-user"></i> Имя пользователя
17
+ </label>
18
+ {{ form.username(class="form-input", placeholder="Придумайте имя пользователя") }}
19
+ {% for error in form.username.errors %}
20
+ <div class="form-error">{{ error }}</div>
21
+ {% endfor %}
22
+ </div>
23
+ <div class="form-group">
24
+ <label for="email" class="form-label">
25
+ <i class="fas fa-envelope"></i> Email
26
+ </label>
27
+ {{ form.email(class="form-input", placeholder="Введите ваш email") }}
28
+ {% for error in form.email.errors %}
29
+ <div class="form-error">{{ error }}</div>
30
+ {% endfor %}
31
+ </div>
32
+ <div class="form-group">
33
+ <label for="password" class="form-label">
34
+ <i class="fas fa-lock"></i> Пароль
35
+ </label>
36
+ {{ form.password(class="form-input", placeholder="Придумайте пароль") }}
37
+ {% for error in form.password.errors %}
38
+ <div class="form-error">{{ error }}</div>
39
+ {% endfor %}
40
+ </div>
41
+ <div class="form-group">
42
+ <label for="confirm_password" class="form-label">
43
+ <i class="fas fa-lock"></i> Подтверждение пароля
44
+ </label>
45
+ {{ form.confirm_password(class="form-input", placeholder="Повторите пароль") }}
46
+ {% for error in form.confirm_password.errors %}
47
+ <div class="form-error">{{ error }}</div>
48
+ {% endfor %}
49
  </div>
50
+ <button type="submit" class="btn-auth">
51
+ <i class="fas fa-user-plus"></i> Зарегистрироваться
52
+ </button>
53
+ </form>
54
+ <div class="auth-footer">
55
+ Уже есть аккаунт? <a href="{{ url_for('auth_bp.login') }}" class="auth-link">Войдите</a>
56
  </div>
57
  </div>
58
  </div>
templates/base.html CHANGED
@@ -7,14 +7,18 @@
7
  <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
 
 
10
  </head>
11
  <body>
12
  <div class="app-container">
13
  <!-- Боковая панель -->
14
  <div class="app-sidebar">
15
- <div class="sidebar-header">
16
- <h2><i class="fas fa-brain"></i> EmotionAnalyzer</h2>
17
- </div>
 
 
18
 
19
  <div class="sidebar-nav">
20
  <a href="{{ url_for('index') }}" class="nav-item">
@@ -24,6 +28,11 @@
24
  <a href="{{ url_for('profile.profile') }}" class="nav-item">
25
  <i class="fas fa-user"></i> Профиль
26
  </a>
 
 
 
 
 
27
  {% endif %}
28
  </div>
29
 
@@ -47,18 +56,27 @@
47
 
48
  <!-- Основное содержимое -->
49
  <div class="app-main">
50
- {% with messages = get_flashed_messages(with_categories=true) %}
51
- {% if messages %}
52
- <div class="flash-messages">
53
- {% for category, message in messages %}
54
- <div class="flash-message flash-{{ category }}">
55
- {{ message }}
56
- <button class="flash-close"><i class="fas fa-times"></i></button>
57
- </div>
58
- {% endfor %}
 
 
 
 
59
  </div>
60
- {% endif %}
61
- {% endwith %}
 
 
 
 
 
62
 
63
  {% block content %}{% endblock %}
64
  </div>
@@ -66,6 +84,11 @@
66
 
67
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
68
  <script src="{{ url_for('static', filename='script.js') }}"></script>
 
 
 
 
 
69
  {% block scripts %}{% endblock %}
70
  </body>
71
  </html>
 
7
  <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <!-- Bootstrap JS (для работы закрытия) -->
11
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
12
  </head>
13
  <body>
14
  <div class="app-container">
15
  <!-- Боковая панель -->
16
  <div class="app-sidebar">
17
+ <div class="sidebar-header">
18
+ <a href="{{ url_for('welcome') }}" class="logo-link">
19
+ <h2><i class="fas fa-brain"></i> EmotionAnalyzer</h2>
20
+ </a>
21
+ </div>
22
 
23
  <div class="sidebar-nav">
24
  <a href="{{ url_for('index') }}" class="nav-item">
 
28
  <a href="{{ url_for('profile.profile') }}" class="nav-item">
29
  <i class="fas fa-user"></i> Профиль
30
  </a>
31
+ {% if current_user.is_authenticated and current_user.is_admin %}
32
+ <a href="{{ url_for('admin_bp.dashboard') }}" class="nav-item">
33
+ <i class="fas fa-lock"></i> Админ-панель
34
+ </a>
35
+ {% endif %}
36
  {% endif %}
37
  </div>
38
 
 
56
 
57
  <!-- Основное содержимое -->
58
  <div class="app-main">
59
+ {% with messages = get_flashed_messages(with_categories=true) %}
60
+ {% if messages %}
61
+ <div class="flash-messages">
62
+ {% for category, message in messages %}
63
+ <div class="flash-message flash-{{ category }} enhanced-alert">
64
+ <div class="alert-icon">
65
+ {% if category == 'error' %}
66
+ <i class="fas fa-exclamation-circle"></i>
67
+ {% elif category == 'success' %}
68
+ <i class="fas fa-check-circle"></i>
69
+ {% else %}
70
+ <i class="fas fa-info-circle"></i>
71
+ {% endif %}
72
  </div>
73
+ <div class="alert-text">{{ message }}</div>
74
+ <button class="flash-close " onclick="this.parentElement.remove()"><i class="fas fa-times"></i></button>
75
+ </div>
76
+ {% endfor %}
77
+ </div>
78
+ {% endif %}
79
+ {% endwith %}
80
 
81
  {% block content %}{% endblock %}
82
  </div>
 
84
 
85
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
86
  <script src="{{ url_for('static', filename='script.js') }}"></script>
87
+ <script>
88
+ document.querySelector('.mobile-menu-toggle').addEventListener('click', () => {
89
+ document.querySelector('.app-container').classList.toggle('sidebar-open');
90
+ });
91
+ </script>
92
  {% block scripts %}{% endblock %}
93
  </body>
94
  </html>
templates/base_public.html ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>{% block title %}Emotion Analyzer{% endblock %}</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
9
+ </head>
10
+
11
+ <body>
12
+ <!-- Логотип -->
13
+ <div class="logo-container">
14
+ <a href="{{ url_for('welcome') }}" class="logo-link">
15
+ <h2><i class="fas fa-brain"></i> EmotionAnalyzer</h2>
16
+ </a>
17
+ </div>
18
+
19
+ <!-- Основной контент -->
20
+ {% with messages = get_flashed_messages(with_categories=true) %}
21
+ {% if messages %}
22
+ <div class="flash-container">
23
+ {% for category, message in messages %}
24
+ <div class="flash flash-{{ category }}">{{ message }}</div>
25
+ {% endfor %}
26
+ </div>
27
+ {% endif %}
28
+ {% endwith %}
29
+
30
+ <div class="public-container">
31
+ {% block content %}{% endblock %}
32
+ </div>
33
+
34
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
35
+ <script>
36
+ setTimeout(() => {
37
+ document.querySelectorAll('.flash').forEach(flash => flash.remove());
38
+ }, 4000);
39
+ </script>
40
+ {% block scripts %}{% endblock %}
41
+ </body>
42
+ </html>
43
+
44
+ <style>
45
+ /*Cтили для окна ошибок*/
46
+ .flash-container {
47
+ position: fixed;
48
+ top: 20px;
49
+ right: 20px;
50
+ z-index: 9999;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 10px;
54
+ }
55
+
56
+ .flash {
57
+ padding: 12px 18px;
58
+ border-radius: 6px;
59
+ color: white;
60
+ font-weight: 500;
61
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
62
+ opacity: 0.95;
63
+ animation: slide-in 0.4s ease;
64
+ }
65
+
66
+ .flash-success {
67
+ background-color: #2ecc71;
68
+ }
69
+
70
+ .flash-danger {
71
+ background-color: #e74c3c;
72
+ }
73
+
74
+ @keyframes slide-in {
75
+ from { transform: translateX(100%); opacity: 0; }
76
+ to { transform: translateX(0); opacity: 1; }
77
+ }
78
+
79
+
80
+ /* Стили для логотипа */
81
+ .logo-container {
82
+ padding-top: 20px;
83
+ padding-left: 20px;
84
+ background: linear-gradient(135deg, var(--darker-bg), var(--dark-bg));
85
+ }
86
+
87
+ .logo-link {
88
+ text-decoration: none !important;
89
+ }
90
+
91
+ .logo-link h2 {
92
+ color: #fff; /* Темно-синий цвет */
93
+ font-size: 2rem;
94
+ margin: 0;
95
+ transition: color 0.3s ease;
96
+ }
97
+
98
+ .logo-link:hover h2 {
99
+ color: #4a4ae8; /* Цвет при наведении */
100
+ }
101
+
102
+ .fa-brain {
103
+ color: inherit; /* Наследует цвет от родителя */
104
+ margin-right: 10px;
105
+ }
106
+
107
+
108
+
109
+ @media (max-width: 768px) {
110
+ .logo-link h2 {
111
+ font-size: 1.5rem;
112
+ }
113
+
114
+ .public-container {
115
+ padding: 20px;
116
+ }
117
+ }
118
+ </style>
templates/edit_profile.html ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <div class="edit-profile">
4
+ <h2><i class="fas fa-user-cog"></i> Редактирование профиля</h2>
5
+ <form method="POST" enctype="multipart/form-data">
6
+ {{ form.hidden_tag() }}
7
+
8
+ <div class="form-group">
9
+ {{ form.username.label }} {{ form.username(class="form-control") }}
10
+ </div>
11
+
12
+ <div class="form-group">
13
+ {{ form.email.label }} {{ form.email(class="form-control") }}
14
+ </div>
15
+
16
+ <div class="form-group">
17
+ {{ form.avatar.label }} {{ form.avatar() }}
18
+ </div>
19
+
20
+ <hr>
21
+
22
+ <h2>Изменить пароль</h2>
23
+
24
+ <div class="form-group">
25
+ {{ form.current_password.label }} {{ form.current_password(class="form-control") }}
26
+ </div>
27
+
28
+ <div class="form-group">
29
+ {{ form.new_password.label }} {{ form.new_password(class="form-control") }}
30
+ </div>
31
+
32
+ <div class="form-group">
33
+ {{ form.confirm_password.label }} {{ form.confirm_password(class="form-control") }}
34
+ </div>
35
+
36
+ {{ form.submit(class="btn btn-primary") }}
37
+ </form>
38
+ </div>
39
+ {% endblock %}
templates/profile.html CHANGED
@@ -1,35 +1,33 @@
1
  {% extends "base.html" %}
2
-
3
  {% block title %}Личный кабинет{% endblock %}
4
-
5
  {% block content %}
6
  <div class="profile-container">
 
 
7
  <div class="profile-header">
8
  <div class="user-info">
9
  <div class="user-avatar">
10
- <i class="fas fa-user-circle"></i>
 
11
  </div>
12
  <div class="user-details">
13
- <h2>{{ current_user.username }}</h2>
14
- <p>{{ current_user.email }}</p>
 
 
 
15
  </div>
16
  </div>
17
-
18
  <div class="stats-cards">
19
  <div class="stat-card">
20
- <div class="stat-icon">
21
- <i class="fas fa-chart-bar"></i>
22
- </div>
23
  <div class="stat-content">
24
  <h3>Всего анализов</h3>
25
  <p class="stat-value">{{ total_reports }}</p>
26
  </div>
27
  </div>
28
-
29
  <div class="stat-card">
30
- <div class="stat-icon">
31
- <i class="fas fa-smile"></i>
32
- </div>
33
  <div class="stat-content">
34
  <h3>Преобладающая эмоция</h3>
35
  <p class="stat-value">{{ emotion_map.get(most_common_emotion, most_common_emotion) }}</p>
@@ -38,26 +36,26 @@
38
  </div>
39
  </div>
40
 
 
41
  <div class="reports-section">
42
- <h3 class="section-title">
43
- <i class="fas fa-history"></i> История анализов
44
- </h3>
45
 
46
  {% if reports %}
47
- <div class="reports-list">
48
- {% for report in reports %}
 
 
 
 
 
49
  <div class="report-card">
50
  <div class="report-header">
51
- <span class="report-date">
52
- {% if report['created_at'] %}
53
- {{ datetime.strptime(report['created_at'], '%Y-%m-%d %H:%M:%S').strftime('%d.%m.%Y %H:%M') }}
54
- {% else %}
55
- Дата не указана
56
- {% endif %}
57
- </span>
58
  <span class="report-emotion {{ report['emotion'] }}">
59
- {{ emotion_map.get(report['emotion'], report['emotion']) }}
60
- </span>
61
  </div>
62
  <div class="report-content">
63
  <p>{{ report['content'][:200] }}{% if report['content']|length > 200 %}...{% endif %}</p>
@@ -72,7 +70,25 @@
72
  {% endfor %}
73
  </div>
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
 
 
 
76
  {% else %}
77
  <div class="empty-state">
78
  <i class="fas fa-comment-slash"></i>
@@ -84,5 +100,225 @@
84
  </div>
85
  {% endif %}
86
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  {% endblock %}
 
1
  {% extends "base.html" %}
 
2
  {% block title %}Личный кабинет{% endblock %}
 
3
  {% block content %}
4
  <div class="profile-container">
5
+
6
+ <!-- User Info -->
7
  <div class="profile-header">
8
  <div class="user-info">
9
  <div class="user-avatar">
10
+ <img src="{{ url_for('static', filename='avatars/' ~ current_user.avatar) }}" alt="Avatar"
11
+ class="avatar-img">
12
  </div>
13
  <div class="user-details">
14
+ <h2>Логин: {{ current_user.username }}</h2>
15
+ <p>Почта: {{ current_user.email }}</p>
16
+ <a href="{{ url_for('edit_profile') }}" class="btn btn-primary edit-profile-btn">
17
+ <i class="fas fa-user-edit"></i> Редактировать профиль
18
+ </a>
19
  </div>
20
  </div>
 
21
  <div class="stats-cards">
22
  <div class="stat-card">
23
+ <div class="stat-icon"><i class="fas fa-chart-bar"></i></div>
 
 
24
  <div class="stat-content">
25
  <h3>Всего анализов</h3>
26
  <p class="stat-value">{{ total_reports }}</p>
27
  </div>
28
  </div>
 
29
  <div class="stat-card">
30
+ <div class="stat-icon"><i class="fas fa-smile"></i></div>
 
 
31
  <div class="stat-content">
32
  <h3>Преобладающая эмоция</h3>
33
  <p class="stat-value">{{ emotion_map.get(most_common_emotion, most_common_emotion) }}</p>
 
36
  </div>
37
  </div>
38
 
39
+ <!-- Reports History -->
40
  <div class="reports-section">
41
+ <h3 class="section-title"><i class="fas fa-history"></i> История анализов</h3>
 
 
42
 
43
  {% if reports %}
44
+ <div class="reports-list" id="reports-list">
45
+ {% set page_size = 6 %}
46
+ {% set current_page = request.args.get('page', default='1') | int %}
47
+ {% set start_index = (current_page - 1) * page_size %}
48
+ {% set end_index = [start_index + page_size, reports|length] | min %}
49
+
50
+ {% for report in reports[start_index:end_index] %}
51
  <div class="report-card">
52
  <div class="report-header">
53
+ <span class="report-date">
54
+ {{ report['created_at'] if report['created_at'] is string else report['created_at'].strftime('%d.%m.%Y %H:%M') }}
55
+ </span>
 
 
 
 
56
  <span class="report-emotion {{ report['emotion'] }}">
57
+ {{ emotion_map.get(report['emotion'], report['emotion']) }}
58
+ </span>
59
  </div>
60
  <div class="report-content">
61
  <p>{{ report['content'][:200] }}{% if report['content']|length > 200 %}...{% endif %}</p>
 
70
  {% endfor %}
71
  </div>
72
 
73
+ <!-- Пагинация -->
74
+ {% if reports|length > page_size %}
75
+ <div class="pagination">
76
+ {% set total_pages = (reports|length / page_size)|round(0, 'ceil')|int %}
77
+
78
+ <!-- Кнопка "Назад" -->
79
+ <a href="?page={{ current_page - 1 }}" class="pagination-btn {% if current_page == 1 %}disabled{% endif %}">&laquo;
80
+ Назад</a>
81
+
82
+ <!-- Номера страниц -->
83
+ {% for i in range(1, total_pages + 1) %}
84
+ <a href="?page={{ i }}" class="pagination-btn {% if current_page == i %}active{% endif %}">{{ i }}</a>
85
+ {% endfor %}
86
 
87
+ <!-- Кнопка "Вперед" -->
88
+ <a href="?page={{ current_page + 1 }}"
89
+ class="pagination-btn {% if current_page >= total_pages %}disabled{% endif %}">Вперед &raquo;</a>
90
+ </div>
91
+ {% endif %}
92
  {% else %}
93
  <div class="empty-state">
94
  <i class="fas fa-comment-slash"></i>
 
100
  </div>
101
  {% endif %}
102
  </div>
103
+
104
+ <!-- Telegram Analysis Section -->
105
+ <div class="telegram-analysis-section">
106
+ <h3 class="section-title"><i class="fab fa-telegram"></i> Анализ Telegram чатов</h3>
107
+ <div class="chat-import-section">
108
+ <form id="telegram-upload-form" enctype="multipart/form-data">
109
+ <div class="file-upload">
110
+ <label for="telegram-file" class="file-upload-btn">
111
+ <i class="fas fa-file-import"></i> Выбрать файл чата
112
+ </label>
113
+ <input type="file" id="telegram-file" accept=".json" required/>
114
+ <div class="file-info" style="display: none;">
115
+ <span id="selected-file-name">Файл не выбран</span>
116
+ <button type="button" id="clear-file" class="clear-file-btn">
117
+ <i class="fas fa-times"></i>
118
+ </button>
119
+ </div>
120
+ <button type="submit" class="btn-primary mt-3">
121
+ <i class="fas fa-chart-bar"></i> Анализировать
122
+ </button>
123
+ </div>
124
+ <p class="hint">Экспортируйте чат через Telegram Desktop (JSON format)</p>
125
+ </form>
126
+
127
+ </div>
128
+
129
+ <!-- Фильтры -->
130
+ <div class="chart-header">
131
+ <h4><i class="fas fa-chart-line"></i> Изменение эмоционального фона</h4>
132
+ <div class="time-filter">
133
+ <button class="time-btn active" data-range="week">Неделя</button>
134
+ <button class="time-btn" data-range="month">Месяц</button>
135
+ <button class="time-btn" data-range="year">Год</button>
136
+ <button class="time-btn" data-range="all">Все время</button>
137
+ </div>
138
+ <div class="user-filter">
139
+ <label for="user-select" style="color: white;"><i class="fas fa-user-friends"></i> Участник:</label>
140
+ <select id="user-select" class="form-control">
141
+ <option value="all">Все участники</option>
142
+ </select>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- Блок автоматического анализа -->
147
+ <div id="analysis-summary" class="analysis-summary">
148
+ <h4><i class="fas fa-lightbulb"></i> Автоматический анализ</h4>
149
+ <ul id="summary-content"></ul>
150
+ </div>
151
+
152
+ <!-- Основной график -->
153
+ <div class="chart-container" id="emotion-timeline"></div>
154
+
155
+ <!-- Тепловая карта -->
156
+ <div class="chart-container" id="calendar-heatmap"></div>
157
+
158
+ <!-- Дополнительная статистика -->
159
+ <div class="chart-row">
160
+ <div class="chart-col">
161
+ <h4><i class="fas fa-chart-pie"></i> Распределение эмоций</h4>
162
+ <div class="chart-container" id="emotion-distribution-pie"></div>
163
+ </div>
164
+ <div class="chart-col">
165
+ <h4><i class="fas fa-percentage"></i> Статистика по эмоциям</h4>
166
+ <div class="emotion-stats" id="emotion-distribution"></div>
167
+ </div>
168
+ </div>
169
+ </div>
170
  </div>
171
+
172
+
173
+ <!-- Plotly -->
174
+ <script src="https://cdn.plot.ly/plotly-2.24.1.min.js "></script>
175
+
176
+ <!-- Main Script -->
177
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
178
+
179
+ <!-- File upload handler -->
180
+ <script>
181
+ const telegramFileInput = document.getElementById('telegram-file');
182
+ if (telegramFileInput) {
183
+ telegramFileInput.addEventListener('change', function(e) {
184
+ const fileName = e.target.files[0]?.name || 'Файл не выбран';
185
+ const selectedFileName = document.getElementById('selected-file-name');
186
+ const fileInfo = document.querySelector('.file-info');
187
+
188
+ if (selectedFileName) {
189
+ selectedFileName.textContent = fileName;
190
+ }
191
+ if (fileInfo) {
192
+ fileInfo.style.display = 'flex';
193
+ }
194
+ });
195
+ }
196
+ </script>
197
+
198
+ <style>
199
+ .edit-profile-btn {
200
+ display: inline-flex;
201
+ align-items: center;
202
+ font-size: 0.85rem;
203
+ padding: 5px 10px;
204
+ border-radius: 5px;
205
+ }
206
+
207
+ .edit-profile-btn i {
208
+ font-size: 0.9rem;
209
+ margin-right: 6px;
210
+ }
211
+
212
+ .avatar-img {
213
+ width: 100px;
214
+ height: 100px;
215
+ border-radius: 10%;
216
+ object-fit: cover;
217
+ }
218
+ .detailed-analysis {
219
+ margin-top: 10px;
220
+ padding: 10px;
221
+ background: rgba(255,255,255,0.05);
222
+ border-radius: 8px;
223
+ }
224
+
225
+ .detailed-analysis-item {
226
+ display: flex;
227
+ justify-content: space-between;
228
+ margin-bottom: 5px;
229
+ }
230
+
231
+ .emotion-bar {
232
+ height: 8px;
233
+ background: rgba(255,255,255,0.1);
234
+ border-radius: 4px;
235
+ margin-top: 3px;
236
+ }
237
+
238
+ .emotion-fill {
239
+ height: 100%;
240
+ border-radius: 4px;
241
+ }
242
+
243
+ .advice-box {
244
+ margin-top: 10px;
245
+ padding: 10px;
246
+ background: rgba(74, 74, 232, 0.1);
247
+ border-left: 3px solid var(--primary-color);
248
+ font-style: italic;
249
+ }
250
+
251
+ .pagination {
252
+ display: flex;
253
+ justify-content: center;
254
+ gap: 8px;
255
+ margin-top: 20px;
256
+ flex-wrap: wrap;
257
+ }
258
+ .pagination-btn {
259
+ padding: 8px 12px;
260
+ border-radius: 4px;
261
+ background: #3c3c5a;
262
+ color: white;
263
+ text-decoration: none;
264
+ transition: 0.2s;
265
+ }
266
+ .pagination-btn:hover:not(.disabled):not(.active) {
267
+ background: #5a5a7e;
268
+ }
269
+ .pagination-btn.active {
270
+ background: #6c63ff;
271
+ font-weight: bold;
272
+ color: #fff;
273
+ }
274
+ .pagination-btn.disabled {
275
+ opacity: 0.5;
276
+ pointer-events: none;
277
+ }
278
+
279
+ /* В секции style в profile.html */
280
+ .chat-controls {
281
+ margin: 15px 0;
282
+ display: flex;
283
+ gap: 10px;
284
+ }
285
+
286
+ .btn-danger {
287
+ background-color: #d63031;
288
+ color: white;
289
+ border: none;
290
+ padding: 8px 15px;
291
+ border-radius: 4px;
292
+ cursor: pointer;
293
+ transition: background-color 0.2s;
294
+ }
295
+
296
+ .btn-danger:hover {
297
+ background-color: #ff4757;
298
+ }
299
+
300
+ .report-emotion.anger {
301
+ color: #d63031; /* Красный для злости */
302
+ }
303
+ .report-emotion.joy {
304
+ color: #00b894; /* Зеленый для радости */
305
+ }
306
+ .report-emotion.sadness {
307
+ color: #0984e3; /* Синий для грусти */
308
+ }
309
+ .report-emotion.surprise {
310
+ color: #fdcb6e; /* Желтый для удивления */
311
+ }
312
+ .report-emotion.fear {
313
+ color: #a29bfe; /* Фиолетовый для страха */
314
+ }
315
+ .report-emotion.neutral {
316
+ color: #636e72; /* Серый для нейтрального */
317
+ }
318
+
319
+ .report-emotion.no_emotion {
320
+ color: #636e72; /* Серый цвет для нейтрального состояния */
321
+ }
322
+ </style>
323
+
324
  {% endblock %}
templates/welcome.html ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base_public.html" %}
2
+
3
+ {% block title %}EmotionAnalyzer — Анализ эмоций в тексте и голосе{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="main-container">
7
+
8
+ <!-- Hero-секция -->
9
+ <section class="hero">
10
+ <div class="hero-content">
11
+ <h1 class="hero-title">EmotionAnalyzer</h1>
12
+ <p class="hero-subtitle">
13
+ ИИ-платформа для глубокого анализа эмоционального состояния по тексту и голосу. Всё просто — введите сообщение или запишите аудио!
14
+ </p>
15
+ <div class="cta-buttons">
16
+ {% if not current_user.is_authenticated %}
17
+ <a href="{{ url_for('auth_bp.register') }}" class="btn-primary">Начать бесплатно</a>
18
+ <a href="{{ url_for('auth_bp.login') }}" class="btn-secondary">Войти</a>
19
+ {% else %}
20
+ <a href="{{ url_for('index') }}" class="btn-primary">Перейти к анализу</a>
21
+ {% endif %}
22
+ </div>
23
+ </div>
24
+ </section>
25
+
26
+ <!-- Особенности -->
27
+ <section class="features">
28
+ <h2 class="section-title">Что умеет EmotionAnalyzer</h2>
29
+ <div class="features-grid">
30
+
31
+ <!-- Текстовый анализ -->
32
+ <div class="feature-card">
33
+ <i class="fas fa-comment-dots feature-icon text"></i>
34
+ <h3>Эмоции в тексте</h3>
35
+ <p>Анализируйте эмоции в сообщениях, письмах, соцсетях. Распознаём: радость, грусть, страх, злобу, удивление, отвращение.</p>
36
+ </div>
37
+
38
+ <!-- Голосовой анализ -->
39
+ <div class="feature-card">
40
+ <i class="fas fa-microphone feature-icon voice"></i>
41
+ <h3>Голос и интонация</h3>
42
+ <p>Анализ эмоций по аудиозаписям: выявление интонационных паттернов и эмоционального фона по голосу.</p>
43
+ </div>
44
+
45
+ <!-- Визуализация -->
46
+ <div class="feature-card">
47
+ <i class="fas fa-chart-line feature-icon chart"></i>
48
+ <h3>Графики и отчёты</h3>
49
+ <p>Просматривайте результаты в виде диаграмм, сравнивайте эмоции в динамике, экспортируйте PDF-отчёты.</p>
50
+ </div>
51
+
52
+ <!-- Пользовательский опыт -->
53
+ <div class="feature-card">
54
+ <i class="fas fa-brain feature-icon ai"></i>
55
+ <h3>ИИ с обучением</h3>
56
+ <p>Нейросеть адаптируется под ваши данные и стиль общения для ещё более точного анализа эмоций.</p>
57
+ </div>
58
+ </div>
59
+ </section>
60
+
61
+ <!-- Ценности -->
62
+ <section class="values">
63
+ <h2 class="section-title">Почему выбирают нас</h2>
64
+ <div class="advantages-grid">
65
+ <div class="advantage-card">
66
+ <i class="fas fa-lock icon-primary"></i>
67
+ <h4>Конфиденциальность</h4>
68
+ <p>Все данные защищены. Ничего не хранится без вашего разрешения.</p>
69
+ </div>
70
+ <div class="advantage-card">
71
+ <i class="fas fa-rocket icon-success"></i>
72
+ <h4>Быстрота</h4>
73
+ <p>Результаты анализа — уже через 2 секунды после отправки.</p>
74
+ </div>
75
+ <div class="advantage-card">
76
+ <i class="fas fa-globe icon-info"></i>
77
+ <h4>Доступ отовсюду</h4>
78
+ <p>Работает на телефоне, планшете, ноутбуке. Без установки.</p>
79
+ </div>
80
+ </div>
81
+ </section>
82
+
83
+ <!-- CTA -->
84
+ <section class="cta-final">
85
+ <div class="cta-final-content">
86
+ <h2>Попробуйте EmotionAnalyzer прямо сейчас</h2>
87
+ <p>Быстрый, точный и удобный анализ эмоций в одном клике</p>
88
+ {% if not current_user.is_authenticated %}
89
+ <a href="{{ url_for('auth_bp.register') }}" class="btn-primary btn-lg">Создать аккаунт</a>
90
+ {% else %}
91
+ <a href="{{ url_for('index') }}" class="btn-primary btn-lg">Начать анализ</a>
92
+ {% endif %}
93
+ </div>
94
+ </section>
95
+
96
+ </div>
97
+ {% endblock %}