zaiffi commited on
Commit
7a745a0
·
1 Parent(s): 6511fd7

Add application files

Browse files
Files changed (3) hide show
  1. README.md +1 -14
  2. app.py +576 -0
  3. requirements.txt +3 -0
README.md CHANGED
@@ -1,14 +1 @@
1
- ---
2
- title: Mehfil E Sukhan
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: pink
6
- sdk: streamlit
7
- sdk_version: 1.43.2
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: Bi-LSTM crafts Roman Urdu poetry—fine-tuned & deployed!
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # Mehfil-e-Sukhan
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ import sentencepiece as spm
6
+ import streamlit as st
7
+ import time
8
+ import asyncio
9
+ import sys
10
+
11
+ os.environ["STREAMLIT_WATCHED_FILES"] = ""
12
+ if sys.platform.startswith("win"):
13
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
14
+
15
+ torch.classes.__path__ = [os.path.join(torch.__path__[0], torch.classes.__file__)]
16
+
17
+ # # or simply:
18
+ # torch.classes.__path__ = []
19
+
20
+ # Page configuration
21
+ st.set_page_config(
22
+ page_title="Mehfil-e-Sukhan",
23
+ page_icon="📜",
24
+ layout="wide" # Using wide layout for side-by-side content
25
+ )
26
+
27
+
28
+ # Custom spinner implementation
29
+ def custom_spinner():
30
+ spinner_html = """
31
+ <style>
32
+ .custom-spinner {
33
+ margin-left: 60px;
34
+ margin-top: 20px;
35
+ display: flex;
36
+ align-items: center;
37
+ }
38
+ .custom-spinner-circle {
39
+ width: 20px;
40
+ height: 20px;
41
+ border-radius: 50%;
42
+ border: 3px solid rgba(230, 74, 74, 0.3);
43
+ border-top: 3px solid #E64A4A;
44
+ animation: spin 1s linear infinite;
45
+ margin-right: 10px;
46
+ }
47
+ .custom-spinner-text {
48
+ font-family: 'Source Sans Pro', sans-serif;
49
+ color: #F5F5F5;
50
+ }
51
+ @keyframes spin {
52
+ 0% { transform: rotate(0deg); }
53
+ 100% { transform: rotate(360deg); }
54
+ }
55
+ </style>
56
+ <div class="custom-spinner">
57
+ <div class="custom-spinner-circle"></div>
58
+ <div class="custom-spinner-text">Creating your poetry...</div>
59
+ </div>
60
+ """
61
+ return spinner_html
62
+
63
+ # Apply custom CSS for a professional UI with true vertical split
64
+ st.markdown("""
65
+ <style>
66
+ /* Import professional fonts */
67
+ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&family=Source+Sans+Pro:wght@300;400;600;700&display=swap');
68
+
69
+ /* Global styles */
70
+ .stApp {
71
+ background-color: #0D0D0D;
72
+ color: #F5F5F5;
73
+ }
74
+
75
+ /* Split screen layout - true vertical split with gap */
76
+ .main-layout {
77
+ display: flex;
78
+ width: 100%;
79
+ }
80
+
81
+ .left-column {
82
+ width: 30%;
83
+ background-color: #141414;
84
+ padding: 2rem;
85
+ box-sizing: border-box;
86
+ }
87
+
88
+ .right-column {
89
+ width: 60%;
90
+ background-color: #1C1C1C;
91
+ padding: 2rem;
92
+ box-sizing: border-box;
93
+ margin-left: 10%;
94
+ }
95
+
96
+ /* Title styling */
97
+ .title {
98
+ font-family: 'Playfair Display', serif;
99
+ color: #E64A4A;
100
+ text-align: center;
101
+ font-size: 2.8rem;
102
+ font-weight: 700;
103
+ margin-bottom: 5px;
104
+ letter-spacing: 1.2px;
105
+ }
106
+
107
+ .subtitle {
108
+ font-family: 'Playfair Display', serif;
109
+ color: #F5F5F5;
110
+ text-align: center;
111
+ font-size: 1.3rem;
112
+ margin-bottom: 40px;
113
+ font-style: italic;
114
+ letter-spacing: 0.8px;
115
+ opacity: 0.85;
116
+ }
117
+
118
+ /* Panel titles */
119
+ .panel-title {
120
+ font-family: 'Playfair Display', serif;
121
+ color: #E64A4A;
122
+ font-size: 1.5rem;
123
+ font-weight: 600;
124
+ margin-bottom: 25px;
125
+ letter-spacing: 0.8px;
126
+ border-bottom: 1px solid rgba(230, 74, 74, 0.3);
127
+ padding-bottom: 10px;
128
+ margin-left: 5px;
129
+ margin-right: 40px;
130
+ width: fit-content;
131
+ }
132
+
133
+ /* Button styling */
134
+ .stButton > button {
135
+ background-color: #E64A4A;
136
+ color: white;
137
+ width: auto !important;
138
+ transition: all 0.3s ease;
139
+ border: none;
140
+ font-family: 'Source Sans Pro', sans-serif;
141
+ font-weight: 600;
142
+ letter-spacing: 0.6px;
143
+ height: 50px;
144
+ border-radius: 4px;
145
+ margin-left: 60px;
146
+ padding-left: 30px;
147
+ padding-right: 30px;
148
+ display: inline-block;
149
+ }
150
+
151
+ .stButton > button:hover {
152
+ background-color: #C52E2E;
153
+ transform: translateY(-2px);
154
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
155
+ }
156
+
157
+
158
+ /* Input wrapper to handle container margins */
159
+ .stTextInput > div {
160
+ margin-left: 60px;
161
+ margin-right: 60px; /* Changed from 100px to 60px to match poetry box */
162
+ width: calc(100% - 120px); /* Added width calculation */
163
+ }
164
+
165
+ /* Input fields */
166
+ .stTextInput > div > div > input {
167
+ border-radius: 4px;
168
+ border: 1px solid rgba(245, 245, 245, 0.2);
169
+ background-color: rgba(245, 245, 245, 0.05);
170
+ color: #000000;
171
+ font-family: 'Source Sans Pro', sans-serif;
172
+ height: 50px;
173
+ width: 100%; /* Make it use full width of container */
174
+ }
175
+
176
+
177
+ /* Poetry output box */
178
+ .poetry-box {
179
+ background-color: rgba(245, 245, 245, 0.03);
180
+ border-radius: 8px;
181
+ padding: 35px;
182
+ border: 1px solid rgba(245, 245, 245, 0.08);
183
+ border-left: 4px solid #E64A4A;
184
+ min-height: 250px;
185
+ width: calc(100% - 160px);
186
+ font-size: 1.3rem;
187
+ line-height: 2;
188
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
189
+ transition: all 0.5s ease;
190
+ margin-top: 20px;
191
+ animation: fadeIn 0.6s ease-out forwards;
192
+ margin-left: 60px;
193
+ margin-right: 10px;
194
+ }
195
+
196
+ @keyframes fadeIn {
197
+ from { opacity: 0; transform: translateY(10px); }
198
+ to { opacity: 1; transform: translateY(0); }
199
+ }
200
+
201
+ /* Control labels */
202
+ .control-label {
203
+ font-family: 'Source Sans Pro', sans-serif;
204
+ font-weight: 600;
205
+ color: #E64A4A;
206
+ margin-bottom: 5px;
207
+ font-size: 1.05rem;
208
+ letter-spacing: 0.5px;
209
+ margin-left: 5px;
210
+ margin-right: 40px;
211
+ }
212
+
213
+ /* Description text */
214
+ .description-text {
215
+ font-family: 'Source Sans Pro', sans-serif;
216
+ color: rgba(245, 245, 245, 0.7);
217
+ font-size: 0.9rem;
218
+ margin-bottom: 5px;
219
+ font-style: italic;
220
+ margin-left: 5px;
221
+ margin-right: 40px;
222
+ }
223
+
224
+ /* Slider styling */
225
+ .stSlider div[data-baseweb="slider"] div {
226
+ background-color: transparent;
227
+ border: none;
228
+ box-shadow: none;
229
+ margin-left: 5px;
230
+ margin-right: 40px;
231
+ }
232
+
233
+ .stSlider div[data-baseweb="slider"] div div div {
234
+ background-color: transparent;
235
+ border: none;
236
+ box-shadow: none;
237
+ }
238
+
239
+ .stSlider div[data-baseweb="slider"] > div > div {
240
+ background-color: transparent !important;
241
+ }
242
+
243
+ .stSlider div[data-baseweb="slider"] div[role="slider"] {
244
+ background: #E64A4A;
245
+ border: none;
246
+ box-shadow: none;
247
+ }
248
+
249
+ .stSlider [data-testid="stTickBarMin"],
250
+ .stSlider [data-testid="stTickBarMax"] {
251
+ color: #F5F5F5;
252
+ background: none !important;
253
+ border: none;
254
+ box-shadow: none;
255
+ }
256
+
257
+ /* Select boxes */
258
+ .stSelectbox {
259
+ margin-bottom: 5px;
260
+ margin-left: 5px;
261
+ margin-right: 40px;
262
+ }
263
+
264
+ .stSelectbox div {
265
+ background-color: rgba(245, 245, 245, 0.05);
266
+ color: #F5F5F5;
267
+ font-family: 'Source Sans Pro', sans-serif;
268
+ }
269
+
270
+ .stSelectbox div[data-baseweb="select"] > div {
271
+ background-color: rgba(245, 245, 245, 0.05);
272
+ border-color: rgba(245, 245, 245, 0.2);
273
+ }
274
+
275
+ /* Input container */
276
+ .input-container {
277
+ margin-bottom: 30px;
278
+ margin-left: 60px;
279
+ margin-right: 100px;
280
+ }
281
+
282
+ /* Starting word label with left margin */
283
+ .starting-word-label {
284
+ font-family: 'Source Sans Pro', sans-serif;
285
+ font-weight: 600;
286
+ color: #E64A4A;
287
+ margin-bottom: 5px;
288
+ font-size: 1.05rem;
289
+ letter-spacing: 0.5px;
290
+ margin-left: 60px;
291
+ margin-right: 40px;
292
+ margin-top: 50px;
293
+ }
294
+
295
+ /* Footer */
296
+ .footer {
297
+ text-align: center;
298
+ width: 100%;
299
+ margin-top: 40px;
300
+ padding: 20px;
301
+ color: rgba(245, 245, 245, 0.6);
302
+ font-style: italic;
303
+ border-top: 1px solid rgba(245, 245, 245, 0.08);
304
+ font-family: 'Playfair Display', serif;
305
+ }
306
+
307
+ /* Indented lines */
308
+ .indented-line {
309
+ padding-left: 35px;
310
+ }
311
+
312
+ /* Control items */
313
+ .control-item {
314
+ margin-bottom: 5px;
315
+ margin-left: 5px;
316
+ margin-right: 40px;
317
+ }
318
+
319
+ /* Poetry text */
320
+ .poetry-text {
321
+ font-family: 'Playfair Display', serif;
322
+ font-size: 1.3rem;
323
+ line-height: 2;
324
+ white-space: pre-wrap;
325
+ color: #F5F5F5;
326
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
327
+ }
328
+
329
+ /* Right column top margin */
330
+ .right-column-content {
331
+ margin-top: 30px;
332
+ }
333
+
334
+ /* Button container for width control */
335
+ .button-container {
336
+ text-align: left;
337
+ margin-left: 60px;
338
+ }
339
+
340
+
341
+
342
+ /* Hide elements */
343
+ #MainMenu {visibility: hidden;}
344
+ footer {visibility: hidden;}
345
+ </style>
346
+ """, unsafe_allow_html=True)
347
+
348
+ # -------------------------------
349
+ # 1. Model Definition
350
+ # -------------------------------
351
+ class BiLSTMLanguageModel(nn.Module):
352
+ def __init__(self, vocab_size, embed_dim=512, hidden_dim=768, num_layers=3, dropout=0.2):
353
+ super(BiLSTMLanguageModel, self).__init__()
354
+ self.embed = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
355
+ self.lstm = nn.LSTM(
356
+ input_size=embed_dim,
357
+ hidden_size=hidden_dim,
358
+ num_layers=num_layers,
359
+ batch_first=True,
360
+ bidirectional=True,
361
+ dropout=dropout
362
+ )
363
+ self.fc = nn.Linear(hidden_dim * 2, vocab_size)
364
+
365
+ def forward(self, x, hidden=None):
366
+ emb = self.embed(x)
367
+ out, hidden = self.lstm(emb, hidden)
368
+ logits = self.fc(out)
369
+ return logits, hidden
370
+
371
+ # -------------------------------
372
+ # 2. Poetry Generation Function
373
+ # -------------------------------
374
+ @st.cache_resource
375
+ def load_models():
376
+ # Load SentencePiece Model
377
+ sp_model_path = "urdu_sp.model"
378
+ sp = spm.SentencePieceProcessor()
379
+
380
+ if not os.path.exists(sp_model_path):
381
+ st.error("SentencePiece model not found! Make sure urdu_sp.model is present.")
382
+ return None, None
383
+
384
+ sp.load(sp_model_path)
385
+
386
+ # Initialize & Load the Trained Model
387
+ vocab_size = sp.get_piece_size()
388
+ model = BiLSTMLanguageModel(vocab_size, embed_dim=512, hidden_dim=768, num_layers=3, dropout=0.2)
389
+
390
+ weights_path = "model_weights.pth"
391
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
392
+ model.to(device)
393
+
394
+ if not os.path.exists(weights_path):
395
+ st.error("Trained model weights not found! Make sure model_weights.pth is present.")
396
+ return sp, None
397
+
398
+ model.load_state_dict(torch.load(weights_path, map_location=device))
399
+ model.eval()
400
+
401
+ return sp, model
402
+
403
+ def generate_poetry_nucleus(model, sp, start_word, num_words=12, temperature=1.2, top_p=0.85):
404
+ """
405
+ Generates poetry using nucleus (top-p) sampling.
406
+ 'num_words' means 1 starting word + (num_words - 1) generated tokens.
407
+ Output is formatted to 6 words per line.
408
+ """
409
+ device = next(model.parameters()).device
410
+ start_ids = sp.encode_as_ids(start_word)
411
+ input_ids = [2] + start_ids # BOS token=2
412
+ input_tensor = torch.tensor([input_ids], dtype=torch.long, device=device)
413
+
414
+ with torch.no_grad():
415
+ logits, hidden = model(input_tensor)
416
+
417
+ generated_ids = input_ids[:]
418
+
419
+ for _ in range(num_words - 1): # generate one less token than num_words
420
+ last_logits = logits[:, -1, :]
421
+ scaled_logits = last_logits / temperature
422
+
423
+ sorted_logits, sorted_indices = torch.sort(scaled_logits, descending=True)
424
+ cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
425
+ filtered_indices = cumulative_probs > top_p
426
+
427
+ if torch.all(filtered_indices):
428
+ filtered_indices[-1] = False
429
+
430
+ sorted_indices = sorted_indices[~filtered_indices]
431
+ sorted_logits = sorted_logits[~filtered_indices]
432
+
433
+ if len(sorted_indices) > 0:
434
+ next_token_id = sorted_indices[torch.multinomial(F.softmax(sorted_logits, dim=-1), 1).item()].item()
435
+ else:
436
+ next_token_id = torch.argmax(last_logits).item()
437
+
438
+ generated_ids.append(next_token_id)
439
+
440
+ next_input = torch.tensor([[next_token_id]], dtype=torch.long, device=device)
441
+ logits, hidden = model(next_input, hidden)
442
+
443
+ # Decode & format with indentation for every second line
444
+ generated_text = sp.decode_ids(generated_ids[1:]) # skip BOS
445
+ words = generated_text.split()
446
+
447
+ # Group words into lines of 6 words each
448
+ lines = []
449
+ for i in range(0, len(words), 6):
450
+ line = " ".join(words[i:i+6])
451
+ lines.append(line)
452
+
453
+ # Format lines with indentation for even-numbered lines (0-indexed)
454
+ formatted_lines = []
455
+ for i, line in enumerate(lines):
456
+ if i % 2 == 1: # Every second line (1, 3, 5...)
457
+ formatted_lines.append(f'<div class="indented-line">{line}</div>')
458
+ else:
459
+ formatted_lines.append(f'<div>{line}</div>')
460
+
461
+ formatted_text = "\n".join(formatted_lines)
462
+ return formatted_text
463
+
464
+ # -------------------------------
465
+ # 3. Main Application
466
+ # -------------------------------
467
+ def main():
468
+ # Load models
469
+ sp, model = load_models()
470
+
471
+ # Setup session state for storing poetry
472
+ if 'poetry' not in st.session_state:
473
+ st.session_state.poetry = ""
474
+
475
+ # Define spinner_placeholder at the function level, before it's used
476
+ spinner_placeholder = st.empty() # Define it here, at the main function level
477
+
478
+ # Title and subtitle
479
+ st.markdown('<h1 class="title">Mehfil-e-Sukhan</h1>', unsafe_allow_html=True)
480
+ st.markdown('<p class="subtitle">Har Lafz Ek Mehfil</p>', unsafe_allow_html=True)
481
+
482
+ # Create the true vertical split
483
+ col1, col2 = st.columns([3, 7]) # 30% left, 70% right
484
+
485
+ # Left Column (30%) - Control Settings
486
+ with col1:
487
+
488
+ st.markdown('<h2 class="panel-title">Control Settings</h2>', unsafe_allow_html=True)
489
+
490
+ # Number of Words Control
491
+ st.markdown('<div class="control-item">', unsafe_allow_html=True)
492
+ st.markdown('<div class="control-label">Number of Words</div>', unsafe_allow_html=True)
493
+ st.markdown('<div class="description-text">Total words in the generated poetry</div>', unsafe_allow_html=True)
494
+ # Number of Words Control
495
+ num_words = st.selectbox("Number of Words", options=[12, 18, 24, 30, 36, 42, 48], index=0, label_visibility="collapsed")
496
+ st.markdown('</div>', unsafe_allow_html=True)
497
+
498
+ # Creativity (Temperature) Control
499
+ st.markdown('<div class="control-item">', unsafe_allow_html=True)
500
+ st.markdown('<div class="control-label">Creativity</div>', unsafe_allow_html=True)
501
+ st.markdown('<div class="description-text">Higher values generate more unique poetry</div>', unsafe_allow_html=True)
502
+ # Creativity (Temperature) Control
503
+ temperature = st.slider("Creativity", 0.5, 2.0, 1.2, 0.1, key="creativity", label_visibility="collapsed")
504
+ st.markdown('</div>', unsafe_allow_html=True)
505
+
506
+ # Focus (Top-p) Control
507
+ st.markdown('<div class="control-item">', unsafe_allow_html=True)
508
+ st.markdown('<div class="control-label">Focus</div>', unsafe_allow_html=True)
509
+ st.markdown('<div class="description-text">Higher focus makes the AI stick to probable words</div>', unsafe_allow_html=True)
510
+ # Focus (Top-p) Control
511
+ top_p = st.slider("Focus", 0.5, 1.0, 0.85, 0.05, key="focus", label_visibility="collapsed")
512
+ st.markdown('</div>', unsafe_allow_html=True)
513
+
514
+ st.markdown('</div>', unsafe_allow_html=True)
515
+
516
+ # Right Column (70%) - Input and Output
517
+ with col2:
518
+ # Add a container with top margin
519
+ st.markdown('<div class="right-column-content">', unsafe_allow_html=True)
520
+
521
+ # Starting Word Input with proper left margin
522
+ st.markdown('<div class="starting-word-label">Starting Word/Phrase</div>', unsafe_allow_html=True)
523
+ # Starting Word Input
524
+ start_word = st.text_input("Starting Word/Phrase", value="ishq", placeholder="Enter a Roman Urdu word", label_visibility="collapsed")
525
+
526
+ # Generate Button with custom width
527
+ st.markdown('<div class="button-container">', unsafe_allow_html=True)
528
+ generate_button = st.button("Generate Poetry")
529
+ st.markdown('</div>', unsafe_allow_html=True)
530
+
531
+ # And then in your button click handler:
532
+ if generate_button:
533
+ if not sp or not model:
534
+ st.error("Models not properly loaded. Please check the model files.")
535
+ else:
536
+ # Create a placeholder for the spinner
537
+ spinner_placeholder = st.empty()
538
+
539
+ # Show custom spinner
540
+ spinner_placeholder.markdown(custom_spinner(), unsafe_allow_html=True)
541
+
542
+ # Generate poetry
543
+ time.sleep(0.1) # Add slight delay for smooth transition
544
+ st.session_state.poetry = generate_poetry_nucleus(
545
+ model,
546
+ sp,
547
+ start_word,
548
+ num_words=num_words,
549
+ temperature=temperature,
550
+ top_p=top_p
551
+ )
552
+
553
+ # Clear the spinner once poetry is generated
554
+ spinner_placeholder.empty()
555
+
556
+ # Display generated poetry
557
+ if st.session_state.poetry:
558
+ st.markdown(f"""
559
+ <div class="poetry-box">
560
+ <div class="poetry-text">
561
+ {st.session_state.poetry}
562
+ </div>
563
+ </div>
564
+ """, unsafe_allow_html=True)
565
+
566
+ st.markdown('</div>', unsafe_allow_html=True)
567
+
568
+ # Footer with quote
569
+ st.markdown("""
570
+ <div class="footer">
571
+ "Poetry is the rhythmical creation of beauty in words" - Edgar Allan Poe
572
+ </div>
573
+ """, unsafe_allow_html=True)
574
+
575
+ if __name__ == "__main__":
576
+ main()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ torch==2.6.0
2
+ sentencepiece==0.2.0
3
+ streamlit==1.43.0