ibraheem007 commited on
Commit
2b5f7c8
Β·
verified Β·
1 Parent(s): cab838e

Upload 4 files

Browse files
components/feedback_handler.py CHANGED
@@ -1,243 +1,243 @@
1
- import streamlit as st
2
- from generator import GroqGenerator
3
- from feedback import save_feedback
4
- from simulate_adapt import adjust_prompt, get_adaptation_explanation
5
- from components.export_handler import generate_pdf
6
- from components.output_renderer import render_output
7
- from db.helpers import save_feedback_to_db
8
-
9
- def render_feedback_section():
10
- """Render the feedback section for 2-step process"""
11
- # Show adaptation explanation for regenerated content FIRST
12
- if (st.session_state.generated_output and
13
- st.session_state.regenerated and
14
- not st.session_state.feedback_given and
15
- st.session_state.get('show_adaptation_message', True)):
16
-
17
- adaptation_explanation = get_adaptation_explanation(
18
- st.session_state.feedback_complexity,
19
- st.session_state.feedback_clarity,
20
- st.session_state.feedback_depth,
21
- comments=st.session_state.feedback_comments
22
- )
23
-
24
- st.success("βœ… Content adapted based on your feedback!")
25
- st.info(f"**πŸ”„ Adaptations applied based on your feedback:**\n{adaptation_explanation}")
26
-
27
- # Mark that we've shown the message
28
- st.session_state.show_adaptation_message = False
29
-
30
- # Show feedback form for current content if no feedback given yet
31
- if st.session_state.generated_output and not st.session_state.feedback_given:
32
- render_feedback_form()
33
-
34
- # Save to history button
35
- if (st.session_state.generated_output and
36
- not st.session_state.get('saved_to_history', False)):
37
- st.markdown("---")
38
- st.write("**πŸ’Ύ Save to History**")
39
- if st.button("πŸ“š Save to My History", type="secondary"):
40
- from components.session_manager import save_current_to_history
41
- entry_id = save_current_to_history()
42
- if entry_id:
43
- st.session_state.saved_to_history = True
44
- st.success("βœ… Content saved to history!")
45
- st.rerun()
46
-
47
- # Handle regeneration logic - ONLY for first generation
48
- if st.session_state.feedback_given and not st.session_state.regenerated:
49
- handle_first_generation_feedback()
50
-
51
- def handle_first_generation_feedback():
52
- """Handle feedback for first generation content - show regenerate button only once"""
53
- if st.session_state.feedback_complexity == "Just right":
54
- st.balloons()
55
- st.success("πŸŽ‰ Perfect! The content matched your needs perfectly.")
56
- # No regenerate button for "Just right"
57
- else:
58
- # Show regenerate button ONLY for first generation
59
- if st.button("πŸ”„ Regenerate with Adjustments", type="secondary"):
60
- regenerate_content()
61
-
62
- def save_feedback_data(clarity, depth, complexity, comments):
63
- """Save feedback to PostgreSQL DB - UPDATED for regenerated content"""
64
- try:
65
- # Ensure we have a current_history_id
66
- if not hasattr(st.session_state, 'current_history_id') or not st.session_state.current_history_id:
67
- # Try to save to history first if not already saved
68
- from components.session_manager import save_current_to_history
69
- entry_id = save_current_to_history()
70
- if not entry_id:
71
- st.error("❌ Cannot save feedback: Content not saved to history yet.")
72
- return
73
-
74
- # Check if this is feedback for regenerated content
75
- is_regenerated_feedback = st.session_state.get('regenerated', False)
76
- regeneration_count = st.session_state.get('regeneration_count', 0)
77
- regeneration_type = st.session_state.get('regeneration_type', None)
78
-
79
- print(f"πŸ”§ DEBUG - Saving regenerated feedback:")
80
- print(f" - is_regenerated_feedback: {is_regenerated_feedback}")
81
- print(f" - regeneration_count: {regeneration_count}")
82
- print(f" - regeneration_type: {regeneration_type}")
83
-
84
- feedback_data = {
85
- "user_id": st.session_state.user_id,
86
- "content_id": st.session_state.current_history_id,
87
- "clarity": clarity,
88
- "depth": depth,
89
- "complexity": complexity,
90
- "comments": comments,
91
- "is_regenerated_feedback": is_regenerated_feedback,
92
- "regeneration_count": regeneration_count,
93
- "regeneration_type": regeneration_type
94
- }
95
-
96
- success = save_feedback_to_db(feedback_data)
97
-
98
- if success:
99
- st.session_state.feedback_given = True
100
- st.session_state.feedback_clarity = clarity
101
- st.session_state.feedback_depth = depth
102
- st.session_state.feedback_complexity = complexity
103
- st.session_state.feedback_comments = comments
104
- st.success("βœ… Feedback saved! This helps improve the system.")
105
- else:
106
- st.error("❌ Failed to save feedback. Please try again.")
107
- except Exception as e:
108
- st.error(f"❌ Error saving feedback: {str(e)}")
109
-
110
- def render_feedback_form():
111
- """Render the feedback collection form"""
112
- st.markdown("---")
113
- st.subheader("πŸ’¬ How Was This Content?")
114
-
115
- quality_example, placeholder = get_feedback_examples()
116
-
117
- with st.form("feedback_form"):
118
- # Feedback metrics
119
- clarity = st.slider("**Clarity** - How clear is the content?", 1, 5, 3,
120
- help="1=Very confusing, 5=Extremely clear")
121
- depth = st.slider("**Depth** - How detailed is the content?", 1, 5, 3,
122
- help="1=Too superficial, 5=Perfect depth")
123
- complexity = st.radio("**Appropriateness**:",
124
- ["Too simple", "Just right", "Too complex"],
125
- help="Is the content appropriate for the target level?")
126
-
127
- # Detailed feedback
128
- st.write("**Specific suggestions (be detailed!):**")
129
- st.caption(quality_example)
130
- comments = st.text_area(
131
- "Your feedback:",
132
- placeholder=placeholder,
133
- height=120,
134
- help="Detailed feedback helps improve the system for everyone"
135
- )
136
-
137
- # Submit button
138
- if st.form_submit_button("πŸ“€ Submit Feedback", type="primary"):
139
- save_feedback_data(clarity, depth, complexity, comments)
140
-
141
- def get_feedback_examples():
142
- """Get feedback examples based on user type"""
143
- if st.session_state.user_type == "student":
144
- quality_example = """For example:
145
- β€’ "The explanation of neural networks was clear, but I'd love more real-world examples"
146
- β€’ "The math notation was confusing - could you explain the steps more intuitively?"
147
- β€’ "The analogies really helped me understand! Maybe add a comparison to how the brain learns?"""
148
- placeholder = "What was confusing? What helped? What would make this even better for your learning?"
149
- else:
150
- quality_example = """For example:
151
- β€’ "The lesson structure is good, but could use more interactive activities"
152
- β€’ "The technical depth is appropriate, but consider adding assessment questions"
153
- β€’ "The examples are relevant - maybe include more diverse applications"""
154
- placeholder = "How could this be more effective for your teaching? What would help your students learn better?"
155
-
156
- return quality_example, placeholder
157
-
158
- def handle_regeneration():
159
- """Handle content regeneration based on feedback"""
160
- if st.session_state.feedback_complexity == "Just right":
161
- st.balloons()
162
- st.success("πŸŽ‰ Perfect! The content matched your needs perfectly.")
163
- st.session_state.regenerated = True
164
- else:
165
- if st.button("πŸ”„ Regenerate with Adjustments", type="secondary"):
166
- regenerate_content()
167
-
168
- def regenerate_content():
169
- """Regenerate content based on feedback - ONE TIME ONLY"""
170
- # Use a different approach - store the regeneration request and let the main flow handle it
171
- st.session_state.pending_regeneration = True
172
- st.session_state.regeneration_type = 'feedback_adjustment'
173
- st.rerun()
174
-
175
- def handle_pending_regeneration():
176
- """Handle pending regeneration in the main flow"""
177
- if st.session_state.get('pending_regeneration'):
178
- print("πŸ”„ DEBUG: handle_pending_regeneration triggered!")
179
- # Clear the flag first
180
- st.session_state.pending_regeneration = False
181
-
182
- with st.spinner("πŸ”„ Adapting content based on your feedback..."):
183
- try:
184
- print("πŸ”„ DEBUG: Handling pending regeneration...")
185
-
186
- # Track regeneration
187
- st.session_state.regeneration_count = st.session_state.get('regeneration_count', 0) + 1
188
- st.session_state.regenerated = True # Mark as regenerated content
189
- st.session_state.regeneration_type = 'feedback_adjustment' # Set the type
190
-
191
- print(f"πŸ”„ DEBUG: Regeneration count: {st.session_state.regeneration_count}")
192
-
193
- # RESET FEEDBACK STATE for the new content
194
- st.session_state.feedback_given = False
195
- st.session_state.feedback_clarity = 3
196
- st.session_state.feedback_depth = 3
197
- st.session_state.feedback_complexity = "Just right"
198
- st.session_state.feedback_comments = ""
199
- st.session_state.saved_to_history = False
200
-
201
- # Adjust prompt based on feedback
202
- adjusted_prompt = adjust_prompt(
203
- st.session_state.original_prompt,
204
- complexity=st.session_state.feedback_complexity,
205
- clarity=st.session_state.feedback_clarity,
206
- depth=st.session_state.feedback_depth,
207
- user_type=st.session_state.user_type,
208
- student_level=st.session_state.student_level,
209
- comments=st.session_state.feedback_comments
210
- )
211
-
212
- # Generate new content
213
- generator = GroqGenerator()
214
- new_output = generator.generate(adjusted_prompt)
215
-
216
- print(f"βœ… DEBUG: New content generated, length: {len(new_output)}")
217
-
218
- # Generate PDF for the new content
219
- if st.session_state.user_type == "student":
220
- pdf_data = generate_pdf(
221
- new_output,
222
- "student",
223
- level=st.session_state.student_level
224
- )
225
- else:
226
- pdf_data = generate_pdf(
227
- new_output,
228
- "tutor",
229
- level=st.session_state.student_level,
230
- topic=st.session_state.tutor_topic,
231
- content_type=st.session_state.tutor_content_type,
232
- objectives=""
233
- )
234
-
235
- # Update session state
236
- st.session_state.generated_output = new_output
237
- st.session_state.pdf_export_data = pdf_data
238
-
239
- print("βœ… DEBUG: Regeneration complete, content should display now")
240
-
241
- except Exception as e:
242
- print(f"❌ DEBUG: Regeneration failed: {e}")
243
  st.error(f"❌ Regeneration failed: {str(e)}")
 
1
+ import streamlit as st
2
+ from generator import GroqGenerator
3
+ from feedback import save_feedback
4
+ from simulate_adapt import adjust_prompt, get_adaptation_explanation
5
+ from components.export_handler import generate_pdf
6
+ from components.output_renderer import render_output
7
+ from db.helpers import save_feedback_to_db
8
+
9
+ def render_feedback_section():
10
+ """Render the feedback section for 2-step process"""
11
+ # Show adaptation explanation for regenerated content FIRST
12
+ if (st.session_state.generated_output and
13
+ st.session_state.regenerated and
14
+ not st.session_state.feedback_given and
15
+ st.session_state.get('show_adaptation_message', True)):
16
+
17
+ adaptation_explanation = get_adaptation_explanation(
18
+ st.session_state.feedback_complexity,
19
+ st.session_state.feedback_clarity,
20
+ st.session_state.feedback_depth,
21
+ comments=st.session_state.feedback_comments
22
+ )
23
+
24
+ st.success("βœ… Content adapted based on your feedback!")
25
+ st.info(f"**πŸ”„ Adaptations applied based on your feedback:**\n{adaptation_explanation}")
26
+
27
+ # Mark that we've shown the message
28
+ st.session_state.show_adaptation_message = False
29
+
30
+ # Show feedback form for current content if no feedback given yet
31
+ if st.session_state.generated_output and not st.session_state.feedback_given:
32
+ render_feedback_form()
33
+
34
+ # Save to history button
35
+ if (st.session_state.generated_output and
36
+ not st.session_state.get('saved_to_history', False)):
37
+ st.markdown("---")
38
+ st.write("**πŸ’Ύ Save to History**")
39
+ if st.button("πŸ“š Save to My History", type="secondary"):
40
+ from components.session_manager import save_current_to_history
41
+ entry_id = save_current_to_history()
42
+ if entry_id:
43
+ st.session_state.saved_to_history = True
44
+ st.success("βœ… Content saved to history!")
45
+ st.rerun()
46
+
47
+ # Handle regeneration logic - ONLY for first generation
48
+ if st.session_state.feedback_given and not st.session_state.regenerated:
49
+ handle_first_generation_feedback()
50
+
51
+ def handle_first_generation_feedback():
52
+ """Handle feedback for first generation content - show regenerate button only once"""
53
+ if st.session_state.feedback_complexity == "Just right":
54
+ st.balloons()
55
+ st.success("πŸŽ‰ Perfect! The content matched your needs perfectly.")
56
+ # No regenerate button for "Just right"
57
+ else:
58
+ # Show regenerate button ONLY for first generation
59
+ if st.button("πŸ”„ Regenerate with Adjustments", type="secondary"):
60
+ regenerate_content()
61
+
62
+ def save_feedback_data(clarity, depth, complexity, comments):
63
+ """Save feedback to PostgreSQL DB - UPDATED for regenerated content"""
64
+ try:
65
+ # Ensure we have a current_history_id
66
+ if not hasattr(st.session_state, 'current_history_id') or not st.session_state.current_history_id:
67
+ # Try to save to history first if not already saved
68
+ from components.session_manager import save_current_to_history
69
+ entry_id = save_current_to_history()
70
+ if not entry_id:
71
+ st.error("❌ Cannot save feedback: Content not saved to history yet.")
72
+ return
73
+
74
+ # Check if this is feedback for regenerated content
75
+ is_regenerated_feedback = st.session_state.get('regenerated', False)
76
+ regeneration_count = st.session_state.get('regeneration_count', 0)
77
+ regeneration_type = st.session_state.get('regeneration_type', None)
78
+
79
+ print(f"πŸ”§ DEBUG - Saving regenerated feedback:")
80
+ print(f" - is_regenerated_feedback: {is_regenerated_feedback}")
81
+ print(f" - regeneration_count: {regeneration_count}")
82
+ print(f" - regeneration_type: {regeneration_type}")
83
+
84
+ feedback_data = {
85
+ "user_id": st.session_state.user_id,
86
+ "content_id": st.session_state.current_history_id,
87
+ "clarity": clarity,
88
+ "depth": depth,
89
+ "complexity": complexity,
90
+ "comments": comments,
91
+ "is_regenerated_feedback": is_regenerated_feedback,
92
+ "regeneration_count": regeneration_count,
93
+ "regeneration_type": regeneration_type
94
+ }
95
+
96
+ success = save_feedback_to_db(feedback_data)
97
+
98
+ if success:
99
+ st.session_state.feedback_given = True
100
+ st.session_state.feedback_clarity = clarity
101
+ st.session_state.feedback_depth = depth
102
+ st.session_state.feedback_complexity = complexity
103
+ st.session_state.feedback_comments = comments
104
+ st.success("βœ… Feedback saved! This helps improve the system.")
105
+ else:
106
+ st.error("❌ Failed to save feedback. Please try again.")
107
+ except Exception as e:
108
+ st.error(f"❌ Error saving feedback: {str(e)}")
109
+
110
+ def render_feedback_form():
111
+ """Render the feedback collection form"""
112
+ st.markdown("---")
113
+ st.subheader("πŸ’¬ How Was This Content?")
114
+
115
+ quality_example, placeholder = get_feedback_examples()
116
+
117
+ with st.form("feedback_form"):
118
+ # Feedback metrics
119
+ clarity = st.slider("**Clarity** - How clear is the content?", 1, 5, 3,
120
+ help="1=Very confusing, 5=Extremely clear")
121
+ depth = st.slider("**Depth** - How detailed is the content?", 1, 5, 3,
122
+ help="1=Too superficial, 5=Perfect depth")
123
+ complexity = st.radio("**Appropriateness**:",
124
+ ["Too simple", "Just right", "Too complex"],
125
+ help="Is the content appropriate for the target level?")
126
+
127
+ # Detailed feedback
128
+ st.write("**Specific suggestions (be detailed!):**")
129
+ st.caption(quality_example)
130
+ comments = st.text_area(
131
+ "Your feedback:",
132
+ placeholder=placeholder,
133
+ height=120,
134
+ help="Detailed feedback helps improve the system for everyone"
135
+ )
136
+
137
+ # Submit button
138
+ if st.form_submit_button("πŸ“€ Submit Feedback", type="primary"):
139
+ save_feedback_data(clarity, depth, complexity, comments)
140
+
141
+ def get_feedback_examples():
142
+ """Get feedback examples based on user type"""
143
+ if st.session_state.user_type == "student":
144
+ quality_example = """For example:
145
+ β€’ "The explanation of neural networks was clear, but I'd love more real-world examples"
146
+ β€’ "The math notation was confusing - could you explain the steps more intuitively?"
147
+ β€’ "The analogies really helped me understand! Maybe add a comparison to how the brain learns?"""
148
+ placeholder = "What was confusing? What helped? What would make this even better for your learning?"
149
+ else:
150
+ quality_example = """For example:
151
+ β€’ "The lesson structure is good, but could use more interactive activities"
152
+ β€’ "The technical depth is appropriate, but consider adding assessment questions"
153
+ β€’ "The examples are relevant - maybe include more diverse applications"""
154
+ placeholder = "How could this be more effective for your teaching? What would help your students learn better?"
155
+
156
+ return quality_example, placeholder
157
+
158
+ def handle_regeneration():
159
+ """Handle content regeneration based on feedback"""
160
+ if st.session_state.feedback_complexity == "Just right":
161
+ st.balloons()
162
+ st.success("πŸŽ‰ Perfect! The content matched your needs perfectly.")
163
+ st.session_state.regenerated = True
164
+ else:
165
+ if st.button("πŸ”„ Regenerate with Adjustments", type="secondary"):
166
+ regenerate_content()
167
+
168
+ def regenerate_content():
169
+ """Regenerate content based on feedback - ONE TIME ONLY"""
170
+ # Use a different approach - store the regeneration request and let the main flow handle it
171
+ st.session_state.pending_regeneration = True
172
+ st.session_state.regeneration_type = 'feedback_adjustment'
173
+ st.rerun()
174
+
175
+ def handle_pending_regeneration():
176
+ """Handle pending regeneration in the main flow"""
177
+ if st.session_state.get('pending_regeneration'):
178
+ print("πŸ”„ DEBUG: handle_pending_regeneration triggered!")
179
+ # Clear the flag first
180
+ st.session_state.pending_regeneration = False
181
+
182
+ with st.spinner("πŸ”„ Adapting content based on your feedback..."):
183
+ try:
184
+ print("πŸ”„ DEBUG: Handling pending regeneration...")
185
+
186
+ # Track regeneration
187
+ st.session_state.regeneration_count = st.session_state.get('regeneration_count', 0) + 1
188
+ st.session_state.regenerated = True # Mark as regenerated content
189
+ st.session_state.regeneration_type = 'feedback_adjustment' # Set the type
190
+
191
+ print(f"πŸ”„ DEBUG: Regeneration count: {st.session_state.regeneration_count}")
192
+
193
+ # RESET FEEDBACK STATE for the new content
194
+ st.session_state.feedback_given = False
195
+ st.session_state.feedback_clarity = 3
196
+ st.session_state.feedback_depth = 3
197
+ st.session_state.feedback_complexity = "Just right"
198
+ st.session_state.feedback_comments = ""
199
+ st.session_state.saved_to_history = False
200
+
201
+ # Adjust prompt based on feedback
202
+ adjusted_prompt = adjust_prompt(
203
+ st.session_state.original_prompt,
204
+ complexity=st.session_state.feedback_complexity,
205
+ clarity=st.session_state.feedback_clarity,
206
+ depth=st.session_state.feedback_depth,
207
+ user_type=st.session_state.user_type,
208
+ student_level=st.session_state.student_level,
209
+ comments=st.session_state.feedback_comments
210
+ )
211
+
212
+ # Generate new content
213
+ generator = GroqGenerator()
214
+ new_output = generator.generate(adjusted_prompt)
215
+
216
+ print(f"βœ… DEBUG: New content generated, length: {len(new_output)}")
217
+
218
+ # Generate PDF for the new content
219
+ if st.session_state.user_type == "student":
220
+ pdf_data = generate_pdf(
221
+ new_output,
222
+ "student",
223
+ level=st.session_state.student_level
224
+ )
225
+ else:
226
+ pdf_data = generate_pdf(
227
+ new_output,
228
+ "tutor",
229
+ level=st.session_state.student_level,
230
+ topic=st.session_state.tutor_topic,
231
+ content_type=st.session_state.tutor_content_type,
232
+ objectives=""
233
+ )
234
+
235
+ # Update session state
236
+ st.session_state.generated_output = new_output
237
+ st.session_state.pdf_export_data = pdf_data
238
+
239
+ print("βœ… DEBUG: Regeneration complete, content should display now")
240
+
241
+ except Exception as e:
242
+ print(f"❌ DEBUG: Regeneration failed: {e}")
243
  st.error(f"❌ Regeneration failed: {str(e)}")
components/history_page.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import base64
3
+ import re
4
+ from datetime import datetime
5
+
6
+ def render_history_page():
7
+ st.header("πŸ“š Your Generated Content History")
8
+
9
+ # Add refresh button
10
+ col1, col2 = st.columns([3, 1])
11
+ with col1:
12
+ st.write("") # Spacer
13
+ with col2:
14
+ if st.button("πŸ”„ Refresh History", use_container_width=True):
15
+ from components.session_manager import load_user_history_from_db
16
+ load_user_history_from_db()
17
+ st.rerun()
18
+
19
+ history = st.session_state.get("user_history", [])
20
+
21
+ if not history:
22
+ render_empty_history()
23
+ return
24
+
25
+ render_history_stats(history)
26
+ filtered_history = render_filters(history)
27
+ render_history_entries(filtered_history)
28
+
29
+ def render_empty_history():
30
+ st.info("""
31
+ ## 🏁 No content generated yet!
32
+ Your generated content will appear here automatically.
33
+ """)
34
+
35
+ col1, col2 = st.columns(2)
36
+ with col1:
37
+ if st.button("πŸŽ“ Start as Student", use_container_width=True):
38
+ st.session_state.user_type = "student"
39
+ st.session_state.current_page = "generator"
40
+ st.rerun()
41
+ with col2:
42
+ if st.button("πŸ‘¨β€πŸ« Start as Tutor", use_container_width=True):
43
+ st.session_state.user_type = "tutor"
44
+ st.session_state.current_page = "generator"
45
+ st.rerun()
46
+
47
+ def render_history_stats(history):
48
+ total_entries = len(history)
49
+ student_entries = len([h for h in history if h.user_type == 'student'])
50
+ tutor_entries = len([h for h in history if h.user_type == 'tutor'])
51
+
52
+ col1, col2, col3 = st.columns(3)
53
+ with col1:
54
+ st.metric("Total Content", total_entries)
55
+ with col2:
56
+ st.metric("Student Content", student_entries)
57
+ with col3:
58
+ st.metric("Tutor Content", tutor_entries)
59
+
60
+ def render_filters(history):
61
+ col1, col2, col3 = st.columns(3)
62
+
63
+ with col1:
64
+ user_type_filter = st.selectbox("Filter by Type", ["All", "Student", "Tutor"])
65
+
66
+ with col2:
67
+ content_type_filter = st.selectbox("Filter by Format", ["All", "Lesson Plan", "Study Guide", "Lecture Notes", "Interactive Activity", "Comprehensive Explanation"])
68
+
69
+ with col3:
70
+ search_term = st.text_input("πŸ” Search content...")
71
+
72
+ filtered_history = history
73
+
74
+ if user_type_filter != "All":
75
+ filtered_history = [h for h in filtered_history if h.user_type.lower() == user_type_filter.lower()]
76
+
77
+ if content_type_filter != "All":
78
+ filtered_history = [h for h in filtered_history if h.content_type == content_type_filter]
79
+
80
+ if search_term:
81
+ filtered_history = [
82
+ h for h in filtered_history
83
+ if search_term.lower() in (h.output or '').lower()
84
+ or search_term.lower() in (h.topic or '').lower()
85
+ or search_term.lower() in (h.prompt or '').lower()
86
+ ]
87
+
88
+ st.caption(f"Showing {len(filtered_history)} of {len(history)} entries")
89
+ return filtered_history
90
+
91
+ def render_history_entries(history_entries):
92
+ for i, entry in enumerate(history_entries):
93
+ with st.container():
94
+ st.markdown("---")
95
+ col1, col2 = st.columns([3, 1])
96
+ with col1:
97
+ render_entry_header(entry)
98
+ if st.checkbox(f"Show Preview #{i+1}", key=f"preview_{entry.id}"):
99
+ render_entry_preview(entry)
100
+ with col2:
101
+ render_entry_actions(entry, i)
102
+
103
+ def render_entry_header(entry):
104
+ user_badge = "πŸŽ“ Student" if entry.user_type == "student" else "πŸ‘¨β€πŸ« Tutor"
105
+ time_str = entry.created_at.strftime("%b %d, %Y at %H:%M")
106
+
107
+ col1, col2, col3 = st.columns([1, 2, 1])
108
+ with col1:
109
+ st.write(f"**{user_badge}**")
110
+ with col2:
111
+ if entry.content_type:
112
+ st.write(f"**{entry.content_type}**: {entry.topic}")
113
+ else:
114
+ st.write(f"**{entry.topic}**")
115
+ with col3:
116
+ st.caption(time_str)
117
+ if entry.student_level:
118
+ st.caption(f"Level: {entry.student_level}")
119
+
120
+ def render_entry_preview(entry):
121
+ preview = (entry.output or '')[:500] + "..." if len(entry.output or '') > 500 else (entry.output or '')
122
+ with st.expander("Content Preview", expanded=True):
123
+ st.markdown(preview)
124
+ if st.checkbox("Show Original Request", key=f"prompt_{entry.id}"):
125
+ st.text_area("Original Prompt", entry.prompt, height=100, key=f"textarea_{entry.id}")
126
+
127
+ def render_entry_actions(entry, index):
128
+ entry_id = entry.id
129
+
130
+ # ULTRA SIMPLE CHECK: Just check if PDF data exists and is reasonably long
131
+ has_pdf_data = entry.pdf_base64 and len(entry.pdf_base64) > 1000
132
+
133
+ if has_pdf_data:
134
+ download_link = create_download_link(entry.pdf_base64, entry.filename or "content.pdf")
135
+ st.markdown(download_link, unsafe_allow_html=True)
136
+ st.caption("βœ… PDF ready for download")
137
+ else:
138
+ st.warning("⚠️ PDF needs regeneration")
139
+
140
+ # Regenerate button
141
+ if st.button("πŸ”„ Regenerate PDF", key=f"regen_pdf_{entry_id}", use_container_width=True):
142
+ with st.spinner("Regenerating PDF..."):
143
+ success = regenerate_pdf_for_history(entry)
144
+ if success:
145
+ st.success("βœ… PDF regenerated successfully! Refreshing...")
146
+ st.rerun()
147
+ else:
148
+ st.error("❌ Failed to regenerate PDF")
149
+
150
+ # Open button
151
+ if st.button("πŸ“ Open", key=f"open_{entry_id}_{index}", use_container_width=True):
152
+ load_entry_to_editor(entry)
153
+
154
+ def create_download_link(pdf_data, filename):
155
+ """Create a download link for PDF file - NO VALIDATION"""
156
+ try:
157
+ # Handle the data - if it has data URL prefix, extract base64 part
158
+ if isinstance(pdf_data, str):
159
+ if pdf_data.startswith('data:application/pdf;base64,'):
160
+ base64_data = pdf_data.split(',')[1]
161
+ else:
162
+ base64_data = pdf_data
163
+ else:
164
+ base64_data = pdf_data
165
+
166
+ # Create safe filename
167
+ safe_filename = re.sub(r'[^a-zA-Z0-9._-]', '_', filename)
168
+ if not safe_filename.endswith('.pdf'):
169
+ safe_filename += '.pdf'
170
+
171
+ # Create the download link WITHOUT ANY VALIDATION
172
+ href = f'''
173
+ <a href="data:application/pdf;base64,{base64_data}" download="{safe_filename}"
174
+ style="background-color:#4CAF50;color:white;padding:12px 24px;text-align:center;
175
+ text-decoration:none;display:inline-block;border-radius:4px;font-weight:bold;
176
+ border:none;cursor:pointer;width:100%;box-sizing:border-box;"
177
+ onclick="console.log('Downloading PDF: {safe_filename}')">
178
+ πŸ“₯ Download PDF
179
+ </a>
180
+ '''
181
+ return href
182
+
183
+ except Exception as e:
184
+ return f'<p style="color: red;">❌ Download error</p>'
185
+
186
+ def regenerate_pdf_for_history(entry):
187
+ """Regenerate PDF for a history entry"""
188
+ try:
189
+ from utils.pdf_export import export_content_to_pdf
190
+
191
+ print(f"πŸ”„ Regenerating PDF for entry: {entry.id}")
192
+
193
+ if entry.user_type == "student":
194
+ pdf_bytes = export_content_to_pdf(
195
+ content=entry.output,
196
+ title=f"Simplified Content - {entry.student_level}",
197
+ student_level=entry.student_level,
198
+ content_type=None,
199
+ objectives=None
200
+ )
201
+ else:
202
+ pdf_bytes = export_content_to_pdf(
203
+ content=entry.output,
204
+ title=f"{entry.content_type} - {entry.topic}",
205
+ student_level=entry.student_level,
206
+ content_type=entry.content_type,
207
+ objectives=""
208
+ )
209
+
210
+ if pdf_bytes and len(pdf_bytes) > 100:
211
+ print(f"βœ… PDF generated: {len(pdf_bytes)} bytes")
212
+
213
+ # Convert to clean base64
214
+ pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
215
+ print(f"βœ… Converted to base64: {len(pdf_base64)} chars")
216
+
217
+ # Update database
218
+ from db.helpers import update_pdf_data
219
+ success = update_pdf_data(entry.id, pdf_base64)
220
+
221
+ if success:
222
+ print("βœ… PDF saved to database")
223
+ return True
224
+ else:
225
+ print("❌ Failed to save PDF to database")
226
+ return False
227
+ else:
228
+ print("❌ PDF generation failed")
229
+ return False
230
+
231
+ except Exception as e:
232
+ print(f"❌ Error regenerating PDF: {str(e)}")
233
+ return False
234
+
235
+ def load_entry_to_editor(entry):
236
+ """Load a history entry back into the main editor"""
237
+ # Clear session state except essentials
238
+ keys_to_preserve = ['user_id', 'current_page']
239
+ preserved = {k: st.session_state[k] for k in keys_to_preserve if k in st.session_state}
240
+
241
+ for key in list(st.session_state.keys()):
242
+ if key not in keys_to_preserve:
243
+ del st.session_state[key]
244
+
245
+ # Restore preserved keys
246
+ for k, v in preserved.items():
247
+ st.session_state[k] = v
248
+
249
+ # Set content data
250
+ st.session_state.generated_output = entry.output
251
+ st.session_state.original_prompt = entry.prompt
252
+ st.session_state.user_type = entry.user_type
253
+ st.session_state.student_level = entry.student_level
254
+ st.session_state.tutor_topic = entry.topic
255
+ st.session_state.tutor_content_type = entry.content_type
256
+
257
+ # Always regenerate PDF when loading from history
258
+ st.session_state.pdf_export_data = None
259
+ print("πŸ”„ Auto-regenerating PDF for loaded content...")
260
+
261
+ success = regenerate_pdf_for_history(entry)
262
+ if success:
263
+ # Get updated entry
264
+ from db.helpers import get_entry_by_id
265
+ updated_entry = get_entry_by_id(entry.id)
266
+ if updated_entry and updated_entry.pdf_base64:
267
+ # Store the base64 data directly in session state
268
+ st.session_state.pdf_export_data = updated_entry.pdf_base64
269
+ print("βœ… PDF data stored in session")
270
+
271
+ st.session_state.feedback_given = entry.feedback_given
272
+ st.session_state.regenerated = False
273
+ st.session_state.current_history_id = str(entry.id)
274
+ st.session_state.from_history = True
275
+ st.session_state.saved_to_history = True
276
+ st.session_state.current_page = "generator"
277
+
278
+ st.rerun()
components/research_dashboard.py ADDED
@@ -0,0 +1,568 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import plotly.graph_objects as go
3
+ import plotly.express as px
4
+ import pandas as pd
5
+ import numpy as np
6
+ from decimal import Decimal
7
+ from db.helpers import get_research_stats, export_research_data_for_analysis
8
+
9
+ def render_research_dashboard():
10
+ st.title("πŸ”¬ Research Dashboard - Advanced Analytics")
11
+
12
+ # DEBUG: Add regeneration debug button
13
+ if st.button("πŸ› Debug Regeneration Data"):
14
+ from db.helpers import debug_regeneration_data
15
+ count = debug_regeneration_data()
16
+ st.info(f"Debug: Found {count} regenerated feedback entries in database")
17
+ st.rerun()
18
+
19
+ try:
20
+ # Get research stats
21
+ stats = get_research_stats()
22
+
23
+ # Calculate advanced metrics
24
+ advanced_metrics = calculate_advanced_metrics(stats)
25
+
26
+ # Basic metrics
27
+ st.header("πŸ“Š Research Overview")
28
+ render_research_overview(stats, advanced_metrics)
29
+
30
+ # NEW: Regeneration Analysis Section
31
+ st.header("πŸ”„ Regeneration Effectiveness")
32
+ render_regeneration_analysis(stats)
33
+
34
+ # Model comparison with advanced metrics
35
+ st.header("βš–οΈ Model Performance Comparison")
36
+ render_model_comparison(stats, advanced_metrics)
37
+
38
+ # Quality Metrics
39
+ st.header("✨ Detailed Quality Analysis")
40
+ render_quality_analysis(stats, advanced_metrics)
41
+
42
+ # NEW: User Behavior Analysis
43
+ st.header("πŸ‘₯ User Behavior & Patterns")
44
+ render_user_behavior_analysis(stats)
45
+
46
+ # Advanced Statistical Analysis
47
+ st.header("πŸ“ˆ Advanced Statistical Analysis")
48
+ render_advanced_analysis(advanced_metrics)
49
+
50
+ # Export functionality
51
+ st.header("πŸ’Ύ Data Management")
52
+ render_data_management()
53
+
54
+ except Exception as e:
55
+ st.error(f"❌ Error loading research data: {str(e)}")
56
+ st.info("This might be because no research data has been collected yet.")
57
+
58
+ def render_regeneration_analysis(stats):
59
+ """Analyze effectiveness of content regeneration"""
60
+ col1, col2, col3, col4 = st.columns(4)
61
+
62
+ with col1:
63
+ total_regenerated = stats.get("regenerated_feedback_count", 0)
64
+ st.metric("Total Regenerated Content", total_regenerated)
65
+
66
+ with col2:
67
+ regenerated_hq = stats.get("regenerated_high_quality", 0)
68
+ hq_rate = (regenerated_hq / total_regenerated * 100) if total_regenerated > 0 else 0
69
+ st.metric("High-Quality Regenerated", f"{regenerated_hq} ({hq_rate:.1f}%)")
70
+
71
+ with col3:
72
+ quality_gap = stats.get("regeneration_quality_comparison", {}).get("quality_gap", 0)
73
+ delta_label = "Better" if quality_gap > 0 else "Worse" if quality_gap < 0 else "Equal"
74
+ st.metric("Quality Gap", f"{quality_gap:.2f}", delta=delta_label)
75
+
76
+ with col4:
77
+ regeneration_types = stats.get("regeneration_types", {})
78
+ total_types = sum(regeneration_types.values())
79
+ st.metric("Regeneration Types", total_types)
80
+
81
+ # Regeneration type breakdown
82
+ if total_regenerated > 0:
83
+ st.subheader("πŸ”„ Regeneration Type Distribution")
84
+ regeneration_types = stats.get("regeneration_types", {})
85
+
86
+ # Filter out zero values for cleaner chart
87
+ non_zero_types = {k: v for k, v in regeneration_types.items() if v > 0}
88
+
89
+ if non_zero_types:
90
+ fig = px.pie(
91
+ values=list(non_zero_types.values()),
92
+ names=list(non_zero_types.keys()),
93
+ title="Regeneration Methods Used",
94
+ color_discrete_sequence=px.colors.qualitative.Set3
95
+ )
96
+ st.plotly_chart(fig)
97
+ else:
98
+ st.info("No regeneration data available yet.")
99
+
100
+ # Quality comparison chart - UPDATED: Show both clarity and depth
101
+ st.subheader("πŸ“Š Original vs Regenerated Content Quality")
102
+ quality_comp = stats.get("regeneration_quality_comparison", {})
103
+
104
+ if quality_comp and quality_comp.get('original_avg_clarity', 0) > 0:
105
+ # Create comparison for both clarity and depth
106
+ metrics = ['Clarity', 'Depth']
107
+ original_values = [
108
+ quality_comp.get('original_avg_clarity', 0),
109
+ quality_comp.get('original_avg_depth', 0)
110
+ ]
111
+ regenerated_values = [
112
+ quality_comp.get('regenerated_avg_clarity', 0),
113
+ quality_comp.get('regenerated_avg_depth', 0)
114
+ ]
115
+
116
+ fig = go.Figure(data=[
117
+ go.Bar(name='Original', x=metrics, y=original_values, marker_color='blue'),
118
+ go.Bar(name='Regenerated', x=metrics, y=regenerated_values, marker_color='orange')
119
+ ])
120
+ fig.update_layout(
121
+ title="Average Quality: Original vs Regenerated",
122
+ barmode='group',
123
+ yaxis_title="Score",
124
+ height=400
125
+ )
126
+ st.plotly_chart(fig)
127
+ else:
128
+ st.info("Not enough data for quality comparison yet.")
129
+
130
+ def render_user_behavior_analysis(stats):
131
+ """Analyze user behavior patterns"""
132
+ col1, col2, col3, col4 = st.columns(4) # Added extra column for Phi-3 usage
133
+
134
+ with col1:
135
+ # User type distribution (if available)
136
+ total_feedback = stats.get("total_feedback", 0)
137
+ groq_feedback = stats.get("groq_feedback_count", 0)
138
+ phi3_feedback = stats.get("phi3_feedback_count", 0)
139
+
140
+ if total_feedback > 0:
141
+ groq_percent = (groq_feedback / total_feedback) * 100
142
+ phi3_percent = (phi3_feedback / total_feedback) * 100
143
+
144
+ st.metric("Groq Usage", f"{groq_percent:.1f}%")
145
+ st.metric("Phi-3 Usage", f"{phi3_percent:.1f}%")
146
+ else:
147
+ st.metric("Groq Usage", "0%")
148
+ st.metric("Phi-3 Usage", "0%")
149
+
150
+ with col2:
151
+ # Content type preferences
152
+ total_content = stats.get("total_content", 0)
153
+ regenerated_content = stats.get("regenerated_feedback_count", 0)
154
+
155
+ if total_content > 0:
156
+ regeneration_rate = (regenerated_content / total_content) * 100
157
+ st.metric("Regeneration Rate", f"{regeneration_rate:.1f}%")
158
+ else:
159
+ st.metric("Regeneration Rate", "0%")
160
+
161
+ with col3:
162
+ # High-quality content analysis for Groq
163
+ groq_hq = stats.get("high_quality_groq", 0)
164
+ groq_feedback = stats.get("groq_feedback_count", 0)
165
+ if groq_feedback > 0:
166
+ groq_hq_rate = (groq_hq / groq_feedback) * 100
167
+ st.metric("Groq HQ Rate", f"{groq_hq_rate:.1f}%")
168
+ else:
169
+ st.metric("Groq HQ Rate", "0%")
170
+
171
+ with col4:
172
+ # High-quality content analysis for Phi-3 - NEW
173
+ phi3_hq = stats.get("high_quality_phi3", 0)
174
+ phi3_feedback = stats.get("phi3_feedback_count", 0)
175
+ if phi3_feedback > 0:
176
+ phi3_hq_rate = (phi3_hq / phi3_feedback) * 100
177
+ st.metric("Phi-3 HQ Rate", f"{phi3_hq_rate:.1f}%")
178
+ else:
179
+ st.metric("Phi-3 HQ Rate", "0%")
180
+
181
+ # Model preference over time (simulated - you'd need timestamp data for real implementation)
182
+ st.subheader("πŸ“ˆ Model Preference Trend")
183
+
184
+ # This would be more meaningful with actual time-series data
185
+ # For now, we'll show a simulated trend based on current usage
186
+ groq_feedback = stats.get("groq_feedback_count", 0)
187
+ phi3_feedback = stats.get("phi3_feedback_count", 0)
188
+ total_feedback = groq_feedback + phi3_feedback
189
+
190
+ if total_feedback > 0:
191
+ groq_percent = (groq_feedback / total_feedback) * 100
192
+ phi3_percent = (phi3_feedback / total_feedback) * 100
193
+
194
+ # Simulate a trend (in a real app, you'd use actual time-series data)
195
+ trend_data = {
196
+ 'Period': ['Week 1', 'Week 2', 'Week 3', 'Current'],
197
+ 'Groq Usage': [
198
+ max(10, groq_percent * 1.3), # Simulated historical data
199
+ max(15, groq_percent * 1.15),
200
+ max(20, groq_percent * 1.05),
201
+ groq_percent
202
+ ],
203
+ 'Phi-3 Usage': [
204
+ max(5, phi3_percent * 0.7), # Simulated historical data
205
+ max(10, phi3_percent * 0.85),
206
+ max(15, phi3_percent * 0.95),
207
+ phi3_percent
208
+ ]
209
+ }
210
+
211
+ df_trend = pd.DataFrame(trend_data)
212
+ fig = px.line(df_trend, x='Period', y=['Groq Usage', 'Phi-3 Usage'],
213
+ title="Model Usage Trend Over Time", markers=True)
214
+ st.plotly_chart(fig)
215
+ else:
216
+ st.info("Not enough data to show usage trends yet.")
217
+
218
+ def safe_convert(value):
219
+ """Safely convert any value to float"""
220
+ if value is None:
221
+ return 0.0
222
+ if isinstance(value, (int, float)):
223
+ return float(value)
224
+ if isinstance(value, Decimal):
225
+ return float(value)
226
+ try:
227
+ return float(value)
228
+ except (ValueError, TypeError):
229
+ return 0.0
230
+
231
+ def calculate_advanced_metrics(stats):
232
+ """Calculate realistic precision, recall, F1 scores without database changes"""
233
+ try:
234
+ # Safely extract and convert all values
235
+ groq_feedback = safe_convert(stats.get("groq_feedback_count", 0))
236
+ phi3_feedback = safe_convert(stats.get("phi3_feedback_count", 0))
237
+
238
+ # High-quality examples (True Positives)
239
+ groq_tp = safe_convert(stats.get("high_quality_groq", 0))
240
+ phi3_tp = safe_convert(stats.get("high_quality_phi3", 0))
241
+
242
+ # Get scores safely
243
+ groq_scores = stats.get("groq_scores", {})
244
+ phi3_scores = stats.get("phi3_scores", {})
245
+
246
+ groq_clarity = safe_convert(groq_scores.get("clarity", 0))
247
+ groq_depth = safe_convert(groq_scores.get("depth", 0))
248
+ phi3_clarity = safe_convert(phi3_scores.get("clarity", 0))
249
+ phi3_depth = safe_convert(phi3_scores.get("depth", 0))
250
+
251
+ # REALISTIC CALCULATIONS:
252
+
253
+ # 1. PRECISION - How many of the generated contents were high quality?
254
+ # Precision = True Positives / (True Positives + False Positives)
255
+ # False Positives = Total feedback - True Positives (low quality content that was generated)
256
+ groq_precision = groq_tp / groq_feedback if groq_feedback > 0 else 0.0
257
+ phi3_precision = phi3_tp / phi3_feedback if phi3_feedback > 0 else 0.0
258
+
259
+ # 2. RECALL - How well does the model capture what users need?
260
+ # We'll estimate this based on multiple factors:
261
+
262
+ # Factor 1: Quality scores (higher scores = better at capturing user needs)
263
+ groq_quality_avg = (groq_clarity + groq_depth) / 2
264
+ phi3_quality_avg = (phi3_clarity + phi3_depth) / 2
265
+
266
+ # Factor 2: High-quality rate (models with more high-quality content have better recall)
267
+ groq_hq_rate = groq_tp / groq_feedback if groq_feedback > 0 else 0
268
+ phi3_hq_rate = phi3_tp / phi3_feedback if phi3_feedback > 0 else 0
269
+
270
+ # Factor 3: Consistency (how consistently the model performs well)
271
+ groq_consistency = min(1.0, (groq_clarity * groq_depth) / 25) # 0-1 scale
272
+ phi3_consistency = min(1.0, (phi3_clarity * phi3_depth) / 25)
273
+
274
+ # Combine factors for realistic recall estimation
275
+ groq_recall = (
276
+ (groq_quality_avg / 5 * 0.4) + # 40% weight on quality scores
277
+ (groq_hq_rate * 0.4) + # 40% weight on high-quality rate
278
+ (groq_consistency * 0.2) # 20% weight on consistency
279
+ )
280
+
281
+ phi3_recall = (
282
+ (phi3_quality_avg / 5 * 0.4) + # 40% weight on quality scores
283
+ (phi3_hq_rate * 0.4) + # 40% weight on high-quality rate
284
+ (phi3_consistency * 0.2) # 20% weight on consistency
285
+ )
286
+
287
+ # Ensure recall is reasonable (not too high or low)
288
+ groq_recall = max(0.1, min(0.95, groq_recall))
289
+ phi3_recall = max(0.1, min(0.95, phi3_recall))
290
+
291
+ # 3. F1 SCORE - Harmonic mean of precision and recall
292
+ groq_f1 = 2 * (groq_precision * groq_recall) / (groq_precision + groq_recall) if (groq_precision + groq_recall) > 0 else 0.0
293
+ phi3_f1 = 2 * (phi3_precision * phi3_recall) / (phi3_precision + phi3_recall) if (phi3_precision + phi3_recall) > 0 else 0.0
294
+
295
+ # Overall quality score (weighted average)
296
+ groq_overall = (groq_clarity + groq_depth + (groq_f1 * 5)) / 3.0
297
+ phi3_overall = (phi3_clarity + phi3_depth + (phi3_f1 * 5)) / 3.0
298
+
299
+ return {
300
+ "precision": {
301
+ "groq": round(groq_precision * 100, 1),
302
+ "phi3": round(phi3_precision * 100, 1)
303
+ },
304
+ "recall": {
305
+ "groq": round(groq_recall * 100, 1),
306
+ "phi3": round(phi3_recall * 100, 1)
307
+ },
308
+ "f1_score": {
309
+ "groq": round(groq_f1 * 100, 1),
310
+ "phi3": round(phi3_f1 * 100, 1)
311
+ },
312
+ "overall_quality": {
313
+ "groq": round(groq_overall, 2),
314
+ "phi3": round(phi3_overall, 2)
315
+ },
316
+ "improvement_gap": {
317
+ "precision": round((groq_precision - phi3_precision) * 100, 1),
318
+ "recall": round((groq_recall - phi3_recall) * 100, 1),
319
+ "f1": round((groq_f1 - phi3_f1) * 100, 1),
320
+ "overall": round(groq_overall - phi3_overall, 2)
321
+ }
322
+ }
323
+
324
+ except Exception as e:
325
+ st.error(f"Error calculating advanced metrics: {e}")
326
+ # Return safe fallback with different values
327
+ return {
328
+ "precision": {"groq": 65.0, "phi3": 45.0},
329
+ "recall": {"groq": 72.0, "phi3": 58.0},
330
+ "f1_score": {"groq": 68.0, "phi3": 51.0},
331
+ "overall_quality": {"groq": 3.8, "phi3": 2.9},
332
+ "improvement_gap": {"precision": 20.0, "recall": 14.0, "f1": 17.0, "overall": 0.9}
333
+ }
334
+
335
+ def render_research_overview(stats, advanced_metrics):
336
+ col1, col2, col3, col4, col5 = st.columns(5) # Added extra column
337
+
338
+ with col1:
339
+ st.metric("Total Feedback", stats.get("total_feedback", 0))
340
+
341
+ with col2:
342
+ st.metric("Groq F1 Score", f"{advanced_metrics['f1_score']['groq']}%")
343
+
344
+ with col3:
345
+ st.metric("Phi-3 F1 Score", f"{advanced_metrics['f1_score']['phi3']}%")
346
+
347
+ with col4:
348
+ f1_gap = advanced_metrics['improvement_gap']['f1']
349
+ st.metric("F1 Gap", f"{f1_gap}%", delta=f"{f1_gap}%")
350
+
351
+ with col5:
352
+ regenerated = stats.get("regenerated_feedback_count", 0)
353
+ st.metric("Regenerated", regenerated)
354
+
355
+ def render_model_comparison(stats, advanced_metrics):
356
+ # Create comprehensive comparison chart
357
+ metrics = ['Clarity', 'Depth', 'Precision', 'Recall', 'F1 Score', 'Overall Quality']
358
+
359
+ # Safely convert all values to float
360
+ groq_scores = stats.get("groq_scores", {})
361
+ phi3_scores = stats.get("phi3_scores", {})
362
+
363
+ groq_values = [
364
+ safe_convert(groq_scores.get("clarity", 0)),
365
+ safe_convert(groq_scores.get("depth", 0)),
366
+ safe_convert(advanced_metrics['precision']['groq']) / 20, # Scale to 0-5
367
+ safe_convert(advanced_metrics['recall']['groq']) / 20, # Scale to 0-5
368
+ safe_convert(advanced_metrics['f1_score']['groq']) / 20, # Scale to 0-5
369
+ safe_convert(advanced_metrics['overall_quality']['groq'])
370
+ ]
371
+
372
+ phi3_values = [
373
+ safe_convert(phi3_scores.get("clarity", 0)),
374
+ safe_convert(phi3_scores.get("depth", 0)),
375
+ safe_convert(advanced_metrics['precision']['phi3']) / 20,
376
+ safe_convert(advanced_metrics['recall']['phi3']) / 20,
377
+ safe_convert(advanced_metrics['f1_score']['phi3']) / 20,
378
+ safe_convert(advanced_metrics['overall_quality']['phi3'])
379
+ ]
380
+
381
+ fig = go.Figure(data=[
382
+ go.Bar(name='Groq (Control)', x=metrics, y=groq_values, marker_color='#1f77b4'),
383
+ go.Bar(name='Phi-3 (Research)', x=metrics, y=phi3_values, marker_color='#ff7f0e')
384
+ ])
385
+
386
+ fig.update_layout(
387
+ title="Comprehensive Model Performance Comparison",
388
+ barmode='group',
389
+ showlegend=True,
390
+ yaxis_title="Score",
391
+ height=400
392
+ )
393
+
394
+ st.plotly_chart(fig)
395
+
396
+ def render_quality_analysis(stats, advanced_metrics):
397
+ col1, col2 = st.columns(2)
398
+
399
+ with col1:
400
+ st.subheader("πŸ“Š Groq (Control Model)")
401
+
402
+ # Basic metrics
403
+ groq_scores = stats.get("groq_scores", {})
404
+ st.metric("Clarity", f"{safe_convert(groq_scores.get('clarity', 0))}/5")
405
+ st.metric("Depth", f"{safe_convert(groq_scores.get('depth', 0))}/5")
406
+ st.metric("High Quality", stats.get("high_quality_groq", 0))
407
+
408
+ # Advanced metrics
409
+ st.metric("Precision", f"{advanced_metrics['precision']['groq']}%")
410
+ st.metric("Recall", f"{advanced_metrics['recall']['groq']}%")
411
+ st.metric("F1 Score", f"{advanced_metrics['f1_score']['groq']}%")
412
+ st.metric("Overall Quality", f"{advanced_metrics['overall_quality']['groq']}/5")
413
+
414
+ with col2:
415
+ st.subheader("πŸ§ͺ Phi-3 (Research Model)")
416
+
417
+ # Basic metrics
418
+ phi3_scores = stats.get("phi3_scores", {})
419
+ st.metric("Clarity", f"{safe_convert(phi3_scores.get('clarity', 0))}/5")
420
+ st.metric("Depth", f"{safe_convert(phi3_scores.get('depth', 0))}/5")
421
+ st.metric("High Quality", stats.get("high_quality_phi3", 0))
422
+
423
+ # Advanced metrics with deltas
424
+ precision_delta = f"{safe_convert(advanced_metrics['precision']['phi3']) - safe_convert(advanced_metrics['precision']['groq']):.1f}%"
425
+ recall_delta = f"{safe_convert(advanced_metrics['recall']['phi3']) - safe_convert(advanced_metrics['recall']['groq']):.1f}%"
426
+ f1_delta = f"{safe_convert(advanced_metrics['f1_score']['phi3']) - safe_convert(advanced_metrics['f1_score']['groq']):.1f}%"
427
+
428
+ st.metric("Precision", f"{advanced_metrics['precision']['phi3']}%", delta=precision_delta)
429
+ st.metric("Recall", f"{advanced_metrics['recall']['phi3']}%", delta=recall_delta)
430
+ st.metric("F1 Score", f"{advanced_metrics['f1_score']['phi3']}%", delta=f1_delta)
431
+ st.metric("Overall Quality", f"{advanced_metrics['overall_quality']['phi3']}/5")
432
+
433
+ def render_advanced_analysis(advanced_metrics):
434
+ col1, col2 = st.columns(2)
435
+
436
+ with col1:
437
+ # Radar chart for comprehensive comparison
438
+ categories = ['Precision', 'Recall', 'F1 Score', 'Overall Quality']
439
+
440
+ groq_radar = [
441
+ safe_convert(advanced_metrics['precision']['groq']) / 20,
442
+ safe_convert(advanced_metrics['recall']['groq']) / 20,
443
+ safe_convert(advanced_metrics['f1_score']['groq']) / 20,
444
+ safe_convert(advanced_metrics['overall_quality']['groq'])
445
+ ]
446
+
447
+ phi3_radar = [
448
+ safe_convert(advanced_metrics['precision']['phi3']) / 20,
449
+ safe_convert(advanced_metrics['recall']['phi3']) / 20,
450
+ safe_convert(advanced_metrics['f1_score']['phi3']) / 20,
451
+ safe_convert(advanced_metrics['overall_quality']['phi3'])
452
+ ]
453
+
454
+ fig = go.Figure()
455
+ fig.add_trace(go.Scatterpolar(
456
+ r=groq_radar,
457
+ theta=categories,
458
+ fill='toself',
459
+ name='Groq (Control)',
460
+ line_color='blue'
461
+ ))
462
+ fig.add_trace(go.Scatterpolar(
463
+ r=phi3_radar,
464
+ theta=categories,
465
+ fill='toself',
466
+ name='Phi-3 (Research)',
467
+ line_color='orange'
468
+ ))
469
+ fig.update_layout(
470
+ polar=dict(radialaxis=dict(visible=True, range=[0, 5])),
471
+ showlegend=True,
472
+ title="Advanced Metrics Radar Comparison"
473
+ )
474
+ st.plotly_chart(fig)
475
+
476
+ with col2:
477
+ # Improvement gap analysis
478
+ st.subheader("πŸ“ˆ Improvement Gap Analysis")
479
+
480
+ gaps = advanced_metrics['improvement_gap']
481
+
482
+ gap_data = {
483
+ 'Metric': ['Precision', 'Recall', 'F1 Score', 'Overall Quality'],
484
+ 'Gap': [
485
+ safe_convert(gaps['precision']),
486
+ safe_convert(gaps['recall']),
487
+ safe_convert(gaps['f1']),
488
+ safe_convert(gaps['overall']) * 20 # Scale for better visualization
489
+ ]
490
+ }
491
+
492
+ df = pd.DataFrame(gap_data)
493
+ fig = px.bar(df, x='Metric', y='Gap',
494
+ title="Performance Gap (Groq - Phi-3)",
495
+ color='Gap',
496
+ color_continuous_scale=['red', 'yellow', 'green'])
497
+ st.plotly_chart(fig)
498
+
499
+ # Performance summary
500
+ st.subheader("🎯 Performance Summary")
501
+ f1_gap = safe_convert(gaps['f1'])
502
+ if f1_gap > 10:
503
+ st.error("🚨 Significant improvement needed in Phi-3")
504
+ elif f1_gap > 5:
505
+ st.warning("⚠️ Moderate improvement needed in Phi-3")
506
+ elif f1_gap > 0:
507
+ st.info("πŸ“ˆ Minor improvement needed in Phi-3")
508
+ else:
509
+ st.success("πŸŽ‰ Phi-3 matching or exceeding Groq performance!")
510
+
511
+ def render_data_management():
512
+ col1, col2, col3 = st.columns(3)
513
+
514
+ with col1:
515
+ if st.button("πŸ“Š Export Research Data", use_container_width=True):
516
+ data = export_research_data_for_analysis()
517
+ if data:
518
+ st.success(f"βœ… Exported {len(data)} research data points!")
519
+ else:
520
+ st.error("❌ Failed to export data")
521
+
522
+ with col2:
523
+ if st.button("πŸ”„ Refresh Data", use_container_width=True):
524
+ st.rerun()
525
+
526
+ with col3:
527
+ if st.button("πŸ§ͺ Export Training Data", use_container_width=True):
528
+ from export_training_data_from_db import export_training_data_from_db
529
+ if export_training_data_from_db():
530
+ st.success("βœ… Training data exported for fine-tuning!")
531
+ else:
532
+ st.error("❌ No high-quality training data available")
533
+
534
+ # Enhanced Fine-tuning Readiness
535
+ st.header("🎯 Fine-tuning Readiness")
536
+
537
+ # Get actual metrics
538
+ stats = get_research_stats()
539
+ groq_feedback = stats.get("groq_feedback_count", 0)
540
+ high_quality_groq = stats.get("high_quality_groq", 0)
541
+
542
+ col1, col2, col3 = st.columns(3)
543
+
544
+ with col1:
545
+ target_examples = 50
546
+ progress = min(high_quality_groq / target_examples, 1.0)
547
+ st.metric("High-Quality Groq Examples", f"{high_quality_groq}/{target_examples}")
548
+ st.progress(progress)
549
+
550
+ with col2:
551
+ if high_quality_groq >= target_examples:
552
+ st.success("βœ… Ready for fine-tuning!")
553
+ else:
554
+ needed = target_examples - high_quality_groq
555
+ st.warning(f"Need {needed} more HQ examples")
556
+
557
+ with col3:
558
+ hq_rate = (high_quality_groq / groq_feedback * 100) if groq_feedback > 0 else 0
559
+ st.metric("HQ Conversion Rate", f"{hq_rate:.1f}%")
560
+
561
+ st.info("""
562
+ **Fine-tuning Requirements:**
563
+ - βœ… 50+ high-quality Groq examples (for training data)
564
+ - βœ… Consistent performance gap analysis
565
+ - βœ… Comprehensive metrics collection
566
+ - βœ… User feedback integration
567
+ - βœ… Regeneration effectiveness data
568
+ """)
components/session_manager.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from streamlit_js_eval import get_cookie, set_cookie
3
+ import uuid
4
+ from datetime import datetime
5
+ import re
6
+ from db.helpers import get_user_history, ensure_user_exists
7
+
8
+ def get_or_create_user_id():
9
+ """Get user ID from cookie or create new one - ENHANCED PERSISTENCE"""
10
+ try:
11
+ # Try multiple cookie names for backward compatibility
12
+ user_id = get_cookie("edugen_user_id")
13
+
14
+ # If not found, try alternative cookie names
15
+ if not user_id:
16
+ user_id = get_cookie("user_id") or get_cookie("edugen_user")
17
+
18
+ # If still not found, create new one
19
+ if not user_id:
20
+ user_id = str(uuid.uuid4())
21
+ # Set cookie with multiple fallback names
22
+ set_cookie("edugen_user_id", user_id, duration_days=365)
23
+ set_cookie("user_id", user_id, duration_days=365) # Backup cookie
24
+ print(f"βœ… Created new user ID: {user_id}")
25
+ else:
26
+ print(f"βœ… Retrieved existing user ID: {user_id[:8]}...")
27
+
28
+ return user_id
29
+ except Exception as e:
30
+ # Fallback: use session state only
31
+ print(f"⚠️ Cookie error, using session fallback: {e}")
32
+ if "user_id" not in st.session_state:
33
+ st.session_state.user_id = str(uuid.uuid4())
34
+ return st.session_state.user_id
35
+
36
+ def initialize_session_state():
37
+ """Initialize session state and load user data from database - ENHANCED"""
38
+ # Initialize user ID first with better persistence
39
+ if "user_id" not in st.session_state:
40
+ st.session_state.user_id = get_or_create_user_id()
41
+
42
+ # ALWAYS ensure user exists in database
43
+ try:
44
+ ensure_user_exists(st.session_state.user_id)
45
+ except Exception as e:
46
+ print(f"⚠️ User creation warning: {e}")
47
+
48
+ # ALWAYS load user history from database
49
+ load_user_history_from_db()
50
+
51
+ session_defaults = {
52
+ "user_type": "",
53
+ "original_prompt": "",
54
+ "generated_output": "",
55
+ "feedback_given": False,
56
+ "regenerated": False,
57
+ "content_source": "",
58
+ "student_level": "",
59
+ "pdf_export_data": None,
60
+ "tutor_topic": "",
61
+ "tutor_content_type": "",
62
+ "feedback_clarity": 3,
63
+ "feedback_depth": 3,
64
+ "feedback_complexity": "Just right",
65
+ "feedback_comments": "",
66
+ "original_filename": "content.pdf",
67
+ "current_page": "generator",
68
+ "current_history_id": None,
69
+ "saved_to_history": False,
70
+ "user_history": [],
71
+ "from_history": False,
72
+ "showing_regeneration_prompt": False,
73
+ "pending_model_switch": None,
74
+ "previous_model": None,
75
+ "regenerate_with_new_model": False,
76
+ "scrolled_to_top": False,
77
+ "regeneration_count": 0,
78
+ "regeneration_type": None,
79
+ "previous_feedback_given": False,
80
+ "pending_regeneration": False,
81
+ "show_adaptation_message": True,
82
+ "session_initialized": True, # NEW: Track initialization
83
+ }
84
+
85
+ for key, value in session_defaults.items():
86
+ if key not in st.session_state:
87
+ st.session_state[key] = value
88
+
89
+ def load_user_history_from_db():
90
+ """Load user's history from database into session state - ENHANCED ERROR HANDLING"""
91
+ try:
92
+ user_id = st.session_state.user_id
93
+ history = get_user_history(user_id)
94
+ st.session_state.user_history = history
95
+ print(f"βœ… Loaded {len(history)} history entries for user {user_id[:8]}...")
96
+
97
+ # If we have history but it's not showing, add a debug message
98
+ if history and not st.session_state.get('user_history'):
99
+ print("⚠️ History loaded but not stored in session state")
100
+
101
+ except Exception as e:
102
+ print(f"❌ Error loading history from database: {e}")
103
+ st.session_state.user_history = []
104
+
105
+ def restore_user_session():
106
+ """Attempt to restore user session from multiple sources"""
107
+ try:
108
+ # Try to get user ID from cookies
109
+ user_id = get_or_create_user_id()
110
+
111
+ # Load history for this user
112
+ history = get_user_history(user_id)
113
+
114
+ if history:
115
+ print(f"🎯 Restored session for user {user_id[:8]} with {len(history)} history entries")
116
+ return True
117
+ else:
118
+ print(f"πŸ†• New session for user {user_id[:8]}")
119
+ return False
120
+
121
+ except Exception as e:
122
+ print(f"❌ Session restoration failed: {e}")
123
+ return False
124
+
125
+ def clear_session():
126
+ """Clear session state but preserve user identity and history"""
127
+ preserved_keys = ['user_id', 'current_page'] # REMOVED 'user_history' from preserved keys
128
+ preserved = {k: st.session_state[k] for k in preserved_keys if k in st.session_state}
129
+
130
+ st.session_state.clear()
131
+
132
+ # Restore preserved keys
133
+ for k, v in preserved.items():
134
+ st.session_state[k] = v
135
+
136
+ # Re-initialize defaults AND reload history
137
+ initialize_session_state()
138
+
139
+ def update_session_state(**kwargs):
140
+ for key, value in kwargs.items():
141
+ st.session_state[key] = value
142
+
143
+ def save_current_to_history():
144
+ """Save current content to database and update session state"""
145
+ from db.helpers import save_content_to_history
146
+ if st.session_state.generated_output and not st.session_state.regenerated:
147
+ content_data = {
148
+ "user_id": st.session_state.user_id,
149
+ "user_type": st.session_state.user_type,
150
+ "student_level": st.session_state.student_level,
151
+ "topic": st.session_state.tutor_topic if st.session_state.user_type == "tutor" else "Simplified Content",
152
+ "content_type": st.session_state.tutor_content_type if st.session_state.user_type == "tutor" else "Simplified Explanation",
153
+ "prompt": st.session_state.original_prompt,
154
+ "output": st.session_state.generated_output,
155
+ "pdf_base64": st.session_state.pdf_export_data,
156
+ "filename": generate_history_filename(),
157
+ "feedback_given": st.session_state.feedback_given,
158
+ "generated_model": st.session_state.get("generated_model", "groq")
159
+ }
160
+ entry_id = save_content_to_history(content_data)
161
+ if entry_id:
162
+ st.session_state.current_history_id = entry_id
163
+ st.session_state.saved_to_history = True
164
+ # Reload history from database to include new entry
165
+ load_user_history_from_db()
166
+ return entry_id
167
+ return None
168
+
169
+ def generate_history_filename():
170
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M")
171
+ if st.session_state.user_type == "student":
172
+ level_clean = re.sub(r'[^a-zA-Z0-9]', '_', st.session_state.student_level)
173
+ return f"student_content_{level_clean}_{timestamp}.pdf"
174
+ else:
175
+ topic_clean = re.sub(r'[^a-zA-Z0-9]', '_', st.session_state.tutor_topic)[:20]
176
+ content_type_clean = st.session_state.tutor_content_type.replace(' ', '_')
177
+ return f"{content_type_clean}_{topic_clean}_{timestamp}.pdf"
178
+
179
+ def get_session_info():
180
+ return {
181
+ "user_id": st.session_state.user_id,
182
+ "user_type": st.session_state.user_type,
183
+ "has_output": bool(st.session_state.generated_output),
184
+ "feedback_given": st.session_state.feedback_given,
185
+ "regenerated": st.session_state.regenerated,
186
+ "current_page": st.session_state.current_page,
187
+ "current_history_id": st.session_state.current_history_id,
188
+ "history_entries": len(st.session_state.user_history)
189
+ }
190
+
191
+ def prepare_for_model_regeneration():
192
+ """Prepare session state for model regeneration - UPDATED tracking"""
193
+ # Preserve the essential content generation context
194
+ preserved_data = {
195
+ 'user_type': st.session_state.user_type,
196
+ 'student_level': st.session_state.student_level,
197
+ 'content_source': st.session_state.content_source,
198
+ 'original_prompt': st.session_state.original_prompt,
199
+ # For tutor flow
200
+ 'tutor_topic': st.session_state.get('tutor_topic', ''),
201
+ 'tutor_content_type': st.session_state.get('tutor_content_type', ''),
202
+ # For student flow
203
+ 'original_filename': st.session_state.get('original_filename', 'content.pdf'),
204
+ }
205
+
206
+ # Clear generation outputs but keep context
207
+ keys_to_clear = [
208
+ 'generated_output', 'pdf_export_data', 'feedback_given',
209
+ 'regenerated', 'current_history_id', 'saved_to_history',
210
+ 'feedback_clarity', 'feedback_depth', 'feedback_complexity',
211
+ 'feedback_comments', 'scrolled_to_top'
212
+ ]
213
+
214
+ for key in keys_to_clear:
215
+ if key in st.session_state:
216
+ del st.session_state[key]
217
+
218
+ # Track regeneration
219
+ st.session_state.regeneration_count = st.session_state.get('regeneration_count', 0) + 1
220
+ st.session_state.regeneration_type = 'model_switch'
221
+
222
+ # Restore preserved context
223
+ for key, value in preserved_data.items():
224
+ if value: # Only restore if we have actual values
225
+ st.session_state[key] = value
226
+
227
+ st.session_state.regenerate_with_new_model = True
228
+
229
+ # import streamlit as st
230
+ # from streamlit_js_eval import get_cookie, set_cookie
231
+ # import uuid
232
+ # from datetime import datetime
233
+ # import re
234
+ # from db.helpers import get_user_history, ensure_user_exists
235
+
236
+ # def get_or_create_user_id():
237
+ # """Get user ID from cookie or create new one"""
238
+ # user_id = get_cookie("edugen_user_id")
239
+ # if not user_id:
240
+ # user_id = str(uuid.uuid4())
241
+ # set_cookie("edugen_user_id", user_id, duration_days=365)
242
+ # print(f"βœ… Created new user ID: {user_id}")
243
+ # return user_id
244
+
245
+ # def initialize_session_state():
246
+ # """Initialize session state and load user data from database"""
247
+ # # Initialize user ID first
248
+ # if "user_id" not in st.session_state:
249
+ # st.session_state.user_id = get_or_create_user_id()
250
+
251
+ # # ALWAYS load user history from database - REMOVE THE CONDITION
252
+ # load_user_history_from_db()
253
+
254
+ # session_defaults = {
255
+ # "user_type": "",
256
+ # "original_prompt": "",
257
+ # "generated_output": "",
258
+ # "feedback_given": False,
259
+ # "regenerated": False,
260
+ # "content_source": "",
261
+ # "student_level": "",
262
+ # "pdf_export_data": None,
263
+ # "tutor_topic": "",
264
+ # "tutor_content_type": "",
265
+ # "feedback_clarity": 3,
266
+ # "feedback_depth": 3,
267
+ # "feedback_complexity": "Just right",
268
+ # "feedback_comments": "",
269
+ # "original_filename": "content.pdf",
270
+ # "current_page": "generator",
271
+ # "current_history_id": None,
272
+ # "saved_to_history": False,
273
+ # "user_history": [],
274
+ # "from_history": False,
275
+ # "showing_regeneration_prompt": False,
276
+ # "pending_model_switch": None,
277
+ # "previous_model": None,
278
+ # "regenerate_with_new_model": False,
279
+ # "scrolled_to_top": False,
280
+ # "regeneration_count": 0,
281
+ # "regeneration_type": None,
282
+ # "previous_feedback_given": False,
283
+ # "pending_regeneration": False,
284
+ # "show_adaptation_message": True,
285
+ # }
286
+
287
+ # for key, value in session_defaults.items():
288
+ # if key not in st.session_state:
289
+ # st.session_state[key] = value
290
+
291
+ # def load_user_history_from_db():
292
+ # """Load user's history from database into session state - CALL THIS ON EVERY LOAD"""
293
+ # try:
294
+ # # Ensure we have a user_id
295
+ # user_id = st.session_state.user_id if hasattr(st.session_state, 'user_id') else get_or_create_user_id()
296
+ # history = get_user_history(user_id)
297
+ # st.session_state.user_history = history
298
+ # print(f"βœ… Loaded {len(history)} history entries for user {user_id}")
299
+ # except Exception as e:
300
+ # print(f"❌ Error loading history from database: {e}")
301
+ # st.session_state.user_history = []
302
+
303
+ # def clear_session():
304
+ # """Clear session state but preserve user identity and history"""
305
+ # preserved_keys = ['user_id', 'current_page'] # REMOVED 'user_history' from preserved keys
306
+ # preserved = {k: st.session_state[k] for k in preserved_keys if k in st.session_state}
307
+
308
+ # st.session_state.clear()
309
+
310
+ # # Restore preserved keys
311
+ # for k, v in preserved.items():
312
+ # st.session_state[k] = v
313
+
314
+ # # Re-initialize defaults AND reload history
315
+ # initialize_session_state()
316
+
317
+ # def update_session_state(**kwargs):
318
+ # for key, value in kwargs.items():
319
+ # st.session_state[key] = value
320
+
321
+ # def save_current_to_history():
322
+ # """Save current content to database and update session state"""
323
+ # from db.helpers import save_content_to_history
324
+ # if st.session_state.generated_output and not st.session_state.regenerated:
325
+ # content_data = {
326
+ # "user_id": st.session_state.user_id,
327
+ # "user_type": st.session_state.user_type,
328
+ # "student_level": st.session_state.student_level,
329
+ # "topic": st.session_state.tutor_topic if st.session_state.user_type == "tutor" else "Simplified Content",
330
+ # "content_type": st.session_state.tutor_content_type if st.session_state.user_type == "tutor" else "Simplified Explanation",
331
+ # "prompt": st.session_state.original_prompt,
332
+ # "output": st.session_state.generated_output,
333
+ # "pdf_base64": st.session_state.pdf_export_data,
334
+ # "filename": generate_history_filename(),
335
+ # "feedback_given": st.session_state.feedback_given,
336
+ # "generated_model": st.session_state.get("generated_model", "groq")
337
+ # }
338
+ # entry_id = save_content_to_history(content_data)
339
+ # if entry_id:
340
+ # st.session_state.current_history_id = entry_id
341
+ # st.session_state.saved_to_history = True
342
+ # # Reload history from database to include new entry
343
+ # load_user_history_from_db()
344
+ # return entry_id
345
+ # return None
346
+
347
+ # def generate_history_filename():
348
+ # timestamp = datetime.now().strftime("%Y%m%d_%H%M")
349
+ # if st.session_state.user_type == "student":
350
+ # level_clean = re.sub(r'[^a-zA-Z0-9]', '_', st.session_state.student_level)
351
+ # return f"student_content_{level_clean}_{timestamp}.pdf"
352
+ # else:
353
+ # topic_clean = re.sub(r'[^a-zA-Z0-9]', '_', st.session_state.tutor_topic)[:20]
354
+ # content_type_clean = st.session_state.tutor_content_type.replace(' ', '_')
355
+ # return f"{content_type_clean}_{topic_clean}_{timestamp}.pdf"
356
+
357
+ # def get_session_info():
358
+ # return {
359
+ # "user_id": st.session_state.user_id,
360
+ # "user_type": st.session_state.user_type,
361
+ # "has_output": bool(st.session_state.generated_output),
362
+ # "feedback_given": st.session_state.feedback_given,
363
+ # "regenerated": st.session_state.regenerated,
364
+ # "current_page": st.session_state.current_page,
365
+ # "current_history_id": st.session_state.current_history_id,
366
+ # "history_entries": len(st.session_state.user_history)
367
+ # }
368
+
369
+ # def prepare_for_model_regeneration():
370
+ # """Prepare session state for model regeneration - UPDATED tracking"""
371
+ # # Preserve the essential content generation context
372
+ # preserved_data = {
373
+ # 'user_type': st.session_state.user_type,
374
+ # 'student_level': st.session_state.student_level,
375
+ # 'content_source': st.session_state.content_source,
376
+ # 'original_prompt': st.session_state.original_prompt,
377
+ # # For tutor flow
378
+ # 'tutor_topic': st.session_state.get('tutor_topic', ''),
379
+ # 'tutor_content_type': st.session_state.get('tutor_content_type', ''),
380
+ # # For student flow
381
+ # 'original_filename': st.session_state.get('original_filename', 'content.pdf'),
382
+ # }
383
+
384
+ # # Clear generation outputs but keep context
385
+ # keys_to_clear = [
386
+ # 'generated_output', 'pdf_export_data', 'feedback_given',
387
+ # 'regenerated', 'current_history_id', 'saved_to_history',
388
+ # 'feedback_clarity', 'feedback_depth', 'feedback_complexity',
389
+ # 'feedback_comments', 'scrolled_to_top'
390
+ # ]
391
+
392
+ # for key in keys_to_clear:
393
+ # if key in st.session_state:
394
+ # del st.session_state[key]
395
+
396
+ # # Track regeneration
397
+ # st.session_state.regeneration_count = st.session_state.get('regeneration_count', 0) + 1
398
+ # st.session_state.regeneration_type = 'model_switch'
399
+
400
+ # # Restore preserved context
401
+ # for key, value in preserved_data.items():
402
+ # if value: # Only restore if we have actual values
403
+ # st.session_state[key] = value
404
+
405
+ # st.session_state.regenerate_with_new_model = True