') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - yakivets/Flash: First Active AI Agent for meetings. Say "Hey Flash!" during the meeting and get anything you need based on current meeting. · GitHub
Skip to content

Repository files navigation

⚡ Flash — your meeting's memory, as a living canvas

Flash joins your Google Meet as a third participant. It listens passively (and watches shared screens), turning the conversation into a live graph on a web canvas. Say "Hey Flash" and it wakes up, answers with voice, and draws diagrams grounded in the live discussion + prep docs. When the meeting ends it writes the summary and files it into your dashboard history — one context, reused live and after.

Built with Superlinked (semantic retrieval), n8n + Gemini (the brain), and SLNG (voice).

Architecture

Dashboard (Face) ──"Add Flash"──▶ P1 /join ──Playwright──▶ Google Meet
P1 ears (SLNG STT) ─▶ POST /ingest ─▶ P2 (SQLite + Superlinked) [memory]
└▶ POST /events ─▶ Face canvas [live nodes]
"Hey Flash …" ─▶ P1 ─▶ n8n /agent ─GET /retrieve─▶ P2 ─Gemini─▶ {text, mermaid, sources}
├─ SLNG TTS speaks it into the call
└─ Face renders the diagram node live
End meeting ─▶ Face /summarize ─▶ n8n /finalize ─▶ summary/decisions/actions
└▶ Face /sync-memory ─▶ P2 /sources (type:canvas) ─▶ Superlinked [query the graph]
Post-meeting Q&A ─▶ n8n /ask ─▶ P2 /retrieve (incl. source:canvas) ─Gemini─▶ grounded answer

Components: P1agent/ (Meet bot), P2src/ (retrieval service), P3Flash-Agent.json (n8n workflow), P4face/ (Next.js canvas). Contracts: shared/contracts.ts + ARCHITECTURE.md.

Setup

cp .env.example .env # fill GEMINI_API_KEY, SUPERLINKED_API_KEY, SLNG_API_KEY
npm install # P2 + n8n deps (repo root)
( cd agent && npm install && npx playwright install chromium )
( cd face && npm install && cp .env.example .env.local )

Run (4 terminals)

#ServiceCommandPort
1P2 Retrievalnpm run seed:retrieval && npm run dev3000
2P3 Brain (n8n)npm run n8n:import && npm run n8n:start5678
3P4 Facecd face && npm run dev -- -p 30013001
4P1 Agentcd agent && npm start8001

After the first n8n:import, activate the workflow once so its production webhooks register: N8N_USER_FOLDER=.n8n-local ./node_modules/.bin/n8n update:workflow --id=flash-agent-p3 --active=true then restart n8n (terminal 2). All five webhooks then live at http://localhost:5678/webhook/{agent,ask,finalize,events,vision}.

Smoke tests

curl "localhost:3000/retrieve?meetingId=m_demo&query=budget&k=3&mode=auto"# superlinked-rerank
curl localhost:3000/meetings # history list
curl -XPOST localhost:5678/webhook/agent -H 'content-type: application/json' \
-d '{"meetingId":"m_demo","requestText":"make a diagram of the budget"}'# grounded mermaid
curl localhost:3001/api/meetings # dashboard data

Demo (the money shot)

  1. Open http://localhost:3001, enter a Meeting ID + the Google Meet link, click Add Flash.
  2. Two people talk; utterance nodes appear live on /m/<id> as they speak.
  3. Say "Hey Flash, make a diagram of the budget" → Flash speaks the answer and a diagram node appears on the canvas, grounded in the conversation (cites speakers).
  4. Ask "Hey Flash, what did we decide?" → grounded spoken answer.
  5. Click End meeting → summary / decisions / action-item nodes; the meeting lands in the dashboard's Recent meetings. Reopen it later — the graph is restored and re-queryable.

Notes

  • Single root .env feeds P1, P2 and n8n; face/.env.local feeds the Next app. Secrets never commit.
  • Grounded, not hallucinated: every live answer/diagram retrieves from Superlinked first and cites sources.

About

First Active AI Agent for meetings. Say "Hey Flash!" during the meeting and get anything you need based on current meeting.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages