Merge branch 'dev'
This commit is contained in:
commit
b4fa61d99a
|
|
@ -18,11 +18,17 @@ const routes = new Hono();
|
|||
|
||||
// ── Meeting Intelligence API helper ──
|
||||
|
||||
async function miApiFetch(path: string): Promise<{ ok: boolean; data?: any; error?: string }> {
|
||||
async function miApiFetch(path: string, options?: { method?: string; body?: any }): Promise<{ ok: boolean; data?: any; error?: string }> {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 8000);
|
||||
const res = await fetch(`${MI_API_URL}${path}`, { signal: controller.signal });
|
||||
const fetchOpts: RequestInit = { signal: controller.signal };
|
||||
if (options?.method) fetchOpts.method = options.method;
|
||||
if (options?.body) {
|
||||
fetchOpts.headers = { "Content-Type": "application/json" };
|
||||
fetchOpts.body = JSON.stringify(options.body);
|
||||
}
|
||||
const res = await fetch(`${MI_API_URL}${path}`, fetchOpts);
|
||||
clearTimeout(timeout);
|
||||
if (!res.ok) return { ok: false, error: `API returned ${res.status}` };
|
||||
return { ok: true, data: await res.json() };
|
||||
|
|
@ -58,6 +64,7 @@ const MI_STYLES = `<style>
|
|||
.mi-badge--processing{background:#eab30822;color:#eab308}
|
||||
.mi-badge--recording{background:#ef444422;color:#ef4444}
|
||||
.mi-badge--failed{background:#ef444422;color:#ef4444}
|
||||
.mi-badge--transcribing,.mi-badge--diarizing,.mi-badge--summarizing,.mi-badge--extracting_audio{background:#3b82f622;color:#3b82f6}
|
||||
.mi-tabs{display:flex;gap:0;border-bottom:1px solid var(--rs-border);margin-bottom:1.5rem}
|
||||
.mi-tab{padding:.75rem 1.25rem;text-decoration:none;color:var(--rs-text-secondary);font-size:.9rem;font-weight:500;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}
|
||||
.mi-tab:hover{color:var(--rs-text-primary)}
|
||||
|
|
@ -160,7 +167,7 @@ routes.get("/recordings", async (c) => {
|
|||
const participants = m.participant_count ?? m.participants?.length ?? "";
|
||||
const status = (m.status || "completed").toLowerCase();
|
||||
const badgeClass = `mi-badge--${status}`;
|
||||
const hasTranscript = m.has_transcript ?? m.transcript_status === "completed";
|
||||
const hasTranscript = m.has_transcript ?? (m.segment_count != null && m.segment_count > 0) ?? m.transcript_status === "completed";
|
||||
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 || `Meeting ${m.id}`)}</h3>
|
||||
|
|
@ -241,8 +248,8 @@ routes.get("/recordings/:id/:tab", async (c) => {
|
|||
const speakers = speakersRes.ok ? (Array.isArray(speakersRes.data) ? speakersRes.data : speakersRes.data?.speakers ?? []) : [];
|
||||
const speakerList = speakers.length > 0
|
||||
? `<div class="mi-speakers">${speakers.map((s: any) => {
|
||||
const time = s.speaking_time_seconds ? `${Math.round(s.speaking_time_seconds / 60)}min speaking` : "";
|
||||
return `<div class="mi-speaker-card"><h4>${escapeHtml(s.name || s.label || `Speaker ${s.id}`)}</h4><p>${escapeHtml(time)}</p></div>`;
|
||||
const time = s.speaking_time ? `${Math.round(s.speaking_time / 60)}min speaking` : s.speaking_time_seconds ? `${Math.round(s.speaking_time_seconds / 60)}min speaking` : "";
|
||||
return `<div class="mi-speaker-card"><h4>${escapeHtml(s.name || s.speaker_label || s.label || `Speaker ${s.speaker_id || s.id}`)}</h4><p>${escapeHtml(time)}</p></div>`;
|
||||
}).join("")}</div>`
|
||||
: `<p style="color:var(--rs-text-secondary)">No speaker data available.</p>`;
|
||||
tabContent = `<div class="mi-detail-meta">
|
||||
|
|
@ -261,7 +268,7 @@ ${speakerList}`;
|
|||
tabContent = `<p style="color:var(--rs-text-secondary)">Transcript is empty.</p>`;
|
||||
} else {
|
||||
tabContent = `<div class="mi-transcript">${segments.map((seg: any) => {
|
||||
const speaker = seg.speaker || seg.speaker_name || seg.label || "Unknown";
|
||||
const speaker = seg.speaker || seg.speaker_name || seg.speaker_label || seg.label || "Unknown";
|
||||
const ts = seg.start_time != null ? formatTimestamp(seg.start_time) : "";
|
||||
const text = seg.text || seg.content || "";
|
||||
return `<div class="mi-segment">
|
||||
|
|
@ -276,7 +283,7 @@ ${speakerList}`;
|
|||
tabContent = `<p style="color:var(--rs-text-secondary)">Summary not available yet.</p>`;
|
||||
} else {
|
||||
const summary = summaryRes.data;
|
||||
const overview = summary.overview || summary.summary || summary.text || "";
|
||||
const overview = summary.summary_text || summary.overview || summary.summary || summary.text || "";
|
||||
const keyPoints = summary.key_points ?? summary.keyPoints ?? [];
|
||||
const actionItems = summary.action_items ?? summary.actionItems ?? [];
|
||||
const decisions = summary.decisions ?? [];
|
||||
|
|
@ -284,7 +291,7 @@ ${speakerList}`;
|
|||
let html = `<div class="mi-summary">`;
|
||||
if (overview) html += `<h2>Overview</h2><p>${escapeHtml(overview)}</p>`;
|
||||
if (keyPoints.length > 0) html += `<h2>Key Points</h2><ul>${keyPoints.map((p: string) => `<li>${escapeHtml(p)}</li>`).join("")}</ul>`;
|
||||
if (actionItems.length > 0) html += `<h2>Action Items</h2><ul>${actionItems.map((a: any) => `<li>${escapeHtml(typeof a === "string" ? a : a.text || a.description || "")}</li>`).join("")}</ul>`;
|
||||
if (actionItems.length > 0) html += `<h2>Action Items</h2><ul>${actionItems.map((a: any) => `<li>${escapeHtml(typeof a === "string" ? a : a.task || a.text || a.description || "")}</li>`).join("")}</ul>`;
|
||||
if (decisions.length > 0) html += `<h2>Decisions</h2><ul>${decisions.map((d: any) => `<li>${escapeHtml(typeof d === "string" ? d : d.text || d.description || "")}</li>`).join("")}</ul>`;
|
||||
if (!overview && keyPoints.length === 0 && actionItems.length === 0) html += `<p style="color:var(--rs-text-secondary)">Summary content is empty.</p>`;
|
||||
html += `</div>`;
|
||||
|
|
@ -296,9 +303,9 @@ ${speakerList}`;
|
|||
tabContent = `<p style="color:var(--rs-text-secondary)">No speaker data available.</p>`;
|
||||
} else {
|
||||
tabContent = `<div class="mi-speakers">${speakers.map((s: any) => {
|
||||
const time = s.speaking_time_seconds ? `${Math.round(s.speaking_time_seconds / 60)}min speaking` : "";
|
||||
const time = s.speaking_time ? `${Math.round(s.speaking_time / 60)}min speaking` : s.speaking_time_seconds ? `${Math.round(s.speaking_time_seconds / 60)}min speaking` : "";
|
||||
const segments = s.segment_count ?? s.num_segments ?? "";
|
||||
return `<div class="mi-speaker-card"><h4>${escapeHtml(s.name || s.label || `Speaker ${s.id}`)}</h4><p>${[time, segments ? `${segments} segments` : ""].filter(Boolean).join(" · ")}</p></div>`;
|
||||
return `<div class="mi-speaker-card"><h4>${escapeHtml(s.name || s.speaker_label || s.label || `Speaker ${s.speaker_id || s.id}`)}</h4><p>${[time, segments ? `${segments} segments` : ""].filter(Boolean).join(" · ")}</p></div>`;
|
||||
}).join("")}</div>`;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -328,7 +335,7 @@ routes.get("/search", async (c) => {
|
|||
|
||||
let resultsHtml = "";
|
||||
if (q) {
|
||||
const result = await miApiFetch(`/search?q=${encodeURIComponent(q)}&limit=30`);
|
||||
const result = await miApiFetch("/search", { method: "POST", body: { query: q, limit: 30 } });
|
||||
if (!result.ok) {
|
||||
resultsHtml = miUnavailableHtml();
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue