PageSourceSearch

https://govchat-nl.github.io/assets/js/8a7cb416.dd335263.js

js govchat-nl.github.io collected 2026-10-03 09:15:51 UTC 11,871 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkgovchat_docs=self.webpackChunkgovchat_docs||[]).push([["9820"],{909(e,n,i){i.r(n),i.d(n,{metadata:()=>t,default:()=>h,frontMatter:()=>l,contentTitle:()=>o,toc:()=>a,assets:()=>d});var t=JSON.parse('{"id":"apps/live-transcriptie","title":"Live transcriptie","description":"De Live transcriptie mini-app zet gesproken audio in bijna realtime om naar tekst via n8n en LiteLLM.","source":"@site/docs/apps/live-transcriptie.md","sourceDirName":"apps","slug":"/apps/live-transcriptie","permalink":"/docs/apps/live-transcriptie","draft":false,"unlisted":false,"editUrl":"https://github.com/GovChat-NL/GovChat-NL.github.io/tree/main/docs/apps/live-transcriptie.md","tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1,"title":"Live transcriptie"},"sidebar":"docsSidebar","previous":{"title":"Versimpelaar","permalink":"/docs/apps/versimpelaar"},"next":{"title":"AI-geletterdheid","permalink":"/docs/ai-geletterdheid"}}'),s=i(4848),r=i(8453);let l={sidebar_position:1,title:"Live transcriptie"},o="Live transcriptie",d={},a=[{value:"Functionaliteit",id:"functionaliteit",level:2},{value:"Bedieningsflow (UI)",id:"bedieningsflow-ui",level:2},{value:"API-route",id:"api-route",level:2},{value:"Request payload",id:"request-payload",level:3},{value:"Sessiehistorie (productie)",id:"sessiehistorie-productie",level:2},{value:"Ondersteunde sorteermodi in de UI",id:"ondersteunde-sorteermodi-in-de-ui",level:3},{value:"Titelgeneratie (LLM)",id:"titelgeneratie-llm",level:2},{value:"n8n workflow (huidige implementatie)",id:"n8n-workflow-huidige-implementatie",level:2},{value:"LiteLLM call",id:"litellm-call",level:3},{value:"Voorbeeld app-configuratie",id:"voorbeeld-app-configuratie",level:2},{value:"Validatie",id:"validatie",level:2}];function c(e){let n={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"live-transcriptie",children:"Live transcriptie"})}),"\n",(0,s.jsxs)(n.p,{children:["De ",(0,s.jsx)(n.strong,{children:"Live transcriptie"})," mini-app zet gesproken audio in bijna realtime om naar tekst via n8n en LiteLLM."]}),"\n",(0,s.jsx)(n.h2,{id:"functionaliteit",children:"Functionaliteit"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Bronkeuze: ",(0,s.jsx)(n.strong,{children:"Microfoon"})," of ",(0,s.jsx)(n.strong,{children:"Systeemgeluid (tab/scherm)"})]}),"\n",(0,s.jsxs)(n.li,{children:["Microfoonselectie + knop ",(0,s.jsx)(n.strong,{children:"Ververs"})," voor device-lijst"]}),"\n",(0,s.jsx)(n.li,{children:"Taalkeuze (NL/EN/FR/DE/NL-BE/IT/ES/PT/PL/TR/AR + custom default)"}),"\n",(0,s.jsx)(n.li,{children:"Slimme chunking met stilte-detectie (VAD) + overlap"}),"\n",(0,s.jsx)(n.li,{children:"Live transcriptieregels tijdens opname + audio-wave visualisatie"}),"\n",(0,s.jsx)(n.li,{children:"Timestamps aan/uit"}),"\n",(0,s.jsxs)(n.li,{children:["Acties: ",(0,s.jsx)(n.strong,{children:"Start"}),", ",(0,s.jsx)(n.strong,{children:"Stop"}),", ",(0,s.jsx)(n.strong,{children:"Kopieer"}),", ",(0,s.jsx)(n.strong,{children:"Nieuwe sessie"})]}),"\n",(0,s.jsxs)(n.li,{children:["Sessiehistorie met ",(0,s.jsx)(n.strong,{children:"opslaan, openen, hernoemen, opnieuw titel genereren, verwijderen, sorteren"})]}),"\n",(0,s.jsx)(n.li,{children:"Automatische titelgeneratie na stoppen via aparte LLM webhook"}),"\n",(0,s.jsx)(n.li,{children:"Duidelijke statusweergave: opname actief, opname gestopt, titel wordt (opnieuw) gegenereerd"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"bedieningsflow-ui",children:"Bedieningsflow (UI)"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Kies bron, device en taal."}),"\n",(0,s.jsxs)(n.li,{children:["Klik ",(0,s.jsx)(n.strong,{children:"Start"})," om op te nemen."]}),"\n",(0,s.jsx)(n.li,{children:"Tijdens opname verschijnen transcriptieregels live."}),"\n",(0,s.jsxs)(n.li,{children:["Klik ",(0,s.jsx)(n.strong,{children:"Stop"})," om opname af te sluiten; sessie wordt opgeslagen en titel automatisch gegenereerd."]}),"\n",(0,s.jsxs)(n.li,{children:["Gebruik in de historie:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Open"})," om sessie terug te laden,"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Hernoem"})," voor handmatige titel,"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Genereer titel opnieuw"})," voor nieuwe LLM-titel,"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Verwijder"})," om sessie te verwijderen."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"De tijdelijke melding \u201Ctitel wordt \u2026 gegenereerd\u201D staat alleen als subtitel/status, niet als definitieve sessietitel."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"api-route",children:"API-route"}),"\n",(0,s.jsx)(n.p,{children:"De app stuurt chunks naar:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"POST /n8n/webhook/transcriptie-litellm"})}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"request-payload",children:"Request payload"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "audio_base64": "<base64-audio>",\n  "mime_type": "audio/webm",\n  "chunk_index": 0,\n  "session_id": "tr-123",\n  "model": "whisper",\n  "language": "nl"\n}\n'})}),"\n",(0,s.jsx)(n.h2,{id:"sessiehistorie-productie",children:"Sessiehistorie (productie)"}),"\n",(0,s.jsx)(n.p,{children:"De mini-app gebruikt een trusted backend-route voor historie:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"GET/POST/PATCH/DELETE /govchat-api/transcript-sessions"})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Eigenschappen:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"gekoppeld aan ingelogde LibreChat-gebruiker (server-side validatie),"}),"\n",(0,s.jsx)(n.li,{children:"multi-device consistent,"}),"\n",(0,s.jsx)(n.li,{children:"metadata per sessie (duur, woorden, bron, taal),"}),"\n",(0,s.jsx)(n.li,{children:"sorteerbaar en bewerkbaar in de UI."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"ondersteunde-sorteermodi-in-de-ui",children:"Ondersteunde sorteermodi in de UI"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Nieuw \u2192 oud"}),"\n",(0,s.jsx)(n.li,{children:"Oud \u2192 nieuw"}),"\n",(0,s.jsx)(n.li,{children:"Langste duur"}),"\n",(0,s.jsx)(n.li,{children:"Meeste woorden"}),"\n",(0,s.jsx)(n.li,{children:"Titel (A-Z)"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"titelgeneratie-llm",children:"Titelgeneratie (LLM)"}),"\n",(0,s.jsxs)(n.p,{children:["Na ",(0,s.jsx)(n.strong,{children:"Stop"})," wordt de sessietitel asynchroon gegenereerd via:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"POST /n8n/webhook/transcriptie-title-litellm"})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Voorbeeld payload:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "text": "<volledige transcriptietekst>",\n  "language": "nl",\n  "model": "govchat-default",\n  "max_chars": 72\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Voorbeeld response:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "title": "Korte, duidelijke vergadertitel"\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Fallback-gedrag:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Bij timeout/fout valt de app terug op een afgeleide titel uit transcriptietekst."}),"\n",(0,s.jsx)(n.li,{children:"Handmatige hernoeming wordt niet overschreven wanneer gebruiker intussen zelf een titel zet."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"n8n-workflow-huidige-implementatie",children:"n8n workflow (huidige implementatie)"}),"\n",(0,s.jsx)(n.p,{children:"De bootstrap-workflow staat in:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"GovChat-NL-LibreChat/n8n/bootstrap/workflows/transcriptie-litellm.json"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"GovChat-NL-LibreChat/n8n/bootstrap/workflows/transcriptie-title-litellm.json"})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Opbouw:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Webhook Transcriptie"})," ontvangt JSON"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Body To Binary Audio"})," zet ",(0,s.jsx)(n.code,{children:"audio_base64"})," om naar binary"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Call Transcriptions API"})," doet multipart upload naar LiteLLM"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Titelworkflow:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Webhook Transcriptie Titel"})," ontvangt tekst + taal + model"]}),"\n",(0,s.jsx)(n.li,{children:"Prompting naar compacte, leesbare sessietitel"}),"\n",(0,s.jsxs)(n.li,{children:["Response met ",(0,s.jsx)(n.code,{children:"title"})]}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"litellm-call",children:"LiteLLM call"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Endpoint: ",(0,s.jsx)(n.code,{children:"POST /v1/audio/transcriptions"})]}),"\n",(0,s.jsxs)(n.li,{children:["Multipart velden: ",(0,s.jsx)(n.code,{children:"file"}),", ",(0,s.jsx)(n.code,{children:"model"}),", ",(0,s.jsx)(n.code,{children:"language"})]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"De workflow gebruikt native n8n nodes voor deze route, zonder Code-node afhankelijkheid in de transcriptieketen."}),"\n",(0,s.jsx)(n.h2,{id:"voorbeeld-app-configuratie",children:"Voorbeeld app-configuratie"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "id": "transcriptie",\n  "name": "Live transcriptie",\n  "icon": "\u{1F399}\uFE0F",\n  "description": "Live spraak-naar-tekst met slimme chunking en sessiehistorie",\n  "url": "/n8n/webhook/transcriptie-litellm",\n  "target": "transcriptie",\n  "config": {\n    "webhook_token": "local_n8n_webhook_token_change_me",\n    "chunk_ms": 5000,\n    "chunk_min_ms": 2200,\n    "chunk_max_ms": 20000,\n    "vad_silence_ms": 900,\n    "silence_threshold": 0.012,\n    "overlap_ms": 650,\n    "litellm_model": "whisper",\n    "language": "nl",\n    "include_timestamps": true,\n    "max_duration_minutes": 120,\n    "transcript_sessions_api": "/govchat-api/transcript-sessions",\n    "title_webhook": "/n8n/webhook/transcriptie-title-litellm",\n    "title_webhook_token": "local_n8n_webhook_token_change_me",\n    "title_model": "govchat-default",\n    "title_max_chars": 72,\n    "title_timeout_ms": 6000\n  }\n}\n'})}),"\n",(0,s.jsx)(n.h2,{id:"validatie",children:"Validatie"}),"\n",(0,s.jsx)(n.p,{children:"De functionele validatie kan worden uitgevoerd met:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"GovChat-NL-LibreChat/tests/transcript/run-validation.js"})}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Pass-criteria:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"HTTP success"}),"\n",(0,s.jsxs)(n.li,{children:["geen ",(0,s.jsx)(n.code,{children:"error"})," veld"]}),"\n",(0,s.jsx)(n.li,{children:"niet-lege transcriptietekst"}),"\n"]})]})}function h(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},8453(e,n,i){i.d(n,{R:()=>l,x:()=>o});var t=i(6540);let s={},r=t.createContext(s);function l(e){let n=t.useContext(r);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:l(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.