ikmalsaid commited on
Commit
a8e0f28
·
verified ·
1 Parent(s): 3f35bf5

Updated src files

Browse files
Files changed (3) hide show
  1. app.py +1 -1
  2. src/khutbahmaker/__init__.py +234 -0
  3. src/khutbahmaker/webui.py +96 -0
app.py CHANGED
@@ -1,2 +1,2 @@
1
- from khutbahmaker import KhutbahMaker
2
  KhutbahMaker(mode='webui')
 
1
+ from src.khutbahmaker import KhutbahMaker
2
  KhutbahMaker(mode='webui')
src/khutbahmaker/__init__.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import uuid
4
+ import tempfile
5
+ from datetime import datetime
6
+ from colorpaws import ColorPaws
7
+ from google import generativeai as genai
8
+ from markdown_pdf import MarkdownPdf, Section
9
+ from google.generativeai.types import GenerationConfig
10
+ from google.generativeai.types.helper_types import RequestOptions
11
+
12
+ class KhutbahMaker:
13
+ """Copyright (C) 2025 Ikmal Said. All rights reserved"""
14
+
15
+ def __init__(self, mode='default', api_key=None, model='gemini-2.0-flash-thinking-exp-01-21', timeout=180):
16
+ """
17
+ Initialize KhutbahMaker module.
18
+
19
+ Parameters:
20
+ mode (str): Startup mode ('default', 'webui', or 'api')
21
+ api_key (str): API key for AI services
22
+ model (str): AI model to use
23
+ timeout (int): Timeout for AI requests in seconds
24
+ """
25
+ self.logger = ColorPaws(name=self.__class__.__name__, log_on=True, log_to=None)
26
+ self.aigc_model = model
27
+ self.api_key = api_key
28
+ self.timeout = timeout * 1000
29
+
30
+ self.logger.info("KhutbahMaker is ready!")
31
+
32
+ if mode != 'default':
33
+ if mode == 'webui':
34
+ self.start_webui()
35
+ else:
36
+ raise ValueError(f"Invalid startup mode: {mode}")
37
+
38
+ def __clean_markdown(self, text):
39
+ """Clean up markdown text"""
40
+ text = re.sub(r'```[a-zA-Z]*\n', '', text)
41
+ text = re.sub(r'```\n?', '', text)
42
+ return text.strip()
43
+
44
+ def __generate_khutbah(self, topic, length, tone, language, task_id):
45
+ """Generate khutbah content using AI"""
46
+ try:
47
+ if self.api_key:
48
+ genai.configure(api_key=self.api_key)
49
+
50
+ self.logger.info(f"[{task_id}] Generating khutbah: '{topic}' ({language} - {tone})")
51
+
52
+ if length.lower() == 'short':
53
+ length = 'short (approximately 10-15 minutes)'
54
+ elif length.lower() == 'medium':
55
+ length = 'medium (approximately 15-20 minutes)'
56
+ elif length.lower() == 'long':
57
+ length = 'long (approximately 20-30 minutes)'
58
+
59
+ prompt = f"You are an expert Islamic scholar in writing khutbahs. You are required to write a {length} Friday khutbah (sermon) in {language} on the topic on '{topic}' with a {tone.lower()} tone. Create a complete, well-structured Islamic khutbah that includes: 1. An appropriate title 2. Opening with praise to Allah and salutations on Prophet Muhammad (peace be upon him) 3. Introduction to the topic with relevant Quranic verses and Hadith 4. Main body with clear points, explanations, and guidance 5. Practical advice for the audience 6. Conclusion with a summary of key points 7. Closing duas (prayers) The khutbah should be scholarly yet accessible, with proper citations of Quranic verses and authentic Hadith. Write the ENTIRE khutbah in {language} only, including the explanation and translation of Quranic verses and Hadith. Only include Arabic script for Quranic verses and Hadith, followed by their translation in {language}. DO NOT mix the khutbah with other languages and copyrighted materials. DO NOT include any opening or closing remarks."
60
+
61
+ model = genai.GenerativeModel(self.aigc_model)
62
+ response = model.generate_content(prompt, request_options=RequestOptions(timeout=self.timeout), generation_config=GenerationConfig(temperature=1.2))
63
+
64
+ return self.__clean_markdown(response.text)
65
+
66
+ except Exception as e:
67
+ self.logger.error(f"[{task_id}] Khutbah generation failed: {str(e)}")
68
+ return None
69
+
70
+ def __khutbah_to_pdf(self, markdown_text, topic, language, task_id):
71
+ """Convert khutbah markdown to PDF"""
72
+ try:
73
+ clean_topic = re.sub(r'[^\w\-]', '_', topic)
74
+ clean_filename = f"{task_id}_{clean_topic}_khutbah_{language.lower().replace(' ', '_')}"
75
+ pdf_path = os.path.join(tempfile.gettempdir(), f"{clean_filename}.pdf")
76
+
77
+ self.logger.info(f"[{task_id}] Generating PDF: {pdf_path}")
78
+
79
+ # Initialize without TOC to avoid hierarchy issues
80
+ pdf = MarkdownPdf(toc_level=0)
81
+
82
+ # Process markdown to ensure proper header hierarchy
83
+ lines = markdown_text.split('\n')
84
+ processed_lines = []
85
+
86
+ # Check if first line is a header, if not add one
87
+ if not lines or not lines[0].startswith('# '):
88
+ title = f"KhutbahMaker Script ({language})"
89
+ processed_lines.append(f"# {title}")
90
+ processed_lines.append("") # Add blank line after title
91
+
92
+ # Process the rest of the content
93
+ for line in lines:
94
+ processed_lines.append(line)
95
+
96
+ # Join back into a single string
97
+ processed_markdown = '\n'.join(processed_lines)
98
+
99
+ # Add main content section with custom CSS
100
+ css = """
101
+ body {
102
+ font-family: 'Amiri', 'Segoe UI', sans-serif;
103
+ text-align: justify;
104
+ text-justify: inter-word;
105
+ line-height: 1.5;
106
+ }
107
+
108
+ /* Arabic text specific */
109
+ [lang='ar'] {
110
+ direction: rtl;
111
+ font-family: 'Amiri', sans-serif;
112
+ font-size: 20px;
113
+ line-height: 1.8;
114
+ }
115
+
116
+ h1 {
117
+ text-align: center;
118
+ color: #2c3e50;
119
+ margin-top: 1.5em;
120
+ margin-bottom: 0.8em;
121
+ font-size: 1.5em;
122
+ font-weight: 600;
123
+ }
124
+
125
+ h2, h3, h4, h5, h6 {
126
+ color: #34495e;
127
+ margin-top: 1.5em;
128
+ margin-bottom: 0.8em;
129
+ }
130
+
131
+ blockquote {
132
+ background-color: #f9f9f9;
133
+ border-left: 4px solid #4CAF50;
134
+ padding: 10px 15px;
135
+ margin: 15px 0;
136
+ font-style: italic;
137
+ }
138
+
139
+ p {
140
+ margin: 0.8em 0;
141
+ }
142
+ """
143
+
144
+ # Add the main content section - without TOC
145
+ main_section = Section(processed_markdown, toc=False)
146
+ pdf.add_section(main_section, user_css=css)
147
+
148
+ # Set PDF metadata with Unicode support
149
+ pdf.meta["title"] = title
150
+ pdf.meta["subject"] = title
151
+ pdf.meta["author"] = "Ikmal Said"
152
+ pdf.meta["creator"] = "KhutbahMaker"
153
+
154
+ # Save the PDF
155
+ pdf.save(pdf_path)
156
+ return pdf_path
157
+
158
+ except Exception as e:
159
+ self.logger.error(f"[{task_id}] PDF generation failed: {str(e)}")
160
+ return None
161
+
162
+ def __get_taskid(self):
163
+ """
164
+ Generate a unique task ID for request tracking.
165
+ Returns a combination of timestamp and UUID to ensure uniqueness.
166
+ Format: YYYYMMDD_HHMMSS_UUID8
167
+ """
168
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
169
+ uuid_part = str(uuid.uuid4())[:8]
170
+ task_id = f"{timestamp}_{uuid_part}"
171
+ return task_id
172
+
173
+ def generate_khutbah(self, topic, length="short", tone="inspirational", language="bahasa malaysia"):
174
+ """Generate an Islamic khutbah based on specified parameters.
175
+
176
+ Parameters:
177
+ topic (str): The main topic or theme of the khutbah
178
+ length (str): Desired length ('short' | 'long')
179
+ tone (str): Tone of the khutbah ('scholarly' | 'inspirational' | 'practical' | 'reflective' | 'motivational' | 'educational' | 'historical' | 'narrative')
180
+ language (str): Target language ('bahasa malaysia' | 'arabic' | 'english' | 'mandarin' | 'tamil')
181
+ """
182
+ if not topic or topic == "":
183
+ self.logger.error("Topic is required!")
184
+ return None, None
185
+
186
+ if length.lower() not in ['short', 'medium', 'long']:
187
+ self.logger.error("Invalid length!")
188
+ return None, None
189
+
190
+ if tone.lower() not in ['scholarly', 'inspirational', 'practical', 'reflective', 'motivational', 'educational', 'historical', 'narrative']:
191
+ self.logger.error("Invalid tone!")
192
+ return None, None
193
+
194
+ if language.lower() not in ['bahasa malaysia', 'arabic', 'english', 'mandarin', 'tamil']:
195
+ self.logger.error("Invalid language!")
196
+ return None, None
197
+
198
+ task_id = self.__get_taskid()
199
+ self.logger.info(f"[{task_id}] Khutbah generation task started!")
200
+ topic = topic.title()
201
+
202
+ try:
203
+ markdown_text = self.__generate_khutbah(topic, length, tone, language, task_id)
204
+ if not markdown_text:
205
+ return None, None
206
+
207
+ pdf_file = self.__khutbah_to_pdf(markdown_text, topic, language, task_id)
208
+ if not pdf_file:
209
+ return None, None
210
+
211
+ self.logger.info(f"[{task_id}] Khutbah generation complete!")
212
+ return pdf_file, markdown_text
213
+
214
+ except Exception as e:
215
+ self.logger.error(f"[{task_id}] Khutbah generation failed: {str(e)}")
216
+ return None, None
217
+
218
+ def start_webui(self, host: str = None, port: int = None, browser: bool = False, upload_size: str = "4MB",
219
+ public: bool = False, limit: int = 10, quiet: bool = False):
220
+ """
221
+ Start KhutbahMaker WebUI with all features.
222
+
223
+ Parameters:
224
+ - host (str): Server host (default: None)
225
+ - port (int): Server port (default: None)
226
+ - browser (bool): Launch browser automatically (default: False)
227
+ - upload_size (str): Maximum file size for uploads (default: "4MB")
228
+ - public (bool): Enable public URL mode (default: False)
229
+ - limit (int): Maximum number of concurrent requests (default: 10)
230
+ - quiet (bool): Enable quiet mode (default: False)
231
+ """
232
+ from .webui import KhutbahMakerWebUI
233
+ KhutbahMakerWebUI(self, host=host, port=port, browser=browser, upload_size=upload_size,
234
+ public=public, limit=limit, quiet=quiet)
src/khutbahmaker/webui.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ def KhutbahMakerWebUI(client, host: str = None, port: int = None, browser: bool = True, upload_size: str = "4MB",
4
+ public: bool = False, limit: int = 10, quiet: bool = True):
5
+ """
6
+ Start KhutbahMaker Web UI with all features.
7
+
8
+ Parameters:
9
+ - client (Client): KhutbahMaker instance
10
+ - host (str): Server host
11
+ - port (int): Server port
12
+ - browser (bool): Launch browser automatically
13
+ - upload_size (str): Maximum file size for uploads
14
+ - public (bool): Enable public URL mode
15
+ - limit (int): Maximum number of concurrent requests
16
+ - quiet (bool): Enable quiet mode
17
+ """
18
+ try:
19
+ gr_css = """
20
+ footer {
21
+ display: none !important;
22
+ }
23
+ """
24
+
25
+ gr_theme = gr.themes.Default(
26
+ primary_hue="purple",
27
+ secondary_hue="purple",
28
+ neutral_hue=gr.themes.colors.zinc,
29
+ font=["Amiri", "system-ui", "sans-serif"]
30
+ )
31
+
32
+ with gr.Blocks(title="KhutbahMaker", analytics_enabled=False, theme=gr_theme, css=gr_css).queue(default_concurrency_limit=limit) as demo:
33
+ gr.Markdown("## <br><center>KhutbahMaker Web UI")
34
+ gr.Markdown("<center>Made for #GodamSahur 2025 by Ikmal Said")
35
+ gr.Markdown("<center>")
36
+
37
+ with gr.Row():
38
+ with gr.Column():
39
+ with gr.Tab("Settings"):
40
+ with gr.Column(scale=1):
41
+ khutbah_topic = gr.Textbox(
42
+ label="Khutbah Topic",
43
+ lines=1, max_lines=1,
44
+ placeholder="Type your topic here...",
45
+ info="Enter the main topic or theme for the khutbah"
46
+ )
47
+
48
+ khutbah_language = gr.Dropdown(
49
+ value="Bahasa Malaysia",
50
+ choices=["Bahasa Malaysia", "Arabic", "English", "Mandarin", "Tamil"],
51
+ label="Khutbah Language",
52
+ info="Select the language of the khutbah"
53
+ )
54
+
55
+ khutbah_length = gr.Dropdown(
56
+ value="Short",
57
+ choices=["Short", "Medium", "Long"],
58
+ label="Khutbah Length",
59
+ info="Short (10-15 minutes), Medium (15-20 minutes) or Long (20-30 minutes)"
60
+ )
61
+
62
+ khutbah_tone = gr.Dropdown(
63
+ value="Scholarly",
64
+ choices=["Scholarly", "Inspirational", "Practical", "Reflective", "Motivational", "Educational", "Historical", "Narrative"],
65
+ label="Khutbah Tone",
66
+ info="Select the tone of the khutbah based on the topic"
67
+ )
68
+
69
+ khutbah_btn = gr.Button("Generate Khutbah", variant="primary")
70
+
71
+ with gr.Column():
72
+ with gr.Tab("Results"):
73
+ with gr.Column(scale=1):
74
+ khutbah_output = gr.File(label="Download Khutbah as PDF")
75
+ with gr.Accordion("Read Khutbah as Text"):
76
+ khutbah_text = gr.Markdown(value="Please generate khutbah first for reading!", height=300)
77
+
78
+ gr.Markdown("<center>")
79
+ gr.Markdown("<center>KhutbahMaker can make mistakes. Check important info.")
80
+ gr.Markdown("<center>")
81
+
82
+ # Setup event handlers
83
+ khutbah_btn.click(fn=client.generate_khutbah, inputs=[khutbah_topic, khutbah_length, khutbah_tone, khutbah_language], outputs=[khutbah_output, khutbah_text])
84
+
85
+ demo.launch(
86
+ server_name=host,
87
+ server_port=port,
88
+ inbrowser=browser,
89
+ max_file_size=upload_size,
90
+ share=public,
91
+ quiet=quiet
92
+ )
93
+
94
+ except Exception as e:
95
+ client.logger.error(f"{str(e)}")
96
+ raise