Merge branch 'dev'
CI/CD / deploy (push) Successful in 2m36s
Details
CI/CD / deploy (push) Successful in 2m36s
Details
This commit is contained in:
commit
71482f0e2a
|
|
@ -182,7 +182,8 @@ routes.get("/meet", (c) => {
|
||||||
routes.get("/recordings", async (c) => {
|
routes.get("/recordings", async (c) => {
|
||||||
const space = c.req.param("space") || "demo";
|
const space = c.req.param("space") || "demo";
|
||||||
const base = `/${escapeHtml(space)}/rmeets`;
|
const base = `/${escapeHtml(space)}/rmeets`;
|
||||||
const result = await miApiFetch("/meetings?limit=50&sort=-created_at");
|
const prefix = encodeURIComponent(space + "_");
|
||||||
|
const result = await miApiFetch(`/meetings?limit=50&sort=-created_at&conference_prefix=${prefix}`);
|
||||||
|
|
||||||
let body: string;
|
let body: string;
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
|
|
@ -490,6 +491,13 @@ routes.get("/", (c) => {
|
||||||
<p>Open the full Jitsi Meet interface directly</p>
|
<p>Open the full Jitsi Meet interface directly</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="${base}/meeting-intelligence">
|
||||||
|
<span class="nav-icon">🧠</span>
|
||||||
|
<div class="nav-body">
|
||||||
|
<h3>Meeting Intelligence</h3>
|
||||||
|
<p>Knowledge objects: action items, decisions, and insights across all meetings</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
<a href="${base}/recordings">
|
<a href="${base}/recordings">
|
||||||
<span class="nav-icon">🎥</span>
|
<span class="nav-icon">🎥</span>
|
||||||
<div class="nav-body">
|
<div class="nav-body">
|
||||||
|
|
@ -509,6 +517,123 @@ routes.get("/", (c) => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Meeting Intelligence knowledge page ──
|
||||||
|
|
||||||
|
routes.get("/meeting-intelligence", async (c) => {
|
||||||
|
const space = c.req.param("space") || "demo";
|
||||||
|
const base = `/${escapeHtml(space)}/rmeets`;
|
||||||
|
const prefix = encodeURIComponent(space + "_");
|
||||||
|
|
||||||
|
// Fetch space-scoped meetings from MI API
|
||||||
|
const result = await miApiFetch(`/meetings?limit=50&sort=-created_at&conference_prefix=${prefix}`);
|
||||||
|
|
||||||
|
let meetingCards = "";
|
||||||
|
let aggregateHtml = "";
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
meetingCards = miUnavailableHtml();
|
||||||
|
} else {
|
||||||
|
const meetings = Array.isArray(result.data) ? result.data : (result.data?.meetings ?? result.data?.items ?? []);
|
||||||
|
|
||||||
|
if (meetings.length === 0) {
|
||||||
|
meetingCards = `<p style="color:var(--rs-text-secondary)">No meetings recorded yet for this space. Start a meeting and enable recording to see it here.</p>`;
|
||||||
|
} else {
|
||||||
|
// Build meeting cards
|
||||||
|
meetingCards = `<div class="mi-cards">${meetings.map((m: any) => {
|
||||||
|
const date = m.created_at ? new Date(m.created_at).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "";
|
||||||
|
const duration = m.duration_seconds ? `${Math.round(m.duration_seconds / 60)}min` : "";
|
||||||
|
const status = (m.status || "completed").toLowerCase();
|
||||||
|
const badgeClass = `mi-badge--${status}`;
|
||||||
|
const hasTranscript = m.has_transcript ?? (m.segment_count != null && m.segment_count > 0);
|
||||||
|
const hasSummary = m.has_summary ?? m.summary_status === "completed";
|
||||||
|
return `<a class="mi-card" href="${base}/recordings/${escapeHtml(String(m.id))}">
|
||||||
|
<h3>${escapeHtml(m.title || m.room_name || m.conference_name || `Meeting ${m.id}`)}</h3>
|
||||||
|
<div class="mi-meta">${[date, duration].filter(Boolean).join(" · ")}</div>
|
||||||
|
<span class="mi-badge ${badgeClass}">${escapeHtml(status)}</span>
|
||||||
|
<div class="mi-chips">
|
||||||
|
${hasTranscript ? `<span class="mi-chip mi-chip--ok">Transcript</span>` : `<span class="mi-chip mi-chip--pending">No transcript</span>`}
|
||||||
|
${hasSummary ? `<span class="mi-chip mi-chip--ok">Summary</span>` : `<span class="mi-chip mi-chip--pending">No summary</span>`}
|
||||||
|
</div>
|
||||||
|
</a>`;
|
||||||
|
}).join("\n")}</div>`;
|
||||||
|
|
||||||
|
// Aggregate knowledge from completed meetings with summaries
|
||||||
|
const readyMeetings = meetings.filter((m: any) => (m.status === "completed" || m.has_summary));
|
||||||
|
if (readyMeetings.length > 0) {
|
||||||
|
// Fetch summaries for up to 10 completed meetings
|
||||||
|
const summaryPromises = readyMeetings.slice(0, 10).map((m: any) =>
|
||||||
|
miApiFetch(`/meetings/${m.id}/summary`).then(r => r.ok ? r.data : null)
|
||||||
|
);
|
||||||
|
const summaries = (await Promise.all(summaryPromises)).filter(Boolean);
|
||||||
|
|
||||||
|
const allActionItems: string[] = [];
|
||||||
|
const allDecisions: string[] = [];
|
||||||
|
const allTopics: string[] = [];
|
||||||
|
|
||||||
|
for (const s of summaries) {
|
||||||
|
const actions = s.action_items ?? s.actionItems ?? [];
|
||||||
|
for (const a of actions) {
|
||||||
|
const text = typeof a === "string" ? a : (a.task || a.text || a.description || "");
|
||||||
|
if (text) allActionItems.push(text);
|
||||||
|
}
|
||||||
|
const decisions = s.decisions ?? [];
|
||||||
|
for (const d of decisions) {
|
||||||
|
const text = typeof d === "string" ? d : (d.text || d.description || "");
|
||||||
|
if (text) allDecisions.push(text);
|
||||||
|
}
|
||||||
|
const topics = s.topics ?? [];
|
||||||
|
for (const t of topics) {
|
||||||
|
const text = typeof t === "string" ? t : (t.name || t.topic || t.text || "");
|
||||||
|
if (text) allTopics.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allActionItems.length > 0 || allDecisions.length > 0 || allTopics.length > 0) {
|
||||||
|
aggregateHtml = `<div class="mi-aggregate">`;
|
||||||
|
if (allActionItems.length > 0) {
|
||||||
|
aggregateHtml += `<h2>Action Items</h2><ul>${allActionItems.map(a => `<li>${escapeHtml(a)}</li>`).join("")}</ul>`;
|
||||||
|
}
|
||||||
|
if (allDecisions.length > 0) {
|
||||||
|
aggregateHtml += `<h2>Decisions</h2><ul>${allDecisions.map(d => `<li>${escapeHtml(d)}</li>`).join("")}</ul>`;
|
||||||
|
}
|
||||||
|
if (allTopics.length > 0) {
|
||||||
|
aggregateHtml += `<h2>Topics</h2><div class="mi-chips">${allTopics.map(t => `<span class="mi-chip mi-chip--ok">${escapeHtml(t)}</span>`).join("")}</div>`;
|
||||||
|
}
|
||||||
|
aggregateHtml += `</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraStyles = `<style>
|
||||||
|
.mi-aggregate{margin-bottom:2rem;padding:1.5rem;border-radius:12px;background:var(--rs-bg-surface);border:1px solid var(--rs-border)}
|
||||||
|
.mi-aggregate h2{font-size:1.1rem;margin:1rem 0 .5rem;color:var(--rs-text-primary)}
|
||||||
|
.mi-aggregate h2:first-child{margin-top:0}
|
||||||
|
.mi-aggregate ul{padding-left:1.5rem;margin:.5rem 0}
|
||||||
|
.mi-aggregate li{margin:.25rem 0;color:var(--rs-text-primary);line-height:1.5}
|
||||||
|
.mi-section-title{font-size:1.2rem;margin:2rem 0 1rem;color:var(--rs-text-primary)}
|
||||||
|
</style>`;
|
||||||
|
|
||||||
|
return c.html(renderShell({
|
||||||
|
title: `Meeting Intelligence — rMeets | rSpace`,
|
||||||
|
moduleId: "rmeets",
|
||||||
|
spaceSlug: space,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
styles: MI_STYLES + extraStyles,
|
||||||
|
body: `<div class="mi-page">
|
||||||
|
<h1>Meeting Intelligence</h1>
|
||||||
|
<p class="mi-subtitle">Knowledge objects, action items, and insights across all meetings</p>
|
||||||
|
<form class="mi-search-form" method="get" action="${base}/search">
|
||||||
|
<input type="text" name="q" placeholder="Search across all meetings...">
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
${aggregateHtml ? `<h2 class="mi-section-title">Aggregate Knowledge</h2>${aggregateHtml}` : ""}
|
||||||
|
<h2 class="mi-section-title">Meetings</h2>
|
||||||
|
${meetingCards}
|
||||||
|
</div>`,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
// ── Room embed (catch-all — must be LAST so /meet, /recordings, /search match first) ──
|
// ── Room embed (catch-all — must be LAST so /meet, /recordings, /search match first) ──
|
||||||
|
|
||||||
routes.get("/:room", (c) => {
|
routes.get("/:room", (c) => {
|
||||||
|
|
@ -543,7 +668,7 @@ routes.get("/:room", (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: clean full-screen Jitsi — no rSpace shell, mobile-friendly
|
// Default: clean full-screen Jitsi — no rSpace shell, mobile-friendly
|
||||||
const jitsiRoom = encodeURIComponent(room);
|
const jitsiRoom = encodeURIComponent(space + "_" + room);
|
||||||
const meetsBase = `/${escapeHtml(space)}/rmeets`;
|
const meetsBase = `/${escapeHtml(space)}/rmeets`;
|
||||||
return c.html(`<!DOCTYPE html>
|
return c.html(`<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
@ -585,6 +710,7 @@ routes.get("/:room", (c) => {
|
||||||
<div class="mi-fab">
|
<div class="mi-fab">
|
||||||
<button class="mi-fab-btn" id="mi-fab-toggle" title="Meeting Intelligence">🧠</button>
|
<button class="mi-fab-btn" id="mi-fab-toggle" title="Meeting Intelligence">🧠</button>
|
||||||
<div class="mi-dropdown" id="mi-dropdown">
|
<div class="mi-dropdown" id="mi-dropdown">
|
||||||
|
<a href="${meetsBase}/meeting-intelligence"><span class="mi-icon">🧠</span> Meeting Intelligence</a>
|
||||||
<a href="${meetsBase}/recordings"><span class="mi-icon">🎥</span> Recordings</a>
|
<a href="${meetsBase}/recordings"><span class="mi-icon">🎥</span> Recordings</a>
|
||||||
<a href="${meetsBase}/search"><span class="mi-icon">🔍</span> Search Transcripts</a>
|
<a href="${meetsBase}/search"><span class="mi-icon">🔍</span> Search Transcripts</a>
|
||||||
<div class="mi-sep"></div>
|
<div class="mi-sep"></div>
|
||||||
|
|
@ -629,7 +755,7 @@ routes.get("/:room", (c) => {
|
||||||
"raisehand","tileview","toggle-camera",
|
"raisehand","tileview","toggle-camera",
|
||||||
"fullscreen","chat","settings",
|
"fullscreen","chat","settings",
|
||||||
"participants-pane","select-background",
|
"participants-pane","select-background",
|
||||||
"sharedvideo",
|
"sharedvideo","shareaudio","meetingintelligence",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
interfaceConfigOverwrite: {
|
interfaceConfigOverwrite: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue