harshdhane commited on
Commit
0ee19e8
ยท
verified ยท
1 Parent(s): e39dfd9

Upload 8 files

Browse files
app.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from flask import Flask, render_template, request, url_for, session
3
+ import random
4
+ import os
5
+ from proof_generator import generate_fake_proof
6
+ from datetime import datetime
7
+ from googletrans import Translator
8
+
9
+
10
+
11
+ app = Flask(__name__)
12
+ app.secret_key = "your-secret-key"
13
+ translator = Translator()
14
+
15
+
16
+ translations = {
17
+ 'en': {
18
+ 'excuse_header': "Excuse Generator",
19
+ 'excuse_intro': "Make your absence legendary ๐Ÿ’Œ",
20
+ 'choose_scenario': "Choose a scenario:",
21
+ 'generate_excuse': "๐ŸŽฒ Generate Excuse",
22
+ 'apology_link': "Need to apologize? Generate a guilt-tripping apology ๐Ÿ’”",
23
+ 'work': "Work",
24
+ 'school': "School",
25
+ 'social': "Social",
26
+ 'family': "Family",
27
+ 'history': "History",
28
+ 'favorites': "Favorites",
29
+ 'your_excuse': "Your Excuse:",
30
+ 'fake_proof': "Fake Proof:",
31
+ 'top_excuses': "Top Excuses:",
32
+ 'prediction': "Prediction: Based on your history, you may need your next excuse on:",
33
+ 'play_excuse': "โ–ถ๏ธ Play Excuse"
34
+ },
35
+ 'es': {
36
+ 'excuse_header': "Generador de Excusas",
37
+ 'excuse_intro': "Haz que tu ausencia sea legendaria ๐Ÿ’Œ",
38
+ 'choose_scenario': "Elige un escenario:",
39
+ 'generate_excuse': "๐ŸŽฒ Generar Excusa",
40
+ 'apology_link': "ยฟNecesitas disculparte? Genera una disculpa emocional ๐Ÿ’”",
41
+ 'work': "Trabajo",
42
+ 'school': "Escuela",
43
+ 'social': "Social",
44
+ 'family': "Familia",
45
+ 'history': "Historial",
46
+ 'favorites': "Favoritos",
47
+ 'your_excuse': "Tu excusa:",
48
+ 'fake_proof': "Prueba falsa:",
49
+ 'top_excuses': "Principales excusas:",
50
+ 'prediction': "Predicciรณn: Segรบn tu historial, podrรญas necesitar tu prรณxima excusa el:",
51
+ 'play_excuse': "โ–ถ๏ธ Reproducir excusa"
52
+ },
53
+ 'fr': {
54
+ 'excuse_header': "Gรฉnรฉrateur d'Excuses",
55
+ 'excuse_intro': "Rendez votre absence lรฉgendaire ๐Ÿ’Œ",
56
+ 'choose_scenario': "Choisissez un scรฉnario :",
57
+ 'generate_excuse': "๐ŸŽฒ Gรฉnรฉrer une excuse",
58
+ 'apology_link': "Besoin de vous excuser ? Gรฉnรฉrer une excuse pleine de culpabilitรฉ ๐Ÿ’”",
59
+ 'work': "Travail",
60
+ 'school': "ร‰cole",
61
+ 'social': "Social",
62
+ 'family': "Famille",
63
+ 'history': "Historique",
64
+ 'favorites': "Favoris",
65
+ 'your_excuse': "Votre excuse:",
66
+ 'fake_proof': "Fausse preuve:",
67
+ 'top_excuses': "Meilleures excuses:",
68
+ 'prediction': "Prรฉvision : Basรฉ sur votre historique, vous pourriez avoir besoin de votre prochaine excuse le:",
69
+ 'play_excuse': "โ–ถ๏ธ Lire l'excuse"
70
+ },
71
+ 'hi': {
72
+ 'excuse_header': "เคฌเคนเคพเคจเคพ เคœเคจเคฐเฅ‡เคŸเคฐ",
73
+ 'excuse_intro': "เค…เคชเคจเฅ€ เค—เฅˆเคฐเคฎเฅŒเคœเฅ‚เคฆเค—เฅ€ เค•เฅ‹ เคฏเคพเคฆเค—เคพเคฐ เคฌเคจเคพเคเค‚ ๐Ÿ’Œ",
74
+ 'choose_scenario': "เคเค• เคธเฅเคฅเคฟเคคเคฟ เคšเฅเคจเฅ‡เค‚:",
75
+ 'generate_excuse': "๐ŸŽฒ เคฌเคนเคพเคจเคพ เคœเคจเคฐเฅ‡เคŸ เค•เคฐเฅ‡เค‚",
76
+ 'apology_link': "เคฎเคพเคซเฅ€ เคฎเคพเค‚เค—เคจเฅ€ เคนเฅˆ? เคเค• เคฆเคฟเคฒ เค›เฅ‚ เคฒเฅ‡เคจเฅ‡ เคตเคพเคฒเฅ€ เคฎเคพเคซเฅ€ เคœเคจเคฐเฅ‡เคŸ เค•เคฐเฅ‡เค‚ ๐Ÿ’”",
77
+ 'work': "เค•เคพเคฎ",
78
+ 'school': "เคธเฅเค•เฅ‚เคฒ",
79
+ 'social': "เคธเคพเคฎเคพเคœเคฟเค•",
80
+ 'family': "เคชเคฐเคฟเคตเคพเคฐ",
81
+ 'history': "เค‡เคคเคฟเคนเคพเคธ",
82
+ 'favorites': "เคชเคธเค‚เคฆเฅ€เคฆเคพ",
83
+ 'your_excuse': "เค†เคชเค•เคพ เคฌเคนเคพเคจเคพ:",
84
+ 'fake_proof': "เคจเค•เคฒเฅ€ เคธเคฌเฅ‚เคค:",
85
+ 'top_excuses': "เคถเฅ€เคฐเฅเคท เคฌเคนเคพเคจเฅ‡:",
86
+ 'prediction': "เคญเคตเคฟเคทเฅเคฏเคตเคพเคฃเฅ€: เค†เคชเค•เฅ‡ เค‡เคคเคฟเคนเคพเคธ เค•เฅ‡ เค†เคงเคพเคฐ เคชเคฐ, เค…เค—เคฒเคพ เคฌเคนเคพเคจเคพ เค†เคชเค•เฅ‹ เค‡เคธ เคฆเคฟเคจ เคœเคผเคฐเฅ‚เคฐเคค เคชเคกเคผ เคธเค•เคคเฅ€ เคนเฅˆ:",
87
+ 'play_excuse': "โ–ถ๏ธ เคฌเคนเคพเคจเคพ เคธเฅเคจเฅ‡เค‚"
88
+ },
89
+ 'te': {
90
+ 'excuse_header': "เฐจเฑ†เฐชเฐ‚ เฐœเฐจเฐฐเฑ‡เฐŸเฐฐเฑ",
91
+ 'excuse_intro': "เฐฎเฑ€ เฐ—เฑˆเฐฐเฑเฐนเฐพเฐœเฐฐเฑเฐจเฑ เฐšเฐฐเฐฟเฐคเฑเฐฐเฐฒเฑ‹ เฐจเฐฟเฐฒเฑเฐชเฑเฐ•เฑ‹เฐ‚เฐกเฐฟ ๐Ÿ’Œ",
92
+ 'choose_scenario': "เฐ’เฐ• เฐชเฐฐเฐฟเฐธเฑเฐฅเฐฟเฐจเฐฟ เฐŽเฐ‚เฐšเฑเฐ•เฑ‹เฐ‚เฐกเฐฟ:",
93
+ 'generate_excuse': "๐ŸŽฒ เฐจเฑ†เฐชเฐ‚ เฐคเฐฏเฐพเฐฐเฑ เฐšเฑ‡เฐฏเฐ‚เฐกเฐฟ",
94
+ 'apology_link': "เฐ•เฑเฐทเฐฎเฐพเฐชเฐฃ เฐšเฑ†เฐชเฑเฐชเฐพเฐฒเฐพ? เฐฎเฐฐเฑเฐฎเฐฎเฑˆเฐจ เฐ•เฑเฐทเฐฎเฐพเฐชเฐฃเฐจเฑ เฐฐเฑ‚เฐชเฑŠเฐ‚เฐฆเฐฟเฐ‚เฐšเฐ‚เฐกเฐฟ ๐Ÿ’”",
95
+ 'work': "เฐชเฐจเฐฟ",
96
+ 'school': "เฐชเฐพเฐ เฐถเฐพเฐฒ",
97
+ 'social': "เฐธเฐพเฐฎเฐพเฐœเฐฟเฐ•",
98
+ 'family': "เฐ•เฑเฐŸเฑเฐ‚เฐฌเฐ‚",
99
+ 'history': "เฐšเฐฐเฐฟเฐคเฑเฐฐ",
100
+ 'favorites': "เฐ‡เฐทเฑเฐŸเฐฎเฑˆเฐจเฐตเฐฟ",
101
+ 'your_excuse': "เฐฎเฑ€ เฐจเฑ†เฐชเฐ‚:",
102
+ 'fake_proof': "เฐจเฐ•เฐฟเฐฒเฑ€ เฐ†เฐงเฐพเฐฐเฐ‚:",
103
+ 'top_excuses': "เฐ…เฐ—เฑเฐฐ เฐจเฑ†เฐชเฐพเฐฒเฑ:",
104
+ 'prediction': "เฐ…เฐ‚เฐšเฐจเฐพ: เฐฎเฑ€ เฐšเฐฐเฐฟเฐคเฑเฐฐ เฐ†เฐงเฐพเฐฐเฐ‚เฐ—เฐพ, เฐฎเฑ€เฐ•เฑ เฐตเฐšเฑเฐšเฑ‡ เฐจเฑ†เฐชเฐ‚ เฐ…เฐตเฐธเฐฐเฐ‚ เฐ…เฐฏเฑเฐฏเฑ‡ เฐฐเฑ‹เฐœเฑ:",
105
+ 'play_excuse': "โ–ถ๏ธ เฐจเฑ†เฐชเฐ‚ เฐตเฐฟเฐจเฐ‚เฐกเฐฟ"
106
+ }
107
+ }
108
+
109
+
110
+ # --- Excuse + Proof Mappings ---
111
+ excuse_proofs = {
112
+ "Work": [
113
+ ("I'm feeling unwell and didnโ€™t want to risk spreading anything.", "proof6.jpg"),
114
+ ("There was an unexpected power outage at my place.", "proof5.jpg"),
115
+ ("My internet went down just before the meeting.", "proof4.jpg"),
116
+ ("I had to handle a family emergency this morning.", "proof13.jpg")
117
+ ],
118
+ "School": [
119
+ ("I had a doctor's appointment that ran longer than expected.", "proof6.jpg"),
120
+ ("My younger sibling was sick and I had to take care of them.", "proof9.jpg"),
121
+ ("There was a transportation strike in my area.", "proof2.jpg"),
122
+ ("I lost track of time while working on another assignment.", "proof12.jpg")
123
+ ],
124
+ "Social": [
125
+ ("I wasn't feeling mentally up to socializing today.", "proof1.jpg"),
126
+ ("My pet had a small emergency and needed my attention.", "proof3.jpg"),
127
+ ("I got caught up in work and lost track of time.", "proof4.jpg"),
128
+ ("There was a last-minute family gathering I couldnโ€™t skip.", "proof8.jpg")
129
+ ],
130
+ "Family": [
131
+ ("I was helping a relative with a medical emergency.", "proof13.jpg"),
132
+ ("There was an issue at home that needed my immediate attention.", "proof9.jpg"),
133
+ ("I was caught up in an important family conversation.", "proof9.jpg"),
134
+ ("We had an unexpected visitor I couldnโ€™t leave unattended.", "proof10.jpg")
135
+ ]
136
+ }
137
+
138
+ # --- Apology Templates ---
139
+ apology_templates = {
140
+ "Work": [
141
+ "I'm truly sorry for missing work. It wasnโ€™t my intention to let anyone down.",
142
+ "I know how important today was โ€” please believe that I deeply regret not being there.",
143
+ "Work matters to me, and I feel awful that I couldnโ€™t show up when it mattered most."
144
+ ],
145
+ "School": [
146
+ "I take full responsibility for not being present. I'm deeply sorry to my classmates and teachers.",
147
+ "I hate that I let this happen โ€” Iโ€™ll do everything I can to make it right.",
148
+ "Itโ€™s not an excuse, just a sincere apology. I hope I can earn back your trust."
149
+ ],
150
+ "Family": [
151
+ "I never meant to let the family down. I hope you know how bad I feel.",
152
+ "Please forgive me โ€” I value our time together and hate that I missed it.",
153
+ "Family means the world to me. Iโ€™m genuinely sorry for my absence."
154
+ ],
155
+ "Social": [
156
+ "I was really looking forward to it. Iโ€™m so sorry I couldnโ€™t make it.",
157
+ "I feel like I let everyone down โ€” I hope you can understand.",
158
+ "Please know I regret not showing up โ€” it wasnโ€™t intentional."
159
+ ]
160
+ }
161
+ top_excuses = []
162
+ scheduled_excuses = []
163
+
164
+ def rank_excuses(excuse_list):
165
+ ranked = []
166
+ for excuse in excuse_list:
167
+ score = 0
168
+ if "emergency" in excuse.lower():
169
+ score += 5
170
+ if "doctor" in excuse.lower() or "medical" in excuse.lower():
171
+ score += 4
172
+ if "internet" in excuse.lower():
173
+ score += 3
174
+ if "transport" in excuse.lower() or "power outage" in excuse.lower():
175
+ score += 2
176
+ if "family" in excuse.lower():
177
+ score += 1
178
+
179
+ ranked.append((score, excuse))
180
+
181
+ ranked.sort(reverse=True)
182
+ return [exc for score, exc in ranked]
183
+
184
+ def predict_next_excuse_time(history):
185
+ from collections import Counter
186
+ from datetime import timedelta
187
+
188
+ if not history:
189
+ return "No data to predict."
190
+
191
+ valid_entries = [entry for entry in history if isinstance(entry, dict) and "timestamp" in entry]
192
+
193
+ if not valid_entries:
194
+ return "No valid history entries."
195
+
196
+ times = [datetime.strptime(entry["timestamp"], "%Y-%m-%d %H:%M:%S") for entry in history]
197
+ days_of_week = [time.weekday() for time in times] # 0 = Monday
198
+
199
+ # Most common day
200
+ common_day = Counter(days_of_week).most_common(1)[0][0]
201
+
202
+ # Estimate next occurrence of that weekday
203
+ today = datetime.now()
204
+ days_until_next = (common_day - today.weekday()) % 7
205
+ predicted_date = today + timedelta(days=days_until_next)
206
+
207
+ # Predict similar time based on average hour
208
+ avg_hour = sum(time.hour for time in times) / len(times)
209
+ predicted_time = predicted_date.replace(hour=int(avg_hour), minute=0, second=0, microsecond=0)
210
+
211
+ return predicted_time.strftime("%A, %Y-%m-%d at %I:%M %p")
212
+
213
+
214
+ @app.route('/', methods=['GET', 'POST'])
215
+ def index():
216
+
217
+ selected_language = request.form.get('language')
218
+ if not selected_language:
219
+ selected_language = request.args.get('lang')
220
+ if not selected_language:
221
+ selected_language = 'en-US'
222
+ lang_code = selected_language.split('-')[0] # en-US -> en
223
+ language_dict = translations.get(lang_code, translations['en'])
224
+
225
+
226
+ if "history" not in session:
227
+ session["history"] = []
228
+ if "favorites" not in session:
229
+ session["favorites"] = []
230
+
231
+ session["history"] = []
232
+
233
+ excuse = None
234
+ proof = None
235
+ top_3_excuses = []
236
+
237
+ #selected_language = request.form.get('language', 'en-US') # default language
238
+
239
+
240
+ if request.method == 'POST':
241
+ scenario = request.form.get('scenario')
242
+ urgency = request.form.get('urgency')
243
+ selected_language = request.form.get('language', 'en-US')
244
+
245
+ if scenario in excuse_proofs:
246
+ # Fetch all excuses for the selected scenario
247
+ all_excuses_proofs = excuse_proofs[scenario]
248
+
249
+ # Extract only excuses (texts) for ranking
250
+ all_excuses = [exc for exc, proof_file in all_excuses_proofs]
251
+
252
+ # Rank excuses
253
+ ranked_excuses = rank_excuses(all_excuses)
254
+
255
+ # Pick the best excuse (top-ranked)
256
+ best_excuse = ranked_excuses[0]
257
+ top_3_excuses = ranked_excuses[:3]
258
+
259
+ # Find corresponding proof image
260
+ for exc, proof_filename in all_excuses_proofs:
261
+ if exc == best_excuse:
262
+ proof = url_for('static', filename=f'proofs/{proof_filename}')
263
+ break
264
+
265
+ excuse = best_excuse
266
+
267
+ else:
268
+ excuse = f"Sorry, I couldn't attend due to a {scenario} emergency."
269
+ proof = url_for('static', filename='proofs/default_proof.jpg')
270
+
271
+ # --- Translate excuse based on selected language ---
272
+ target_lang = selected_language.split('-')[0] # en-US -> en
273
+ if target_lang != 'en': # Only translate if not English
274
+ try:
275
+ translation = translator.translate(excuse, dest=target_lang)
276
+ excuse = translation.text
277
+ except Exception as e:
278
+ print("Translation error:", e)
279
+ # If translation fails, keep the original English excuse
280
+
281
+ # Save excuse to history with timestamp
282
+ session["history"].append({
283
+ "excuse": excuse,
284
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
285
+ })
286
+ session.modified = True
287
+
288
+ language_dict = translations.get(selected_language[:2], translations['en'])
289
+ predicted_schedule = predict_next_excuse_time(session.get("history", []))
290
+
291
+
292
+ return render_template('index.html', excuse=excuse, proof=proof,selected_language=selected_language, language_dict=language_dict,top_excuses=top_3_excuses, predicted_schedule=predicted_schedule)
293
+
294
+
295
+ @app.route("/history")
296
+ def history():
297
+ lang = request.args.get('lang', 'en')
298
+ language_dict = translations.get(lang, translations['en'])
299
+
300
+ return render_template("history.html",
301
+ history=session.get("history", []),
302
+ favorites=session.get("favorites", []),
303
+ language_dict=language_dict)
304
+
305
+ @app.route("/favorite", methods=["POST"])
306
+ def favorite():
307
+ excuse = request.form.get("excuse")
308
+ if excuse and excuse not in session["favorites"]:
309
+ session["favorites"].append(excuse)
310
+ session.modified = True
311
+ return (url_for("history"))
312
+
313
+
314
+ @app.route('/apology', methods=['GET', 'POST'])
315
+ def apology():
316
+ apology_message = None
317
+ proof = None
318
+ lang = request.args.get('lang', 'en')
319
+ language_dict = translations.get(lang, translations['en'])
320
+
321
+
322
+ if request.method == 'POST':
323
+ scenario = request.form.get('scenario')
324
+ urgency = request.form.get("urgency")
325
+ tone = request.form.get('tone')
326
+
327
+ if scenario in apology_templates:
328
+ apology_message = random.choice(apology_templates[scenario])
329
+ proof = generate_fake_proof(urgency)
330
+
331
+ return render_template('apology.html', apology=apology_message, proof=proof, language_dict=language_dict)
332
+
333
+ if __name__ == "__main__":
334
+ app.run(debug=True)
proof_generator.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import random
3
+
4
+ def generate_fake_proof(scenario):
5
+ proofs = {
6
+ "Work": ["proofs/proof1.jpg", "proofs/proof2.jpg"],
7
+ "Family": ["proofs/proof3.jpg", "proofs/proof4.jpg"]
8
+ }
9
+ return random.choice(proofs.get(scenario, ["proofs/proof1.jpg"]))
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Flask==2.3.3
2
+ googletrans==4.0.0-rc1
3
+ httpx==0.27.0
static/style.css ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Base layout */
2
+ body {
3
+ font-family: 'Segoe UI', sans-serif;
4
+ background: linear-gradient(to right, #f0f8ff, #ffffff);
5
+ text-align: center;
6
+ margin: 0;
7
+ padding: 20px;
8
+ color: #333;
9
+ }
10
+
11
+ /* Main container */
12
+ .container {
13
+ background: #ffffff;
14
+ padding: 30px 25px;
15
+ margin: auto;
16
+ max-width: 600px;
17
+ border-radius: 15px;
18
+ box-shadow: 0 4px 20px rgba(0,0,0,0.1);
19
+ }
20
+
21
+ /* Headings */
22
+ h1 {
23
+ color: #6C63FF;
24
+ font-size: 2rem;
25
+ margin-bottom: 15px;
26
+ }
27
+
28
+ h2, h3 {
29
+ color: #333;
30
+ margin-top: 30px;
31
+ }
32
+
33
+ /* Forms */
34
+ form {
35
+ margin-top: 20px;
36
+ text-align: left;
37
+ }
38
+
39
+ label {
40
+ display: block;
41
+ font-weight: 600;
42
+ margin-top: 20px;
43
+ margin-bottom: 6px;
44
+ }
45
+
46
+ select,
47
+ input[type="radio"] {
48
+ margin-top: 5px;
49
+ }
50
+
51
+ input[type="submit"],
52
+ button {
53
+ background-color: #6C63FF;
54
+ color: white;
55
+ padding: 10px 20px;
56
+ border: none;
57
+ border-radius: 8px;
58
+ font-size: 16px;
59
+ cursor: pointer;
60
+ margin-top: 20px;
61
+ transition: background-color 0.3s;
62
+ }
63
+
64
+ input[type="submit"]:hover,
65
+ button:hover {
66
+ background-color: #574fd6;
67
+ }
68
+
69
+ /* Excuse result display */
70
+ .excuse-box {
71
+ margin-top: 25px;
72
+ padding: 18px;
73
+ background: #fff3cd;
74
+ border-left: 6px solid #ffc107;
75
+ border-radius: 10px;
76
+ color: #333;
77
+ font-size: 1.1rem;
78
+ line-height: 1.5;
79
+ }
80
+
81
+ /* Proof image */
82
+ .proof-img {
83
+ max-width: 400px;
84
+ margin-top: auto;
85
+ border-radius: 10px;
86
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
87
+ }
88
+
89
+ /* Top excuses list */
90
+ ol {
91
+ list-style-type: none;
92
+ padding: 0;
93
+ }
94
+
95
+ li {
96
+ margin: 10px 0;
97
+ font-size: 1rem;
98
+ }
99
+
100
+ li strong {
101
+ font-weight: 600;
102
+ }
103
+
104
+ /* Prediction box */
105
+ .prediction-box {
106
+ background-color: #eef;
107
+ padding: 12px;
108
+ border-radius: 10px;
109
+ margin-top: 25px;
110
+ font-size: 1rem;
111
+ }
112
+
113
+ /* Links */
114
+ a {
115
+ color: #0077cc;
116
+ text-decoration: none;
117
+ }
118
+
119
+ a:hover {
120
+ text-decoration: underline;
121
+ }
static/voice.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // static/js/voice.js
3
+
4
+ document.addEventListener('DOMContentLoaded', function () {
5
+ const micBtn = document.getElementById('mic-btn');
6
+ const urgencyInputs = document.getElementsByName('urgency');
7
+
8
+ if (micBtn) {
9
+ micBtn.addEventListener('click', () => {
10
+ const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
11
+ recognition.lang = 'en-US';
12
+ recognition.start();
13
+
14
+ recognition.onresult = function(event) {
15
+ const transcript = event.results[0][0].transcript.toLowerCase();
16
+ console.log("Heard:", transcript);
17
+ if (transcript.includes('low')) urgencyInputs[0].checked = true;
18
+ else if (transcript.includes('medium')) urgencyInputs[1].checked = true;
19
+ else if (transcript.includes('high')) urgencyInputs[2].checked = true;
20
+ };
21
+ });
22
+ }
23
+ });
templates/apology.html ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ !-- templates/apology.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Apology Generator</title>
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <h1>๐Ÿ˜ข Guilt-Tripping Apology Generator</h1>
13
+ <p style="color: green;">Template loaded โœ…</p>
14
+
15
+
16
+ <form method="POST" action="/apology">
17
+ <label>Choose a scenario:</label>
18
+ <select name="scenario" required>
19
+ <option value="Work">Work</option>
20
+ <option value="School">School</option>
21
+ <option value="Social">Social</option>
22
+ <option value="Family">Family</option>
23
+ </select>
24
+
25
+ <label>Pick a tone (optional):</label>
26
+ <select name="tone">
27
+ <option value="sincere">Sincere</option>
28
+ <option value="emotional">Emotional</option>
29
+ <option value="dramatic">Dramatic</option>
30
+ </select>
31
+
32
+ <button type="submit">Generate Apology</button>
33
+ </form>
34
+ {% if apology %}
35
+ <h2>Your Apology:</h2>
36
+ <p id="apologyText">{{ apology }}</p>
37
+ <button onclick="speakApology()">๐Ÿ”Š Play Apology</button>
38
+ <script>
39
+ function speakApology() {
40
+ var apologyText = document.getElementById('apologyText').innerText;
41
+ var utterance = new SpeechSynthesisUtterance(apologyText);
42
+ utterance.lang = 'en-US';
43
+ window.speechSynthesis.speak(utterance);
44
+ };
45
+
46
+ </script>
47
+ {% endif %}
48
+
49
+ <p><a href="{{ url_for('index') }}">Back to Excuse Generator</a></p>
50
+
51
+ <script>
52
+ function startVoiceInput(fieldName) {
53
+ const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
54
+ recognition.lang = 'en-US';
55
+ recognition.start();
56
+
57
+ recognition.onresult = function(event) {
58
+ const result = event.results[0][0].transcript.toLowerCase();
59
+ document.querySelectorAll(`input[name="${fieldName}"]`).forEach(radio => {
60
+ if (radio.value.toLowerCase() === result) {
61
+ radio.checked = true;
62
+ }
63
+ });
64
+ };
65
+ }
66
+ </script>
67
+
68
+ </body>
69
+ </html>
70
+
71
+
72
+
73
+
74
+
templates/history.html ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Excuse History</title></head>
4
+ <body>
5
+ <h1>๐Ÿ•’ Your Excuse History</h1>
6
+
7
+ {% if history %}
8
+ <ul>
9
+ {% for item in history %}
10
+ <li>
11
+ {{ item }}
12
+ {% if item not in favorites %}
13
+ <form action="{{ url_for('favorite') }}" method="post" style="display:inline;">
14
+ <input type="hidden" name="excuse" value="{{ item }}">
15
+ <button type="submit">โญ Add to Favorites</button>
16
+ </form>
17
+ {% else %}
18
+ <span>โญ Favorited</span>
19
+ {% endif %}
20
+ </li>
21
+ {% endfor %}
22
+ </ul>
23
+ {% else %}
24
+ <p>No excuses generated yet!</p>
25
+ {% endif %}
26
+
27
+ <a href="{{ url_for('index') }}">โฌ… Back</a>
28
+ </body>
29
+ </html>
templates/index.html ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="{{ language_dict['lang'] }}">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>{{ language_dict['excuse_header'] }} ๐ŸŽ™๏ธ</title>
6
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', sans-serif;
10
+ background-color: #edf2fa;
11
+ color: #333;
12
+ padding: 20px;
13
+ max-width: 800px;
14
+ margin: auto;
15
+ }
16
+ h1, h2, h3 {
17
+ color: #2c3e50;
18
+ }
19
+ form {
20
+ background-color: #fff;
21
+ padding: 20px;
22
+ border-radius: 12px;
23
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05);
24
+ margin-bottom: 20px;
25
+ }
26
+ label {
27
+ font-weight: bold;
28
+ display: block;
29
+ margin-top: 15px;
30
+ }
31
+ select, input[type="radio"] {
32
+ margin-top: 5px;
33
+ }
34
+ input[type="submit"], button {
35
+ margin-top: 20px;
36
+ background-color: #4CAF50;
37
+ color: white;
38
+ padding: 10px 18px;
39
+ border: none;
40
+ border-radius: 6px;
41
+ cursor: pointer;
42
+ }
43
+ input[type="submit"]:hover, button:hover {
44
+ background-color: #45a049;
45
+ }
46
+ .proof-img {
47
+ max-width: 400px;
48
+ margin-top: auto;
49
+ border-radius: 10px;
50
+ box-shadow: 0 0 8px rgba(0,0,0,0.1);
51
+ }
52
+ ol {
53
+ padding-left: 0;
54
+ }
55
+ li {
56
+ margin: 10px 0;
57
+ }
58
+ a {
59
+ text-decoration: none;
60
+ color: #0077cc;
61
+ }
62
+ a:hover {
63
+ text-decoration: underline;
64
+ }
65
+ .prediction-box {
66
+ background-color: #eef;
67
+ padding: 12px;
68
+ border-radius: 10px;
69
+ margin-top: 20px;
70
+ }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div class="container">
75
+
76
+ <h1>{{ language_dict['excuse_header'] }}</h1>
77
+ <p>{{ language_dict['excuse_intro'] }}</p>
78
+
79
+ <form method="post">
80
+ <label>{{ language_dict['choose_scenario'] }}:</label>
81
+ <select name="scenario">
82
+ <option value="Work" {% if request.form.get('scenario') == 'Work' %}selected{% endif %}>{{ language_dict['work'] }}</option>
83
+ <option value="School" {% if request.form.get('scenario') == 'School' %}selected{% endif %}>{{ language_dict['school'] }}</option>
84
+ <option value="Social" {% if request.form.get('scenario') == 'Social' %}selected{% endif %}>{{ language_dict['social'] }}</option>
85
+ <option value="Family" {% if request.form.get('scenario') == 'Family' %}selected{% endif %}>{{ language_dict['family'] }}</option>
86
+ </select>
87
+
88
+ <label>{{ language_dict['urgency'] }}:</label>
89
+ <input type="radio" name="urgency" value="Low" {% if request.form.get('urgency') == 'Low' %}checked{% endif %}> Low
90
+ <input type="radio" name="urgency" value="Medium" {% if request.form.get('urgency') == 'Medium' %}checked{% endif %}> Medium
91
+ <input type="radio" name="urgency" value="High" {% if request.form.get('urgency') == 'High' %}checked{% endif %}> High
92
+
93
+ <label>Select Language for Voice Output:</label>
94
+ <select name="language" id="language">
95
+ <option value="en-US" {% if selected_language == 'en-US' %}selected{% endif %}>English ๐Ÿ‡บ๐Ÿ‡ธ</option>
96
+ <option value="hi-IN" {% if selected_language == 'hi-IN' %}selected{% endif %}>Hindi ๐Ÿ‡ฎ๐Ÿ‡ณ</option>
97
+ <option value="te-IN" {% if selected_language == 'te-IN' %}selected{% endif %}>Telugu ๐Ÿ‡ฎ๐Ÿ‡ณ</option>
98
+ <option value="es-ES" {% if selected_language == 'es-ES' %}selected{% endif %}>Spanish ๐Ÿ‡ช๐Ÿ‡ธ</option>
99
+ <option value="fr-FR" {% if selected_language == 'fr-FR' %}selected{% endif %}>French ๐Ÿ‡ซ๐Ÿ‡ท</option>
100
+ </select>
101
+
102
+ <input type="submit" value="{{ language_dict['generate_excuse'] }}">
103
+ </form>
104
+ </div>
105
+
106
+ {% if excuse %}
107
+ <h2>Your Excuse:</h2>
108
+ <p id="excuseText">{{ excuse }}</p>
109
+ <button onclick="speakExcuse()">๐Ÿ”Š Play Excuse</button>
110
+
111
+ <script>
112
+ function speakExcuse() {
113
+ const text = document.getElementById('excuseText').innerText;
114
+ const language = document.getElementById('language').value;
115
+ const utterance = new SpeechSynthesisUtterance(text);
116
+ utterance.lang = language;
117
+
118
+ function loadAndSpeak() {
119
+ const voices = window.speechSynthesis.getVoices();
120
+ let matchedVoice = voices.find(voice => voice.lang === language);
121
+
122
+ // Telugu fallback
123
+ if (language === 'te-IN' && !matchedVoice) {
124
+ matchedVoice = voices.find(voice => voice.lang.startsWith('en'));
125
+ utterance.lang = 'en-US';
126
+ alert("โš ๏ธ Telugu voice not supported on your browser. Using English voice instead.");
127
+ }
128
+
129
+ if (matchedVoice) {
130
+ utterance.voice = matchedVoice;
131
+ }
132
+
133
+ window.speechSynthesis.speak(utterance);
134
+ }
135
+
136
+ if (window.speechSynthesis.getVoices().length === 0) {
137
+ // Wait for voices to load
138
+ window.speechSynthesis.onvoiceschanged = loadAndSpeak;
139
+ } else {
140
+ loadAndSpeak();
141
+ }
142
+ }
143
+ </script>
144
+
145
+
146
+ </script>
147
+ {% endif %}
148
+
149
+ {% if proof %}
150
+ <h3>Fake Proof:</h3>
151
+ <img src="{{ proof }}" alt="Fake Proof" class="proof-img">
152
+ {% endif %}
153
+
154
+ {% if top_excuses %}
155
+ <h2>๐Ÿ† Top Excuses:</h2>
156
+ <ol>
157
+ {% for excuse in top_excuses %}
158
+ <li>
159
+ {% if loop.index == 1 %}๐Ÿฅ‡{% elif loop.index == 2 %}๐Ÿฅˆ{% elif loop.index == 3 %}๐Ÿฅ‰{% endif %}
160
+ <strong>{{ excuse }}</strong>
161
+ </li>
162
+ {% endfor %}
163
+ </ol>
164
+ {% endif %}
165
+
166
+ {% if predicted_schedule %}
167
+ <div class="prediction-box">
168
+ ๐Ÿ“… <strong>Prediction:</strong> Based on your history, you may need your next excuse on: <strong>{{ predicted_schedule }}</strong>
169
+ </div>
170
+ {% endif %}
171
+
172
+ <p><a href="{{ url_for('apology') }}">๐Ÿ’” Generate a guilt-tripping apology</a></p>
173
+ <p><a href="{{ url_for('history') }}">๐Ÿ•’ View History and Favorites</a></p>
174
+
175
+ </body>
176
+ </html>
177
+
178
+
179
+
180
+