@ai-matrx/meet 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/index.cjs +567 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +538 -232
- package/dist/index.d.ts +538 -232
- package/dist/index.js +567 -19
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +530 -33
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +530 -33
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -212,6 +212,12 @@ var MEET_ROUTES = {
|
|
|
212
212
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
213
213
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
214
214
|
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
215
|
+
/**
|
|
216
|
+
* Meet wave 3: the recap email after the meeting. `GET` = the draft (subject,
|
|
217
|
+
* body from the wrap-up, who it can go to); `POST` = send what the host
|
|
218
|
+
* reviewed (`dry_run` renders and queues nothing). Never sent automatically.
|
|
219
|
+
*/
|
|
220
|
+
meetingFollowUp: "/api/v1/meet/meetings/{meeting_id}/follow-up",
|
|
215
221
|
/**
|
|
216
222
|
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
217
223
|
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
@@ -341,10 +347,11 @@ function createTokenClient(options) {
|
|
|
341
347
|
throw error;
|
|
342
348
|
}
|
|
343
349
|
if (response.status === 401 || response.status === 403) {
|
|
350
|
+
const said = await serverSentence(response);
|
|
344
351
|
throw new MeetError(
|
|
345
352
|
"forbidden",
|
|
346
|
-
|
|
347
|
-
"
|
|
353
|
+
said?.message ?? "You can't join this meeting.",
|
|
354
|
+
said?.remedy ?? "Ask the person who shared the link to let you in, then open the link again."
|
|
348
355
|
);
|
|
349
356
|
}
|
|
350
357
|
if (response.status === 404) {
|
|
@@ -355,12 +362,7 @@ function createTokenClient(options) {
|
|
|
355
362
|
);
|
|
356
363
|
}
|
|
357
364
|
if (response.status === 410) {
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
const body = await response.json();
|
|
361
|
-
if (typeof body?.detail?.message === "string") reason = body.detail.message;
|
|
362
|
-
} catch {
|
|
363
|
-
}
|
|
365
|
+
const reason = (await serverSentence(response))?.message ?? "This meeting was cancelled.";
|
|
364
366
|
throw new MeetError(
|
|
365
367
|
"cancelled",
|
|
366
368
|
reason,
|
|
@@ -368,10 +370,11 @@ function createTokenClient(options) {
|
|
|
368
370
|
);
|
|
369
371
|
}
|
|
370
372
|
if (response.status === 423) {
|
|
373
|
+
const said = await serverSentence(response);
|
|
371
374
|
throw new MeetError(
|
|
372
375
|
"room-locked",
|
|
373
|
-
"
|
|
374
|
-
"The host locked the room. Ask them to unlock
|
|
376
|
+
said?.message ?? "The meeting is locked.",
|
|
377
|
+
said?.remedy ?? "The host locked the room. Ask them to unlock it, then join again."
|
|
375
378
|
);
|
|
376
379
|
}
|
|
377
380
|
if (response.status === 503) {
|
|
@@ -442,6 +445,19 @@ function placeholder(roomName) {
|
|
|
442
445
|
lobby: false
|
|
443
446
|
};
|
|
444
447
|
}
|
|
448
|
+
async function serverSentence(response) {
|
|
449
|
+
try {
|
|
450
|
+
const body = await response.json();
|
|
451
|
+
const source = body !== null && typeof body.detail === "object" && body.detail !== null ? body.detail : body;
|
|
452
|
+
if (source === null) return null;
|
|
453
|
+
const message = typeof source.user_message === "string" && source.user_message.trim() !== "" ? source.user_message : typeof source.message === "string" && source.message.trim() !== "" ? source.message : null;
|
|
454
|
+
if (message === null) return null;
|
|
455
|
+
const remedy = typeof source.remedy === "string" && source.remedy.trim() !== "" ? source.remedy : void 0;
|
|
456
|
+
return { message, remedy };
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
445
461
|
|
|
446
462
|
// src/core/ai.ts
|
|
447
463
|
var MEET_ANSWER_EVENTS = {
|
|
@@ -885,7 +901,7 @@ var DEFAULT_RING_TIMEOUT_MS = 45e3;
|
|
|
885
901
|
function createCallCenter(options) {
|
|
886
902
|
const { manager, repository, identity } = options;
|
|
887
903
|
const timers = options.timers ?? {
|
|
888
|
-
setTimeout: (run,
|
|
904
|
+
setTimeout: (run, ms2) => setTimeout(run, ms2),
|
|
889
905
|
clearTimeout: (handle) => clearTimeout(handle),
|
|
890
906
|
now: () => Date.now()
|
|
891
907
|
};
|
|
@@ -1823,7 +1839,9 @@ function createMeetingSession(options) {
|
|
|
1823
1839
|
return identity.userId;
|
|
1824
1840
|
}
|
|
1825
1841
|
function knockIdentity() {
|
|
1826
|
-
return asParticipantIdentity(
|
|
1842
|
+
return asParticipantIdentity(
|
|
1843
|
+
identity.userId !== null ? `user:${identity.userId}` : `guest:${identity.deviceId}`
|
|
1844
|
+
);
|
|
1827
1845
|
}
|
|
1828
1846
|
function requireMeeting(operation) {
|
|
1829
1847
|
const meeting = store.snapshot().meeting;
|
|
@@ -1858,8 +1876,9 @@ function createMeetingSession(options) {
|
|
|
1858
1876
|
verdict.admitted ? "admitted" : "denied",
|
|
1859
1877
|
verdict.by === null ? null : verdict.by
|
|
1860
1878
|
);
|
|
1861
|
-
if (
|
|
1862
|
-
options.onAdmitted?.();
|
|
1879
|
+
if (decided === knockIdentity()) {
|
|
1880
|
+
if (verdict.admitted) options.onAdmitted?.();
|
|
1881
|
+
else options.onDenied?.();
|
|
1863
1882
|
}
|
|
1864
1883
|
}
|
|
1865
1884
|
},
|
|
@@ -2196,7 +2215,16 @@ var RPCS = {
|
|
|
2196
2215
|
/** One meeting's occurrences between two instants (INVOKER — the reader's RLS). */
|
|
2197
2216
|
meetingOccurrences: "meet_meeting_occurrences",
|
|
2198
2217
|
/** Every occurrence the reader can see between two instants — the Upcoming list. */
|
|
2199
|
-
occurrencesBetween: "meet_occurrences_between"
|
|
2218
|
+
occurrencesBetween: "meet_occurrences_between",
|
|
2219
|
+
/*
|
|
2220
|
+
* ── MEET WAVE 3 — after the meeting. Both INVOKER (the reader's RLS) and both
|
|
2221
|
+
* scoped to the reader's OWN meetings (host, invitee, attended) — RLS is the
|
|
2222
|
+
* ceiling, never the view.
|
|
2223
|
+
*/
|
|
2224
|
+
/** The recordings library. */
|
|
2225
|
+
myRecordings: "meet_my_recordings",
|
|
2226
|
+
/** Titles, agendas, current notes and final transcript lines; snippets mark hits with ⟦ ⟧. */
|
|
2227
|
+
search: "meet_search"
|
|
2200
2228
|
};
|
|
2201
2229
|
var VOID_RPCS = [RPCS.admit, RPCS.recordConsent];
|
|
2202
2230
|
var MEETING_COLUMNS = "id,organization_id,room_name,slug,title,kind,host_user_id,scheduled_for,scheduled_duration_minutes,started_at,ended_at,locked,lobby_enabled,recording_policy,ai_enabled,metadata,time_zone,agenda,recurrence_rule,join_before_host,cancelled_at,cancellation_reason,deleted_at,calendar_sequence,version";
|
|
@@ -2901,12 +2929,117 @@ function createMeetRepository(options) {
|
|
|
2901
2929
|
);
|
|
2902
2930
|
},
|
|
2903
2931
|
async recordings(meetingId) {
|
|
2904
|
-
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: false });
|
|
2932
|
+
const response = await db.from(TABLES.recordings).select(RECORDING_COLUMNS).eq("meeting_id", meetingId).is("deleted_at", null).order("started_at", { ascending: false });
|
|
2905
2933
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.recordings");
|
|
2906
2934
|
return (response.data ?? []).map(projectRecording);
|
|
2935
|
+
},
|
|
2936
|
+
async myRecordings({ includeArchived = false, limit = 200 } = {}) {
|
|
2937
|
+
const rows = await guard(
|
|
2938
|
+
"meet.myRecordings",
|
|
2939
|
+
() => db.rpc(RPCS.myRecordings, { p_include_archived: includeArchived, p_limit: limit })
|
|
2940
|
+
);
|
|
2941
|
+
return rows.map(projectLibraryRecording);
|
|
2942
|
+
},
|
|
2943
|
+
async searchMeetings(query, limit = 60) {
|
|
2944
|
+
if (query.trim().length === 0) return [];
|
|
2945
|
+
const rows = await guard(
|
|
2946
|
+
"meet.searchMeetings",
|
|
2947
|
+
() => db.rpc(RPCS.search, { p_query: query, p_limit: limit })
|
|
2948
|
+
);
|
|
2949
|
+
return rows.map(projectSearchHit);
|
|
2950
|
+
},
|
|
2951
|
+
async renameRecording(recordingId, title) {
|
|
2952
|
+
const clean = title === null ? null : title.trim().slice(0, 200) || null;
|
|
2953
|
+
const response = await db.from(TABLES.recordings).update({ title: clean }).eq("id", recordingId).select("id");
|
|
2954
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.renameRecording");
|
|
2955
|
+
if ((response.data ?? []).length === 0) {
|
|
2956
|
+
throw normalizeMeetError(
|
|
2957
|
+
{ message: "permission denied: only the meeting's host or an editor can rename it", code: "42501" },
|
|
2958
|
+
"meet.renameRecording"
|
|
2959
|
+
);
|
|
2960
|
+
}
|
|
2961
|
+
},
|
|
2962
|
+
async setRecordingArchived(recordingId, archived) {
|
|
2963
|
+
const response = await db.from(TABLES.recordings).update({ deleted_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", recordingId).select("id");
|
|
2964
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setRecordingArchived");
|
|
2965
|
+
if ((response.data ?? []).length === 0) {
|
|
2966
|
+
throw normalizeMeetError(
|
|
2967
|
+
{ message: "permission denied: only the meeting's host or an editor can archive it", code: "42501" },
|
|
2968
|
+
"meet.setRecordingArchived"
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2907
2971
|
}
|
|
2908
2972
|
};
|
|
2909
2973
|
}
|
|
2974
|
+
var LIBRARY_STATES = [
|
|
2975
|
+
"idle",
|
|
2976
|
+
"starting",
|
|
2977
|
+
"recording",
|
|
2978
|
+
"stopping",
|
|
2979
|
+
"processing",
|
|
2980
|
+
"available",
|
|
2981
|
+
"expired",
|
|
2982
|
+
"failed"
|
|
2983
|
+
];
|
|
2984
|
+
function projectLibraryRecording(row) {
|
|
2985
|
+
const operation = "meet.projectLibraryRecording";
|
|
2986
|
+
const state = text(row, "state");
|
|
2987
|
+
const role = text(row, "my_role");
|
|
2988
|
+
return {
|
|
2989
|
+
id: requireText(row, "recording_id", operation),
|
|
2990
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
2991
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
2992
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
2993
|
+
role: role === "host" || role === "cohost" || role === "invitee" ? role : "attendee",
|
|
2994
|
+
state: LIBRARY_STATES.includes(state) ? state : "failed",
|
|
2995
|
+
title: text(row, "recording_title"),
|
|
2996
|
+
fileId: text(row, "file_id"),
|
|
2997
|
+
startedAt: text(row, "started_at"),
|
|
2998
|
+
endedAt: text(row, "ended_at"),
|
|
2999
|
+
durationMs: num(row, "duration_ms"),
|
|
3000
|
+
sizeBytes: num(row, "size_bytes"),
|
|
3001
|
+
failureReason: text(row, "failure_reason"),
|
|
3002
|
+
archivedAt: text(row, "deleted_at"),
|
|
3003
|
+
organizationId: asOrganizationId(requireText(row, "organization_id", operation))
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
var SEARCH_SOURCES = [
|
|
3007
|
+
"title",
|
|
3008
|
+
"summary",
|
|
3009
|
+
"decision",
|
|
3010
|
+
"action_item",
|
|
3011
|
+
"note",
|
|
3012
|
+
"transcript"
|
|
3013
|
+
];
|
|
3014
|
+
function projectSearchHit(row) {
|
|
3015
|
+
const operation = "meet.projectSearchHit";
|
|
3016
|
+
const source = text(row, "source");
|
|
3017
|
+
return {
|
|
3018
|
+
meetingId: asMeetingId(requireText(row, "meeting_id", operation)),
|
|
3019
|
+
meetingTitle: text(row, "meeting_title") ?? "Meeting",
|
|
3020
|
+
meetingSlug: requireText(row, "meeting_slug", operation),
|
|
3021
|
+
meetingAt: text(row, "meeting_at"),
|
|
3022
|
+
source: SEARCH_SOURCES.includes(source) ? source : "note",
|
|
3023
|
+
refId: requireText(row, "ref_id", operation),
|
|
3024
|
+
speaker: text(row, "speaker"),
|
|
3025
|
+
snippet: text(row, "snippet") ?? "",
|
|
3026
|
+
at: text(row, "said_at"),
|
|
3027
|
+
rank: num(row, "rank") ?? 0
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function snippetRuns(snippet) {
|
|
3031
|
+
const runs = [];
|
|
3032
|
+
const pattern = /⟦([^⟧]*)⟧/g;
|
|
3033
|
+
let last = 0;
|
|
3034
|
+
for (const match of snippet.matchAll(pattern)) {
|
|
3035
|
+
const start = match.index ?? 0;
|
|
3036
|
+
if (start > last) runs.push({ text: snippet.slice(last, start), hit: false });
|
|
3037
|
+
runs.push({ text: match[1] ?? "", hit: true });
|
|
3038
|
+
last = start + match[0].length;
|
|
3039
|
+
}
|
|
3040
|
+
if (last < snippet.length) runs.push({ text: snippet.slice(last), hit: false });
|
|
3041
|
+
return runs;
|
|
3042
|
+
}
|
|
2910
3043
|
|
|
2911
3044
|
// src/core/session-identity.ts
|
|
2912
3045
|
var RESOLVING = Object.freeze({
|
|
@@ -4537,6 +4670,11 @@ function MeetRuntime(props) {
|
|
|
4537
4670
|
// publish nor subscribe, so the host saying yes has to mint a new one.
|
|
4538
4671
|
onAdmitted: () => {
|
|
4539
4672
|
void engine.rejoin();
|
|
4673
|
+
},
|
|
4674
|
+
// A "no" is ALSO a mint: the server refuses it with its own sentence, which
|
|
4675
|
+
// turns the waiting room into the pre-join screen showing that sentence.
|
|
4676
|
+
onDenied: () => {
|
|
4677
|
+
void engine.rejoin().catch(() => void 0);
|
|
4540
4678
|
}
|
|
4541
4679
|
});
|
|
4542
4680
|
const resolveRoomToken = () => {
|
|
@@ -6662,22 +6800,27 @@ function MeetingOverlays() {
|
|
|
6662
6800
|
const room = useRoom();
|
|
6663
6801
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
6664
6802
|
/* @__PURE__ */ jsx3(ReactionOverlay, { reactions }),
|
|
6665
|
-
notice !== null && notice.kind !== "removed" ?
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6803
|
+
notice !== null && notice.kind !== "removed" ? (
|
|
6804
|
+
// A host's request ("the host is asking you to unmute"), not an error:
|
|
6805
|
+
// an assertive status, so it is announced at once without being filed
|
|
6806
|
+
// as a failure (design-system error-box census).
|
|
6807
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__notice-bar", role: "status", "aria-live": "assertive", children: [
|
|
6808
|
+
/* @__PURE__ */ jsx3("span", { children: notice.text }),
|
|
6809
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ jsx3(
|
|
6810
|
+
"button",
|
|
6811
|
+
{
|
|
6812
|
+
type: "button",
|
|
6813
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
6814
|
+
onClick: () => {
|
|
6815
|
+
dismiss();
|
|
6816
|
+
void room.toggleMicrophone();
|
|
6817
|
+
},
|
|
6818
|
+
children: "Unmute"
|
|
6819
|
+
}
|
|
6820
|
+
) : null,
|
|
6821
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
|
|
6822
|
+
] })
|
|
6823
|
+
) : null
|
|
6681
6824
|
] });
|
|
6682
6825
|
}
|
|
6683
6826
|
function ShortcutsSheet({ onClose }) {
|
|
@@ -8610,6 +8753,341 @@ function createMeetingInviteHandler(options) {
|
|
|
8610
8753
|
};
|
|
8611
8754
|
}
|
|
8612
8755
|
|
|
8756
|
+
// src/core/after.ts
|
|
8757
|
+
import { formatDurationMs as formatDurationMs3 } from "@ai-matrx/kit/format";
|
|
8758
|
+
function kindOf(person) {
|
|
8759
|
+
if (person.isAgent) return "assistant";
|
|
8760
|
+
if (person.role === "host") return "host";
|
|
8761
|
+
if (person.role === "cohost") return "cohost";
|
|
8762
|
+
if (person.role === "guest") return person.userId !== null ? "signed-in guest" : "guest";
|
|
8763
|
+
return "member";
|
|
8764
|
+
}
|
|
8765
|
+
function ms(iso) {
|
|
8766
|
+
if (iso === null) return null;
|
|
8767
|
+
const at = new Date(iso).getTime();
|
|
8768
|
+
return Number.isNaN(at) ? null : at;
|
|
8769
|
+
}
|
|
8770
|
+
function attendanceReport(meeting, attendees, names = {}) {
|
|
8771
|
+
const end = ms(meeting.endedAt);
|
|
8772
|
+
const rows = attendees.map((person) => {
|
|
8773
|
+
const joined = ms(person.joinedAt);
|
|
8774
|
+
const left = ms(person.leftAt);
|
|
8775
|
+
const until = left ?? end;
|
|
8776
|
+
const duration = joined !== null && until !== null && until >= joined ? until - joined : null;
|
|
8777
|
+
return {
|
|
8778
|
+
id: person.id,
|
|
8779
|
+
identity: person.identity,
|
|
8780
|
+
name: displayNameFor(names, person.identity, person.displayName),
|
|
8781
|
+
userId: person.userId,
|
|
8782
|
+
kind: kindOf(person),
|
|
8783
|
+
joinedAt: person.joinedAt,
|
|
8784
|
+
leftAt: person.leftAt,
|
|
8785
|
+
durationMs: duration,
|
|
8786
|
+
durationToEnd: duration !== null && left === null,
|
|
8787
|
+
admission: person.admissionState
|
|
8788
|
+
};
|
|
8789
|
+
});
|
|
8790
|
+
const rank = {
|
|
8791
|
+
host: 0,
|
|
8792
|
+
cohost: 1,
|
|
8793
|
+
member: 2,
|
|
8794
|
+
"signed-in guest": 3,
|
|
8795
|
+
guest: 4,
|
|
8796
|
+
assistant: 5
|
|
8797
|
+
};
|
|
8798
|
+
return rows.sort((a, b) => {
|
|
8799
|
+
if (rank[a.kind] !== rank[b.kind]) return rank[a.kind] - rank[b.kind];
|
|
8800
|
+
return (ms(a.joinedAt) ?? Number.MAX_SAFE_INTEGER) - (ms(b.joinedAt) ?? Number.MAX_SAFE_INTEGER);
|
|
8801
|
+
});
|
|
8802
|
+
}
|
|
8803
|
+
function attendanceTotals(rows) {
|
|
8804
|
+
const humans = rows.filter((row) => row.kind !== "assistant");
|
|
8805
|
+
const attended = humans.filter((row) => row.joinedAt !== null);
|
|
8806
|
+
const timed = attended.filter((row) => row.durationMs !== null);
|
|
8807
|
+
return {
|
|
8808
|
+
people: attended.length,
|
|
8809
|
+
guests: attended.filter((row) => row.kind === "guest" || row.kind === "signed-in guest").length,
|
|
8810
|
+
neverAdmitted: humans.filter((row) => row.joinedAt === null).length,
|
|
8811
|
+
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
8812
|
+
};
|
|
8813
|
+
}
|
|
8814
|
+
function csvCell(value) {
|
|
8815
|
+
if (value === null) return "";
|
|
8816
|
+
const text3 = String(value);
|
|
8817
|
+
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
8818
|
+
}
|
|
8819
|
+
var ATTENDANCE_CSV_HEADER = [
|
|
8820
|
+
"Meeting",
|
|
8821
|
+
"Meeting started",
|
|
8822
|
+
"Name",
|
|
8823
|
+
"Attended as",
|
|
8824
|
+
"Joined",
|
|
8825
|
+
"Left",
|
|
8826
|
+
"Minutes in the meeting",
|
|
8827
|
+
"Admission"
|
|
8828
|
+
];
|
|
8829
|
+
function attendanceCsv(reports) {
|
|
8830
|
+
const lines = [ATTENDANCE_CSV_HEADER.join(",")];
|
|
8831
|
+
for (const { meeting, rows } of reports) {
|
|
8832
|
+
for (const row of rows) {
|
|
8833
|
+
lines.push(
|
|
8834
|
+
[
|
|
8835
|
+
meeting.title,
|
|
8836
|
+
meeting.startedAt ?? meeting.scheduledFor,
|
|
8837
|
+
row.name,
|
|
8838
|
+
row.kind,
|
|
8839
|
+
row.joinedAt,
|
|
8840
|
+
row.leftAt,
|
|
8841
|
+
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
8842
|
+
row.admission
|
|
8843
|
+
].map(csvCell).join(",")
|
|
8844
|
+
);
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8847
|
+
return `${lines.join("\r\n")}\r
|
|
8848
|
+
`;
|
|
8849
|
+
}
|
|
8850
|
+
function recordingOffsetMs(at, recording) {
|
|
8851
|
+
if (recording === null || recording.state !== "available") return null;
|
|
8852
|
+
const start = ms(recording.startedAt);
|
|
8853
|
+
const moment = ms(at);
|
|
8854
|
+
if (start === null || moment === null || moment < start) return null;
|
|
8855
|
+
const end = ms(recording.endedAt);
|
|
8856
|
+
if (end !== null && moment > end) return null;
|
|
8857
|
+
return moment - start;
|
|
8858
|
+
}
|
|
8859
|
+
function clockPosition(offsetMs) {
|
|
8860
|
+
const total = Math.max(0, Math.floor(offsetMs / 1e3));
|
|
8861
|
+
const h = Math.floor(total / 3600);
|
|
8862
|
+
const m = Math.floor(total % 3600 / 60);
|
|
8863
|
+
const s = total % 60;
|
|
8864
|
+
const ss = String(s).padStart(2, "0");
|
|
8865
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
8866
|
+
}
|
|
8867
|
+
function searchTranscript(segments, query, names = {}) {
|
|
8868
|
+
const needle = fold(query.trim());
|
|
8869
|
+
if (needle.length === 0) return [];
|
|
8870
|
+
const out = [];
|
|
8871
|
+
for (const segment of segments) {
|
|
8872
|
+
const hay = fold(segment.text);
|
|
8873
|
+
const ranges = [];
|
|
8874
|
+
let from = 0;
|
|
8875
|
+
for (; ; ) {
|
|
8876
|
+
const at = hay.indexOf(needle, from);
|
|
8877
|
+
if (at < 0) break;
|
|
8878
|
+
ranges.push([at, at + needle.length]);
|
|
8879
|
+
from = at + needle.length;
|
|
8880
|
+
}
|
|
8881
|
+
const speaker = fold(displayNameFor(names, segment.identity, segment.speaker));
|
|
8882
|
+
if (ranges.length > 0 || speaker.includes(needle)) {
|
|
8883
|
+
out.push({ segmentId: segment.id, ranges });
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
return out;
|
|
8887
|
+
}
|
|
8888
|
+
function fold(value) {
|
|
8889
|
+
return value.normalize("NFD").replace(/[̀-ͯ]/g, (mark) => mark.length === 1 ? "" : mark).toLowerCase();
|
|
8890
|
+
}
|
|
8891
|
+
function clockOfDay(iso, options) {
|
|
8892
|
+
const at = new Date(iso);
|
|
8893
|
+
if (Number.isNaN(at.getTime())) return "";
|
|
8894
|
+
return at.toLocaleTimeString(options.locale, {
|
|
8895
|
+
hour: "numeric",
|
|
8896
|
+
minute: "2-digit",
|
|
8897
|
+
second: "2-digit",
|
|
8898
|
+
...options.timeZone !== void 0 ? { timeZone: options.timeZone } : {}
|
|
8899
|
+
});
|
|
8900
|
+
}
|
|
8901
|
+
function vttStamp(offsetMs) {
|
|
8902
|
+
const total = Math.max(0, Math.round(offsetMs));
|
|
8903
|
+
const h = Math.floor(total / 36e5);
|
|
8904
|
+
const m = Math.floor(total % 36e5 / 6e4);
|
|
8905
|
+
const s = Math.floor(total % 6e4 / 1e3);
|
|
8906
|
+
const milli = total % 1e3;
|
|
8907
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${String(milli).padStart(3, "0")}`;
|
|
8908
|
+
}
|
|
8909
|
+
function transcriptDocument(bundle, format, options = {}) {
|
|
8910
|
+
const names = options.names ?? bundle.names;
|
|
8911
|
+
const lines = bundle.transcript.filter((line) => line.text.trim().length > 0);
|
|
8912
|
+
const speaker = (line) => displayNameFor(names, line.identity, line.speaker);
|
|
8913
|
+
if (format === "vtt") {
|
|
8914
|
+
const zeroIso = bundle.recording?.state === "available" && bundle.recording.startedAt !== null ? bundle.recording.startedAt : bundle.meeting.startedAt ?? lines[0]?.startedAt ?? null;
|
|
8915
|
+
const zero = ms(zeroIso) ?? 0;
|
|
8916
|
+
const cues = ["WEBVTT", ""];
|
|
8917
|
+
lines.forEach((line, index) => {
|
|
8918
|
+
const start = (ms(line.startedAt) ?? zero) - zero;
|
|
8919
|
+
if (start < 0) return;
|
|
8920
|
+
const next = lines[index + 1];
|
|
8921
|
+
const nextStart = next !== void 0 ? (ms(next.startedAt) ?? zero) - zero : start + 4e3;
|
|
8922
|
+
const end = Math.max(start + 500, Math.min(nextStart, start + 1e4));
|
|
8923
|
+
cues.push(`${vttStamp(start)} --> ${vttStamp(end)}`);
|
|
8924
|
+
cues.push(`<v ${speaker(line).replace(/[<>]/g, "")}>${line.text.replace(/-->/g, "->")}`);
|
|
8925
|
+
cues.push("");
|
|
8926
|
+
});
|
|
8927
|
+
return cues.join("\n");
|
|
8928
|
+
}
|
|
8929
|
+
const out = [];
|
|
8930
|
+
const title = bundle.meeting.title;
|
|
8931
|
+
out.push(format === "md" ? `# ${title} \u2014 transcript` : `${title} \u2014 transcript`);
|
|
8932
|
+
out.push("");
|
|
8933
|
+
let last = null;
|
|
8934
|
+
for (const line of lines) {
|
|
8935
|
+
if (line.identity !== last) {
|
|
8936
|
+
if (last !== null) out.push("");
|
|
8937
|
+
const who = speaker(line);
|
|
8938
|
+
const clock = clockOfDay(line.startedAt, options);
|
|
8939
|
+
out.push(format === "md" ? `**${who}** \xB7 ${clock}` : `${who} (${clock})`);
|
|
8940
|
+
last = line.identity;
|
|
8941
|
+
}
|
|
8942
|
+
out.push(line.text.trim());
|
|
8943
|
+
}
|
|
8944
|
+
if (lines.length === 0) out.push("No transcript was captured for this meeting.");
|
|
8945
|
+
return `${out.join("\n")}
|
|
8946
|
+
`;
|
|
8947
|
+
}
|
|
8948
|
+
function bullet(note) {
|
|
8949
|
+
const owner = note.assigneeDisplayName !== null ? ` \u2014 ${note.assigneeDisplayName}` : "";
|
|
8950
|
+
return `- ${note.text.trim()}${owner}`;
|
|
8951
|
+
}
|
|
8952
|
+
function notesDocument(bundle, options = {}) {
|
|
8953
|
+
const out = [`# ${bundle.meeting.title}`, ""];
|
|
8954
|
+
if (options.when) out.push(`_${options.when}_`, "");
|
|
8955
|
+
const people = bundle.attendees.filter((person) => !person.isAgent && person.joinedAt !== null).map((person) => displayNameFor(bundle.names, person.identity, person.displayName));
|
|
8956
|
+
if (people.length > 0) out.push(`**Attended:** ${people.join(", ")}`, "");
|
|
8957
|
+
out.push("## Summary", "", bundle.summary?.text.trim() || "No summary was written.", "");
|
|
8958
|
+
out.push("## Decisions", "");
|
|
8959
|
+
out.push(...bundle.decisions.length > 0 ? bundle.decisions.map(bullet) : ["None recorded."], "");
|
|
8960
|
+
out.push("## Action items", "");
|
|
8961
|
+
out.push(
|
|
8962
|
+
...bundle.actionItems.length > 0 ? bundle.actionItems.map(bullet) : ["None recorded."],
|
|
8963
|
+
""
|
|
8964
|
+
);
|
|
8965
|
+
if (options.link) out.push(`[Open the meeting record](${options.link})`, "");
|
|
8966
|
+
return out.join("\n");
|
|
8967
|
+
}
|
|
8968
|
+
function meetingLength(meeting) {
|
|
8969
|
+
const start = ms(meeting.startedAt);
|
|
8970
|
+
const end = ms(meeting.endedAt);
|
|
8971
|
+
if (start === null || end === null || end < start) return null;
|
|
8972
|
+
return formatDurationMs3(end - start, { style: "long" });
|
|
8973
|
+
}
|
|
8974
|
+
async function loadMeetingChat(api, meetingId, roomToken = null) {
|
|
8975
|
+
const operation = "meet.loadMeetingChat";
|
|
8976
|
+
let response;
|
|
8977
|
+
try {
|
|
8978
|
+
response = await api.request({
|
|
8979
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(meetingId)}`,
|
|
8980
|
+
method: "GET",
|
|
8981
|
+
operation,
|
|
8982
|
+
requireAuth: roomToken === null,
|
|
8983
|
+
...roomToken !== null ? { headers: { [ROOM_TOKEN_HEADER]: roomToken } } : {}
|
|
8984
|
+
});
|
|
8985
|
+
} catch (error) {
|
|
8986
|
+
return {
|
|
8987
|
+
messages: [],
|
|
8988
|
+
refusal: {
|
|
8989
|
+
message: `The chat could not be read (${error.message}).`,
|
|
8990
|
+
remedy: error.remedy ?? "Reload the page."
|
|
8991
|
+
}
|
|
8992
|
+
};
|
|
8993
|
+
}
|
|
8994
|
+
if (!response.ok) {
|
|
8995
|
+
let body = null;
|
|
8996
|
+
try {
|
|
8997
|
+
body = await response.json();
|
|
8998
|
+
} catch {
|
|
8999
|
+
body = null;
|
|
9000
|
+
}
|
|
9001
|
+
const said = readServerRefusal(body);
|
|
9002
|
+
return {
|
|
9003
|
+
messages: [],
|
|
9004
|
+
refusal: {
|
|
9005
|
+
message: said?.reason ?? `The chat could not be read (the server answered ${response.status}).`,
|
|
9006
|
+
remedy: said?.remedy || "Reload the page."
|
|
9007
|
+
}
|
|
9008
|
+
};
|
|
9009
|
+
}
|
|
9010
|
+
const payload = await response.json();
|
|
9011
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
9012
|
+
return {
|
|
9013
|
+
messages: rows.map(
|
|
9014
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
9015
|
+
).filter((message) => message !== null),
|
|
9016
|
+
refusal: null
|
|
9017
|
+
};
|
|
9018
|
+
}
|
|
9019
|
+
async function refusalOf(response, operation) {
|
|
9020
|
+
let body = null;
|
|
9021
|
+
try {
|
|
9022
|
+
body = await response.json();
|
|
9023
|
+
} catch {
|
|
9024
|
+
body = null;
|
|
9025
|
+
}
|
|
9026
|
+
const said = readServerRefusal(body);
|
|
9027
|
+
return new MeetError(
|
|
9028
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
9029
|
+
`${operation}: ${said?.reason ?? `the server answered ${response.status}`}`,
|
|
9030
|
+
said?.remedy || "Nothing was sent. Try again."
|
|
9031
|
+
);
|
|
9032
|
+
}
|
|
9033
|
+
function asRecipient(row) {
|
|
9034
|
+
const sources = Array.isArray(row["sources"]) ? row["sources"] : [];
|
|
9035
|
+
return {
|
|
9036
|
+
key: String(row["key"] ?? ""),
|
|
9037
|
+
name: String(row["name"] ?? ""),
|
|
9038
|
+
email: typeof row["email"] === "string" ? row["email"] : null,
|
|
9039
|
+
userId: typeof row["user_id"] === "string" ? row["user_id"] : null,
|
|
9040
|
+
sources: sources.filter((s) => s === "invitee" || s === "attendee"),
|
|
9041
|
+
reachable: row["reachable"] !== false
|
|
9042
|
+
};
|
|
9043
|
+
}
|
|
9044
|
+
async function loadFollowUpDraft(api, meetingId) {
|
|
9045
|
+
const operation = "meet.loadFollowUpDraft";
|
|
9046
|
+
const response = await api.request({
|
|
9047
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9048
|
+
method: "GET",
|
|
9049
|
+
requireAuth: true,
|
|
9050
|
+
operation
|
|
9051
|
+
});
|
|
9052
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9053
|
+
const body = await response.json();
|
|
9054
|
+
const last = body["last_sent"];
|
|
9055
|
+
return {
|
|
9056
|
+
subject: String(body["subject"] ?? ""),
|
|
9057
|
+
body: String(body["body"] ?? ""),
|
|
9058
|
+
recipients: (Array.isArray(body["recipients"]) ? body["recipients"] : []).map(
|
|
9059
|
+
(row) => asRecipient(row)
|
|
9060
|
+
),
|
|
9061
|
+
wrapUpWritten: body["wrap_up_written"] === true,
|
|
9062
|
+
lastSent: last && typeof last["at"] === "string" ? { at: last["at"], count: typeof last["count"] === "number" ? last["count"] : 0 } : null
|
|
9063
|
+
};
|
|
9064
|
+
}
|
|
9065
|
+
async function sendFollowUp(api, meetingId, input) {
|
|
9066
|
+
const operation = "meet.sendFollowUp";
|
|
9067
|
+
const response = await api.request({
|
|
9068
|
+
path: meetingPath(MEET_ROUTES.meetingFollowUp, meetingId),
|
|
9069
|
+
method: "POST",
|
|
9070
|
+
requireAuth: true,
|
|
9071
|
+
operation,
|
|
9072
|
+
body: {
|
|
9073
|
+
subject: input.subject,
|
|
9074
|
+
body: input.body,
|
|
9075
|
+
recipient_keys: [...input.recipientKeys],
|
|
9076
|
+
dry_run: input.dryRun === true
|
|
9077
|
+
}
|
|
9078
|
+
});
|
|
9079
|
+
if (!response.ok) throw await refusalOf(response, operation);
|
|
9080
|
+
const body = await response.json();
|
|
9081
|
+
return {
|
|
9082
|
+
dryRun: body["dry_run"] === true,
|
|
9083
|
+
queued: Array.isArray(body["queued"]) ? body["queued"] : [],
|
|
9084
|
+
skipped: Array.isArray(body["skipped"]) ? body["skipped"].map((row) => ({
|
|
9085
|
+
key: String(row["key"] ?? ""),
|
|
9086
|
+
reason: String(row["reason"] ?? "")
|
|
9087
|
+
})) : []
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
|
|
8613
9091
|
// src/core/management.ts
|
|
8614
9092
|
async function refusal(response, operation) {
|
|
8615
9093
|
let body = null;
|
|
@@ -8661,6 +9139,7 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
|
8661
9139
|
return await response.json();
|
|
8662
9140
|
}
|
|
8663
9141
|
export {
|
|
9142
|
+
ATTENDANCE_CSV_HEADER,
|
|
8664
9143
|
AiPanel,
|
|
8665
9144
|
AttendanceNotice,
|
|
8666
9145
|
Avatar,
|
|
@@ -8764,6 +9243,7 @@ export {
|
|
|
8764
9243
|
TRANSCRIPTION_TOPIC,
|
|
8765
9244
|
WifiLowIcon,
|
|
8766
9245
|
absenceSentence,
|
|
9246
|
+
activeWrapUpSummary,
|
|
8767
9247
|
announceMeeting,
|
|
8768
9248
|
applyRoomEvent,
|
|
8769
9249
|
asCallId,
|
|
@@ -8772,6 +9252,9 @@ export {
|
|
|
8772
9252
|
asParticipantIdentity,
|
|
8773
9253
|
asRoomName,
|
|
8774
9254
|
asUserId,
|
|
9255
|
+
attendanceCsv,
|
|
9256
|
+
attendanceReport,
|
|
9257
|
+
attendanceTotals,
|
|
8775
9258
|
callInviteAction,
|
|
8776
9259
|
callInviteText,
|
|
8777
9260
|
callStateLabel,
|
|
@@ -8780,6 +9263,7 @@ export {
|
|
|
8780
9263
|
canTransition,
|
|
8781
9264
|
captionSegment,
|
|
8782
9265
|
changesPatch,
|
|
9266
|
+
clockPosition,
|
|
8783
9267
|
computeStage,
|
|
8784
9268
|
countLabel,
|
|
8785
9269
|
createCallCenter,
|
|
@@ -8820,11 +9304,14 @@ export {
|
|
|
8820
9304
|
learnName,
|
|
8821
9305
|
learnNames,
|
|
8822
9306
|
listenOnlyNotice,
|
|
9307
|
+
loadFollowUpDraft,
|
|
8823
9308
|
loadGuestMeetingRecord,
|
|
9309
|
+
loadMeetingChat,
|
|
8824
9310
|
loadMeetingRecord,
|
|
8825
9311
|
mapConnectionState,
|
|
8826
9312
|
mapQuality,
|
|
8827
9313
|
meetingInviteAction,
|
|
9314
|
+
meetingLength,
|
|
8828
9315
|
meetingLink,
|
|
8829
9316
|
meetingPath,
|
|
8830
9317
|
meetingSlug,
|
|
@@ -8839,16 +9326,19 @@ export {
|
|
|
8839
9326
|
noteTakerLabel,
|
|
8840
9327
|
noteTakerParticipant,
|
|
8841
9328
|
noteTakerStatus,
|
|
9329
|
+
notesDocument,
|
|
8842
9330
|
orderParticipants,
|
|
8843
9331
|
parseRoomToken,
|
|
8844
9332
|
participantSummary,
|
|
8845
9333
|
playableRecordingFileId,
|
|
8846
9334
|
projectInvitee,
|
|
9335
|
+
projectLibraryRecording,
|
|
8847
9336
|
projectNote,
|
|
8848
9337
|
projectOccurrence,
|
|
8849
9338
|
projectOverride,
|
|
8850
9339
|
projectParticipant,
|
|
8851
9340
|
projectRsvpInvitation,
|
|
9341
|
+
projectSearchHit,
|
|
8852
9342
|
projectSegment,
|
|
8853
9343
|
projectUpcoming,
|
|
8854
9344
|
pushMeetingToGoogleCalendar,
|
|
@@ -8856,11 +9346,17 @@ export {
|
|
|
8856
9346
|
readableTranscript,
|
|
8857
9347
|
recordingLabel,
|
|
8858
9348
|
recordingNotice,
|
|
9349
|
+
recordingOffsetMs,
|
|
8859
9350
|
roleOf,
|
|
8860
9351
|
roomTopic,
|
|
9352
|
+
searchTranscript,
|
|
9353
|
+
sendFollowUp,
|
|
8861
9354
|
setGuestRecordAccess,
|
|
8862
9355
|
settingsPatch,
|
|
8863
9356
|
snapshotParticipant,
|
|
9357
|
+
snippetRuns,
|
|
9358
|
+
summarizeMeetingRecordRestrictions,
|
|
9359
|
+
transcriptDocument,
|
|
8864
9360
|
unavailableFrom,
|
|
8865
9361
|
useAudioLevel,
|
|
8866
9362
|
useCalls,
|
|
@@ -8896,6 +9392,7 @@ export {
|
|
|
8896
9392
|
useStage,
|
|
8897
9393
|
useTrack,
|
|
8898
9394
|
userTopic,
|
|
8899
|
-
wasRefused
|
|
9395
|
+
wasRefused,
|
|
9396
|
+
wrapUpStatus
|
|
8900
9397
|
};
|
|
8901
9398
|
//# sourceMappingURL=react.js.map
|