tonyhui2234 commited on
Commit
6669689
·
verified ·
1 Parent(s): d2ce2d0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +324 -0
app.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import random
3
+ import pandas as pd
4
+ import requests
5
+ from io import BytesIO
6
+ from PIL import Image
7
+ from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM
8
+ import re
9
+ import time
10
+
11
+ # --------------------------- Configuration & CSS ---------------------------
12
+ MAX_SIZE = (450, 450)
13
+
14
+ st.set_page_config(page_title="🔮 Divine Fortune Teller", page_icon=":crystal_ball:")
15
+
16
+ # Updated CSS: added rules to force text color to black for inputs, text areas, and markdown
17
+ st.markdown(
18
+ """
19
+ <style>
20
+ .reportview-container {
21
+ background: linear-gradient(135deg, #f6d365, #fda085);
22
+ }
23
+ .card {
24
+ background: rgba(255, 255, 255, 0.95);
25
+ padding: 30px;
26
+ border-radius: 12px;
27
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
28
+ max-width: 800px;
29
+ margin: auto;
30
+ text-align: center;
31
+ }
32
+ /* Force all text to be black */
33
+ body, input, textarea, .stMarkdown, label {
34
+ color: black !important;
35
+ }
36
+ </style>
37
+ """,
38
+ unsafe_allow_html=True,
39
+ )
40
+
41
+ # --------------------------- Session State Initialization ---------------------------
42
+ if 'submitted' not in st.session_state:
43
+ st.session_state.submitted = False
44
+ if 'error_message' not in st.session_state:
45
+ st.session_state.error_message = ""
46
+ if 'question' not in st.session_state:
47
+ st.session_state.question = ""
48
+ if 'fortune_number' not in st.session_state:
49
+ st.session_state.fortune_number = None
50
+ if 'fortune_row' not in st.session_state:
51
+ st.session_state.fortune_row = None
52
+ if "button_count_temp" not in st.session_state:
53
+ st.session_state.button_count_temp = 0
54
+ if "cfu_explain_text" not in st.session_state:
55
+ st.session_state.cfu_explain_text = ""
56
+
57
+ # --------------------------- Load Fortune CSV ---------------------------
58
+ if "fortune_data" not in st.session_state:
59
+ try:
60
+ st.session_state.fortune_data = pd.read_csv("/home/user/app/resources/detail.csv")
61
+ except Exception as e:
62
+ st.error(f"Error loading CSV: {e}")
63
+ st.session_state.fortune_data = None
64
+
65
+ # --------------------------- Helper Functions ---------------------------
66
+ def load_and_resize_image(path, max_size=MAX_SIZE):
67
+ """
68
+ Loads an image from a local file path and resizes it to fit within a specified maximum size.
69
+ """
70
+ try:
71
+ img = Image.open(path)
72
+ img.thumbnail(max_size, Image.Resampling.LANCZOS)
73
+ return img
74
+ except Exception as e:
75
+ st.error(f"Error loading image: {e}")
76
+ return None
77
+
78
+ def download_and_resize_image(url, max_size=MAX_SIZE):
79
+ """
80
+ Downloads an image from a given URL, then resizes it to a predefined maximum size.
81
+ """
82
+ try:
83
+ response = requests.get(url)
84
+ response.raise_for_status()
85
+ image_bytes = BytesIO(response.content)
86
+ img = Image.open(image_bytes)
87
+ img.thumbnail(max_size, Image.Resampling.LANCZOS)
88
+ return img
89
+ except Exception as e:
90
+ st.error(f"Error loading image from URL: {e}")
91
+ return None
92
+
93
+ def display_text_field(label, text, height):
94
+ """
95
+ Creates and displays a custom-styled text field with a title and scrollable content.
96
+ """
97
+ html = f"""
98
+ <h6 style="display: block; margin-top: 10px;">{label}</h6>
99
+ <div style="border: 1px solid #ccc; border-radius: 4px; background-color: #f0f0f0;
100
+ padding: 10px; height: {height}px; overflow-y: auto; color: black; font-size: 16px;">
101
+ <div>{text}</div>
102
+ </div>
103
+ """
104
+ st.markdown(html, unsafe_allow_html=True)
105
+
106
+ # --------------------------- Model Functions ---------------------------
107
+ def load_finetuned_classifier_model(question):
108
+ """
109
+ Uses a fine-tuned text classification model to categorize the user's question into one of several predefined fortune themes.
110
+ """
111
+ label_list = ["Geomancy", "Lost Property", "Personal Well-Being", "Future Prospect", "Traveling"]
112
+ mapping = {f"LABEL_{i}": label for i, label in enumerate(label_list)}
113
+ pipe = pipeline("text-classification", model="tonyhui2234/CustomModel_classifier_model_10")
114
+ prediction = pipe(question)[0]['label']
115
+ predicted_label = mapping.get(prediction, prediction)
116
+ return predicted_label
117
+
118
+ def generate_answer(question, fortune):
119
+ """
120
+ Generates a detailed explanation by feeding the question and the selected fortune text into a fine-tuned sequence-to-sequence language model.
121
+ """
122
+ start_time = time.perf_counter()
123
+ tokenizer = AutoTokenizer.from_pretrained("tonyhui2234/finetuned_model_text_gen")
124
+ model = AutoModelForSeq2SeqLM.from_pretrained("tonyhui2234/finetuned_model_text_gen", device_map="auto")
125
+ input_text = "Question: " + question + " Fortune: " + fortune
126
+ inputs = tokenizer(input_text, return_tensors="pt", truncation=True)
127
+ outputs = model.generate(
128
+ **inputs,
129
+ max_length=256,
130
+ num_beams=4,
131
+ early_stopping=True,
132
+ repetition_penalty=2.0,
133
+ no_repeat_ngram_size=3
134
+ )
135
+ answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
136
+ run_time = time.perf_counter() - start_time
137
+ print(f"Runtime: {run_time:.4f} seconds")
138
+ return answer
139
+
140
+ def analysis(row_detail, classifiy, question):
141
+ """
142
+ Extracts a specific portion of the fortune details based on the classification result and then generates an answer using the text generation model.
143
+ """
144
+ pattern = re.compile(re.escape(classifiy) + r":\s*(.*?)(?:\.|$)", re.IGNORECASE)
145
+ match = pattern.search(row_detail)
146
+ if match:
147
+ result = match.group(1)
148
+ return generate_answer(question, result)
149
+ else:
150
+ return "Heaven's secret cannot be revealed."
151
+
152
+ def check_sentence_is_english_model(question):
153
+ """
154
+ Checks if the provided text is in English using a language detection model.
155
+ """
156
+ pipe_english = pipeline("text-classification", model="eleldar/language-detection")
157
+ return pipe_english(question)[0]['label'] == 'en'
158
+
159
+ def check_sentence_is_question_model(question):
160
+ """
161
+ Determines whether the input text is formulated as a question using a question vs. statement classifier.
162
+ """
163
+ pipe_question = pipeline("text-classification", model="shahrukhx01/question-vs-statement-classifier")
164
+ return pipe_question(question)[0]['label'] == 'LABEL_1'
165
+
166
+ # --------------------------- Callback Functions ---------------------------
167
+ def random_draw():
168
+ """
169
+ Randomly selects a fortune entry from the loaded CSV based on a randomly generated number and updates the session state with the fortune’s details.
170
+ """
171
+ st.session_state.fortune_number = random.randint(1, 100)
172
+ df = st.session_state.fortune_data
173
+ if df is not None:
174
+ matching_row = df[df['CNumber'] == st.session_state.fortune_number]
175
+ if not matching_row.empty:
176
+ row = matching_row.iloc[0]
177
+ st.session_state.fortune_row = {
178
+ "Header": row.get("Header", "N/A"),
179
+ "Luck": row.get("Luck", "N/A"),
180
+ "Description": row.get("Description", "No description available."),
181
+ "Detail": row.get("Detail", "No detail available."),
182
+ "HeaderLink": row.get("link", None)
183
+ }
184
+ else:
185
+ st.session_state.fortune_row = {
186
+ "Header": "N/A",
187
+ "Luck": "N/A",
188
+ "Description": "No description available.",
189
+ "Detail": "No detail available.",
190
+ "HeaderLink": None
191
+ }
192
+ else:
193
+ st.session_state.error_message = "Fortune data is not available."
194
+
195
+ st.session_state.submitted = True
196
+ st.session_state.show_explain = False
197
+
198
+ def submit_callback():
199
+ """
200
+ Validates the initial user input (ensuring it’s non-empty, in English, and a question), prompts the user to reflect, and then triggers a random fortune draw if the criteria are met.
201
+ """
202
+ question = st.session_state.get("question_input", "").strip()
203
+ if not question:
204
+ st.session_state.error_message = "Please enter a valid question."
205
+ st.session_state.submitted = False
206
+ return
207
+
208
+ if not check_sentence_is_english_model(question):
209
+ st.session_state.error_message = "Please enter in English!"
210
+ st.session_state.button_count_temp = 0
211
+ return
212
+
213
+ if not check_sentence_is_question_model(question):
214
+ st.session_state.error_message = "This is not a question. Please enter again!"
215
+ st.session_state.button_count_temp = 0
216
+ return
217
+
218
+ if st.session_state.button_count_temp == 0:
219
+ st.session_state.error_message = "Please take a moment to quietly reflect on your question in your mind, then click submit again!"
220
+ st.session_state.button_count_temp = 1
221
+ return
222
+
223
+ st.session_state.error_message = ""
224
+ st.session_state.question = question
225
+ st.session_state.button_count_temp = 0
226
+ random_draw()
227
+
228
+ def resubmit_callback():
229
+ """
230
+ Allows the user to submit a revised question with similar validations, then updates the fortune selection accordingly.
231
+ """
232
+ new_question = st.session_state.get("resubmit_input", "").strip()
233
+ if new_question == "":
234
+ st.session_state.error_message = "Please enter a valid question."
235
+ return
236
+
237
+ if not check_sentence_is_english_model(new_question):
238
+ st.session_state.error_message = "Please enter in English!"
239
+ st.session_state.button_count_temp = 0
240
+ return
241
+
242
+ if not check_sentence_is_question_model(new_question):
243
+ st.session_state.error_message = "This is not a question. Please enter again!"
244
+ st.session_state.button_count_temp = 0
245
+ return
246
+
247
+ if st.session_state.button_count_temp == 0:
248
+ st.session_state.error_message = "Please take a moment to quietly reflect on your question in your mind, then click submit again!"
249
+ st.session_state.button_count_temp = 1
250
+ return
251
+
252
+ st.session_state.error_message = ""
253
+ if new_question != st.session_state.question:
254
+ st.session_state.question = new_question
255
+ st.session_state.button_count_temp = 0
256
+ random_draw()
257
+
258
+ def explain_callback():
259
+ """
260
+ Uses the selected fortune details and the classifier to generate and display a customized explanation for the user's question using the text generation model.
261
+ """
262
+ question = st.session_state.question
263
+ if not st.session_state.fortune_row:
264
+ st.error("Fortune data is not available. Please submit your question first.")
265
+ return
266
+ row_detail = st.session_state.fortune_row.get("Detail", "No detail available.")
267
+
268
+ classify = load_finetuned_classifier_model(question)
269
+ print(f"classify Checking: {classify}\nQuestion: {question}")
270
+ cfu_explain = analysis(row_detail, classify, question)
271
+ st.session_state.cfu_explain_text = cfu_explain
272
+ st.session_state.show_explain = True
273
+
274
+ # --------------------------- Layout & Display ---------------------------
275
+ st.title("🔮 Divine Fortune Teller")
276
+
277
+ if not st.session_state.submitted:
278
+ st.image("/home/user/app/resources/front.png", use_container_width=True)
279
+ st.text_input("Ask your fortune question...", key="question_input")
280
+ st.button("Submit", on_click=submit_callback)
281
+
282
+ if st.session_state.error_message:
283
+ st.error(st.session_state.error_message)
284
+ else:
285
+ st.text_input("Your Question", value=st.session_state.question, key="resubmit_input")
286
+ st.button("Resubmit", on_click=resubmit_callback)
287
+ if st.session_state.error_message:
288
+ st.error(st.session_state.error_message)
289
+
290
+ col1, col2 = st.columns([2, 3])
291
+ with col1:
292
+ if st.session_state.fortune_row and st.session_state.fortune_row.get("HeaderLink"):
293
+ img_from_url = download_and_resize_image(st.session_state.fortune_row.get("HeaderLink"))
294
+ if img_from_url:
295
+ st.markdown("<h6> </h6>", unsafe_allow_html=True)
296
+ st.image(img_from_url, use_container_width=False)
297
+ else:
298
+ default_img = load_and_resize_image("/home/user/app/resources/error.png")
299
+ if default_img:
300
+ st.image(default_img, caption="Default image", use_container_width=False)
301
+ else:
302
+ default_img = load_and_resize_image("/home/user/app/resources/error.png")
303
+ if default_img:
304
+ st.image(default_img, caption="Default image", use_container_width=False)
305
+
306
+ with col2:
307
+ if st.session_state.fortune_row:
308
+ luck_text = st.session_state.fortune_row.get("Luck", "N/A")
309
+ summary = f"""
310
+ <div style="font-size: 24px; font-weight: bold;">
311
+ Fortune Stick Number: {st.session_state.fortune_number}<br>
312
+ Luck: {luck_text}
313
+ </div>
314
+ """
315
+ st.markdown(summary, unsafe_allow_html=True)
316
+ description_text = st.session_state.fortune_row.get("Description", "No description available.")
317
+ detail_text = st.session_state.fortune_row.get("Detail", "No detail available.")
318
+ # Replace text_area with our custom text field
319
+ display_text_field("Description:", description_text, 180)
320
+ display_text_field("Detail:", detail_text, 180)
321
+
322
+ st.button("CFU Explain", on_click=explain_callback)
323
+ if st.session_state.show_explain:
324
+ display_text_field("Explanation:", st.session_state.cfu_explain_text, 200)