a47195722d
- CC BY-SA 4.0 voor documenten, dia's en beelden, MIT voor scripts; colofon met eigenaar en contact in elk gebouwd document en op de slotdia - Presentatie (22 dia's) als bron, PDF en sprekersnotities in 00-workshop/presentatie - README en CHANGELOG zonder procesnotities; instructieblokken heten Voor de bewerker - Lokaal extractiescript uit de uitgave gehaald Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
341 lines
26 KiB
Python
341 lines
26 KiB
Python
"""Bouwt de dia's van de workshop (22 stuks) als HTML-secties van 1920x1080 plus deck.json.
|
|
|
|
Standaard verwijzen de beelden naar de map beelden/ naast dit script, zodat bouw_pdf.py er een
|
|
PDF van kan maken. Met --artifact krijgen de beelden de paden van de online presentatie (asset store).
|
|
Licentie: CC BY-SA 4.0, zie ../../LICENSE.md. Logo van AI Netwerk Limburg uitgezonderd.
|
|
"""
|
|
import json, sys
|
|
from pathlib import Path
|
|
HERE = Path(__file__).resolve().parent
|
|
P = HERE / "project"; S = P / "slides"; S.mkdir(parents=True, exist_ok=True)
|
|
ARTIFACT = "--artifact" in sys.argv
|
|
BEELD = {
|
|
"logo": ("../beelden/logo-ai-netwerk-limburg.png", "/_blob/120fdff17b59f8e1cc380d9086a1ad9f"),
|
|
"afgekeurd": ("../beelden/schoolnieuws-afgekeurd.jpg", "/_blob/9b09f7c211d54f2d5c5b5bdaa2b69fae"),
|
|
"flow": ("../beelden/studiecoach-flow.jpg", "/_blob/1c5f3486aa7ea647a8ee49269f3f95bf"),
|
|
"docent": ("../beelden/studiecoach-docent-antwoord.jpg", "/_blob/244acf99a4fa9215357a5e95e1eec2f4"),
|
|
}
|
|
def beeld(naam): return BEELD[naam][1 if ARTIFACT else 0]
|
|
EIGENAAR = "Guido van Dijk, LeX Consultancy B.V. en AI Netwerk Limburg"
|
|
CONTACT = "guido@l-e-x.nl · guido@ai-netwerk-limburg.nl"
|
|
BRON = "git.lexconsultancy.nl/guido/dify-handleidingen"
|
|
DARK="#1E2A3A"; LIGHT="#F7F4EC"; LIGHT2="#EFEAE0"; ACC="#D9480F"; BLUE="#2563EB"; BODY="#3D4756"; BODYD="#D9DEE6"; LINE="#D8D2C4"
|
|
H="'Rubik', Arial, sans-serif"; T="'Nunito Sans', Verdana, sans-serif"
|
|
LOGO=beeld("logo")
|
|
order=[]; sections={}
|
|
def slide(id_, body, bg=LIGHT, color=DARK, pad="128px 128px 160px", extra="display:flex; flex-direction:column; gap:40px", notes="", section=None, transition="fade", footer=True):
|
|
fcol = "#8A8F98" if bg in (LIGHT, LIGHT2) else "#9AA3B1"
|
|
foot = f'<p style="position:absolute; left:128px; bottom:64px; font-size:24px; color:{fcol}">Workshop AI Agents met Dify · LeX Consultancy · AI Netwerk Limburg · CC BY-SA 4.0</p>' if footer else ""
|
|
html = (f'<section id="{id_}" data-transition="{transition}" style="background:{bg}; color:{color}; font-family:{T}; padding:{pad}; {extra}">\n'
|
|
f'{body}\n{foot}\n<aside>{notes}</aside>\n</section>\n')
|
|
(S / f"{id_}.html").write_text(html, encoding="utf-8"); order.append(id_)
|
|
if section: sections[id_]={"description":section,"start":id_}
|
|
def h2(t): return f'<h2 style="font-family:{H}; font-size:64px; font-weight:600; line-height:1.1">{t}</h2>'
|
|
def eyebrow(t, c=ACC): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{c}">{t}</p>'
|
|
def p(t, size=32, c=BODY): return f'<p style="font-size:{size}px; color:{c}; line-height:1.4">{t}</p>'
|
|
def card(title, text, w="flex:1", bg="#FFFFFF", tc=DARK, bc=BODY, border=LINE):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:16px; background:{bg}; padding:36px; border:1px solid {border}; border-radius:16px">'
|
|
f'<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{tc}; line-height:1.2">{title}</h3>'
|
|
f'<p style="font-size:27px; color:{bc}; line-height:1.4">{text}</p></div>')
|
|
def num(n, label, w="flex:1"):
|
|
return (f'<div style="{w}; display:flex; flex-direction:column; gap:8px"><p style="font-family:{H}; font-size:120px; font-weight:600; color:{ACC}; line-height:1">{n}</p>'
|
|
f'<p style="font-size:28px; color:{BODY}; line-height:1.3">{label}</p></div>')
|
|
def ul(items, size=30, c=BODY):
|
|
return f'<ul style="font-size:{size}px; color:{c}; line-height:1.5">' + "".join(f"<li>{i}</li>" for i in items) + "</ul>"
|
|
def table(rows, widths, size=27, head=True):
|
|
out=[f'<table style="font-size:{size}px; color:{BODY}; border:1px solid {LINE}; background:#FFFFFF">']
|
|
for i,r in enumerate(rows):
|
|
tag = "th" if (head and i==0) else "td"
|
|
bg = f' style="background:{LIGHT2}"' if (head and i==0) else ""
|
|
cells="".join(f'<{tag} style="width:{widths[j]}%; color:{DARK if tag=="th" else BODY}">{c}</{tag}>' for j,c in enumerate(r))
|
|
out.append(f"<tr{bg}>{cells}</tr>")
|
|
out.append("</table>"); return "".join(out)
|
|
def interactie(label): return f'<p style="font-family:{H}; font-size:26px; font-weight:600; letter-spacing:2px; text-transform:uppercase; color:{BLUE}">Interactie · {label}</p>'
|
|
|
|
# 1 cover
|
|
slide("cover", f'''
|
|
<img src="{LOGO}" alt="AI Netwerk Limburg" style="position:absolute; right:128px; top:96px; width:260px; height:210px; object-fit:contain">
|
|
<div style="flex:1"></div>
|
|
{eyebrow("Workshop · 2 uur · LAI Kennisfestival", "#F4B183")}
|
|
<h1 style="font-family:{H}; font-size:112px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1300px">AI Agents bouwen<br>met Dify</h1>
|
|
{p("Van één prompt naar een app die je collega's kunnen gebruiken. Zelf bouwen, zelf testen, zelf breken.", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
{p("Guido van Dijk · LeX Consultancy B.V. · AI Netwerk Limburg", 28, "#9AA3B1")}
|
|
''', bg=DARK, color=LIGHT, footer=False, section="Opening: wat gaan we doen en wie zit er in de zaal",
|
|
notes="Welkom. Laptops open, cloud.dify.ai ingelogd? Wie nog geen account heeft: sandbox-account maken duurt 2 minuten, buddy naast je helpt. Doel van vandaag: iedereen gaat naar huis met één werkende app en een ontwerp voor de volgende.")
|
|
|
|
# 2 programma
|
|
slide("programma", h2("Twee uur, vier blokken") + table([
|
|
["Tijd","Blok","Wat je doet"],
|
|
["0:00 - 0:15","Het landschap","Peiling, drie lagen, vijf patronen"],
|
|
["0:15 - 0:40","Bouwblok 1: workflow","Tekst op niveau, in duo's, eigen niveaus"],
|
|
["0:40 - 0:55","Demo en sabotage","Agent met kennisbank; laat hem struikelen"],
|
|
["0:55 - 1:05","Pauze",""],
|
|
["1:05 - 1:35","Bouwblok 2: kies je spoor","A kennisbank-agent · B poortwachter"],
|
|
["1:35 - 1:50","Ontwerpkaart en pitch","Jouw app in vijf vakken, 30 seconden pitch"],
|
|
["1:50 - 2:00","Afsluiting","Wat mag, wat mee naar huis, exit ticket"],
|
|
],[22,30,48]),
|
|
notes="Strak op tijd. Bouwblokken zijn heilig; als de demo uitloopt, kort de demo in, niet het bouwen.")
|
|
|
|
# 3 peiling
|
|
slide("peiling", interactie("2 minuten") + h2("Sta op als...") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1", "...je deze week een AI-chatbot hebt gebruikt voor je werk", bg="#FFFFFF")}
|
|
{card("2", "...je collega's een tool gebruiken waarvan je niet zeker weet of het mag", bg="#FFFFFF")}
|
|
{card("3", "...je ooit een prompt hebt gedeeld met een collega die daarna niet werkte", bg="#FFFFFF")}
|
|
</div>
|
|
{p("Blijf staan bij 3. Kijk om je heen: dat is het probleem dat we vandaag oplossen. Een prompt is van jou; een app is van het team.", 30)}
|
|
''', notes="Laat mensen echt staan. Bij vraag 3 staat meestal driekwart. Benoem: prompts zijn persoonlijk en breekbaar; vandaag bouwen we iets wat blijft staan als jij er niet bent.")
|
|
|
|
# 4 drie lagen
|
|
slide("lagen", h2("Drie lagen, en waar wij vandaag zitten") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{card("1 · Chatbot", "Je typt, je krijgt antwoord, elke keer opnieuw uitleggen. ChatGPT, Gemini, Copilot Chat, Claude, Le Chat.")}
|
|
{card("2 · Assistent in je pakket", "AI in de tools die je al hebt. Microsoft 365 Copilot, Gemini in Workspace. De school beslist via licenties.")}
|
|
{card("3 · Bouwplatform", "Jij bouwt een app met vaste stappen, eigen regels en eigen documenten, en deelt hem. Dify, Copilot Studio, Gems, GPT's.", bg=DARK, tc=LIGHT, bc=BODYD, border=DARK)}
|
|
</div>
|
|
{p("Het verschil zit niet in het model maar in het account: gratis privé, of school met verwerkersovereenkomst.", 30)}
|
|
''', notes="Laag 3 is vandaag. Dify is geen model: het gebruikt de modellen van OpenAI, Anthropic, Google via hun API. Later meer over wat mag.")
|
|
|
|
# 5 patronen
|
|
slide("patronen", h2("Vijf patronen: hoeveel mag het model zelf beslissen?") + table([
|
|
["Patroon","Wie beslist de stappen","Vandaag"],
|
|
["1 · Eén aanroep","Jij, vooraf; het model doet één ding","Tekst op niveau"],
|
|
["2 · Redeneren met gereedschap","Het model, stap voor stap, met kennisbank","Schoolgids-assistent"],
|
|
["3 · Plannen en uitvoeren","Eerst een plan, dan losse uitvoerders","Schoolnieuws: drie kanalen"],
|
|
["4 · Zelfkritiek","Het model kijkt zijn eigen werk na","Controleur"],
|
|
["5 · Poortwachter","Code of een mens laat het wel of niet door","Poortwachter, docent in de lus"],
|
|
],[34,40,26], size=28) + p("Vuistregel: kies het laagste patroon dat werkt. Elk stapje omhoog kost tijd, credits en verrassingen.", 28),
|
|
section="Het landschap: lagen, patronen en het eerste voorbeeld",
|
|
notes="Niet uitputtend behandelen; de bijlage Ontwerppatronen gaat mee naar huis. Wel de vuistregel benoemen.")
|
|
|
|
# 6 demo 01
|
|
slide("demo01", eyebrow("Demo · 3 minuten") + h2("Tekst op niveau") + f'''
|
|
<div style="display:flex; gap:48px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("Plak een tekst, kies een niveau (groep 7, brugklas, 3 havo, 5 vwo), en krijg een herschreven versie plus drie begripsvragen.", 30)}
|
|
{ul(["Twee LLM-stappen achter elkaar: herschrijven, dan vragen maken","De tweede stap werkt met wat de eerste maakte","Gepubliceerd als webapp: link delen, klaar"], 28)}
|
|
</div>
|
|
<div style="width:640px; display:flex; flex-direction:column; gap:20px; background:{LIGHT2}; padding:36px; border-radius:16px; border:1px solid {LINE}">
|
|
{interactie("roep het maar")}
|
|
<p style="font-family:{H}; font-size:40px; font-weight:600; color:{DARK}; line-height:1.2">Welk patroon is dit?</p>
|
|
{p("En: wat gebeurt er als ik een tekst met een leerlingnaam plak?", 28)}
|
|
</div>
|
|
</div>
|
|
''', notes="Live demo op udify.app/workflow/eINp9Jdd6ICodDZF. Antwoord: patroon 1, ook al zijn het twee stappen; jij bepaalt de volgorde. De naam-vraag leidt naar de checklist eigen materiaal.")
|
|
|
|
# 7 bouwblok 1
|
|
slide("bouw1", eyebrow("Bouwblok 1 · 25 minuten · in duo's") + h2("Bouw Tekst op niveau, met jouw niveaus") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px">
|
|
{ul(["Handleiding 1, stap 1 tot 8 (blz. 3 tot 15)","Model: <b>gpt-5-mini</b>, niet gpt-5","Vervang de vier niveaus door jouw groepen","Plak een tekst uit je eigen lesmateriaal, zonder namen","Klaar? Publiceer en stuur de link naar je buurduo"], 29)}
|
|
</div>
|
|
<div style="width:600px; display:flex; flex-direction:column; gap:16px; background:#ECFDF5; padding:32px; border-radius:16px; border:1px solid #A7D9BF">
|
|
<h3 style="font-family:{H}; font-size:32px; font-weight:600; color:#14532D">Zelf kiezen</h3>
|
|
{p("Klaar voor de tijd? Verander de tweede stap: woordenlijst, samenvatting, of een vertaling voor een nieuwkomer. Eén ding per keer, dan testen.", 27, "#14532D")}
|
|
</div>
|
|
</div>
|
|
''', section="Bouwblok 1: iedereen bouwt een workflow", transition="push",
|
|
notes="Loop rond. Meest voorkomende fouten: variabele niet via / ingevoegd; Publish vergeten; gpt-5 laten staan. Na 20 minuten: wie heeft een link? Laat twee duo's elkaars app proberen.")
|
|
|
|
# 8 demo agent + sabotage
|
|
slide("demo02", eyebrow("Demo · 10 minuten") + h2("Een agent met een kennisbank") + f'''
|
|
<div style="display:flex; gap:48px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("De Schoolgids-assistent beantwoordt vragen van ouders en leerlingen uit een schoolgids van twee pagina's. De Ouderbrief-assistent herschrijft een wollige brief volgens de schrijfwijzer en zegt wat hij veranderde.", 29)}
|
|
{ul(["Een kennisbank knipt je document in stukken; de knip bepaalt de kwaliteit","De beschrijving van de kennisbank is voor de agent, niet voor jou","Used Knowledge boven het antwoord: hij heeft echt gezocht"], 27)}
|
|
</div>
|
|
<div style="width:640px; display:flex; flex-direction:column; gap:20px; background:{LIGHT2}; padding:36px; border-radius:16px; border:1px solid {LINE}">
|
|
{interactie("sabotage")}
|
|
<p style="font-family:{H}; font-size:40px; font-weight:600; color:{DARK}; line-height:1.2">Laat hem struikelen</p>
|
|
{p("Bedenk in je duo één vraag waarop de assistent het antwoord niet mag weten, en één waarop hij een fout antwoord zou kunnen verzinnen. We proberen er drie live.", 27)}
|
|
</div>
|
|
</div>
|
|
''', section="Demo: agent, kennisbank en de sabotage", notes="udify.app/chat/cvLAmpk60m9HUXUy en udify.app/chat/OJCfxjBeBHCEbBPx. De sabotage laat zien waarom de regel 'zeg dat je het niet weet' in de instructie staat. Als hij toch verzint: perfect, dat is de les.")
|
|
|
|
# 9 pauze
|
|
slide("pauze", f'''
|
|
<div style="flex:1"></div>
|
|
<h1 style="font-family:{H}; font-size:140px; font-weight:600; color:{LIGHT}; line-height:1">Pauze</h1>
|
|
{p("10 minuten. Laat je app open staan; straks kies je een spoor.", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
''', bg=ACC, color=LIGHT, footer=False, notes="Tijdens de pauze: check bij twijfelende duo's of de kennisbank van handleiding 2 al klaarstaat, dat scheelt in blok 2.")
|
|
|
|
# 10 bouwblok 2 keuze
|
|
slide("bouw2", eyebrow("Bouwblok 2 · 30 minuten · kies je spoor") + h2("Spoor A of spoor B") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:36px; border:2px solid {BLUE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:38px; font-weight:600; color:{BLUE}">A · Agent met jouw document</h3>
|
|
{p("Handleiding 2. Upload een document van je school (protocol, studiewijzer, reglement), knip het goed, schrijf de instructie, test met vijf echte vragen en één vraag die er niet in staat.", 27)}
|
|
{p("Voor wie: iedereen die documenten heeft waar vragen over komen.", 26, "#6B7280")}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:36px; border:2px solid {ACC}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:38px; font-weight:600; color:{ACC}">B · Poortwachter op je workflow</h3>
|
|
{p("Handleiding 7, deel B en C. Zet een Code-blok achter je Tekst op niveau: te lang, verboden woorden, een naam erin? Dan gaat de tekst niet door maar naar een mens, met de reden.", 27)}
|
|
{p("Voor wie: wie blok 1 vlot afhad en wil zien wat code hier doet. Plakken, niet typen.", 26, "#6B7280")}
|
|
</div>
|
|
</div>
|
|
''', section="Bouwblok 2: twee sporen, zelf kiezen", transition="push",
|
|
notes="Spoor B is pittiger; zet duo's met een programmeer-affiniteit bij elkaar. In beide sporen: het eigen materiaal moet zonder persoonsgegevens zijn, zeg dat nog een keer hardop.")
|
|
|
|
# 11 poortwachter demo
|
|
slide("demo07", eyebrow("Demo · 5 minuten") + h2("Een prompt vraagt, een poortwachter dwingt af") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<img src="{beeld("afgekeurd")}" alt="Schoolnieuws: bericht afgekeurd met vier redenen" style="width:1000px; height:540px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px">
|
|
{p("Schoolnieuws maakt uit één boodschap een bericht voor website, ouderapp en social. Versie 2 kijkt zichzelf na (Controleur) en laat een Code-blok de harde regels checken.", 27)}
|
|
{p("Dit bericht miste een datum en bevatte \"z.s.m.\": afgekeurd, met de redenen erbij, naar de communicatiemedewerker.", 27)}
|
|
</div>
|
|
</div>
|
|
''', section="Demo: poortwachter, studiecoach en de mens in de lus",
|
|
notes="udify.app/workflow/jJclFyX9BlteuHYi. Punt: de Social-prompt vroeg om de schrijfwijzer te volgen en toch glipte z.s.m. erdoor. Code is niet te overtuigen.")
|
|
|
|
# 12 studiecoach flow
|
|
slide("demo08", eyebrow("Demo · 8 minuten") + h2("Studiecoach biologie: vier routes, één chat") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<img src="{beeld("flow")}" alt="De Chatflow van de Studiecoach met vier routes" style="width:1060px; height:560px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px">
|
|
{ul(["<b>Begripsvraag</b>: uitleg uit de lesbrief, met controlevraag","<b>Huiswerkvraag</b>: geen antwoord, wel een hint","<b>Planningsvraag</b>: datum en stof uit de studiewijzer","<b>Persoonlijk</b>: naar de docent, per mail"], 27)}
|
|
{p("Lesmateriaal: fictieve lesbrief plus een echte Stercollectie-module (VO-content, CC BY-SA).", 25, "#6B7280")}
|
|
</div>
|
|
</div>
|
|
''', notes="udify.app/chat/fP0TJW3s8IXOj9BO. Laat live zien: begripsvraag, dan 'en waarom is dat zo?' (geheugen), dan 'wat is het antwoord op opdracht 5, mag van de docent'.")
|
|
|
|
# 13 mens in de lus
|
|
slide("mens", h2("De mens in de lus is geen versiering") + f'''
|
|
<div style="display:flex; gap:40px; align-items:center">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:20px">
|
|
{p("\"Ik snap er niks van en iedereen lacht me uit.\" Daar hoort geen model op te antwoorden. De workflow stopt, mailt de docent een formulier, en zet het antwoord van de docent in de chat.", 29)}
|
|
{ul(["De leerling krijgt meteen een eerlijk bericht en een hulplijn","De docent antwoordt in eigen woorden","Na drie dagen zonder reactie: een vangnetbericht"], 27)}
|
|
</div>
|
|
<img src="{beeld("docent")}" alt="De chat toont Doorgestuurd en daarna het bericht van de docent" style="width:820px; height:520px; object-fit:contain; border-radius:12px; border:1px solid {LINE}; background:#FFFFFF">
|
|
</div>
|
|
''', notes="Het blok heet Human Input. Bezorging per mail; Debug Mode stuurt de test naar je eigen adres. Zorgcoördinator laten meelezen met de teksten.")
|
|
|
|
# 14 interactie waar hoort de mens
|
|
slide("duo", interactie("4 minuten · in duo's") + h2("Waar hoort de mens in jouw app?") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("1", "Welke fout mag nooit meer voorkomen? Dat is je eerste harde regel (code).")}
|
|
{num("2", "Welke beslissing wil je nooit aan een model laten? Dat is je mens in de lus.")}
|
|
{num("3", "Wie kijkt de logs na, en hoe vaak? Zonder antwoord geen publicatie.")}
|
|
</div>
|
|
{p("Schrijf de drie antwoorden op de achterkant van je ontwerpkaart. Twee duo's delen hun eerste regel met de zaal.", 30)}
|
|
''', notes="Laat twee duo's hun 'nooit meer'-regel noemen. Vaak: namen van leerlingen, verzonnen data, toon. Koppel terug naar de poortwachter.")
|
|
|
|
# 15 ontwerpkaart
|
|
slide("ontwerpkaart", eyebrow("Ontwerpen · 10 minuten · in duo's") + h2("Jouw app in vijf vakken") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("1 · Voor wie", "Eén persoon, geen doelgroep. Wat weet die al, waar heeft die geen tijd voor?")}
|
|
{card("2 · Wat erin", "Tekst, bestand, keuze? Welke vragen stel je altijd vooraf?")}
|
|
{card("3 · Wat eruit", "Schrijf één voorbeeld met de hand uit.")}
|
|
{card("4 · Regels", "Huisstijl, wat nooit verzonnen mag worden, welk document de regels bevat.")}
|
|
{card("5 · Werkt het?", "Drie tests: twee gewone, één waarbij hij moet weigeren.")}
|
|
</div>
|
|
{p("Pen en papier. Pas als vak 3 een voorbeeld heeft en vak 5 drie tests, klik je op Create.", 30)}
|
|
''', section="Ontwerpen en pitchen: jouw volgende app", transition="push",
|
|
notes="Ontwerpkaart uitdelen (A4). Loop rond en stel bij elk duo de vraag: wie is die ene persoon?")
|
|
|
|
# 16 pitch
|
|
slide("pitch", interactie("5 minuten") + h2("Pitch in 30 seconden") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
{num("30", "seconden per duo: voor wie, wat erin, wat eruit, welk patroon.")}
|
|
{num("4", "duo's pitchen; de zaal kiest de app die ze zelf zouden gebruiken.")}
|
|
{num("1", "vraag uit de zaal per pitch: waar hoort de mens?")}
|
|
</div>
|
|
''', notes="Kies duo's met verschillende doelgroepen: docent, ondersteuning, communicatie, directie. Applaus, geen jury.")
|
|
|
|
# 17 wat mag
|
|
slide("watmag", h2("Wat mag: vier vragen voor elke tool") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Onder welk account?", "Privé, gratis, betaald of school met verwerkersovereenkomst. Alleen in het laatste geval heeft de school iets te zeggen.")}
|
|
{card("Welke gegevens gaan erin?", "Geen persoonsgegevens van leerlingen, ouders, collega's. Ook geen ongepubliceerd toetsmateriaal.")}
|
|
{card("Wordt erop getraind?", "Consumentenaccounts vaak wel, zakelijke accounts en API-verkeer meestal niet. Controleer het.")}
|
|
{card("Wie beslist en kijkt na?", "Voor jezelf: jij. Voor leerlingen: een schoolbesluit, toetsingskader, iemand die de uitvoer leest.")}
|
|
</div>
|
|
{p("Copilot en Gemini zitten al in school; of ze aan mogen is een bestuursbesluit op basis van de DPIA's van SURF en SIVON. Dify-cloud is voor oefenen met fictief materiaal; echt gebruik hoort bij het bestuur.", 27)}
|
|
''', section="Afsluiting: wat mag, wat mee naar huis, exit ticket",
|
|
notes="Bijlage AI-tools in het onderwijs gaat mee. AI Toetsingskader Funderend Onderwijs 1.0 (SIVON/Kennisnet, april 2026). Sandbox is oefenen; Experimenteerruimte ICT SOML is de plek voor echt gebruik.")
|
|
|
|
# 17b AVG en AI Act
|
|
slide("avgaiact", h2("AVG en AI Act: wat het betekent voor de apps van vandaag") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{BLUE}">AVG</h3>
|
|
{ul(["De school is verantwoordelijke; Dify en de modelaanbieder zijn verwerkers, buiten de EU",
|
|
"Geen verwerkersovereenkomst (sandbox) = geen persoonsgegevens erin",
|
|
"Wél persoonsgegevens: het bericht van een leerling aan de docent (Studiecoach). Dan: grondslag, DPIA, bewaartermijn van de logs"], 26)}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:14px; background:#FFFFFF; padding:32px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:34px; font-weight:600; color:{ACC}">EU AI Act</h3>
|
|
{ul(["<b>Art. 4</b>: AI-geletterdheid verplicht voor wie AI inzet (sinds feb 2025). Deze workshop is zo'n maatregel",
|
|
"<b>Verboden</b>: emotieherkenning in het onderwijs",
|
|
"<b>Hoog risico</b>: toelating, beoordelen van leerresultaten, toezicht bij toetsen. Onze apps beslissen niets over personen; een coach die leerlingen beoordeelt, wel",
|
|
"<b>Art. 50</b>: een chatbot zegt dat hij AI is"], 26)}
|
|
</div>
|
|
</div>
|
|
{p('Vuistregel: zodra een app iets beslist of vastlegt over een persoon, verandert de vraag van "werkt het?" in "mag het, en wie is verantwoordelijk?" Stand september 2026; toetsingskader SIVON/Kennisnet voor het actuele overzicht.', 26)}
|
|
''', notes="Niet als juridische les brengen. Twee zinnen per kant. De Studiecoach is het voorbeeld dat beide kanten laat zien: de persoonlijke route verwerkt persoonsgegevens (AVG), en een coach die zou beoordelen wie het niet snapt, schuift naar hoog risico (AI Act). Dat is een ontwerpkeuze.")
|
|
|
|
# 17c EU-kaders
|
|
slide("eukaders", h2("De EU-kaders: waar deze workshop in past") + f'''
|
|
<div style="display:flex; gap:24px">
|
|
{card("Engage with AI", "Begrijpen hoe het werkt: drie lagen, vijf patronen, de sabotage van de agent.")}
|
|
{card("Create with AI", "Zelf maken: bouwblok 1 en 2, de app met eigen materiaal.")}
|
|
{card("Manage AI", "Verantwoord inzetten: poortwachter, mens in de lus, wat mag, logs.")}
|
|
{card("Shape AI", "Keuzes maken en beïnvloeden: ontwerpkaart, pitch, de vraag waar de mens hoort.")}
|
|
</div>
|
|
{p("De vier domeinen van het <b>AI Literacy Framework</b> (OESO en Europese Commissie, 2026) voor leerlingen; voor docenten is <b>DigCompEdu</b> het kader. Elk domein combineert kennis, vaardigheden en houding. Zet deze workshop zo in je professionaliseringsplan: art. 4 AI Act vraagt erom.", 27)}
|
|
''', notes="Voor schoolleiders: dit is de zin die ze nodig hebben om de workshop te verantwoorden. Bronnen: ailiteracyframework.org en education.ec.europa.eu. Niet uitweiden; de bijlage AI-tools heeft de koppeling per blok.")
|
|
|
|
# 18 mee naar huis
|
|
slide("mee", h2("Wat je meeneemt") + table([
|
|
["Nr","Handleiding","Wat je ermee bouwt"],
|
|
["1","Van workflow naar agent","Tekst op niveau"],
|
|
["2","Agent met eigen kennisbank","Schoolgids-assistent"],
|
|
["3","Schoolcommunicatie","Ouderbrief-assistent, Schoolnieuws, Balie-stand"],
|
|
["4","Agent Console","Toetsweekplanner"],
|
|
["5","Transcriptietool","Van opname naar verslag"],
|
|
["7","Schoolnieuws met controle","Zelfkritiek en poortwachter"],
|
|
["8","Studiecoach voor je vak","Chatflow met routes, geheugen, docent in de lus"],
|
|
["Bijlagen","Ontwerpkaart · Ontwerppatronen · AI-tools in het onderwijs","Elke handleiding eindigt met Maak hem van jou"],
|
|
],[10,40,50], size=26),
|
|
notes="Alle apps staan live op udify.app-links in de README. Elke handleiding heeft groene Zelf kiezen-kaders en een slothoofdstuk met opdrachten en een checklist eigen materiaal.")
|
|
|
|
# 19 exit ticket
|
|
slide("exit", interactie("2 minuten") + h2("Exit ticket") + f'''
|
|
<div style="display:flex; gap:32px">
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:40px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:36px; font-weight:600">Maak de zin af</h3>
|
|
{p("\"Morgen bouw ik ... voor ... en de eerste harde regel is ...\"", 32)}
|
|
</div>
|
|
<div style="flex:1; display:flex; flex-direction:column; gap:16px; background:#FFFFFF; padding:40px; border:1px solid {LINE}; border-radius:16px">
|
|
<h3 style="font-family:{H}; font-size:36px; font-weight:600">Eén vraag die open bleef</h3>
|
|
{p("Op een post-it bij de deur. We beantwoorden ze in de LAI-community.", 32)}
|
|
</div>
|
|
</div>
|
|
''', notes="Post-its meenemen. De open vragen zijn de agenda voor de vervolgbijeenkomst.")
|
|
|
|
# 20 slot
|
|
slide("slot", f'''
|
|
<img src="{LOGO}" alt="AI Netwerk Limburg" style="position:absolute; right:128px; top:96px; width:220px; height:180px; object-fit:contain">
|
|
<div style="flex:1"></div>
|
|
<h1 style="font-family:{H}; font-size:96px; font-weight:600; line-height:1.05; color:{LIGHT}; width:1400px">Kies het eenvoudigste patroon dat werkt.</h1>
|
|
{p("En ontwerp voor de fout: wat gebeurt er als het misgaat, en wie ziet dat?", 34, BODYD)}
|
|
<div style="flex:1"></div>
|
|
{p("Handleidingen, dia's en links: " + BRON + " · ai-netwerk-limburg.nl · Experimenteerruimte ICT SOML", 28, "#9AA3B1")}
|
|
<div style="display:flex; gap:40px; align-items:flex-start; border-top:1px solid #3A4757; padding-top:24px; margin-top:-16px">
|
|
<div style="flex:0 0 520px; display:flex; flex-direction:column; gap:6px">
|
|
<p style="font-family:{H}; font-size:28px; font-weight:600; color:{LIGHT}; letter-spacing:1px">CC BY-SA 4.0</p>
|
|
<p style="font-size:22px; color:#9AA3B1; line-height:1.4">Eigenaar en contact:<br>{EIGENAAR}<br>{CONTACT}</p>
|
|
</div>
|
|
{p("Deze presentatie en de handleidingen mag je kopiëren, bewerken en hergebruiken, ook op je eigen school. Noem de bron (" + EIGENAAR + ", " + BRON + ") en deel je bewerking onder dezelfde licentie. Het logo van AI Netwerk Limburg valt buiten de licentie; namen van producten zijn merken van hun eigenaren.", 22, "#9AA3B1")}
|
|
</div>
|
|
''', bg=DARK, color=LIGHT, footer=False, notes="Bedankt. Wijs op de community en de Experimenteerruimte voor wie echt wil bouwen. Noem de licentie: alles mag mee naar school, met bronvermelding.")
|
|
|
|
deck = {"v":4,"createdOnFiles":{"v":1,"at":"2026-09-20T18:00:00Z"},"title":"Workshop AI Agents met Dify","order":order,"sections":sections,"cover":"cover",
|
|
"faces":{"rubik":{"family":"Rubik","href":"https://fonts.googleapis.com/css2?family=Rubik:wght@400;600&display=swap"},
|
|
"nunito-sans":{"family":"Nunito Sans","href":"https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600;700&display=swap"}},
|
|
"designSystems":[]}
|
|
(P/"deck.json").write_text(json.dumps(deck, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
print(len(order), "slides:", order)
|