Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import json | |
| import csv | |
| from datetime import datetime | |
| import os | |
| import hashlib | |
| # ADMIN HESLO - Změňte toto! | |
| ADMIN_PASSWORD_HASH = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" # "password" - ZMĚŇTE! | |
| # Pro vytvoření vlastního hashe: import hashlib; hashlib.sha256("vase_heslo".encode()).hexdigest() | |
| # Data úložiště | |
| RESULTS_FILE = "vysledky_testu.csv" | |
| FEEDBACK_FILE = "zpetna_vazba.csv" | |
| # Inicializace souborů | |
| if not os.path.exists(RESULTS_FILE): | |
| with open(RESULTS_FILE, 'w', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow(['timestamp', 'kod_ucastnika', 'typ_testu', 'skore', 'maximalni_skore', 'procenta', 'odpovedi']) | |
| if not os.path.exists(FEEDBACK_FILE): | |
| with open(FEEDBACK_FILE, 'w', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow(['timestamp', 'kod_ucastnika', 'typ_testu', 'obtiznost', 'srozumitelnost', 'uzitecnost', 'komentar']) | |
| def check_admin_password(password): | |
| """Ověří admin heslo""" | |
| return hashlib.sha256(password.encode()).hexdigest() == ADMIN_PASSWORD_HASH | |
| # Definice testů (bez správných odpovědí viditelných v UI) | |
| TESTS = { | |
| "STAFF": { | |
| "nazev": "Test pro administrativní pracovníky (STAFF)", | |
| "popis": "10 otázek zaměřených na praktické použití AI v kanceláři", | |
| "otazky": [ | |
| { | |
| "q": "Jaká je doporučená struktura efektivního promptu pro práci s AI?", | |
| "opts": [ | |
| "A) Role + Kontext + Úkol + Formát", | |
| "B) Pouze úkol, ostatní AI domyslí sama", | |
| "C) Poskytnutí jasné role AI zlepšuje kvalitu odpovědí", | |
| "D) Formát výstupu není důležitý", | |
| "E) Kontext pomáhá AI lépe pochopit situaci" | |
| ], | |
| "spravne": ["A", "C", "E"] | |
| }, | |
| { | |
| "q": "Co přesně znamená termín 'zero-shot prompting'?", | |
| "opts": [ | |
| "A) Poskytnutí jednoho vzorového příkladu", | |
| "B) Zadání instrukce bez jakýchkoliv příkladů", | |
| "C) Poskytnutí tří vzorových příkladů", | |
| "D) Nejjednodušší forma práce s AI", | |
| "E) Vždy poskytuje nejlepší výsledky" | |
| ], | |
| "spravne": ["B", "D"] | |
| }, | |
| { | |
| "q": "Co je 'answer pressure testing' a k čemu slouží?", | |
| "opts": [ | |
| "A) Technika pro zrychlení generování odpovědí", | |
| "B) Žádost, aby AI zkontrolovala a zdůvodnila své odpovědi", | |
| "C) Příklad: 'Jsi si tímto výsledkem jistý? Zkontroluj svoje úvahy.'", | |
| "D) Pomáhá odhalit případné halucinace AI", | |
| "E) Snižuje kvalitu odpovědí tím, že AI zmate" | |
| ], | |
| "spravne": ["B", "C", "D"] | |
| }, | |
| { | |
| "q": "Které z následujících úkolů lze bezpečně řešit pomocí AI v kancelářské práci?", | |
| "opts": [ | |
| "A) Shrnutí dlouhých dokumentů do kratší formy", | |
| "B) Extrakce konkrétních informací (jména, data, částky) z dokumentů", | |
| "C) Anonymizace osobních údajů v dokumentech", | |
| "D) Fyzická archivace papírových dokumentů", | |
| "E) Překlad textů a změna stylu komunikace" | |
| ], | |
| "spravne": ["A", "B", "C", "E"] | |
| }, | |
| { | |
| "q": "Co přesně jsou 'halucinace' v kontextu AI systémů?", | |
| "opts": [ | |
| "A) AI vymýšlí informace, které vypadají věrohodně, ale jsou nepravdivé", | |
| "B) Halucinace jsou vždy snadno rozpoznatelné", | |
| "C) Je nezbytné vždy ověřovat důležitá fakta z jiných zdrojů", | |
| "D) Answer pressure testing může pomoci odhalit halucinace", | |
| "E) Nové modely AI už halucinace nemají" | |
| ], | |
| "spravne": ["A", "C", "D"] | |
| }, | |
| { | |
| "q": "Co je 'token' a proč je tento pojem důležitý?", | |
| "opts": [ | |
| "A) Token odpovídá přibližně 4 znakům textu", | |
| "B) Token je základní jednotka zpracování textu v AI", | |
| "C) Token je měna pro placení AI služeb", | |
| "D) AI modely mají limit na počet tokenů", | |
| "E) Token je totéž co jedno slovo" | |
| ], | |
| "spravne": ["A", "B", "D"] | |
| }, | |
| { | |
| "q": "Jak správně pracovat s AI při respektování GDPR?", | |
| "opts": [ | |
| "A) Anonymizovat všechny osobní údaje před nahráním do AI", | |
| "B) GDPR se na práci s AI nevztahuje", | |
| "C) Nikdy neposílat citlivá data do veřejných AI systémů", | |
| "D) Znát politiku zpracování dat AI platformy", | |
| "E) Osobní údaje lze do AI nahrávat bez omezení" | |
| ], | |
| "spravne": ["A", "C", "D"] | |
| }, | |
| { | |
| "q": "Jak správně pracovat s češtinou vs. angličtinou při použití AI?", | |
| "opts": [ | |
| "A) Pro češtinu explicitně uvést 'v češtině'", | |
| "B) AI automaticky rozpozná jazyk vždy správně", | |
| "C) Pro vědu preferovat angličtinu", | |
| "D) Kvalita je stejná pro všechny jazyky", | |
| "E) Lze kombinovat jazyky podle potřeby" | |
| ], | |
| "spravne": ["A", "C", "E"] | |
| }, | |
| { | |
| "q": "Co znamená 'chain-of-thought prompting' a kdy ho použít?", | |
| "opts": [ | |
| "A) Žádost o postupné vysvětlení kroků", | |
| "B) Nejrychlejší způsob generování", | |
| "C) Užitečné pro komplexní analýzy", | |
| "D) Příklad: 'Analyzuj: 1) témata 2) problémy 3) řešení'", | |
| "E) Nevhodné pro analytické úlohy" | |
| ], | |
| "spravne": ["A", "C", "D"] | |
| }, | |
| { | |
| "q": "Co je 'role-playing' v kontextu práce s AI?", | |
| "opts": [ | |
| "A) AI přiřadíte konkrétní roli", | |
| "B) Příklad: 'Jsi zkušený HR manažer s 20 lety praxe'", | |
| "C) Role nijak neovlivňuje odpovědi", | |
| "D) Role pomáhá přizpůsobit jazyk a odbornost", | |
| "E) Používá se pouze pro zábavu" | |
| ], | |
| "spravne": ["A", "B", "D"] | |
| } | |
| ] | |
| }, | |
| "ACADEMIC": { | |
| "nazev": "Test pro akademické pracovníky (ACADEMIC)", | |
| "popis": "12 otázek: 6 základních + 6 metodologických", | |
| "otazky": [ | |
| { | |
| "q": "Jaká je struktura akademického promptu?", | |
| "opts": [ | |
| "A) Role + Kontext + Úkol + Formát + Omezení", | |
| "B) Pouze úkol", | |
| "C) Požadavek na rozlišení fakta/interpretace", | |
| "D) Žádost o označení míry jistoty", | |
| "E) Formát není důležitý" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Co je 'answer pressure testing' a proč je v akademii kritický?", | |
| "opts": [ | |
| "A) Technika systematického zpochybňování", | |
| "B) Příklad: 'Jsi si jistý? Odkud to? Alternativy?'", | |
| "C) Pomáhá odhalit halucinace", | |
| "D) V akademii méně důležitý než v kanceláři", | |
| "E) Nutí AI přiznat nejistotu" | |
| ], | |
| "spravne": ["A", "B", "C", "E"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Proč jsou halucinace v akademii zvlášť problematické?", | |
| "opts": [ | |
| "A) Jedna vymyšlená citace diskredituje celou práci", | |
| "B) V nových modelech již vyřešeny", | |
| "C) AI prezentuje vymyšlené info přesvědčivě", | |
| "D) Týkají se především citací a faktů", | |
| "E) Jsou vždy snadno rozpoznatelné" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Rozdíl mezi 'asistencí' a 'nahrazením úsudku'?", | |
| "opts": [ | |
| "A) Asistence = AI strukturuje MOJE myšlenky", | |
| "B) Nahrazení = AI vytváří závěry které přebírám", | |
| "C) V akademii irelevantní", | |
| "D) Asistence = finální odpovědnost na mně", | |
| "E) Oba postupy stejně přijatelné" | |
| ], | |
| "spravne": ["A", "B", "D"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Jak správně pracovat s AI a GDPR v akademickém výzkumu?", | |
| "opts": [ | |
| "A) Anonymizovat data před nahráním", | |
| "B) Data z výzkumu lze nahrávat bez omezení", | |
| "C) Necitlivá data respondentů do veřejných AI", | |
| "D) Enterprise AI nabízí lepší ochranu", | |
| "E) GDPR se na akademii nevztahuje" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Proč je pro akademii doporučena angličtina?", | |
| "opts": [ | |
| "A) AI trénovány primárně na anglických akademických textech", | |
| "B) Kvalita v češtině a angličtině je stejná", | |
| "C) Pro vědu přesnější odpovědi v angličtině", | |
| "D) Pro admin práci lepší čeština", | |
| "E) Angličtina není důležitá" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1 | |
| }, | |
| { | |
| "q": "Které použití AI je přijatelné a které ne?", | |
| "opts": [ | |
| "A) PŘIJATELNÉ: Návrh struktury mých myšlenek", | |
| "B) PŘIJATELNÉ: Generování závěrů výzkumu", | |
| "C) PŘIJATELNÉ: Identifikace slabých míst argumentace", | |
| "D) NEPŘIJATELNÉ: Auto-psaní sekcí bez kontroly", | |
| "E) PŘIJATELNÉ: Delegování hodnotových soudů" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1.5 | |
| }, | |
| { | |
| "q": "Jak pracovat s epistemickou nejistotou?", | |
| "opts": [ | |
| "A) Požadovat explicitní míru jistoty", | |
| "B) Důvěřovat autoritativnímu tónu AI", | |
| "C) Ověřovat tvrzení z primárních zdrojů", | |
| "D) Žádat alternativní interpretace", | |
| "E) AI jako konečný arbitr" | |
| ], | |
| "spravne": ["A", "C", "D"], | |
| "body": 1.5 | |
| }, | |
| { | |
| "q": "Jak správně pracovat se zdroji a citacemi?", | |
| "opts": [ | |
| "A) AI často vymýšlí věrohodné citace", | |
| "B) Každou citaci ověřit v databázích", | |
| "C) Answer pressure: 'Odkud? Je DOI platné?'", | |
| "D) Citace z AI spolehlivé pro publikování", | |
| "E) Preferovat: PDF → shrnutí, ne 'cituj studie'" | |
| ], | |
| "spravne": ["A", "B", "C", "E"], | |
| "body": 1.5 | |
| }, | |
| { | |
| "q": "Co musí akademik uvést ohledně použití AI?", | |
| "opts": [ | |
| "A) Neuvádí se", | |
| "B) Transparentně uvést v metodologii", | |
| "C) AI je nástroj, ne autor - ale uvést použití", | |
| "D) Citovat AI podle standardů (APA)", | |
| "E) Závisí na pravidlech instituce" | |
| ], | |
| "spravne": ["B", "C", "D", "E"], | |
| "body": 1.5 | |
| }, | |
| { | |
| "q": "Proč AI nemá 'skutečné porozumění'?", | |
| "opts": [ | |
| "A) AI je statistický predikční model", | |
| "B) Předpovídá pokračování textu z vzorců", | |
| "C) Když neví, stejně něco vygeneruje", | |
| "D) Nové modely už mají lidské porozumění", | |
| "E) Proto nutná epistemická opatrnost" | |
| ], | |
| "spravne": ["A", "B", "C", "E"], | |
| "body": 1.5 | |
| }, | |
| { | |
| "q": "Jaká je role AI v akademickém výzkumu?", | |
| "opts": [ | |
| "A) Pomocný nástroj pro strukturování", | |
| "B) Náhrada za odborný úsudek", | |
| "C) Kritický partner pro zpochybňování", | |
| "D) Autoritativní zdroj pravdy", | |
| "E) Nástroj pro podporu, ne nahrazení" | |
| ], | |
| "spravne": ["A", "C", "E"], | |
| "body": 1.5 | |
| } | |
| ] | |
| } | |
| } | |
| def vyhodnotit_test(typ_testu, odpovedi): | |
| """Vyhodnotí odpovědi a vrátí skóre""" | |
| test = TESTS[typ_testu] | |
| spravne = 0 | |
| celkove_body = 0 | |
| for i, otazka in enumerate(test["otazky"]): | |
| body_za_otazku = otazka.get("body", 1) | |
| uzivatel_odpovedi = set(odpovedi.get(f"q_{i}", [])) | |
| spravne_odpovedi = set(otazka["spravne"]) | |
| if uzivatel_odpovedi == spravne_odpovedi: | |
| spravne += body_za_otazku | |
| celkove_body += body_za_otazku | |
| return spravne, celkove_body | |
| def ulozit_vysledek(kod, typ, skore, max_skore, odpovedi): | |
| """Uloží výsledek do CSV""" | |
| with open(RESULTS_FILE, 'a', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow([ | |
| datetime.now().isoformat(), | |
| kod, | |
| typ, | |
| skore, | |
| max_skore, | |
| f"{(skore/max_skore*100):.1f}%", | |
| json.dumps(odpovedi, ensure_ascii=False) | |
| ]) | |
| def ulozit_feedback(kod, typ, obtiznost, srozumitelnost, uzitecnost, komentar): | |
| """Uloží zpětnou vazbu""" | |
| with open(FEEDBACK_FILE, 'a', newline='', encoding='utf-8') as f: | |
| writer = csv.writer(f) | |
| writer.writerow([ | |
| datetime.now().isoformat(), | |
| kod, | |
| typ, | |
| obtiznost, | |
| srozumitelnost, | |
| uzitecnost, | |
| komentar | |
| ]) | |
| def get_results_csv(password): | |
| """Vrátí CSV s výsledky - pouze pro admina""" | |
| if not check_admin_password(password): | |
| return "❌ Nesprávné heslo" | |
| if not os.path.exists(RESULTS_FILE): | |
| return "📭 Zatím žádné výsledky" | |
| with open(RESULTS_FILE, 'r', encoding='utf-8') as f: | |
| return f.read() | |
| def get_feedback_csv(password): | |
| """Vrátí CSV se zpětnou vazbou - pouze pro admina""" | |
| if not check_admin_password(password): | |
| return "❌ Nesprávné heslo" | |
| if not os.path.exists(FEEDBACK_FILE): | |
| return "📭 Zatím žádná zpětná vazba" | |
| with open(FEEDBACK_FILE, 'r', encoding='utf-8') as f: | |
| return f.read() | |
| # Gradio UI | |
| with gr.Blocks(title="Test AI znalostí KTF UK") as demo: | |
| gr.Markdown("# 🎓 Test znalostí: Generativní AI") | |
| gr.Markdown("**Katolická teologická fakulta UK, Praha 2025**") | |
| user_kod = gr.State("") | |
| user_typ = gr.State("") | |
| user_odpovedi = gr.State({}) | |
| with gr.Tab("🔐 Začátek"): | |
| gr.Markdown("### Vítejte v testu znalostí generativní AI") | |
| gr.Markdown("Pro zahájení testu zadejte váš unikátní kód a vyberte typ testu.") | |
| kod_input = gr.Textbox( | |
| label="Váš unikátní kód", | |
| placeholder="Např: KTF2025-001", | |
| info="Tento kód obdržíte od organizátora" | |
| ) | |
| typ_radio = gr.Radio( | |
| choices=[ | |
| ("STAFF - Pro administrativní pracovníky (10 otázek)", "STAFF"), | |
| ("ACADEMIC - Pro výzkumníky (12 otázek)", "ACADEMIC") | |
| ], | |
| label="Typ testu", | |
| info="Vyberte test odpovídající vaší roli" | |
| ) | |
| start_btn = gr.Button("Začít test", variant="primary", size="lg") | |
| start_msg = gr.Markdown("") | |
| with gr.Tab("📝 Test") as test_tab: | |
| test_info = gr.Markdown("") | |
| otazky_ui = [] | |
| for i in range(12): | |
| with gr.Group(visible=False) as q_group: | |
| q_text = gr.Markdown("") | |
| q_check = gr.CheckboxGroup(label="Vyberte VŠECHNY správné odpovědi") | |
| otazky_ui.append((q_group, q_text, q_check)) | |
| submit_btn = gr.Button("Odeslat test", variant="primary", size="lg", visible=False) | |
| with gr.Tab("📊 Výsledky") as results_tab: | |
| result_md = gr.Markdown("") | |
| gr.Markdown("### 💬 Zpětná vazba") | |
| gr.Markdown("Pomozte mi prosím zlepšit kurz:") | |
| obtiznost = gr.Radio( | |
| choices=["Příliš snadný", "Přiměřený", "Příliš těžký"], | |
| label="Obtížnost testu" | |
| ) | |
| srozumitelnost = gr.Radio( | |
| choices=["Výborná", "Dobrá", "Špatná"], | |
| label="Srozumitelnost prezentací" | |
| ) | |
| uzitecnost = gr.Radio( | |
| choices=["Velmi užitečné", "Užitečné", "Málo užitečné"], | |
| label="Užitečnost krzu pro praxi" | |
| ) | |
| komentar = gr.Textbox( | |
| label="Váš komentář ke kurzu (nepovinné)", | |
| placeholder="Sdílejte své postřehy...", | |
| lines=3 | |
| ) | |
| feedback_btn = gr.Button("Odeslat zpětnou vazbu", variant="secondary") | |
| feedback_msg = gr.Markdown("") | |
| with gr.Tab("👨💼 Admin") as admin_tab: | |
| gr.Markdown("### 🔒 Administrátorský přístup") | |
| gr.Markdown("Pouze pro organizátory testu") | |
| admin_password = gr.Textbox( | |
| label="Admin heslo", | |
| type="password", | |
| placeholder="Zadejte heslo" | |
| ) | |
| with gr.Row(): | |
| results_btn = gr.Button("Stáhnout výsledky", variant="secondary") | |
| feedback_download_btn = gr.Button("Stáhnout zpětnou vazbu", variant="secondary") | |
| admin_output = gr.Textbox( | |
| label="Data", | |
| lines=20, | |
| max_lines=30 | |
| ) | |
| gr.Markdown("**ℹ️ Poznámka:** Data můžete zkopírovat a uložit jako CSV soubor.") | |
| # Logika | |
| def start_test(kod, typ): | |
| if not kod or not typ: | |
| return { | |
| start_msg: "❌ Vyplňte prosím kód i typ testu", | |
| user_kod: "", | |
| user_typ: "" | |
| } | |
| test = TESTS[typ] | |
| num_q = len(test["otazky"]) | |
| updates = { | |
| start_msg: f"✅ Test zahájen! Přejděte na záložku 'Test'", | |
| user_kod: kod, | |
| user_typ: typ, | |
| test_info: f"## {test['nazev']}\n\n{test['popis']}\n\n**Počet otázek:** {num_q}" | |
| } | |
| for i in range(12): | |
| if i < num_q: | |
| q = test["otazky"][i] | |
| updates[otazky_ui[i][0]] = gr.update(visible=True) | |
| updates[otazky_ui[i][1]] = f"### Otázka {i+1}\n\n{q['q']}" | |
| updates[otazky_ui[i][2]] = gr.update(choices=q["opts"], value=[]) | |
| else: | |
| updates[otazky_ui[i][0]] = gr.update(visible=False) | |
| updates[submit_btn] = gr.update(visible=True) | |
| return updates | |
| def submit_test(kod, typ, *args): | |
| if not kod or not typ: | |
| return "❌ Chyba: Test nebyl správně zahájen" | |
| test = TESTS[typ] | |
| odpovedi = {} | |
| for i in range(len(test["otazky"])): | |
| odpovedi[f"q_{i}"] = args[i] if i < len(args) else [] | |
| skore, max_skore = vyhodnotit_test(typ, odpovedi) | |
| procenta = (skore / max_skore * 100) | |
| ulozit_vysledek(kod, typ, skore, max_skore, odpovedi) | |
| if typ == "STAFF": | |
| uspech = skore >= 7 | |
| pozadavek = "min. 7 bodů" | |
| else: | |
| uspech = skore >= 8 | |
| pozadavek = "min. 8 bodů" | |
| result = f""" | |
| ## {'✅ Úspěch!' if uspech else '❌ Neúspěch'} | |
| **Váš výsledek:** {skore:.1f} / {max_skore} bodů ({procenta:.1f}%) | |
| **Požadavek:** {pozadavek} | |
| {'🎉 Gratulujeme! Test jste úspěšně absolvovali.' if uspech else '😔 Bohužel jste nesplnili minimální požadavek.'} | |
| --- | |
| **Kód účastníka:** {kod} | |
| **Datum:** {datetime.now().strftime('%d.%m.%Y %H:%M')} | |
| """ | |
| return result | |
| def submit_feedback(kod, typ, obt, sroz, uzit, kom): | |
| if not kod: | |
| return "❌ Chyba při odesílání zpětné vazby" | |
| ulozit_feedback(kod, typ, obt, sroz, uzit, kom) | |
| return "✅ Děkujeme za zpětnou vazbu!" | |
| # Events | |
| start_btn.click( | |
| fn=start_test, | |
| inputs=[kod_input, typ_radio], | |
| outputs=[start_msg, user_kod, user_typ, test_info] + | |
| [item for group in otazky_ui for item in group] + | |
| [submit_btn] | |
| ) | |
| submit_btn.click( | |
| fn=submit_test, | |
| inputs=[user_kod, user_typ] + [q[2] for q in otazky_ui], | |
| outputs=[result_md] | |
| ) | |
| feedback_btn.click( | |
| fn=submit_feedback, | |
| inputs=[user_kod, user_typ, obtiznost, srozumitelnost, uzitecnost, komentar], | |
| outputs=[feedback_msg] | |
| ) | |
| results_btn.click( | |
| fn=get_results_csv, | |
| inputs=[admin_password], | |
| outputs=[admin_output] | |
| ) | |
| feedback_download_btn.click( | |
| fn=get_feedback_csv, | |
| inputs=[admin_password], | |
| outputs=[admin_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(share=True) | |