@ai-matrx/meet 0.7.78 → 0.7.80
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 +27 -0
- package/dist/index.cjs +230 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +230 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +489 -219
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -3
- package/dist/react.d.ts +4 -3
- package/dist/react.js +384 -114
- package/dist/react.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/meet`
|
|
2
2
|
|
|
3
|
+
## 0.7.80
|
|
4
|
+
|
|
5
|
+
- `attendanceCsv` writes through THE one writer, `@ai-matrx/kit/delimited` (AP-2): CRLF records and trailing CRLF unchanged; a name that opens like a formula gets a leading `'`.
|
|
6
|
+
|
|
7
|
+
**Consumer action:** none.
|
|
8
|
+
|
|
9
|
+
## 0.7.79
|
|
10
|
+
|
|
11
|
+
**A meeting's record is read once per tab, and kept live.** `useMeetingRecord` held the bundle
|
|
12
|
+
in its own state and read all four parts (`meet_participants`, `meet_notes`,
|
|
13
|
+
`meet_transcript_segments`, `meet_recordings`) on every mount — so a board tile that slept
|
|
14
|
+
and woke (React `<Activity>` re-runs every effect), a Remove + Undo, and every second view of
|
|
15
|
+
the same meeting read the whole record again. The record now lives in one per-tab cache
|
|
16
|
+
(`core/record-cache.ts`, keyed by reader + meeting): the first view reads, later views and
|
|
17
|
+
remounts render the kept record and read nothing; a read in flight is joined; Retry and a
|
|
18
|
+
reconnect read again, and an older answer never paints over a newer one. A member's record is
|
|
19
|
+
kept current by ONE change-feed channel per meeting (notes, transcript, attendance,
|
|
20
|
+
recordings — applied row by row, no re-read), held while any view is mounted and 60 s after;
|
|
21
|
+
past that the record is marked stale and the next view reads it once. A meeting rename
|
|
22
|
+
re-assembles the kept record instead of re-reading it. New exports: `loadMeetingRecordParts`,
|
|
23
|
+
`assembleMeetingRecord`, `createMeetingRecordCache`, `MEETING_RECORD_GRACE_MS`, and
|
|
24
|
+
`meetingRecordCache()` from `/react`. Guard: `react/meeting-record-reads-once.test.tsx`
|
|
25
|
+
(4/4 red on 0.7.78).
|
|
26
|
+
|
|
27
|
+
Consumer action: none. A host test that renders the record for the same meeting twice and
|
|
28
|
+
expects two reads resets the cache between cases (`meetingRecordCache().reset()`).
|
|
29
|
+
|
|
3
30
|
## 0.7.78
|
|
4
31
|
|
|
5
32
|
**A meeting can run inside a box.** `.mx-meet` was `height: 100dvh`, so a stage mounted in a
|
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(src_exports, {
|
|
|
50
50
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
51
51
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
52
52
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
53
|
+
MEETING_RECORD_GRACE_MS: () => MEETING_RECORD_GRACE_MS,
|
|
53
54
|
MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
|
|
54
55
|
MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
|
|
55
56
|
MEET_EVENTS: () => MEET_EVENTS,
|
|
@@ -84,6 +85,7 @@ __export(src_exports, {
|
|
|
84
85
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
85
86
|
asRoomName: () => asRoomName,
|
|
86
87
|
asUserId: () => asUserId,
|
|
88
|
+
assembleMeetingRecord: () => assembleMeetingRecord,
|
|
87
89
|
assignedRoom: () => assignedRoom,
|
|
88
90
|
attendanceCsv: () => attendanceCsv,
|
|
89
91
|
attendanceReport: () => attendanceReport,
|
|
@@ -113,6 +115,7 @@ __export(src_exports, {
|
|
|
113
115
|
createMeetRepository: () => createMeetRepository,
|
|
114
116
|
createMeetStore: () => createMeetStore,
|
|
115
117
|
createMeetingInviteHandler: () => createMeetingInviteHandler,
|
|
118
|
+
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
116
119
|
createMeetingSession: () => createMeetingSession,
|
|
117
120
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
118
121
|
createRecordingController: () => createRecordingController,
|
|
@@ -147,6 +150,7 @@ __export(src_exports, {
|
|
|
147
150
|
loadMeetingActivities: () => loadMeetingActivities,
|
|
148
151
|
loadMeetingChat: () => loadMeetingChat,
|
|
149
152
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
153
|
+
loadMeetingRecordParts: () => loadMeetingRecordParts,
|
|
150
154
|
mapConnectionState: () => mapConnectionState,
|
|
151
155
|
mapQuality: () => mapQuality,
|
|
152
156
|
meetingInviteAction: () => meetingInviteAction,
|
|
@@ -1033,13 +1037,13 @@ function mergeCaption(list, caption) {
|
|
|
1033
1037
|
return list.map((entry, position) => position === index ? caption : entry);
|
|
1034
1038
|
}
|
|
1035
1039
|
function mergeRecord(list, record) {
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1038
|
-
const existing = list[
|
|
1040
|
+
const byId2 = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
|
|
1041
|
+
if (byId2 !== -1) {
|
|
1042
|
+
const existing = list[byId2];
|
|
1039
1043
|
if (existing !== void 0 && existing.text === record.text && existing.final === record.final) {
|
|
1040
1044
|
return list;
|
|
1041
1045
|
}
|
|
1042
|
-
return list.map((entry, position) => position ===
|
|
1046
|
+
return list.map((entry, position) => position === byId2 ? record : entry);
|
|
1043
1047
|
}
|
|
1044
1048
|
const captionIndex = list.findIndex((entry) => supersedes(record, entry));
|
|
1045
1049
|
if (captionIndex === -1) return [...list, record];
|
|
@@ -4547,6 +4551,7 @@ function fileDownloadPath(fileId) {
|
|
|
4547
4551
|
}
|
|
4548
4552
|
|
|
4549
4553
|
// src/core/after.ts
|
|
4554
|
+
var import_delimited = require("@ai-matrx/kit/delimited");
|
|
4550
4555
|
var import_format = require("@ai-matrx/kit/format");
|
|
4551
4556
|
function kindOf(person) {
|
|
4552
4557
|
if (person.isAgent) return "assistant";
|
|
@@ -4685,11 +4690,6 @@ function attendanceTotals(rows) {
|
|
|
4685
4690
|
averageMs: timed.length === 0 ? null : Math.round(timed.reduce((sum, row) => sum + (row.durationMs ?? 0), 0) / timed.length)
|
|
4686
4691
|
};
|
|
4687
4692
|
}
|
|
4688
|
-
function csvCell(value) {
|
|
4689
|
-
if (value === null) return "";
|
|
4690
|
-
const text3 = String(value);
|
|
4691
|
-
return /[",\n\r]/.test(text3) ? `"${text3.replace(/"/g, '""')}"` : text3;
|
|
4692
|
-
}
|
|
4693
4693
|
var ATTENDANCE_CSV_HEADER = [
|
|
4694
4694
|
"Meeting",
|
|
4695
4695
|
"Meeting started",
|
|
@@ -4701,11 +4701,11 @@ var ATTENDANCE_CSV_HEADER = [
|
|
|
4701
4701
|
"Admission"
|
|
4702
4702
|
];
|
|
4703
4703
|
function attendanceCsv(reports) {
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
[
|
|
4704
|
+
return (0, import_delimited.toDelimited)(
|
|
4705
|
+
[
|
|
4706
|
+
ATTENDANCE_CSV_HEADER,
|
|
4707
|
+
...reports.flatMap(
|
|
4708
|
+
({ meeting, rows }) => rows.map((row) => [
|
|
4709
4709
|
meeting.title,
|
|
4710
4710
|
meeting.startedAt ?? meeting.scheduledFor,
|
|
4711
4711
|
row.name,
|
|
@@ -4714,12 +4714,11 @@ function attendanceCsv(reports) {
|
|
|
4714
4714
|
row.leftAt,
|
|
4715
4715
|
row.durationMs === null ? null : Math.round(row.durationMs / 6e3) / 10,
|
|
4716
4716
|
row.admission
|
|
4717
|
-
]
|
|
4718
|
-
)
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
`;
|
|
4717
|
+
])
|
|
4718
|
+
)
|
|
4719
|
+
],
|
|
4720
|
+
{ lineEnding: "\r\n", trailingNewline: true }
|
|
4721
|
+
);
|
|
4723
4722
|
}
|
|
4724
4723
|
function recordingOffsetMs(at, recording) {
|
|
4725
4724
|
if (recording === null || recording.state !== "available") return null;
|
|
@@ -5869,6 +5868,9 @@ function restrictionFor(part, error) {
|
|
|
5869
5868
|
};
|
|
5870
5869
|
}
|
|
5871
5870
|
async function loadMeetingRecord(repository, meetingId, meeting, reader = { userId: null }) {
|
|
5871
|
+
return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));
|
|
5872
|
+
}
|
|
5873
|
+
async function loadMeetingRecordParts(repository, meetingId, reader = { userId: null }) {
|
|
5872
5874
|
const restrictions = [];
|
|
5873
5875
|
async function attempt(part, run, empty) {
|
|
5874
5876
|
if (reader.userId === null) {
|
|
@@ -5896,6 +5898,10 @@ async function loadMeetingRecord(repository, meetingId, meeting, reader = { user
|
|
|
5896
5898
|
[]
|
|
5897
5899
|
)
|
|
5898
5900
|
]);
|
|
5901
|
+
return { attendees, notes, transcript, recordings, restrictions };
|
|
5902
|
+
}
|
|
5903
|
+
function assembleMeetingRecord(meeting, parts) {
|
|
5904
|
+
const { attendees, notes, transcript, recordings, restrictions } = parts;
|
|
5899
5905
|
return {
|
|
5900
5906
|
meeting,
|
|
5901
5907
|
attendees,
|
|
@@ -6240,4 +6246,208 @@ async function setGuestRecordAccess(args) {
|
|
|
6240
6246
|
};
|
|
6241
6247
|
}
|
|
6242
6248
|
var GUEST_RECORD_NOT_SHARED = NOT_SHARED;
|
|
6249
|
+
|
|
6250
|
+
// src/core/record-cache.ts
|
|
6251
|
+
var MEETING_RECORD_GRACE_MS = 6e4;
|
|
6252
|
+
var INITIAL = { loading: true, record: null, failure: null };
|
|
6253
|
+
function upsert2(rows, row, removedId, order) {
|
|
6254
|
+
const id = row?.id ?? removedId;
|
|
6255
|
+
if (id === void 0) return rows;
|
|
6256
|
+
const rest = rows.filter((existing) => existing.id !== id);
|
|
6257
|
+
return row === null ? rest : [...rest, row].sort(order);
|
|
6258
|
+
}
|
|
6259
|
+
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
6260
|
+
var ascending = (a, b) => a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? -1 : 1;
|
|
6261
|
+
function createMeetingRecordCache(options = {}) {
|
|
6262
|
+
const graceMs = options.graceMs ?? MEETING_RECORD_GRACE_MS;
|
|
6263
|
+
const entries = /* @__PURE__ */ new Map();
|
|
6264
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6265
|
+
const notify = () => {
|
|
6266
|
+
for (const listener of listeners) listener();
|
|
6267
|
+
};
|
|
6268
|
+
const entryOf = (key) => {
|
|
6269
|
+
let entry = entries.get(key);
|
|
6270
|
+
if (!entry) {
|
|
6271
|
+
entry = {
|
|
6272
|
+
state: INITIAL,
|
|
6273
|
+
meeting: null,
|
|
6274
|
+
parts: null,
|
|
6275
|
+
fresh: false,
|
|
6276
|
+
seq: 0,
|
|
6277
|
+
inflight: null,
|
|
6278
|
+
holders: 0,
|
|
6279
|
+
close: null,
|
|
6280
|
+
owner: null,
|
|
6281
|
+
grace: null
|
|
6282
|
+
};
|
|
6283
|
+
entries.set(key, entry);
|
|
6284
|
+
}
|
|
6285
|
+
return entry;
|
|
6286
|
+
};
|
|
6287
|
+
const assemble = (entry) => {
|
|
6288
|
+
if (entry.meeting === null || entry.parts === null) return;
|
|
6289
|
+
entry.state = { loading: false, failure: null, record: assembleMeetingRecord(entry.meeting, entry.parts) };
|
|
6290
|
+
notify();
|
|
6291
|
+
};
|
|
6292
|
+
return {
|
|
6293
|
+
state: (key) => key === null ? INITIAL : entries.get(key)?.state ?? INITIAL,
|
|
6294
|
+
subscribe(listener) {
|
|
6295
|
+
listeners.add(listener);
|
|
6296
|
+
return () => {
|
|
6297
|
+
listeners.delete(listener);
|
|
6298
|
+
};
|
|
6299
|
+
},
|
|
6300
|
+
ensure(key, read, { force = false } = {}) {
|
|
6301
|
+
const entry = entryOf(key);
|
|
6302
|
+
if (!force) {
|
|
6303
|
+
if (entry.inflight !== null) return entry.inflight;
|
|
6304
|
+
if (entry.fresh) return Promise.resolve();
|
|
6305
|
+
}
|
|
6306
|
+
const seq = ++entry.seq;
|
|
6307
|
+
if (entry.state.failure !== null) {
|
|
6308
|
+
entry.state = { ...entry.state, loading: true, failure: null };
|
|
6309
|
+
notify();
|
|
6310
|
+
}
|
|
6311
|
+
const request = read().then(
|
|
6312
|
+
(answer) => {
|
|
6313
|
+
if (seq !== entry.seq) return;
|
|
6314
|
+
entry.fresh = true;
|
|
6315
|
+
if (answer.kind === "parts") {
|
|
6316
|
+
entry.meeting = answer.meeting;
|
|
6317
|
+
entry.parts = answer.parts;
|
|
6318
|
+
assemble(entry);
|
|
6319
|
+
} else {
|
|
6320
|
+
entry.meeting = answer.bundle.meeting;
|
|
6321
|
+
entry.parts = null;
|
|
6322
|
+
entry.state = { loading: false, failure: null, record: answer.bundle };
|
|
6323
|
+
notify();
|
|
6324
|
+
}
|
|
6325
|
+
},
|
|
6326
|
+
(thrown) => {
|
|
6327
|
+
if (seq !== entry.seq) return;
|
|
6328
|
+
entry.fresh = false;
|
|
6329
|
+
entry.state = {
|
|
6330
|
+
loading: false,
|
|
6331
|
+
record: entry.state.record,
|
|
6332
|
+
failure: {
|
|
6333
|
+
message: thrown?.message ?? String(thrown),
|
|
6334
|
+
remedy: thrown?.remedy ?? "The meeting record could not be read. Reload the page; nothing was changed."
|
|
6335
|
+
}
|
|
6336
|
+
};
|
|
6337
|
+
notify();
|
|
6338
|
+
}
|
|
6339
|
+
);
|
|
6340
|
+
const tracked = request.finally(() => {
|
|
6341
|
+
if (entry.inflight === tracked) entry.inflight = null;
|
|
6342
|
+
});
|
|
6343
|
+
entry.inflight = tracked;
|
|
6344
|
+
return tracked;
|
|
6345
|
+
},
|
|
6346
|
+
setMeeting(key, meeting) {
|
|
6347
|
+
const entry = entries.get(key);
|
|
6348
|
+
if (!entry || entry.meeting === meeting) return;
|
|
6349
|
+
if (entry.parts !== null) {
|
|
6350
|
+
entry.meeting = meeting;
|
|
6351
|
+
assemble(entry);
|
|
6352
|
+
} else if (entry.state.record !== null) {
|
|
6353
|
+
entry.meeting = meeting;
|
|
6354
|
+
entry.state = { ...entry.state, record: { ...entry.state.record, meeting } };
|
|
6355
|
+
notify();
|
|
6356
|
+
}
|
|
6357
|
+
},
|
|
6358
|
+
apply(key, change) {
|
|
6359
|
+
const entry = entries.get(key);
|
|
6360
|
+
const parts = entry?.parts;
|
|
6361
|
+
if (!entry || !parts) return;
|
|
6362
|
+
const refused = parts.restrictions.some((r) => r.part === change.part);
|
|
6363
|
+
if (refused) return;
|
|
6364
|
+
switch (change.part) {
|
|
6365
|
+
case "transcript":
|
|
6366
|
+
entry.parts = {
|
|
6367
|
+
...parts,
|
|
6368
|
+
transcript: upsert2(
|
|
6369
|
+
parts.transcript,
|
|
6370
|
+
change.row,
|
|
6371
|
+
change.removedId,
|
|
6372
|
+
(a, b) => ascending(a.startedAt, b.startedAt) || byId(a, b)
|
|
6373
|
+
)
|
|
6374
|
+
};
|
|
6375
|
+
break;
|
|
6376
|
+
case "notes":
|
|
6377
|
+
entry.parts = {
|
|
6378
|
+
...parts,
|
|
6379
|
+
notes: upsert2(
|
|
6380
|
+
parts.notes,
|
|
6381
|
+
change.row,
|
|
6382
|
+
change.removedId,
|
|
6383
|
+
(a, b) => ascending(a.createdAt, b.createdAt) || byId(a, b)
|
|
6384
|
+
)
|
|
6385
|
+
};
|
|
6386
|
+
break;
|
|
6387
|
+
case "attendance":
|
|
6388
|
+
entry.parts = {
|
|
6389
|
+
...parts,
|
|
6390
|
+
attendees: upsert2(
|
|
6391
|
+
parts.attendees,
|
|
6392
|
+
change.row,
|
|
6393
|
+
change.removedId,
|
|
6394
|
+
(a, b) => ascending(a.joinedAt, b.joinedAt) || byId(a, b)
|
|
6395
|
+
)
|
|
6396
|
+
};
|
|
6397
|
+
break;
|
|
6398
|
+
case "recording":
|
|
6399
|
+
entry.parts = {
|
|
6400
|
+
...parts,
|
|
6401
|
+
// Newest first, as the read orders them.
|
|
6402
|
+
recordings: upsert2(
|
|
6403
|
+
parts.recordings,
|
|
6404
|
+
change.row,
|
|
6405
|
+
change.removedId,
|
|
6406
|
+
(a, b) => ascending(b.startedAt, a.startedAt) || byId(a, b)
|
|
6407
|
+
)
|
|
6408
|
+
};
|
|
6409
|
+
break;
|
|
6410
|
+
}
|
|
6411
|
+
assemble(entry);
|
|
6412
|
+
},
|
|
6413
|
+
hold(key, open, owner = null) {
|
|
6414
|
+
const entry = entryOf(key);
|
|
6415
|
+
if (entry.grace !== null) {
|
|
6416
|
+
clearTimeout(entry.grace);
|
|
6417
|
+
entry.grace = null;
|
|
6418
|
+
}
|
|
6419
|
+
if (entry.close !== null && entry.owner !== owner) {
|
|
6420
|
+
entry.close();
|
|
6421
|
+
entry.close = null;
|
|
6422
|
+
}
|
|
6423
|
+
if (entry.close === null) {
|
|
6424
|
+
entry.close = open();
|
|
6425
|
+
entry.owner = owner;
|
|
6426
|
+
}
|
|
6427
|
+
entry.holders += 1;
|
|
6428
|
+
let released = false;
|
|
6429
|
+
return () => {
|
|
6430
|
+
if (released) return;
|
|
6431
|
+
released = true;
|
|
6432
|
+
entry.holders -= 1;
|
|
6433
|
+
if (entry.holders > 0) return;
|
|
6434
|
+
entry.grace = setTimeout(() => {
|
|
6435
|
+
entry.grace = null;
|
|
6436
|
+
if (entry.holders > 0) return;
|
|
6437
|
+
entry.close?.();
|
|
6438
|
+
entry.close = null;
|
|
6439
|
+
entry.fresh = false;
|
|
6440
|
+
}, graceMs);
|
|
6441
|
+
};
|
|
6442
|
+
},
|
|
6443
|
+
reset() {
|
|
6444
|
+
for (const entry of entries.values()) {
|
|
6445
|
+
if (entry.grace !== null) clearTimeout(entry.grace);
|
|
6446
|
+
entry.close?.();
|
|
6447
|
+
}
|
|
6448
|
+
entries.clear();
|
|
6449
|
+
notify();
|
|
6450
|
+
}
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6243
6453
|
//# sourceMappingURL=index.cjs.map
|