diff --git a/docker-compose.yml b/docker-compose.yml index b6588399..2b070a65 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,6 +66,9 @@ services: - SCRIBUS_NOVNC_URL=https://design.rspace.online - IPFS_API_URL=http://collab-server-ipfs-1:5001 - IPFS_GATEWAY_URL=https://ipfs.jeffemmett.com + - MEETING_INTELLIGENCE_API_URL=${MEETING_INTELLIGENCE_API_URL:-http://meeting-intelligence-api:8000} + - MI_INTERNAL_KEY=${MI_INTERNAL_KEY} + - JITSI_URL=${JITSI_URL:-https://jeffsi.localvibe.live} depends_on: rspace-db: condition: service_healthy diff --git a/modules/rmeets/mod.ts b/modules/rmeets/mod.ts index d2d382c8..853a1d0a 100644 --- a/modules/rmeets/mod.ts +++ b/modules/rmeets/mod.ts @@ -35,6 +35,7 @@ function ensureMeetsDoc(space: string): MeetsDoc { const JITSI_URL = process.env.JITSI_URL || "https://jeffsi.localvibe.live"; const MI_API_URL = process.env.MEETING_INTELLIGENCE_API_URL || "http://meeting-intelligence-api:8000"; +const MI_INTERNAL_KEY = process.env.MI_INTERNAL_KEY || ""; const routes = new Hono(); // ── Meeting Intelligence API helper ── @@ -43,10 +44,12 @@ async function miApiFetch(path: string, options?: { method?: string; body?: any try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); - const fetchOpts: RequestInit = { signal: controller.signal }; + const headers: Record = {}; + if (MI_INTERNAL_KEY) headers["X-MI-Internal-Key"] = MI_INTERNAL_KEY; + const fetchOpts: RequestInit = { signal: controller.signal, headers }; if (options?.method) fetchOpts.method = options.method; if (options?.body) { - fetchOpts.headers = { "Content-Type": "application/json" }; + headers["Content-Type"] = "application/json"; fetchOpts.body = JSON.stringify(options.body); } const res = await fetch(`${MI_API_URL}${path}`, fetchOpts); @@ -419,9 +422,11 @@ routes.all("/api/mi-proxy/*", async (c) => { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); + const proxyHeaders: Record = { "Content-Type": "application/json" }; + if (MI_INTERNAL_KEY) proxyHeaders["X-MI-Internal-Key"] = MI_INTERNAL_KEY; const upstream = await fetch(url.toString(), { method: c.req.method, - headers: { "Content-Type": "application/json" }, + headers: proxyHeaders, body: c.req.method !== "GET" && c.req.method !== "HEAD" ? await c.req.text() : undefined, signal: controller.signal, }); @@ -701,6 +706,15 @@ routes.get("/:room", (c) => { .mi-dropdown a:hover,.mi-dropdown button:hover{background:rgba(99,102,241,0.15)} .mi-dropdown .mi-icon{font-size:1.1rem;width:22px;text-align:center;flex-shrink:0} .mi-dropdown .mi-sep{height:1px;background:rgba(99,102,241,0.15);margin:4px 0} + /* Live captions overlay */ + #mi-captions{position:fixed;left:50%;bottom:92px;transform:translateX(-50%);z-index:9999;max-width:min(780px,94vw);display:flex;flex-direction:column;gap:4px;pointer-events:none;align-items:center} + #mi-captions.hidden{display:none} + .mi-cap-line{background:rgba(0,0,0,0.72);color:#f8fafc;padding:6px 14px;border-radius:10px;font-size:1rem;line-height:1.35;text-align:center;max-width:100%;backdrop-filter:blur(6px);box-shadow:0 2px 10px rgba(0,0,0,0.4);word-wrap:break-word} + .mi-cap-line .mi-cap-speaker{color:#a5b4fc;font-weight:600;margin-right:6px;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.03em} + .mi-cap-line.interim{opacity:0.75;font-style:italic} + .mi-cap-status{position:fixed;left:12px;bottom:12px;z-index:10000;font-size:0.72rem;color:rgba(226,232,240,0.7);background:rgba(0,0,0,0.5);padding:4px 10px;border-radius:999px;pointer-events:none;max-width:80vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} + .mi-cap-status.hidden{display:none} + @media(max-width:600px){#mi-captions{bottom:72px}.mi-cap-line{font-size:0.9rem;padding:5px 10px}} @@ -710,6 +724,8 @@ routes.get("/:room", (c) => {
+ +
🧠 Meeting Intelligence 🎥 Recordings 🔍 Search Transcripts @@ -717,6 +733,8 @@ routes.get("/:room", (c) => { 🏠 rMeets Hub
+ +