Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import random
|
| 3 |
import hashlib
|
|
|
|
| 4 |
import threading
|
| 5 |
from typing import Dict, List, Tuple, Any, Optional
|
| 6 |
|
|
@@ -378,7 +379,15 @@ class PrivacyMemoryStore:
|
|
| 378 |
def __init__(self):
|
| 379 |
self._lock = threading.Lock()
|
| 380 |
self._records: List[Dict[str, Any]] = []
|
| 381 |
-
self._admin_pin =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
def upsert_entry(self, code: str, scores: Dict[str, Any], previous_code: Optional[str] = None) -> str:
|
| 384 |
with self._lock:
|
|
@@ -704,11 +713,30 @@ def switch_language(lang_choice: str):
|
|
| 704 |
)
|
| 705 |
)
|
| 706 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
updates.append(gr.update(value=t["submit_btn"]))
|
|
|
|
| 708 |
updates.append(t["sources_footer"])
|
| 709 |
return updates
|
| 710 |
|
| 711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
def process_student_submission(lang_code: str, custom_code: str, current_token: Optional[str], *answers):
|
| 713 |
t = TRANSLATIONS.get(lang_code, TRANSLATIONS["DE"])
|
| 714 |
missing_nccs = [i + 1 for i in range(16) if answers[i] is None]
|
|
@@ -723,6 +751,7 @@ def process_student_submission(lang_code: str, custom_code: str, current_token:
|
|
| 723 |
return (
|
| 724 |
"\n".join(msg_parts),
|
| 725 |
gr.update(visible=False, value=None),
|
|
|
|
| 726 |
current_token
|
| 727 |
)
|
| 728 |
|
|
@@ -752,9 +781,17 @@ def process_student_submission(lang_code: str, custom_code: str, current_token:
|
|
| 752 |
title=f"Psychometrisches 2x2-Profil ({final_code})"
|
| 753 |
)
|
| 754 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 755 |
return (
|
| 756 |
code_banner,
|
| 757 |
gr.update(visible=True, value=fig),
|
|
|
|
| 758 |
final_code
|
| 759 |
)
|
| 760 |
|
|
@@ -810,16 +847,75 @@ def refresh_lecturer_dashboard(
|
|
| 810 |
return status_md, fig
|
| 811 |
|
| 812 |
|
| 813 |
-
def
|
| 814 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
|
| 816 |
-
return
|
| 817 |
|
| 818 |
|
| 819 |
-
def handle_admin_demo(
|
| 820 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 821 |
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
|
| 822 |
-
return
|
| 823 |
|
| 824 |
|
| 825 |
# ==============================================================================
|
|
@@ -992,6 +1088,12 @@ with gr.Blocks(
|
|
| 992 |
|
| 993 |
feedback_output = gr.Markdown()
|
| 994 |
personal_plot_output = gr.Plot(visible=False, label="Psychometrisches 2x2-Profil")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 995 |
|
| 996 |
sources_footer_md = gr.Markdown(
|
| 997 |
TRANSLATIONS["DE"]["sources_footer"],
|
|
@@ -1011,6 +1113,7 @@ with gr.Blocks(
|
|
| 1011 |
part_b_header_md,
|
| 1012 |
*ie4_inputs,
|
| 1013 |
submit_btn,
|
|
|
|
| 1014 |
sources_footer_md,
|
| 1015 |
],
|
| 1016 |
api_name=False
|
|
@@ -1019,81 +1122,107 @@ with gr.Blocks(
|
|
| 1019 |
submit_btn.click(
|
| 1020 |
fn=process_student_submission,
|
| 1021 |
inputs=[lang_state, custom_code_input, student_token_state] + nccs_inputs + ie4_inputs,
|
| 1022 |
-
outputs=[feedback_output, personal_plot_output, student_token_state],
|
| 1023 |
api_name=False
|
| 1024 |
)
|
| 1025 |
|
| 1026 |
# ----------------------------------------------------------------------
|
| 1027 |
-
# TAB 2: LIVE-AUSWERTUNG HÖRSAAL (DOZENT)
|
| 1028 |
# ----------------------------------------------------------------------
|
| 1029 |
with gr.TabItem("🖥️ 2. Live-Auswertung Hörsaal"):
|
| 1030 |
-
|
| 1031 |
-
|
| 1032 |
-
|
| 1033 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
)
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
"🔄 Aktualisieren",
|
| 1038 |
variant="primary",
|
| 1039 |
-
size="lg"
|
|
|
|
| 1040 |
)
|
|
|
|
| 1041 |
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
label="Psychometrisches 2x2-Profil"
|
| 1045 |
-
)
|
| 1046 |
-
|
| 1047 |
-
with gr.Accordion("⚙️ Anzeige- & Admin-Optionen", open=False):
|
| 1048 |
with gr.Row():
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
)
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
| 1070 |
-
],
|
| 1071 |
-
value="EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)",
|
| 1072 |
-
label="Y-Achse"
|
| 1073 |
-
)
|
| 1074 |
-
x_cutoff_radio = gr.Radio(
|
| 1075 |
-
choices=[
|
| 1076 |
-
"Empirischer Referenzwert (M = 3.30)",
|
| 1077 |
-
"Theoretische Skalenmitte (3.50)",
|
| 1078 |
-
],
|
| 1079 |
-
value="Empirischer Referenzwert (M = 3.30)",
|
| 1080 |
-
label="Trennlinie X-Achse"
|
| 1081 |
-
)
|
| 1082 |
-
with gr.Row():
|
| 1083 |
-
admin_pin_input = gr.Textbox(
|
| 1084 |
-
label="PIN",
|
| 1085 |
-
type="password",
|
| 1086 |
-
placeholder="PIN",
|
| 1087 |
-
scale=2
|
| 1088 |
-
)
|
| 1089 |
-
reset_btn = gr.Button("🗑️ Reset", variant="stop", scale=1)
|
| 1090 |
-
demo_btn = gr.Button("🧪 Demo-Daten", variant="secondary", scale=1)
|
| 1091 |
-
admin_msg_output = gr.Markdown()
|
| 1092 |
|
| 1093 |
-
|
| 1094 |
-
|
| 1095 |
-
|
| 1096 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1097 |
|
| 1098 |
plot_controls = [
|
| 1099 |
expected_course_size,
|
|
@@ -1103,10 +1232,50 @@ with gr.Blocks(
|
|
| 1103 |
beamer_mode_checkbox
|
| 1104 |
]
|
| 1105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1106 |
refresh_btn.click(
|
| 1107 |
fn=refresh_lecturer_dashboard,
|
| 1108 |
inputs=plot_controls,
|
| 1109 |
-
outputs=
|
| 1110 |
api_name=False
|
| 1111 |
)
|
| 1112 |
|
|
@@ -1114,21 +1283,21 @@ with gr.Blocks(
|
|
| 1114 |
ctrl.change(
|
| 1115 |
fn=refresh_lecturer_dashboard,
|
| 1116 |
inputs=plot_controls,
|
| 1117 |
-
outputs=
|
| 1118 |
api_name=False
|
| 1119 |
)
|
| 1120 |
|
| 1121 |
reset_btn.click(
|
| 1122 |
fn=handle_admin_reset,
|
| 1123 |
-
inputs=[
|
| 1124 |
-
outputs=[
|
| 1125 |
api_name=False
|
| 1126 |
)
|
| 1127 |
|
| 1128 |
demo_btn.click(
|
| 1129 |
fn=handle_admin_demo,
|
| 1130 |
-
inputs=[
|
| 1131 |
-
outputs=[
|
| 1132 |
api_name=False
|
| 1133 |
)
|
| 1134 |
|
|
|
|
| 1 |
import os
|
| 2 |
import random
|
| 3 |
import hashlib
|
| 4 |
+
import tempfile
|
| 5 |
import threading
|
| 6 |
from typing import Dict, List, Tuple, Any, Optional
|
| 7 |
|
|
|
|
| 379 |
def __init__(self):
|
| 380 |
self._lock = threading.Lock()
|
| 381 |
self._records: List[Dict[str, Any]] = []
|
| 382 |
+
self._admin_pin = (
|
| 383 |
+
os.environ.get("ADMIN_PIN")
|
| 384 |
+
or os.environ.get("PIN")
|
| 385 |
+
or os.environ.get("LECTURER_PIN")
|
| 386 |
+
or "2026"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
def verify_pin(self, pin: str) -> bool:
|
| 390 |
+
return bool(pin) and pin.strip() == self._admin_pin
|
| 391 |
|
| 392 |
def upsert_entry(self, code: str, scores: Dict[str, Any], previous_code: Optional[str] = None) -> str:
|
| 393 |
with self._lock:
|
|
|
|
| 713 |
)
|
| 714 |
)
|
| 715 |
|
| 716 |
+
dl_labels = {
|
| 717 |
+
"DE": "📥 Persönliche Ergebnisse herunterladen (.md)",
|
| 718 |
+
"EN": "📥 Download Personal Results (.md)",
|
| 719 |
+
"ZH": "📥 下载个人评估结果 (.md)",
|
| 720 |
+
}
|
| 721 |
updates.append(gr.update(value=t["submit_btn"]))
|
| 722 |
+
updates.append(gr.update(label=dl_labels.get(lang_code, dl_labels["DE"])))
|
| 723 |
updates.append(t["sources_footer"])
|
| 724 |
return updates
|
| 725 |
|
| 726 |
|
| 727 |
+
def create_student_download_file(code: str, scores: Dict[str, Any], lang: str = "DE") -> str:
|
| 728 |
+
report_md = (
|
| 729 |
+
f"# 🧭 Umgang mit Ungewissheit & Kontrollüberzeugung – Ergebnisprofil ({code})\n\n"
|
| 730 |
+
+ generate_profile_feedback(scores, lang=lang)
|
| 731 |
+
)
|
| 732 |
+
tmp_dir = tempfile.mkdtemp()
|
| 733 |
+
safe_code = "".join(c for c in code if c.isalnum() or c in ("-", "_")) or "Profil"
|
| 734 |
+
file_path = os.path.join(tmp_dir, f"Ergebnis_{safe_code}.md")
|
| 735 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
| 736 |
+
f.write(report_md)
|
| 737 |
+
return file_path
|
| 738 |
+
|
| 739 |
+
|
| 740 |
def process_student_submission(lang_code: str, custom_code: str, current_token: Optional[str], *answers):
|
| 741 |
t = TRANSLATIONS.get(lang_code, TRANSLATIONS["DE"])
|
| 742 |
missing_nccs = [i + 1 for i in range(16) if answers[i] is None]
|
|
|
|
| 751 |
return (
|
| 752 |
"\n".join(msg_parts),
|
| 753 |
gr.update(visible=False, value=None),
|
| 754 |
+
gr.update(visible=False, value=None),
|
| 755 |
current_token
|
| 756 |
)
|
| 757 |
|
|
|
|
| 781 |
title=f"Psychometrisches 2x2-Profil ({final_code})"
|
| 782 |
)
|
| 783 |
|
| 784 |
+
download_path = create_student_download_file(final_code, scores, lang=lang_code)
|
| 785 |
+
dl_labels = {
|
| 786 |
+
"DE": "📥 Persönliche Ergebnisse herunterladen (.md)",
|
| 787 |
+
"EN": "📥 Download Personal Results (.md)",
|
| 788 |
+
"ZH": "📥 下载个人评估结果 (.md)",
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
return (
|
| 792 |
code_banner,
|
| 793 |
gr.update(visible=True, value=fig),
|
| 794 |
+
gr.update(visible=True, value=download_path, label=dl_labels.get(lang_code, dl_labels["DE"])),
|
| 795 |
final_code
|
| 796 |
)
|
| 797 |
|
|
|
|
| 847 |
return status_md, fig
|
| 848 |
|
| 849 |
|
| 850 |
+
def unlock_lecturer_view(
|
| 851 |
+
pin: str,
|
| 852 |
+
expected_count: int,
|
| 853 |
+
y_mode_label: str,
|
| 854 |
+
x_cutoff_label: str,
|
| 855 |
+
show_labels: bool,
|
| 856 |
+
beamer_mode: bool
|
| 857 |
+
):
|
| 858 |
+
if not SESSION_STORE.verify_pin(pin):
|
| 859 |
+
return (
|
| 860 |
+
False,
|
| 861 |
+
"",
|
| 862 |
+
"❌ **PIN ungültig.** Bitte prüfen Sie das im Space hinterlegte Secret.",
|
| 863 |
+
gr.update(visible=True),
|
| 864 |
+
gr.update(visible=False),
|
| 865 |
+
gr.skip(),
|
| 866 |
+
gr.skip(),
|
| 867 |
+
)
|
| 868 |
+
status_md, fig = refresh_lecturer_dashboard(
|
| 869 |
+
expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode
|
| 870 |
+
)
|
| 871 |
+
return (
|
| 872 |
+
True,
|
| 873 |
+
"",
|
| 874 |
+
"",
|
| 875 |
+
gr.update(visible=False),
|
| 876 |
+
gr.update(visible=True),
|
| 877 |
+
status_md,
|
| 878 |
+
fig,
|
| 879 |
+
)
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
def lock_lecturer_view():
|
| 883 |
+
return (
|
| 884 |
+
False,
|
| 885 |
+
"",
|
| 886 |
+
gr.update(visible=True),
|
| 887 |
+
gr.update(visible=False),
|
| 888 |
+
)
|
| 889 |
+
|
| 890 |
+
|
| 891 |
+
def handle_admin_reset(
|
| 892 |
+
is_unlocked: bool,
|
| 893 |
+
expected_count: int,
|
| 894 |
+
y_mode_label: str,
|
| 895 |
+
x_cutoff_label: str,
|
| 896 |
+
show_labels: bool,
|
| 897 |
+
beamer_mode: bool
|
| 898 |
+
):
|
| 899 |
+
if not is_unlocked:
|
| 900 |
+
return "❌ Nicht autorisiert.", gr.skip(), gr.skip()
|
| 901 |
+
_, msg = SESSION_STORE.clear_all(SESSION_STORE._admin_pin)
|
| 902 |
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
|
| 903 |
+
return msg, status_md, fig
|
| 904 |
|
| 905 |
|
| 906 |
+
def handle_admin_demo(
|
| 907 |
+
is_unlocked: bool,
|
| 908 |
+
expected_count: int,
|
| 909 |
+
y_mode_label: str,
|
| 910 |
+
x_cutoff_label: str,
|
| 911 |
+
show_labels: bool,
|
| 912 |
+
beamer_mode: bool
|
| 913 |
+
):
|
| 914 |
+
if not is_unlocked:
|
| 915 |
+
return "❌ Nicht autorisiert.", gr.skip(), gr.skip()
|
| 916 |
+
_, msg = SESSION_STORE.seed_demo_data(SESSION_STORE._admin_pin, count=int(expected_count))
|
| 917 |
status_md, fig = refresh_lecturer_dashboard(expected_count, y_mode_label, x_cutoff_label, show_labels, beamer_mode)
|
| 918 |
+
return msg, status_md, fig
|
| 919 |
|
| 920 |
|
| 921 |
# ==============================================================================
|
|
|
|
| 1088 |
|
| 1089 |
feedback_output = gr.Markdown()
|
| 1090 |
personal_plot_output = gr.Plot(visible=False, label="Psychometrisches 2x2-Profil")
|
| 1091 |
+
student_download_btn = gr.DownloadButton(
|
| 1092 |
+
label="📥 Persönliche Ergebnisse herunterladen (.md)",
|
| 1093 |
+
visible=False,
|
| 1094 |
+
variant="secondary",
|
| 1095 |
+
size="lg",
|
| 1096 |
+
)
|
| 1097 |
|
| 1098 |
sources_footer_md = gr.Markdown(
|
| 1099 |
TRANSLATIONS["DE"]["sources_footer"],
|
|
|
|
| 1113 |
part_b_header_md,
|
| 1114 |
*ie4_inputs,
|
| 1115 |
submit_btn,
|
| 1116 |
+
student_download_btn,
|
| 1117 |
sources_footer_md,
|
| 1118 |
],
|
| 1119 |
api_name=False
|
|
|
|
| 1122 |
submit_btn.click(
|
| 1123 |
fn=process_student_submission,
|
| 1124 |
inputs=[lang_state, custom_code_input, student_token_state] + nccs_inputs + ie4_inputs,
|
| 1125 |
+
outputs=[feedback_output, personal_plot_output, student_download_btn, student_token_state],
|
| 1126 |
api_name=False
|
| 1127 |
)
|
| 1128 |
|
| 1129 |
# ----------------------------------------------------------------------
|
| 1130 |
+
# TAB 2: LIVE-AUSWERTUNG HÖRSAAL (DOZENT MIT PIN-SCHUTZ)
|
| 1131 |
# ----------------------------------------------------------------------
|
| 1132 |
with gr.TabItem("🖥️ 2. Live-Auswertung Hörsaal"):
|
| 1133 |
+
lecturer_unlocked_state = gr.State(value=False)
|
| 1134 |
+
|
| 1135 |
+
# PIN-Sperrbildschirm (standardmäßig sichtbar)
|
| 1136 |
+
with gr.Column(visible=True) as lecturer_login_group:
|
| 1137 |
+
gr.Markdown(
|
| 1138 |
+
"### 🔒 Geschützter Dozenten-Bereich (Beamer-Ansicht)\n"
|
| 1139 |
+
"Bitte geben Sie die Dozenten-PIN ein (hinterlegt als Secret `ADMIN_PIN` bzw. `PIN` im Space), "
|
| 1140 |
+
"um die Live-Auswertung für den Hörsaal freizuschalten."
|
| 1141 |
+
)
|
| 1142 |
+
with gr.Row():
|
| 1143 |
+
lecturer_pin_input = gr.Textbox(
|
| 1144 |
+
label="Dozenten-PIN",
|
| 1145 |
+
type="password",
|
| 1146 |
+
placeholder="PIN eingeben und mit Enter oder Button bestätigen...",
|
| 1147 |
+
scale=3,
|
| 1148 |
)
|
| 1149 |
+
unlock_btn = gr.Button(
|
| 1150 |
+
"🔓 Beamer-Ansicht freischalten",
|
|
|
|
| 1151 |
variant="primary",
|
| 1152 |
+
size="lg",
|
| 1153 |
+
scale=1,
|
| 1154 |
)
|
| 1155 |
+
login_msg_output = gr.Markdown()
|
| 1156 |
|
| 1157 |
+
# Eigentliche Beamer-Ansicht (erst nach erfolgreicher PIN-Eingabe sichtbar)
|
| 1158 |
+
with gr.Column(visible=False) as lecturer_content_group:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1159 |
with gr.Row():
|
| 1160 |
+
with gr.Column(scale=3):
|
| 1161 |
+
lecturer_status_md = gr.Markdown(
|
| 1162 |
+
"<div class='beamer-stats'><span>👥 Eingegangene Antworten: <b>0 / 14</b></span></div>"
|
| 1163 |
+
)
|
| 1164 |
+
with gr.Column(scale=1):
|
| 1165 |
+
refresh_btn = gr.Button(
|
| 1166 |
+
"🔄 Aktualisieren",
|
| 1167 |
+
variant="primary",
|
| 1168 |
+
size="lg"
|
| 1169 |
+
)
|
| 1170 |
+
with gr.Column(scale=1):
|
| 1171 |
+
lock_btn = gr.Button(
|
| 1172 |
+
"🔒 Ansicht sperren",
|
| 1173 |
+
variant="secondary",
|
| 1174 |
+
size="lg"
|
| 1175 |
+
)
|
| 1176 |
+
|
| 1177 |
+
lecturer_plot_output = gr.Plot(
|
| 1178 |
+
value=build_compass_plot([], beamer_mode=True, title="Live-Auswertung Hörsaal (n = 0)"),
|
| 1179 |
+
label="Psychometrisches 2x2-Profil"
|
| 1180 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1181 |
|
| 1182 |
+
with gr.Accordion("⚙️ Anzeige- & Admin-Optionen", open=False):
|
| 1183 |
+
with gr.Row():
|
| 1184 |
+
expected_course_size = gr.Number(
|
| 1185 |
+
value=14,
|
| 1186 |
+
label="Kursgröße",
|
| 1187 |
+
precision=0,
|
| 1188 |
+
minimum=1,
|
| 1189 |
+
maximum=200
|
| 1190 |
+
)
|
| 1191 |
+
show_labels_checkbox = gr.Checkbox(
|
| 1192 |
+
value=True,
|
| 1193 |
+
label="Teilnehmer-Codes anzeigen"
|
| 1194 |
+
)
|
| 1195 |
+
beamer_mode_checkbox = gr.Checkbox(
|
| 1196 |
+
value=True,
|
| 1197 |
+
label="Beamer-Modus (Maximaler Kontrast & große Schrift)"
|
| 1198 |
+
)
|
| 1199 |
+
with gr.Row():
|
| 1200 |
+
y_mode_dropdown = gr.Dropdown(
|
| 1201 |
+
choices=[
|
| 1202 |
+
"EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)",
|
| 1203 |
+
"IKÜ: Internale Kontrollüberzeugung (1.0 bis 5.0)",
|
| 1204 |
+
"Netto-Internalität: IKÜ minus EKÜ (-4.0 bis +4.0)",
|
| 1205 |
+
],
|
| 1206 |
+
value="EKÜ: Externale Kontrollüberzeugung (1.0 bis 5.0)",
|
| 1207 |
+
label="Y-Achse"
|
| 1208 |
+
)
|
| 1209 |
+
x_cutoff_radio = gr.Radio(
|
| 1210 |
+
choices=[
|
| 1211 |
+
"Empirischer Referenzwert (M = 3.30)",
|
| 1212 |
+
"Theoretische Skalenmitte (3.50)",
|
| 1213 |
+
],
|
| 1214 |
+
value="Empirischer Referenzwert (M = 3.30)",
|
| 1215 |
+
label="Trennlinie X-Achse"
|
| 1216 |
+
)
|
| 1217 |
+
with gr.Row():
|
| 1218 |
+
reset_btn = gr.Button("🗑️ Reset (Arbeitsspeicher leeren)", variant="stop", scale=1)
|
| 1219 |
+
demo_btn = gr.Button("🧪 Demo-Daten laden", variant="secondary", scale=1)
|
| 1220 |
+
admin_msg_output = gr.Markdown()
|
| 1221 |
+
|
| 1222 |
+
gr.Markdown(
|
| 1223 |
+
TRANSLATIONS["DE"]["sources_footer"],
|
| 1224 |
+
elem_classes=["sources-box"]
|
| 1225 |
+
)
|
| 1226 |
|
| 1227 |
plot_controls = [
|
| 1228 |
expected_course_size,
|
|
|
|
| 1232 |
beamer_mode_checkbox
|
| 1233 |
]
|
| 1234 |
|
| 1235 |
+
dashboard_outputs = [
|
| 1236 |
+
lecturer_status_md,
|
| 1237 |
+
lecturer_plot_output,
|
| 1238 |
+
]
|
| 1239 |
+
|
| 1240 |
+
unlock_outputs = [
|
| 1241 |
+
lecturer_unlocked_state,
|
| 1242 |
+
lecturer_pin_input,
|
| 1243 |
+
login_msg_output,
|
| 1244 |
+
lecturer_login_group,
|
| 1245 |
+
lecturer_content_group,
|
| 1246 |
+
*dashboard_outputs,
|
| 1247 |
+
]
|
| 1248 |
+
|
| 1249 |
+
unlock_btn.click(
|
| 1250 |
+
fn=unlock_lecturer_view,
|
| 1251 |
+
inputs=[lecturer_pin_input] + plot_controls,
|
| 1252 |
+
outputs=unlock_outputs,
|
| 1253 |
+
api_name=False
|
| 1254 |
+
)
|
| 1255 |
+
|
| 1256 |
+
lecturer_pin_input.submit(
|
| 1257 |
+
fn=unlock_lecturer_view,
|
| 1258 |
+
inputs=[lecturer_pin_input] + plot_controls,
|
| 1259 |
+
outputs=unlock_outputs,
|
| 1260 |
+
api_name=False
|
| 1261 |
+
)
|
| 1262 |
+
|
| 1263 |
+
lock_btn.click(
|
| 1264 |
+
fn=lock_lecturer_view,
|
| 1265 |
+
inputs=[],
|
| 1266 |
+
outputs=[
|
| 1267 |
+
lecturer_unlocked_state,
|
| 1268 |
+
login_msg_output,
|
| 1269 |
+
lecturer_login_group,
|
| 1270 |
+
lecturer_content_group,
|
| 1271 |
+
],
|
| 1272 |
+
api_name=False
|
| 1273 |
+
)
|
| 1274 |
+
|
| 1275 |
refresh_btn.click(
|
| 1276 |
fn=refresh_lecturer_dashboard,
|
| 1277 |
inputs=plot_controls,
|
| 1278 |
+
outputs=dashboard_outputs,
|
| 1279 |
api_name=False
|
| 1280 |
)
|
| 1281 |
|
|
|
|
| 1283 |
ctrl.change(
|
| 1284 |
fn=refresh_lecturer_dashboard,
|
| 1285 |
inputs=plot_controls,
|
| 1286 |
+
outputs=dashboard_outputs,
|
| 1287 |
api_name=False
|
| 1288 |
)
|
| 1289 |
|
| 1290 |
reset_btn.click(
|
| 1291 |
fn=handle_admin_reset,
|
| 1292 |
+
inputs=[lecturer_unlocked_state] + plot_controls,
|
| 1293 |
+
outputs=[admin_msg_output] + dashboard_outputs,
|
| 1294 |
api_name=False
|
| 1295 |
)
|
| 1296 |
|
| 1297 |
demo_btn.click(
|
| 1298 |
fn=handle_admin_demo,
|
| 1299 |
+
inputs=[lecturer_unlocked_state] + plot_controls,
|
| 1300 |
+
outputs=[admin_msg_output] + dashboard_outputs,
|
| 1301 |
api_name=False
|
| 1302 |
)
|
| 1303 |
|