musawar32ali commited on
Commit
7d0f9ff
·
verified ·
1 Parent(s): 21f7dd6

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -137
app.py CHANGED
@@ -1,27 +1,34 @@
1
  """
2
  Automatic Time Table Generation Agent (Improved)
3
- - Gradio app for Hugging Face Spaces
4
- - Features: multi-class, teacher names, Sunday holiday, PDF/DOCX/TXT exports, language selection
5
- - Save as app.py in your Space
 
 
 
 
 
 
6
  """
7
 
8
  import io
 
9
  import random
10
- import datetime
11
- from typing import List, Dict, Tuple
12
 
13
  import gradio as gr
14
  from fpdf import FPDF
15
  from docx import Document
16
 
17
  # ---------------------------
18
- # Localization (UI + headings)
19
  # ---------------------------
20
  LANG_MAP = {
21
  "English": {
22
  "title": "Automatic Time Table Generation (Improved)",
23
  "subtitle": "Unique, professional timetables — Sunday is holiday by default.",
24
- "days_label": "Days (comma-separated, omit Sunday; Sunday will be added automatically)",
25
  "subjects_label": "Subjects (comma-separated)",
26
  "teachers_label": "Teachers (comma-separated) — will cycle if fewer than subjects",
27
  "classes_label": "Class list (comma-separated, e.g. 1,2,3,4,5)",
@@ -30,17 +37,17 @@ LANG_MAP = {
30
  "slots_per_day": "Slots per day (0 = use subjects count)",
31
  "export_format": "Export format",
32
  "generate_btn": "Generate & Export",
33
- "preview_title": "Preview",
34
  "holiday_text": "Holiday",
35
  "time_col": "Time",
36
  "subject_col": "Subject",
37
  "teacher_col": "Teacher",
 
38
  },
39
  "Sindhi": {
40
- # Sindhi translations (basic, short labels). Adjust phrasing as needed.
41
  "title": "ٽائيم ٽيبل جنريٽر (بهتر ٿيل)",
42
- "subtitle": "منفرد ٽائيم ٽيبل — آچر آرام جو ڏينھن آهي.",
43
- "days_label": "ڏينھن (ڪاما سان الڳ ڪريو، آچر پاڻمرادو شامل ڪيو ويندو)",
44
  "subjects_label": "مضمون (ڪاما سان الڳ ڪريو)",
45
  "teachers_label": "استادن جا نالا (ڪاما سان) — مضمونن کان گهٽ هجن ته ورجايا ويندا",
46
  "classes_label": "ڪلاس لسٽ (ڪاما سان، مثال: 1,2,3,4,5)",
@@ -49,50 +56,56 @@ LANG_MAP = {
49
  "slots_per_day": "في روز سيشن (0 = مضمونن جي ڳڻپ استعمال)",
50
  "export_format": "برآمد جو فارميٽ",
51
  "generate_btn": "تيار ڪريو ۽ برآمد ڪريو",
52
- "preview_title": "پريوئو",
53
  "holiday_text": "موڪلون",
54
  "time_col": "وقت",
55
  "subject_col": "مضمون",
56
  "teacher_col": "استاد",
 
57
  },
58
  "Urdu": {
59
  "title": "خودکار وقتِ جدول جنریٹر (بہتر شدہ)",
60
- "subtitle": "منفرد، پیشہ ورانہ جدول — اتوار بطور تعطیل شامل ہے۔",
61
- "days_label": "دن (کاما سے جدا کریں، اتوار خود شامل ہوگا)",
62
  "subjects_label": "مضامین (کاما سے جدا کریں)",
63
- "teachers_label": "اساتذہ (کاما سے جدا کریں) — اگر کم ہوں گے تو دہرائے جائیں گے",
64
- "classes_label": "کلاس فہرست (کاما سے، مثال: 1,2,3,4,5)",
65
- "start_hour": "شروع کا وقت (24h)",
66
  "slot_duration": "سیشن کا دورانیہ (گھنٹے)",
67
  "slots_per_day": "روزانہ سیٹس (0 = مضامین کی گنتی)",
68
  "export_format": "ایکسپورٹ فارمیٹ",
69
  "generate_btn": "جنریٹ اور ایکسپورٹ کریں",
70
- "preview_title": "پریویو",
71
  "holiday_text": "چھٹی",
72
  "time_col": "وقت",
73
  "subject_col": "مضمون",
74
  "teacher_col": "استاد",
 
75
  },
76
  }
77
 
78
- # ---------------------------
79
- # Core timetable logic
80
- # ---------------------------
81
  HOLIDAY = "Sunday"
82
 
 
 
 
83
  def parse_csv_list(s: str) -> List[str]:
84
  return [p.strip() for p in s.split(",") if p.strip()]
85
 
86
  def ensure_days(days: List[str]) -> List[str]:
87
- # Normalize: capitalize each day
88
- days = [d.strip().capitalize() for d in days if d and d.strip()]
89
- # If user omitted Sunday, we will add it for display/honoring holiday
90
- if HOLIDAY not in days:
91
- # Keep working order Mon..Sat then Sunday last
92
- base = [d for d in days if d.lower() != HOLIDAY.lower()]
93
- base.append(HOLIDAY)
94
- days = base
95
- return days
 
 
 
 
96
 
97
  def build_teacher_map(subjects: List[str], teachers: List[str]) -> Dict[str, str]:
98
  if not teachers:
@@ -105,12 +118,8 @@ def generate_timetable_for_class(
105
  teachers: List[str],
106
  start_hour: int = 8,
107
  slot_duration: int = 1,
108
- slots_per_day: int = 0,
109
  ) -> Dict[str, List[Dict[str,str]]]:
110
- """
111
- Returns: {day: [ {time, subject, teacher}, ... ], ... }
112
- Sunday returns empty list to mark holiday.
113
- """
114
  if slots_per_day <= 0:
115
  slots_per_day = len(subjects) if subjects else 0
116
 
@@ -118,10 +127,9 @@ def generate_timetable_for_class(
118
  timetable = {}
119
  for day in days:
120
  if day.lower() == HOLIDAY.lower():
121
- timetable[day] = [] # holiday marker
122
  continue
123
 
124
- # create a copy and shuffle for uniqueness
125
  order = subjects.copy()
126
  random.shuffle(order)
127
  day_slots = []
@@ -142,121 +150,113 @@ def generate_full_timetable(
142
  teachers: List[str],
143
  start_hour: int,
144
  slot_duration: int,
145
- slots_per_day: int,
146
  ) -> Dict[str, Dict]:
147
- output = {}
148
- for cls in class_list:
149
- output[cls] = generate_timetable_for_class(days, subjects, teachers, start_hour, slot_duration, slots_per_day)
150
- return output
151
 
152
  # ---------------------------
153
- # Export helpers (return BytesIO)
154
  # ---------------------------
155
-
156
- class StyledPDF(FPDF):
157
- def header(self):
158
- # small header spacing
159
- self.set_font("Helvetica", "", 9)
160
- self.cell(0, 6, "", ln=True)
161
-
162
- def export_pdf_bytes(full_tt: Dict[str, Dict], lang_labels: Dict[str,str]) -> io.BytesIO:
163
- pdf = StyledPDF(orientation="P", unit="mm", format="A4")
164
  pdf.set_auto_page_break(auto=True, margin=12)
165
  pdf.add_page()
 
166
  # Title
167
  pdf.set_font("Helvetica", "B", 18)
168
- pdf.cell(0, 10, lang_labels["title"], ln=True, align="C")
169
  pdf.ln(2)
170
  pdf.set_font("Helvetica", "", 11)
171
- pdf.multi_cell(0, 6, lang_labels["subtitle"])
172
- pdf.ln(4)
173
 
174
- # Per class tables
175
- for class_name, tt in full_tt.items():
176
  pdf.set_font("Helvetica", "B", 14)
177
- pdf.cell(0, 8, f"Class {class_name}", ln=True)
178
  pdf.ln(1)
179
 
180
- # Table header
181
  pdf.set_font("Helvetica", "B", 11)
182
  w_time, w_subj, w_teacher = 35, 80, 60
183
- pdf.cell(w_time, 8, lang_labels["time_col"], border=1)
184
- pdf.cell(w_subj, 8, lang_labels["subject_col"], border=1)
185
- pdf.cell(w_teacher, 8, lang_labels["teacher_col"], border=1)
186
  pdf.ln(8)
187
 
188
  pdf.set_font("Helvetica", "", 10)
189
  for day, slots in tt.items():
190
- # Day row
191
  pdf.set_font("Helvetica", "B", 11)
192
  pdf.cell(0, 7, f"{day}", ln=True)
193
  pdf.set_font("Helvetica", "", 10)
194
  if not slots:
195
- # holiday
196
- pdf.cell(0, 7, f" {lang_labels['holiday_text']}", ln=True)
197
  else:
198
  for s in slots:
199
  pdf.cell(w_time, 7, s["time"], border=1)
200
- pdf.cell(w_subj, 7, s["subject"], border=1)
 
 
 
 
 
201
  pdf.cell(w_teacher, 7, s["teacher"], border=1)
202
  pdf.ln(7)
203
  pdf.ln(1)
204
- pdf.ln(4)
205
 
206
- bio = io.BytesIO()
207
- pdf.output(bio)
208
- bio.seek(0)
209
- return bio
210
 
211
- def export_docx_bytes(full_tt: Dict[str, Dict], lang_labels: Dict[str,str]) -> io.BytesIO:
212
  doc = Document()
213
- doc.add_heading(lang_labels["title"], level=0)
214
- doc.add_paragraph(lang_labels["subtitle"])
215
 
216
- for class_name, tt in full_tt.items():
217
- doc.add_heading(f"Class {class_name}", level=1)
218
  for day, slots in tt.items():
219
  doc.add_heading(day, level=2)
220
  if not slots:
221
- doc.add_paragraph(f"{lang_labels['holiday_text']}")
222
  else:
223
  table = doc.add_table(rows=1, cols=3)
224
  hdr = table.rows[0].cells
225
- hdr[0].text = lang_labels["time_col"]
226
- hdr[1].text = lang_labels["subject_col"]
227
- hdr[2].text = lang_labels["teacher_col"]
228
  for s in slots:
229
  row = table.add_row().cells
230
  row[0].text = s["time"]
231
  row[1].text = s["subject"]
232
  row[2].text = s["teacher"]
233
- doc.add_paragraph("") # small space
234
- bio = io.BytesIO()
235
- doc.save(bio)
236
- bio.seek(0)
237
- return bio
238
 
239
- def export_text_bytes(full_tt: Dict[str, Dict], lang_labels: Dict[str,str]) -> io.BytesIO:
240
  lines = []
241
- for class_name, tt in full_tt.items():
242
- lines.append(f"Class {class_name}")
243
- lines.append("-" * (8 + len(str(class_name))))
244
  for day, slots in tt.items():
245
  lines.append(f"{day}:")
246
  if not slots:
247
- lines.append(f" {lang_labels['holiday_text']}")
248
  else:
249
  for s in slots:
250
  lines.append(f" {s['time']} | {s['subject']} (Teacher: {s['teacher']})")
251
- lines.append("") # blank line between classes
252
- txt = "\n".join(lines)
253
- bio = io.BytesIO()
254
- bio.write(txt.encode("utf-8"))
255
- bio.seek(0)
256
- return bio
257
 
258
  # ---------------------------
259
- # Gradio app function
260
  # ---------------------------
261
  def build_and_export(
262
  language: str,
@@ -267,66 +267,60 @@ def build_and_export(
267
  start_hour: int,
268
  slot_duration: int,
269
  slots_per_day: int,
270
- export_format: str,
271
- ) -> Tuple[str, Tuple]:
272
  labels = LANG_MAP.get(language, LANG_MAP["English"])
273
 
274
- # parse inputs
275
  days = parse_csv_list(days_input)
276
- days = ensure_days(days) # ensures Sunday included last
277
  subjects = [s.title() for s in parse_csv_list(subjects_input)]
278
  teachers = [t.title() for t in parse_csv_list(teachers_input)]
279
  class_list = parse_csv_list(classes_input)
280
 
281
- # Basic validation
282
  if not days or not subjects or not class_list:
283
- return ("Please enter Days, Subjects and Classes (comma-separated).", None)
284
 
285
  full_tt = generate_full_timetable(class_list, days, subjects, teachers, start_hour, slot_duration, slots_per_day)
286
 
287
- # Create a human readable preview
288
- preview_lines = []
289
  for cls, tt in full_tt.items():
290
- preview_lines.append(f"Class {cls}")
291
- preview_lines.append("-" * (8 + len(str(cls))))
292
  for day, slots in tt.items():
293
- preview_lines.append(f"{day}:")
294
  if not slots:
295
- preview_lines.append(f" {labels['holiday_text']}")
296
  else:
297
  for s in slots:
298
- preview_lines.append(f" {s['time']} | {s['subject']} (Teacher: {s['teacher']})")
299
- preview_lines.append("")
300
- preview_text = "\n".join(preview_lines)
301
 
302
- # Export
303
- file_tuple = None
304
  if export_format == "PDF":
305
- bio = export_pdf_bytes(full_tt, labels)
306
- file_tuple = ("timetable.pdf", bio, "application/pdf")
307
  elif export_format == "Word":
308
- bio = export_docx_bytes(full_tt, labels)
309
- file_tuple = ("timetable.docx", bio, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
310
  elif export_format == "Text":
311
- bio = export_text_bytes(full_tt, labels)
312
- file_tuple = ("timetable.txt", bio, "text/plain")
313
  else:
314
- file_tuple = None
315
 
316
- return preview_text, file_tuple
317
 
318
  # ---------------------------
319
- # Gradio UI
320
  # ---------------------------
321
- with gr.Blocks(title="Automatic Time Table Generation (Improved)", css="""
322
- .gradio-container { max-width: 1100px; margin: auto; }
323
- .title { font-family: 'Helvetica'; }
324
- """) as demo:
325
 
326
- # Top row: language + title
327
  with gr.Row():
328
- language = gr.Dropdown(list(LANG_MAP.keys()), value="English", label="Language / ٻولي / زبان")
329
- gr.Markdown("<h2 style='margin-left:16px'>Automatic Time Table Generation Agent (Improved)</h2>")
330
 
331
  with gr.Row():
332
  with gr.Column(scale=2):
@@ -338,16 +332,16 @@ with gr.Blocks(title="Automatic Time Table Generation (Improved)", css="""
338
  with gr.Row():
339
  start_hour = gr.Slider(6, 10, value=8, step=1, label=LANG_MAP["English"]["start_hour"])
340
  slot_duration = gr.Slider(1, 3, value=1, step=1, label=LANG_MAP["English"]["slot_duration"])
341
- slots_per_day = gr.Number(label=LANG_MAP["English"]["slots_per_day"], value=0)
342
 
 
343
  export_format = gr.Dropdown(["None", "PDF", "Word", "Text"], value="PDF", label=LANG_MAP["English"]["export_format"])
344
  generate_btn = gr.Button(LANG_MAP["English"]["generate_btn"], variant="primary")
345
 
346
  with gr.Column(scale=1):
347
  preview = gr.Textbox(label=LANG_MAP["English"]["preview_title"], lines=20, interactive=False)
348
- download = gr.File(label="Download file")
349
 
350
- # Update label language when dropdown changes (client-side)
351
  def update_labels(selected_lang):
352
  labels = LANG_MAP.get(selected_lang, LANG_MAP["English"])
353
  return (
@@ -359,7 +353,9 @@ with gr.Blocks(title="Automatic Time Table Generation (Improved)", css="""
359
  gr.update(label=labels["slot_duration"]),
360
  gr.update(label=labels["slots_per_day"]),
361
  gr.update(label=labels["export_format"]),
362
- gr.update(value=labels["generate_btn"])
 
 
363
  )
364
 
365
  language.change(
@@ -367,14 +363,15 @@ with gr.Blocks(title="Automatic Time Table Generation (Improved)", css="""
367
  inputs=[language],
368
  outputs=[
369
  days_input, subjects_input, teachers_input, classes_input,
370
- start_hour, slot_duration, slots_per_day, export_format, generate_btn
371
- ]
 
372
  )
373
 
374
  generate_btn.click(
375
  fn=build_and_export,
376
  inputs=[language, days_input, subjects_input, teachers_input, classes_input, start_hour, slot_duration, slots_per_day, export_format],
377
- outputs=[preview, download],
378
  )
379
 
380
  if __name__ == "__main__":
 
1
  """
2
  Automatic Time Table Generation Agent (Improved)
3
+ Professional Gradio app for Hugging Face Spaces.
4
+
5
+ Features:
6
+ - Multi-class timetable (e.g. 1,2,3,4,5)
7
+ - Teacher names (cycles if fewer than subjects)
8
+ - Sunday is holiday automatically
9
+ - Export to PDF, DOCX, or TXT (downloadable)
10
+ - Language options: English, Sindhi, Urdu (UI + headings)
11
+ - Clean, professional PDF styling
12
  """
13
 
14
  import io
15
+ import os
16
  import random
17
+ import tempfile
18
+ from typing import Dict, List, Tuple
19
 
20
  import gradio as gr
21
  from fpdf import FPDF
22
  from docx import Document
23
 
24
  # ---------------------------
25
+ # Localization
26
  # ---------------------------
27
  LANG_MAP = {
28
  "English": {
29
  "title": "Automatic Time Table Generation (Improved)",
30
  "subtitle": "Unique, professional timetables — Sunday is holiday by default.",
31
+ "days_label": "Days (comma-separated; omit Sunday; Sunday will be shown as holiday)",
32
  "subjects_label": "Subjects (comma-separated)",
33
  "teachers_label": "Teachers (comma-separated) — will cycle if fewer than subjects",
34
  "classes_label": "Class list (comma-separated, e.g. 1,2,3,4,5)",
 
37
  "slots_per_day": "Slots per day (0 = use subjects count)",
38
  "export_format": "Export format",
39
  "generate_btn": "Generate & Export",
40
+ "preview_title": "Preview (read-only)",
41
  "holiday_text": "Holiday",
42
  "time_col": "Time",
43
  "subject_col": "Subject",
44
  "teacher_col": "Teacher",
45
+ "download_label": "Download file"
46
  },
47
  "Sindhi": {
 
48
  "title": "ٽائيم ٽيبل جنريٽر (بهتر ٿيل)",
49
+ "subtitle": "منفرد ۽ پيشه ور ٽائيم ٽيبل — آچر موڪت جو ڏينھن آهي.",
50
+ "days_label": "ڏينھن (ڪاما سان الڳ ڪريو، آچر پاڻ شامل ڪيو ويندو)",
51
  "subjects_label": "مضمون (ڪاما سان الڳ ڪريو)",
52
  "teachers_label": "استادن جا نالا (ڪاما سان) — مضمونن کان گهٽ هجن ته ورجايا ويندا",
53
  "classes_label": "ڪلاس لسٽ (ڪاما سان، مثال: 1,2,3,4,5)",
 
56
  "slots_per_day": "في روز سيشن (0 = مضمونن جي ڳڻپ استعمال)",
57
  "export_format": "برآمد جو فارميٽ",
58
  "generate_btn": "تيار ڪريو ۽ برآمد ڪريو",
59
+ "preview_title": "پريوئو (صرف پڙهڻ)",
60
  "holiday_text": "موڪلون",
61
  "time_col": "وقت",
62
  "subject_col": "مضمون",
63
  "teacher_col": "استاد",
64
+ "download_label": "فائل ڊائونلوڊ ڪريو"
65
  },
66
  "Urdu": {
67
  "title": "خودکار وقتِ جدول جنریٹر (بہتر شدہ)",
68
+ "subtitle": "منفرد، پیشہ ورانہ جدول — اتوار تعطیل ہے۔",
69
+ "days_label": "دن (کاما سے جدا کریں، اتوار خود شامل کیا جائے گا)",
70
  "subjects_label": "مضامین (کاما سے جدا کریں)",
71
+ "teachers_label": "اساتذہ (کاما سے جدا کریں) — تعداد کم ہو تو دہرائے جائیں گے",
72
+ "classes_label": "کلاس فہرست (مثال: 1,2,3,4,5)",
73
+ "start_hour": "شروعات کا وقت (24h)",
74
  "slot_duration": "سیشن کا دورانیہ (گھنٹے)",
75
  "slots_per_day": "روزانہ سیٹس (0 = مضامین کی گنتی)",
76
  "export_format": "ایکسپورٹ فارمیٹ",
77
  "generate_btn": "جنریٹ اور ایکسپورٹ کریں",
78
+ "preview_title": "پریویو (صرف پڑھیں)",
79
  "holiday_text": "چھٹی",
80
  "time_col": "وقت",
81
  "subject_col": "مضمون",
82
  "teacher_col": "استاد",
83
+ "download_label": "فائل ڈاؤن لوڈ کریں"
84
  },
85
  }
86
 
 
 
 
87
  HOLIDAY = "Sunday"
88
 
89
+ # ---------------------------
90
+ # Core functions
91
+ # ---------------------------
92
  def parse_csv_list(s: str) -> List[str]:
93
  return [p.strip() for p in s.split(",") if p.strip()]
94
 
95
  def ensure_days(days: List[str]) -> List[str]:
96
+ # normalize and ensure Sunday is included at end
97
+ norm = [d.strip().capitalize() for d in days if d and d.strip()]
98
+ # remove duplicates preserving order
99
+ seen = set()
100
+ ordered = []
101
+ for d in norm:
102
+ if d.lower() not in seen:
103
+ seen.add(d.lower())
104
+ ordered.append(d)
105
+ # remove any 'Sunday' if present and re-add at end
106
+ ordered = [d for d in ordered if d.lower() != HOLIDAY.lower()]
107
+ ordered.append(HOLIDAY)
108
+ return ordered
109
 
110
  def build_teacher_map(subjects: List[str], teachers: List[str]) -> Dict[str, str]:
111
  if not teachers:
 
118
  teachers: List[str],
119
  start_hour: int = 8,
120
  slot_duration: int = 1,
121
+ slots_per_day: int = 0
122
  ) -> Dict[str, List[Dict[str,str]]]:
 
 
 
 
123
  if slots_per_day <= 0:
124
  slots_per_day = len(subjects) if subjects else 0
125
 
 
127
  timetable = {}
128
  for day in days:
129
  if day.lower() == HOLIDAY.lower():
130
+ timetable[day] = [] # holiday
131
  continue
132
 
 
133
  order = subjects.copy()
134
  random.shuffle(order)
135
  day_slots = []
 
150
  teachers: List[str],
151
  start_hour: int,
152
  slot_duration: int,
153
+ slots_per_day: int
154
  ) -> Dict[str, Dict]:
155
+ return {cls: generate_timetable_for_class(days, subjects, teachers, start_hour, slot_duration, slots_per_day) for cls in class_list}
 
 
 
156
 
157
  # ---------------------------
158
+ # Export helpers (create temp files and return path)
159
  # ---------------------------
160
+ def export_pdf_file(full_tt: Dict[str, Dict], labels: Dict[str, str]) -> str:
161
+ pdf = FPDF(orientation="P", unit="mm", format="A4")
 
 
 
 
 
 
 
162
  pdf.set_auto_page_break(auto=True, margin=12)
163
  pdf.add_page()
164
+
165
  # Title
166
  pdf.set_font("Helvetica", "B", 18)
167
+ pdf.cell(0, 10, labels["title"], ln=True, align="C")
168
  pdf.ln(2)
169
  pdf.set_font("Helvetica", "", 11)
170
+ pdf.multi_cell(0, 6, labels["subtitle"])
171
+ pdf.ln(6)
172
 
173
+ for cls, tt in full_tt.items():
 
174
  pdf.set_font("Helvetica", "B", 14)
175
+ pdf.cell(0, 8, f"Class {cls}", ln=True)
176
  pdf.ln(1)
177
 
178
+ # table header
179
  pdf.set_font("Helvetica", "B", 11)
180
  w_time, w_subj, w_teacher = 35, 80, 60
181
+ pdf.cell(w_time, 8, labels["time_col"], border=1)
182
+ pdf.cell(w_subj, 8, labels["subject_col"], border=1)
183
+ pdf.cell(w_teacher, 8, labels["teacher_col"], border=1)
184
  pdf.ln(8)
185
 
186
  pdf.set_font("Helvetica", "", 10)
187
  for day, slots in tt.items():
 
188
  pdf.set_font("Helvetica", "B", 11)
189
  pdf.cell(0, 7, f"{day}", ln=True)
190
  pdf.set_font("Helvetica", "", 10)
191
  if not slots:
192
+ pdf.cell(0, 7, f" {labels['holiday_text']}", ln=True)
 
193
  else:
194
  for s in slots:
195
  pdf.cell(w_time, 7, s["time"], border=1)
196
+ # subject cell may be long; use multi_cell trick within table by moving cursor
197
+ x_before = pdf.get_x()
198
+ y_before = pdf.get_y()
199
+ pdf.multi_cell(w_subj, 7, s["subject"], border=1)
200
+ # after multicell, set cursor for teacher column: move to right of time+subject
201
+ pdf.set_xy(x_before + w_subj, y_before)
202
  pdf.cell(w_teacher, 7, s["teacher"], border=1)
203
  pdf.ln(7)
204
  pdf.ln(1)
205
+ pdf.ln(6)
206
 
207
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
208
+ pdf.output(tmp.name)
209
+ tmp.close()
210
+ return tmp.name
211
 
212
+ def export_docx_file(full_tt: Dict[str, Dict], labels: Dict[str,str]) -> str:
213
  doc = Document()
214
+ doc.add_heading(labels["title"], level=0)
215
+ doc.add_paragraph(labels["subtitle"])
216
 
217
+ for cls, tt in full_tt.items():
218
+ doc.add_heading(f"Class {cls}", level=1)
219
  for day, slots in tt.items():
220
  doc.add_heading(day, level=2)
221
  if not slots:
222
+ doc.add_paragraph(labels["holiday_text"])
223
  else:
224
  table = doc.add_table(rows=1, cols=3)
225
  hdr = table.rows[0].cells
226
+ hdr[0].text = labels["time_col"]
227
+ hdr[1].text = labels["subject_col"]
228
+ hdr[2].text = labels["teacher_col"]
229
  for s in slots:
230
  row = table.add_row().cells
231
  row[0].text = s["time"]
232
  row[1].text = s["subject"]
233
  row[2].text = s["teacher"]
234
+ doc.add_paragraph("")
235
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx")
236
+ doc.save(tmp.name)
237
+ tmp.close()
238
+ return tmp.name
239
 
240
+ def export_text_file(full_tt: Dict[str, Dict], labels: Dict[str,str]) -> str:
241
  lines = []
242
+ for cls, tt in full_tt.items():
243
+ lines.append(f"Class {cls}")
244
+ lines.append("-" * (8 + len(str(cls))))
245
  for day, slots in tt.items():
246
  lines.append(f"{day}:")
247
  if not slots:
248
+ lines.append(f" {labels['holiday_text']}")
249
  else:
250
  for s in slots:
251
  lines.append(f" {s['time']} | {s['subject']} (Teacher: {s['teacher']})")
252
+ lines.append("")
253
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="w", encoding="utf-8")
254
+ tmp.write("\n".join(lines))
255
+ tmp.close()
256
+ return tmp.name
 
257
 
258
  # ---------------------------
259
+ # Main handler for Gradio
260
  # ---------------------------
261
  def build_and_export(
262
  language: str,
 
267
  start_hour: int,
268
  slot_duration: int,
269
  slots_per_day: int,
270
+ export_format: str
271
+ ) -> Tuple[str, object]:
272
  labels = LANG_MAP.get(language, LANG_MAP["English"])
273
 
 
274
  days = parse_csv_list(days_input)
275
+ days = ensure_days(days)
276
  subjects = [s.title() for s in parse_csv_list(subjects_input)]
277
  teachers = [t.title() for t in parse_csv_list(teachers_input)]
278
  class_list = parse_csv_list(classes_input)
279
 
 
280
  if not days or not subjects or not class_list:
281
+ return ("Please enter Days, Subjects and Class list (comma-separated).", None)
282
 
283
  full_tt = generate_full_timetable(class_list, days, subjects, teachers, start_hour, slot_duration, slots_per_day)
284
 
285
+ # Prepare preview text
286
+ lines = []
287
  for cls, tt in full_tt.items():
288
+ lines.append(f"Class {cls}")
289
+ lines.append("-" * (8 + len(str(cls))))
290
  for day, slots in tt.items():
291
+ lines.append(f"{day}:")
292
  if not slots:
293
+ lines.append(f" {labels['holiday_text']}")
294
  else:
295
  for s in slots:
296
+ lines.append(f" {s['time']} | {s['subject']} (Teacher: {s['teacher']})")
297
+ lines.append("")
298
+ preview_text = "\n".join(lines)
299
 
300
+ # Export file and return a path that Gradio can present as a File
 
301
  if export_format == "PDF":
302
+ file_path = export_pdf_file(full_tt, labels)
 
303
  elif export_format == "Word":
304
+ file_path = export_docx_file(full_tt, labels)
 
305
  elif export_format == "Text":
306
+ file_path = export_text_file(full_tt, labels)
 
307
  else:
308
+ file_path = None
309
 
310
+ return preview_text, file_path
311
 
312
  # ---------------------------
313
+ # Gradio UI definition
314
  # ---------------------------
315
+ css = """
316
+ .gradio-container { max-width: 1100px; margin: auto; font-family: Arial, Helvetica, sans-serif; }
317
+ .header-title { font-weight: 700; margin-left: 12px; }
318
+ """
319
 
320
+ with gr.Blocks(title="Automatic Time Table Generation (Improved)", css=css) as demo:
321
  with gr.Row():
322
+ language = gr.Dropdown(list(LANG_MAP.keys()), value="English", label="Language / ٻولي / زبان")
323
+ gr.Markdown("<h2 class='header-title'>Automatic Time Table Generation Agent (Improved)</h2>")
324
 
325
  with gr.Row():
326
  with gr.Column(scale=2):
 
332
  with gr.Row():
333
  start_hour = gr.Slider(6, 10, value=8, step=1, label=LANG_MAP["English"]["start_hour"])
334
  slot_duration = gr.Slider(1, 3, value=1, step=1, label=LANG_MAP["English"]["slot_duration"])
 
335
 
336
+ slots_per_day = gr.Number(label=LANG_MAP["English"]["slots_per_day"], value=0)
337
  export_format = gr.Dropdown(["None", "PDF", "Word", "Text"], value="PDF", label=LANG_MAP["English"]["export_format"])
338
  generate_btn = gr.Button(LANG_MAP["English"]["generate_btn"], variant="primary")
339
 
340
  with gr.Column(scale=1):
341
  preview = gr.Textbox(label=LANG_MAP["English"]["preview_title"], lines=20, interactive=False)
342
+ download = gr.File(label=LANG_MAP["English"]["download_label"])
343
 
344
+ # Update labels when language changes
345
  def update_labels(selected_lang):
346
  labels = LANG_MAP.get(selected_lang, LANG_MAP["English"])
347
  return (
 
353
  gr.update(label=labels["slot_duration"]),
354
  gr.update(label=labels["slots_per_day"]),
355
  gr.update(label=labels["export_format"]),
356
+ gr.update(value=labels["generate_btn"]),
357
+ gr.update(label=labels["download_label"]),
358
+ gr.update(label=labels["preview_title"])
359
  )
360
 
361
  language.change(
 
363
  inputs=[language],
364
  outputs=[
365
  days_input, subjects_input, teachers_input, classes_input,
366
+ start_hour, slot_duration, slots_per_day, export_format, generate_btn,
367
+ download, preview
368
+ ],
369
  )
370
 
371
  generate_btn.click(
372
  fn=build_and_export,
373
  inputs=[language, days_input, subjects_input, teachers_input, classes_input, start_hour, slot_duration, slots_per_day, export_format],
374
+ outputs=[preview, download]
375
  )
376
 
377
  if __name__ == "__main__":