anggars commited on
Commit
c7e5db4
·
1 Parent(s): 9dd7ae0

setup sentimind

Browse files
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.pyc
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile untuk Hugging Face Spaces
2
+ # Build dan jalankan backend FastAPI saja
3
+
4
+ FROM python:3.10-slim
5
+
6
+ # Create non-root user (required by HF Spaces)
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+ ENV PATH="/home/user/.local/bin:$PATH"
10
+
11
+ WORKDIR /app
12
+
13
+ # Copy requirements dan install dependencies
14
+ COPY --chown=user api/requirements.txt requirements.txt
15
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
16
+
17
+ # Copy folder api ke dalam container
18
+ COPY --chown=user api/ ./api/
19
+
20
+ # Expose port 7860 (default HF Spaces)
21
+ EXPOSE 7860
22
+
23
+ # Jalankan uvicorn dengan path module yang benar
24
+ CMD ["uvicorn", "api.index:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,12 +1,27 @@
1
  ---
2
- title: Sentimind Api
3
- emoji: 🏆
4
- colorFrom: gray
5
- colorTo: red
6
  sdk: docker
 
7
  pinned: false
8
- license: mit
9
- short_description: Backend API for Sentimind
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Sentimind API
3
+ emoji: 🧠
4
+ colorFrom: orange
5
+ colorTo: yellow
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
 
 
9
  ---
10
 
11
+ # Sentimind API Backend
12
+
13
+ Backend API untuk Sentimind - AI Personality Profiler.
14
+
15
+ ## Endpoints
16
+
17
+ - `POST /api/predict` - Prediksi MBTI dari teks
18
+ - `POST /api/chat` - Chat dengan AI assistant
19
+ - `GET /api/quiz` - Get quiz questions
20
+ - `POST /api/quiz` - Submit quiz answers
21
+ - `GET /api/youtube/{video_id}` - Analyze YouTube video
22
+
23
+ ## Environment Variables
24
+
25
+ Set these in HF Spaces Settings > Repository Secrets:
26
+ - `GOOGLE_API_KEY` - Gemini API key
27
+ - `YOUTUBE_API_KEY` - YouTube Data API key
api/core/__init__.py ADDED
File without changes
api/core/chatbot.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/core/chatbot.py
2
+ import os
3
+ import google.generativeai as genai
4
+
5
+ class MBTIChatbot:
6
+ def __init__(self):
7
+ print("🚀 Initializing MBTI Chatbot (Lite Version)...")
8
+
9
+ # 1. Setup Google Gemini
10
+ api_key = os.getenv("GEMINI_API_KEY")
11
+ if not api_key:
12
+ print("⚠️ WARNING: GEMINI_API_KEY not found in .env.")
13
+ else:
14
+ genai.configure(api_key=api_key)
15
+
16
+ try:
17
+ # Pake Gemini 2.0 Flash (Standard)
18
+ self.model = genai.GenerativeModel('gemini-2.0-flash')
19
+ except Exception:
20
+ print("⚠️ 2.0 Flash failed, fallback to Lite")
21
+ self.model = genai.GenerativeModel('gemini-2.0-flash-lite')
22
+
23
+ def generate_response(self, user_query, lang="en"):
24
+ # Versi Lite: Gak pake RAG (Database lokal), langsung pake knowledge LLM yang luas.
25
+
26
+ lang_instruction = "Answer in English." if lang == "en" else "Jawab dalam Bahasa Indonesia gaul (Slang Jakarta/Lo-Gue), maskulin, santai, dan to the point. Panggil user 'bro' atau 'bre'. JANGAN panggil 'bestie', 'kak', atau 'gan'. Gaya bicara tongkrongan cowok tapi tetap edukatif soal MBTI."
27
+
28
+ system_prompt = f"""
29
+ You are Sentimind AI, an expert in MBTI personality types and mental health.
30
+ {lang_instruction}
31
+
32
+ USER QUERY:
33
+ {user_query}
34
+
35
+ INSTRUCTIONS:
36
+ - Answer directly based on your extensive knowledge about MBTI and Psychology.
37
+ - Be empathetic, insightful, and use formatting (bullet points) if helpful.
38
+ - Keep answers concise (under 200 words) unless asked for details.
39
+ """
40
+ try:
41
+ response = self.model.generate_content(system_prompt)
42
+ return response.text
43
+ except Exception as e:
44
+ return f"Maaf, saya sedang mengalami gangguan koneksi ke otak AI saya. (Error: {str(e)})"
api/core/nlp_handler.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import os
3
+ import re
4
+ import requests
5
+ import numpy as np
6
+ import html
7
+ from deep_translator import GoogleTranslator
8
+ from youtube_transcript_api import YouTubeTranscriptApi
9
+
10
+ # --- CONFIG PATH ---
11
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12
+ MBTI_PATH = os.path.join(BASE_DIR, 'data', 'model_mbti.pkl')
13
+ EMOTION_PATH = os.path.join(BASE_DIR, 'data', 'model_emotion.pkl')
14
+
15
+ _model_mbti = None
16
+ _model_emotion = None
17
+
18
+ EMOTION_TRANSLATIONS = {
19
+ 'admiration': 'Kagum', 'amusement': 'Terhibur', 'anger': 'Marah',
20
+ 'annoyance': 'Kesal', 'approval': 'Setuju', 'caring': 'Peduli',
21
+ 'confusion': 'Bingung', 'curiosity': 'Penasaran', 'desire': 'Keinginan',
22
+ 'disappointment': 'Kecewa', 'disapproval': 'Tidak Setuju', 'disgust': 'Jijik',
23
+ 'embarrassment': 'Malu', 'excitement': 'Semangat', 'fear': 'Takut',
24
+ 'gratitude': 'Bersyukur', 'grief': 'Berduka', 'joy': 'Gembira',
25
+ 'love': 'Cinta', 'nervousness': 'Gugup', 'optimism': 'Optimis',
26
+ 'pride': 'Bangga', 'realization': 'Sadar', 'relief': 'Lega',
27
+ 'remorse': 'Menyesal', 'sadness': 'Sedih', 'surprise': 'Terkejut',
28
+ 'neutral': 'Netral'
29
+ }
30
+
31
+ class NLPHandler:
32
+ @staticmethod
33
+ def load_models():
34
+ global _model_mbti, _model_emotion
35
+ if _model_mbti is None and os.path.exists(MBTI_PATH):
36
+ try: _model_mbti = joblib.load(MBTI_PATH)
37
+ except: pass
38
+ if _model_emotion is None and os.path.exists(EMOTION_PATH):
39
+ try: _model_emotion = joblib.load(EMOTION_PATH)
40
+ except: pass
41
+
42
+ @staticmethod
43
+ def translate_to_english(text):
44
+ try:
45
+ if len(text) > 4500: text = text[:4500]
46
+ return GoogleTranslator(source='auto', target='en').translate(text)
47
+ except: return text
48
+
49
+ @staticmethod
50
+ def extract_keywords(text):
51
+ stopwords = ["the", "and", "is", "to", "in", "it", "of", "for", "with", "on", "that", "this", "my", "was", "as", "are", "have", "you", "but", "so", "ini", "itu", "dan", "yang", "di", "ke"]
52
+ words = re.findall(r'\w+', text.lower())
53
+ filtered = [w for w in words if len(w) > 3 and w not in stopwords]
54
+ freq = {}
55
+ for w in filtered: freq[w] = freq.get(w, 0) + 1
56
+ sorted_words = sorted(freq.items(), key=lambda x: x[1], reverse=True)
57
+
58
+ keywords_en = [w[0] for w in sorted_words[:5]]
59
+ keywords_id = []
60
+ try:
61
+ translator = GoogleTranslator(source='auto', target='id')
62
+ for k in keywords_en: keywords_id.append(translator.translate(k))
63
+ except: keywords_id = keywords_en
64
+ return {"en": keywords_en, "id": keywords_id}
65
+
66
+ @staticmethod
67
+ def predict_all(raw_text):
68
+ NLPHandler.load_models()
69
+ processed_text = NLPHandler.translate_to_english(raw_text)
70
+
71
+ mbti_result = "UNKNOWN"
72
+ if _model_mbti:
73
+ try: mbti_result = _model_mbti.predict([processed_text])[0]
74
+ except: pass
75
+
76
+ emotion_data = {"id": "Kompleks", "en": "Complex", "raw": "unknown"}
77
+ if _model_emotion:
78
+ try:
79
+ pred_label = "neutral"
80
+ if hasattr(_model_emotion, "predict_proba"):
81
+ probs = _model_emotion.predict_proba([processed_text])[0]
82
+ classes = _model_emotion.classes_
83
+ neutral_indices = [i for i, c in enumerate(classes) if c.lower() == 'neutral']
84
+ if neutral_indices:
85
+ idx = neutral_indices[0]
86
+ if probs[idx] < 0.65: probs[idx] = 0.0
87
+ if np.sum(probs) > 0:
88
+ best_idx = np.argmax(probs)
89
+ pred_label = classes[best_idx]
90
+ else:
91
+ pred_label = _model_emotion.predict([processed_text])[0]
92
+ else:
93
+ pred_label = _model_emotion.predict([processed_text])[0]
94
+
95
+ indo_label = EMOTION_TRANSLATIONS.get(pred_label, pred_label.capitalize())
96
+ emotion_data = {"id": indo_label, "en": pred_label.capitalize(), "raw": pred_label}
97
+ except: pass
98
+
99
+ return {
100
+ "mbti": mbti_result,
101
+ "emotion": emotion_data,
102
+ "keywords": NLPHandler.extract_keywords(processed_text)
103
+ }
104
+
105
+ # --- JALUR RESMI: YOUTUBE DATA API ---
106
+ @staticmethod
107
+ def _fetch_official_api(video_id, api_key):
108
+ print(f"🔑 Using Official API Key for {video_id}...")
109
+ text_parts = []
110
+
111
+ try:
112
+ # 1. Ambil Metadata
113
+ url_meta = f"https://www.googleapis.com/youtube/v3/videos?part=snippet&id={video_id}&key={api_key}"
114
+ res_meta = requests.get(url_meta, timeout=5)
115
+
116
+ if res_meta.status_code == 200:
117
+ data = res_meta.json()
118
+ if "items" in data and len(data["items"]) > 0:
119
+ snippet = data["items"][0]["snippet"]
120
+ # Unescape biar &quot; jadi " dan &#39; jadi '
121
+ title = html.unescape(snippet['title'])
122
+ desc = html.unescape(snippet['description'])
123
+ text_parts.append(f"Title: {title}")
124
+ text_parts.append(f"Description: {desc}")
125
+
126
+ # 2. Ambil Komentar
127
+ url_comm = f"https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&videoId={video_id}&maxResults=30&order=relevance&key={api_key}"
128
+ res_comm = requests.get(url_comm, timeout=5)
129
+
130
+ if res_comm.status_code == 200:
131
+ data = res_comm.json()
132
+ comments = []
133
+ for item in data.get("items", []):
134
+ raw_comm = item["snippet"]["topLevelComment"]["snippet"]["textDisplay"]
135
+ # Bersihkan tag HTML <b> <br>
136
+ clean_comm = re.sub(r'<[^>]+>', '', raw_comm)
137
+ # Bersihkan entities &quot; &#39;
138
+ clean_comm = html.unescape(clean_comm)
139
+ comments.append(clean_comm)
140
+
141
+ if comments:
142
+ text_parts.append("\n\n--- Top Comments (Community Vibe) ---\n")
143
+ text_parts.extend(comments)
144
+
145
+ if not text_parts:
146
+ return None
147
+
148
+ return "\n\n".join(text_parts)
149
+
150
+ except Exception as e:
151
+ print(f"❌ Official API Error: {e}")
152
+ return None
153
+
154
+ @staticmethod
155
+ def fetch_youtube_transcript(video_id):
156
+ # 1. PRIORITAS UTAMA: Cek API Key
157
+ api_key = os.getenv("YOUTUBE_API_KEY")
158
+
159
+ if api_key:
160
+ official_data = NLPHandler._fetch_official_api(video_id, api_key)
161
+ if official_data:
162
+ return official_data
163
+
164
+ # 2. PRIORITAS KEDUA: Fallback Scraping
165
+ print(f"🎬 Fetching transcript (fallback) for: {video_id}")
166
+ try:
167
+ transcript_list = YouTubeTranscriptApi.get_transcript(video_id, languages=['id', 'en', 'en-US'])
168
+ full_text = " ".join([item['text'] for item in transcript_list])
169
+ clean_text = re.sub(r'\[.*?\]|\(.*?\)', '', full_text).strip()
170
+ # Unescape juga buat hasil scraping
171
+ return html.unescape(clean_text)
172
+ except Exception:
173
+ pass
174
+
175
+ return None
api/core/quiz_logic.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+
4
+ # --- CONFIG PATH ---
5
+ # Mengambil path folder "api"
6
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+ # Mengarah ke api/data/questions.json
8
+ DB_PATH = os.path.join(BASE_DIR, 'data', 'questions.json')
9
+
10
+ class QuizHandler:
11
+ @staticmethod
12
+ def get_questions():
13
+ """Mengambil semua soal dari database JSON"""
14
+ try:
15
+ if not os.path.exists(DB_PATH):
16
+ return []
17
+ with open(DB_PATH, 'r') as f:
18
+ return json.load(f)
19
+ except Exception as e:
20
+ print(f"Error reading quiz db: {e}")
21
+ return []
22
+
23
+ @staticmethod
24
+ def calculate_mbti(answers):
25
+ """
26
+ Hitung MBTI berdasarkan jawaban user.
27
+ Format answers: { "1": 2, "2": -1, ... } (Key=ID Soal, Value=Skala -3 s/d 3)
28
+ """
29
+ questions = QuizHandler.get_questions()
30
+ if not questions:
31
+ return "UNKNOWN"
32
+
33
+ # Skor Awal (Balance 0)
34
+ scores = {'EI': 0, 'SN': 0, 'TF': 0, 'JP': 0}
35
+
36
+ for q in questions:
37
+ q_id = str(q['id'])
38
+ if q_id in answers:
39
+ # Rumus: Nilai User (-3 s/d 3) * Arah Soal (1 atau -1)
40
+ # Contoh: Soal Introvert (Dir -1), User Jawab Sangat Setuju (3)
41
+ # Hitungan: 3 * -1 = -3 (Skor bergerak ke arah I)
42
+ val = int(answers[q_id])
43
+ scores[q['dimension']] += (val * q['direction'])
44
+
45
+ # Tentukan Hasil Akhir
46
+ # Positif = E, S, T, J
47
+ # Negatif = I, N, F, P
48
+ result = ""
49
+ result += "E" if scores['EI'] >= 0 else "I"
50
+ result += "S" if scores['SN'] >= 0 else "N"
51
+ result += "T" if scores['TF'] >= 0 else "F"
52
+ result += "J" if scores['JP'] >= 0 else "P"
53
+
54
+ return result
api/data/model_emotion.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8aedddc9609c31c78f5b2d169962e1bc97bfe228933986373a51df620e37f4a7
3
+ size 3145820
api/data/model_mbti.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:058d5de1e06f1c305e133eceb4a62a6c2b18a304fc16dd6866ef315eefe10b9a
3
+ size 2497720
api/data/questions.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "text_id": "Saya merasa lebih berenergi setelah bergaul dengan banyak orang.",
5
+ "text_en": "I feel more energized after socializing with a large group of people.",
6
+ "dimension": "EI",
7
+ "direction": 1
8
+ },
9
+ {
10
+ "id": 2,
11
+ "text_id": "Saya lebih suka fokus pada fakta nyata daripada ide abstrak.",
12
+ "text_en": "I prefer to focus on real facts rather than abstract ideas.",
13
+ "dimension": "SN",
14
+ "direction": 1
15
+ },
16
+ {
17
+ "id": 3,
18
+ "text_id": "Saya mengambil keputusan berdasarkan logika, bukan perasaan.",
19
+ "text_en": "I make decisions based on logic rather than feelings.",
20
+ "dimension": "TF",
21
+ "direction": 1
22
+ },
23
+ {
24
+ "id": 4,
25
+ "text_id": "Saya suka membuat rencana detail sebelum melakukan sesuatu.",
26
+ "text_en": "I like to have a detailed plan before doing anything.",
27
+ "dimension": "JP",
28
+ "direction": 1
29
+ },
30
+ {
31
+ "id": 5,
32
+ "text_id": "Saya sering merasa lelah jika harus bersosialisasi terlalu lama.",
33
+ "text_en": "I often feel drained if I have to socialize for too long.",
34
+ "dimension": "EI",
35
+ "direction": -1
36
+ },
37
+ {
38
+ "id": 6,
39
+ "text_id": "Saya sering membayangkan masa depan dan kemungkinan-kemungkinannya.",
40
+ "text_en": "I often imagine the future and its possibilities.",
41
+ "dimension": "SN",
42
+ "direction": -1
43
+ },
44
+ {
45
+ "id": 7,
46
+ "text_id": "Saya mudah tersentuh secara emosional oleh cerita orang lain.",
47
+ "text_en": "I am easily emotionally moved by other people's stories.",
48
+ "dimension": "TF",
49
+ "direction": -1
50
+ },
51
+ {
52
+ "id": 8,
53
+ "text_id": "Saya lebih suka bertindak spontan daripada mengikuti jadwal kaku.",
54
+ "text_en": "I prefer to be spontaneous rather than following a rigid schedule.",
55
+ "dimension": "JP",
56
+ "direction": -1
57
+ }
58
+ ]
api/index.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from dotenv import load_dotenv
3
+ from .core.nlp_handler import NLPHandler
4
+ import os
5
+
6
+ # Load environment variables dari file .env
7
+ load_dotenv()
8
+
9
+ from api.predict import predict_endpoint
10
+ from api.quiz import get_quiz_questions, submit_quiz
11
+ from api.core.chatbot import MBTIChatbot
12
+ from pydantic import BaseModel
13
+
14
+ # Init Chatbot (Load dataset sekali di awal)
15
+ chatbot = MBTIChatbot()
16
+
17
+ class ChatRequest(BaseModel):
18
+ message: str
19
+ lang: str = "id" # Default ke Indo kalo gak dikirim
20
+
21
+
22
+ from fastapi.middleware.cors import CORSMiddleware
23
+
24
+ app = FastAPI()
25
+
26
+ # Tambahkan CORS biar frontend (port 3000) bisa akses backend (port 8000)
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"], # Di produksi, ganti "*" dengan domain frontend lu
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ # --- TAMBAHAN DEBUGGING (CEK SAAT SERVER NYALA) ---
36
+
37
+ @app.on_event("startup")
38
+ async def startup_event():
39
+ api_key = os.getenv("YOUTUBE_API_KEY")
40
+ print("\n" + "="*40)
41
+ if api_key:
42
+ print(f"✅ API KEY DITEMUKAN: {api_key[:5]}...******")
43
+ print("🚀 Mode: OFFICIAL API (Anti-Blokir)")
44
+ else:
45
+ print("❌ API KEY TIDAK DITEMUKAN!")
46
+ print("⚠️ Mode: FALLBACK SCRAPING (Rawan Error)")
47
+ print("="*40 + "\n")
48
+
49
+ app.add_api_route("/api/predict", predict_endpoint, methods=["POST"])
50
+ app.add_api_route("/api/quiz", get_quiz_questions, methods=["GET"])
51
+ app.add_api_route("/api/quiz", submit_quiz, methods=["POST"])
52
+
53
+ @app.post("/api/chat")
54
+ async def chat_endpoint(request: ChatRequest):
55
+ return {"response": chatbot.generate_response(request.message, request.lang)}
56
+
57
+
58
+ @app.get("/api/hello")
59
+ def health_check():
60
+ # Biar bisa dicek lewat browser: http://localhost:8000/api/hello
61
+ has_key = bool(os.getenv("YOUTUBE_API_KEY"))
62
+ return {
63
+ "status": "online",
64
+ "mode": "youtube_ready",
65
+ "api_key_detected": has_key
66
+ }
67
+
68
+ # --- ROUTE YOUTUBE BARU ---
69
+ @app.get("/api/youtube/{video_id}")
70
+ def analyze_youtube_video(video_id: str):
71
+ # Panggil fungsi fetch YouTube
72
+ text = NLPHandler.fetch_youtube_transcript(video_id)
73
+
74
+ if not text:
75
+ return {
76
+ "success": False,
77
+ "error": "NO_TRANSCRIPT" # Kode error kalau video gak ada subtitle
78
+ }
79
+
80
+ # Analisis teks transkripnya
81
+ result = NLPHandler.predict_all(text)
82
+
83
+ response_data = {
84
+ "success": True,
85
+ "mbti_type": result["mbti"],
86
+ "emotion": result["emotion"],
87
+ "keywords": result["keywords"],
88
+ }
89
+
90
+ # Handle kalo inputnya dari YouTube (dict ada 'meta')
91
+ if isinstance(text, dict) and "meta" in text:
92
+ response_data["fetched_text"] = text["text_for_analysis"]
93
+ response_data["meta"] = text["meta"]
94
+ else:
95
+ response_data["fetched_text"] = text
96
+
97
+ return response_data
api/predict.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ # Import Logic dari Core
4
+ from .core.nlp_handler import NLPHandler
5
+
6
+ app = FastAPI()
7
+
8
+ class UserInput(BaseModel):
9
+ text: str
10
+
11
+ @app.post("/api/predict")
12
+ def predict_endpoint(input_data: UserInput):
13
+ if not input_data.text:
14
+ raise HTTPException(status_code=400, detail="No text provided")
15
+
16
+ # Panggil Logic NLP (Auto-Translate -> Predict)
17
+ result = NLPHandler.predict_all(input_data.text)
18
+
19
+ # Return format JSON
20
+ return {
21
+ "success": True,
22
+ "mbti_type": result["mbti"],
23
+ "emotion": result["emotion"],
24
+ "keywords": result["keywords"]
25
+ }
api/quiz.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import Dict
4
+ # Import Logic dari Core
5
+ from .core.quiz_logic import QuizHandler
6
+
7
+ app = FastAPI()
8
+
9
+ # Model untuk menerima jawaban dari frontend
10
+ class QuizSubmission(BaseModel):
11
+ answers: Dict[str, int] # Contoh: {"1": 3, "2": -2}
12
+
13
+ @app.get("/api/quiz")
14
+ def get_quiz_questions():
15
+ """Endpoint untuk Frontend mengambil soal"""
16
+ questions = QuizHandler.get_questions()
17
+ if not questions:
18
+ # Jika file json tidak terbaca/kosong
19
+ raise HTTPException(status_code=500, detail="Database soal tidak ditemukan")
20
+ return {"questions": questions}
21
+
22
+ @app.post("/api/quiz")
23
+ def submit_quiz(submission: QuizSubmission):
24
+ """Endpoint untuk Frontend kirim jawaban dan dapat hasil MBTI"""
25
+ result = QuizHandler.calculate_mbti(submission.answers)
26
+ return {"mbti": result}
api/requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-dotenv
4
+ pydantic
5
+ numpy
6
+ scikit-learn
7
+ joblib
8
+ deep-translator
9
+ requests
10
+ youtube-transcript-api
11
+ google-generativeai