Spaces:
Running
Running
| from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request, current_app | |
| from sqlalchemy import extract, String # String ์ถ๊ฐ | |
| import datetime | |
| import time | |
| from . import db | |
| from .models import Diary, User | |
| from .emotion_engine import predict_emotion | |
| from .recommender import Recommender | |
| import logging | |
| import os | |
| import google.generativeai as genai | |
| bp = Blueprint('main', __name__) | |
| recommender = Recommender() | |
| # --- Gemini API ์ค์ --- | |
| try: | |
| api_key = os.environ.get('GEMINI_API_KEY') | |
| if not api_key: | |
| logging.warning("๐ฅ๐ฅ๐ฅ GEMINI_API_KEY ํ๊ฒฝ ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. ๐ฅ๐ฅ๐ฅ") | |
| genai.configure(api_key=api_key) | |
| except Exception as e: | |
| logging.error(f"๐ฅ๐ฅ๐ฅ Gemini API ์ค์ ์ค ์ค๋ฅ ๋ฐ์: {e} ๐ฅ๐ฅ๐ฅ") | |
| # ๊ฐ์ ๋ณ ์ด๋ชจ์ง ๋งต | |
| emotion_emoji_map = { | |
| '๋ถ๋ ธ': '๐ ', '๋ถ์': '๐', '์ฌํ': '๐ข', | |
| '๋นํฉ': '๐ฎ', '๊ธฐ์จ': '๐', '์์ฒ': '๐', | |
| } | |
| default_recommendations = { | |
| '๋ถ๋ ธ': 'ํ๊ฐ ๋ ๋๋ ์ ๋๋ ์์ ์ ๋ฃ๊ฑฐ๋, ๊ฐ๋ฒผ์ด ์ฝ๋ฏธ๋ ์ํ๋ฅผ ๋ณด๋ฉฐ ๊ธฐ๋ถ์ ์ ํํด ๋ณด์ธ์.', | |
| '๋ถ์': '๋ถ์ํ ๋๋ ์ฐจ๋ถํ ํด๋์ ์์ ์ ๋ฃ๊ฑฐ๋, ๋ฐ๋ปํ ์ฐจ๋ฅผ ๋ง์๋ฉฐ ๋ช ์์ ํด๋ณด๋ ๊ฑด ์ด๋จ๊น์?', | |
| '์ฌํ': '์ฌํ ๋๋ ์๋ก๊ฐ ๋๋ ์ํ๋ ์ฑ ์ ๋ณด๋ฉฐ ๊ฐ์ ์ ์ถฉ๋ถํ ๋๊ปด๋ณด๋ ๊ฒ๋ ์ข์์. ํน์ ์น๊ตฌ์ ๋ํ๋ฅผ ๋๋ ๋ณด์ธ์.', | |
| '๋นํฉ': '๋นํฉ์ค๋ฌ์ธ ๋๋ ์ ์ ์จ์ ๊ณ ๋ฅด๊ณ , ์ข์ํ๋ ์์ ์ ๋ค์ผ๋ฉฐ ๋ง์์ ์ง์ ์์ผ ๋ณด์ธ์.', | |
| '๊ธฐ์จ': '๊ธฐ์ ๋๋ ์ ๋๋ ๋์ค ์์ ๊ณผ ํจ๊ป ์ถค์ ์ถ๊ฑฐ๋, ์น๊ตฌ๋ค๊ณผ ๋ง๋ ์ฆ๊ฑฐ์์ ๋๋ ๋ณด์ธ์!', | |
| '์์ฒ': '๋ง์์ ์์ฒ๋ฅผ ๋ฐ์์ ๋๋, ์๋ก๊ฐ ๋๋ ์์ ์ ๋ฃ๊ฑฐ๋, ์กฐ์ฉํ ๊ณณ์์ ์ฑ ์ ์ฝ์ผ๋ฉฐ ๋ง์์ ๋ฌ๋๋ณด์ธ์.' | |
| } | |
| def generate_recommendation(user_diary, predicted_emotion): | |
| """ | |
| ์ฃผ์ด์ง ์ผ๊ธฐ ๋ด์ฉ๊ณผ ๊ฐ์ ์ ๋ฐํ์ผ๋ก Gemini API๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธํ์ํ ์ถ์ฒ์ ์์ฑํฉ๋๋ค. | |
| """ | |
| start_time = time.time() | |
| logging.info("Gemini API ํธ์ถ ์์...") | |
| try: | |
| model = genai.GenerativeModel('gemini-flash-latest') | |
| prompt = f""" | |
| ์ฌ์ฉ์์ ์ผ๊ธฐ ๋ด์ฉ๊ณผ ๊ฐ์ ์ ๋ฐํ์ผ๋ก ๋ฌธํ์ํ์ ์ถ์ฒํด์ค. | |
| ์ฌ์ฉ์๋ ํ์ฌ '{predicted_emotion}' ๊ฐ์ ์ ๋๋ผ๊ณ ์์ด. | |
| ์ผ๊ธฐ ๋ด์ฉ: | |
| --- | |
| {user_diary} | |
| --- | |
| ์๋ ๋ ๊ฐ์ง ์๋๋ฆฌ์ค์ ๋ง์ถฐ ์ํ, ์์ , ๋์๋ง ์ถ์ฒํด์ค. | |
| ๊ฐ ์ถ์ฒ ํญ๋ชฉ์ "์ข ๋ฅ: ์ถ์ฒ ์ฝํ ์ธ ์ ๋ชฉ (์ํฐ์คํธ/๊ฐ๋ /์๊ฐ ๋ฑ)" ํ์์ผ๋ก ์์ฑํ๊ณ , ๊ฐ๋จํ ์ถ์ฒ ์ด์ ๋ฅผ ๋ง๋ถ์ฌ์ค. | |
| ๊ฒฐ๊ณผ๋ Markdown ํ์์ผ๋ก ๋ณด๊ธฐ ์ข๊ฒ ์ ๋ฆฌํด์ค. | |
| ## [์์ฉ] | |
| ํ์ฌ ๊ฐ์ ์ ๋ ๊น์ด ๋๋ผ๊ฑฐ๋ ์๋ก๋ฐ๊ณ ์ถ์ ๋. | |
| ## [์ ํ] | |
| ํ์ฌ ๊ฐ์ ์์ ๋ฒ์ด๋ ์๋ก์ด ํ๋ ฅ์ ์ป๊ณ ์ถ์ ๋. | |
| """ | |
| response = model.generate_content(prompt) | |
| end_time = time.time() | |
| logging.info(f"Gemini API ํธ์ถ ์๋ฃ. ์์ ์๊ฐ: {end_time - start_time:.2f}์ด") | |
| return response.text | |
| except Exception as e: | |
| logging.error(f"๐ฅ๐ฅ๐ฅ Gemini API ํธ์ถ ์ค ์ค๋ฅ ๋ฐ์: {e} ๐ฅ๐ฅ๐ฅ") | |
| return default_recommendations.get(predicted_emotion, "์ค๋์ ์ข์ํ๋ ์์ ์ ๋ค์ผ๋ฉฐ ํธ์ํ ํ๋ฃจ๋ฅผ ๋ณด๋ด๋ ๊ฑด ์ด๋ ์ธ์?") | |
| def home(): | |
| if 'user_id' not in session: | |
| return redirect(url_for('auth.login')) | |
| logged_in = 'user_id' in session | |
| display_name = None | |
| if logged_in: | |
| user_id = session.get('user_id') | |
| user = User.query.get(user_id) | |
| if user: | |
| display_name = user.nickname if user.nickname else user.username | |
| else: | |
| display_name = session.get('username') # Fallback if user not found | |
| logging.info(f"๋ฉ์ธ ํ์ด์ง ์ ์: ๋ก๊ทธ์ธ ์ํ: {logged_in}, ์ฌ์ฉ์: {display_name}") | |
| return render_template("main.html", logged_in=logged_in, display_name=display_name) | |
| def api_predict(): | |
| if 'user_id' not in session: | |
| return jsonify({"error": "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค."}), 401 | |
| user_diary = request.json.get("diary") | |
| if not user_diary: | |
| return jsonify({"error": "์ผ๊ธฐ ๋ด์ฉ์ด ์์ต๋๋ค."}), 400 | |
| try: | |
| # 1. Predict top 3 emotions | |
| emotion_results = predict_emotion(user_diary, top_k=3) | |
| if not emotion_results: | |
| logging.error("[/api/predict] ๊ฐ์ ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.") | |
| return jsonify({"error": "๊ฐ์ ์ ๋ถ์ํ ์ ์์ต๋๋ค."}), 500 | |
| # 2. Process results | |
| top_emotion_data = emotion_results[0] | |
| top_emotion_label = top_emotion_data['label'] | |
| top_emotion_score = top_emotion_data['score'] | |
| # 3. Create candidates list | |
| candidates = [] | |
| for result in emotion_results: | |
| emotion_label = result['label'] | |
| candidates.append({ | |
| 'emotion': emotion_label, | |
| 'score': result['score'], | |
| 'emoji': emotion_emoji_map.get(emotion_label, '๐ค') | |
| }) | |
| # 4. Generate recommendation ONLY for the top emotion initially | |
| recommendation_text = generate_recommendation(user_diary, top_emotion_label) | |
| # 5. Return the new structure | |
| # Note: Diary is NOT saved here. It will be saved via a separate '/diary/save' call later. | |
| return jsonify({ | |
| "top_emotion": top_emotion_label, | |
| "top_score": top_emotion_score, | |
| "candidates": candidates, | |
| "recommendation": recommendation_text | |
| }) | |
| except Exception as e: | |
| logging.error(f"[/api/predict] ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {e}") | |
| db.session.rollback() # ํน์ ๋ชจ๋ฅผ ํธ๋์ญ์ ๋กค๋ฐฑ | |
| return jsonify({"error": "์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."}), 500 | |
| def api_recommend(): | |
| logging.info("[/api/recommend] ์์ฒญ ์์ ๋จ.") | |
| user_diary = request.json.get("diary") | |
| predicted_emotion = request.json.get("emotion") # ๊ฐ์ ์ ์ง์ ๋ฐ์ | |
| if not user_diary or not predicted_emotion: | |
| logging.warning("[/api/recommend] ์ผ๊ธฐ ๋ด์ฉ ๋๋ ๊ฐ์ ์ด ์์ต๋๋ค.") | |
| return jsonify({"error": "์ผ๊ธฐ ๋ด์ฉ ๋๋ ๊ฐ์ ์ด ์์ต๋๋ค."}), 400 | |
| recommendation_text = generate_recommendation(user_diary, predicted_emotion) | |
| response_data = { | |
| "emotion": predicted_emotion, | |
| "emoji": emotion_emoji_map.get(predicted_emotion, '๐ค'), | |
| "recommendation": recommendation_text | |
| } | |
| return jsonify(response_data) | |
| def api_diaries(): | |
| if 'user_id' not in session: | |
| return jsonify({"error": "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค."}), 401 | |
| user_id = session['user_id'] | |
| year = request.args.get('year', type=int) | |
| month = request.args.get('month', type=int) | |
| logging.info(f"API ์์ฒญ: user_id={user_id}, year={year}, month={month}") | |
| if not year or not month: | |
| today = datetime.date.today() | |
| year = today.year | |
| month = today.month | |
| start_date = datetime.date(year, month, 1) | |
| if month == 12: | |
| end_date = datetime.date(year + 1, 1, 1) | |
| else: | |
| end_date = datetime.date(year, month + 1, 1) | |
| logging.info(f"DB ์ฟผ๋ฆฌ ๋ฒ์: {start_date} <= created_at < {end_date}") | |
| user_diaries = Diary.query.filter( | |
| Diary.user_id == user_id, | |
| Diary.created_at >= start_date, | |
| Diary.created_at < end_date | |
| ).order_by(Diary.created_at.asc()).all() | |
| diaries_data = [] | |
| utc_tz = datetime.timezone.utc | |
| kst_tz = datetime.timezone(datetime.timedelta(hours=9)) | |
| for diary in user_diaries: | |
| created_at_utc = diary.created_at | |
| # ํ์์กด ์ ๋ณด๊ฐ ์๋ ๊ฒฝ์ฐ UTC๋ก ๊ฐ์ฃผ | |
| if created_at_utc.tzinfo is None: | |
| created_at_utc = created_at_utc.replace(tzinfo=utc_tz) | |
| created_at_kst = created_at_utc.astimezone(kst_tz) | |
| diaries_data.append({ | |
| "id": diary.id, | |
| "date": created_at_kst.strftime('%Y-%m-%d'), | |
| "createdAt": created_at_kst.strftime('%Y-%m-%d %H:%M:%S'), | |
| "content": diary.content, | |
| "emotion": diary.emotion, | |
| "recommendation": diary.recommendation | |
| }) | |
| return jsonify(diaries_data) | |
| def api_diaries_counts(): | |
| if 'user_id' not in session: | |
| return jsonify({"error": "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค."}), 401 | |
| user_id = session['user_id'] | |
| year = request.args.get('year', type=int) | |
| if not year: | |
| year = datetime.date.today().year | |
| # PostgreSQL์ extract๋ฅผ ์ง์ํ๋ฏ๋ก ์๋ ๋ก์ง์ผ๋ก ๋ณต์ | |
| counts = db.session.query( | |
| extract('month', Diary.created_at).cast(String), # ์์ ๋ฌธ์์ด๋ก ์บ์คํ | |
| db.func.count(Diary.id) | |
| ).filter( | |
| Diary.user_id == user_id, | |
| extract('year', Diary.created_at) == year | |
| ).group_by( | |
| extract('month', Diary.created_at) | |
| ).all() | |
| counts_dict = {month: count for month, count in counts} | |
| return jsonify(counts_dict) | |
| def my_diary(): | |
| if 'user_id' not in session: | |
| return redirect(url_for('auth.login')) | |
| user_id = session.get('user_id') | |
| user = User.query.get(user_id) | |
| display_name = user.nickname if user.nickname else user.username | |
| return render_template('diary.html', display_name=display_name) | |
| def mypage(): | |
| if 'user_id' not in session: | |
| return redirect(url_for('auth.login')) | |
| user_id = session['user_id'] | |
| user = User.query.get(user_id) | |
| user_info = { | |
| 'username': user.username, | |
| 'nickname': user.nickname, | |
| 'display_name': user.nickname if user.nickname else user.username | |
| } | |
| return render_template('page.html', user_info=user_info) | |
| def update_nickname(): | |
| if 'user_id' not in session: | |
| return redirect(url_for('auth.login')) | |
| user_id = session['user_id'] | |
| user = User.query.get(user_id) | |
| new_nickname = request.form.get('nickname') | |
| # ๋๋ค์์ด ๋น์ด์๊ฑฐ๋, ๊ณต๋ฐฑ๋ง ์์ ๊ฒฝ์ฐ None์ผ๋ก ์ ์ฅ | |
| if not new_nickname or not new_nickname.strip(): | |
| user.nickname = None | |
| else: | |
| user.nickname = new_nickname | |
| db.session.commit() | |
| # ์ธ์ ์ ๋ณด ์ ๋ฐ์ดํธ (์ ํ ์ฌํญ, ๋๋ค์์ ์ธ์ ์ ์ ์ฅํ ๊ฒฝ์ฐ) | |
| # session['nickname'] = user.nickname | |
| return redirect(url_for('main.mypage')) | |
| def diary_save(): | |
| if 'user_id' not in session: | |
| return jsonify({"error": "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค."}), 401 | |
| user_id = session['user_id'] | |
| diary_content = request.form.get('diary') | |
| predicted_emotion = request.form.get('emotion') | |
| if not diary_content or not predicted_emotion: | |
| return jsonify({"error": "์ผ๊ธฐ ๋ด์ฉ์ด๋ ๊ฐ์ ์ด ์์ต๋๋ค."}), 400 | |
| try: | |
| # ์ถ์ฒ ์์ฑ | |
| recommendation_text = generate_recommendation(diary_content, predicted_emotion) | |
| # Gemini API ์คํจ ์ Recommender ํด๋์ค๋ก ๋์ฒด | |
| if recommendation_text is None: | |
| logging.info("Gemini ์ถ์ฒ ์คํจ. Recommender ํด๋์ค๋ก ๋์ฒดํฉ๋๋ค.") | |
| su_yoong_recs = recommender.recommend(predicted_emotion, '์์ฉ') | |
| jeon_hwan_recs = recommender.recommend(predicted_emotion, '์ ํ') | |
| # diary_logic.js๊ฐ ํ์ฑํ ์ ์๋ ํ์์ผ๋ก ๋ง๋ญ๋๋ค. | |
| recommendation_text = f"## [์์ฉ]\n" | |
| for rec in su_yoong_recs: | |
| recommendation_text += f"* {rec}\n" | |
| recommendation_text += f"\n## [์ ํ]\n" | |
| for rec in jeon_hwan_recs: | |
| recommendation_text += f"* {rec}\n" | |
| # ์ผ๊ธฐ ์ ์ฅ | |
| new_diary = Diary( | |
| content=diary_content, | |
| emotion=predicted_emotion, | |
| recommendation=recommendation_text, | |
| user_id=user_id | |
| ) | |
| db.session.add(new_diary) | |
| db.session.commit() | |
| return jsonify({ | |
| "success": "์ผ๊ธฐ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋์์ต๋๋ค.", | |
| "recommendation": recommendation_text # ํด๋ผ์ด์ธํธ์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋๋ก ์ถ์ฒ ๋ด์ฉ ๋ฐํ | |
| }), 200 | |
| except Exception as e: | |
| db.session.rollback() | |
| logging.error(f"์ผ๊ธฐ ์ ์ฅ ์ค ์ค๋ฅ ๋ฐ์: {e}") | |
| return jsonify({"error": "์ผ๊ธฐ ์ ์ฅ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."}), 500 | |
| def delete_diary(diary_id): | |
| if 'user_id' not in session: | |
| return jsonify({"error": "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค."}), 401 | |
| diary_to_delete = Diary.query.get(diary_id) | |
| if not diary_to_delete: | |
| return jsonify({"error": "์ผ๊ธฐ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."}), 404 | |
| if diary_to_delete.user_id != session['user_id']: | |
| return jsonify({"error": "์ญ์ ๊ถํ์ด ์์ต๋๋ค."}), 403 | |
| try: | |
| db.session.delete(diary_to_delete) | |
| db.session.commit() | |
| return jsonify({"success": "์ผ๊ธฐ๊ฐ ์ญ์ ๋์์ต๋๋ค."}), 200 | |
| except Exception as e: | |
| db.session.rollback() | |
| return jsonify({"error": "์ญ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."}), 500 | |
| def test_animation(): | |
| return render_template('test_animation.html', display_name='ํ ์คํธ') |