@agent-native/core 0.114.9 → 0.114.10
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
- package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
- package/corpus/core/src/client/AssistantChat.tsx +7 -3
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
- package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
- package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
- package/corpus/core/src/integrations/remote-push-store.ts +3 -3
- package/corpus/core/src/observability/traces.ts +38 -1
- package/corpus/core/src/server/action-routes.ts +23 -1
- package/corpus/core/src/server/http-response-telemetry.ts +6 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
- package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
- package/corpus/templates/calendar/actions/create-event.ts +5 -0
- package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
- package/corpus/templates/calendar/app/i18n-data.ts +110 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/app/root.tsx +4 -2
- package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/clips/AGENTS.md +7 -0
- package/corpus/templates/clips/actions/create-recording.ts +51 -10
- package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
- package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
- package/corpus/templates/clips/actions/list-recordings.ts +15 -4
- package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
- package/corpus/templates/clips/actions/search-recordings.ts +28 -6
- package/corpus/templates/clips/actions/view-screen.ts +54 -47
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
- package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
- package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
- package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
- package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
- package/corpus/templates/clips/desktop/src/styles.css +27 -145
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
- package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
- package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
- package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +27 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +19 -4
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +40 -7
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/first-event-timeout.d.ts +25 -0
- package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
- package/dist/agent/engine/first-event-timeout.js +49 -0
- package/dist/agent/engine/first-event-timeout.js.map +1 -0
- package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +2 -2
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +1 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/integrations/computer-supervision-store.js +4 -4
- package/dist/integrations/computer-supervision-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.js +5 -5
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.js +3 -3
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.js +3 -3
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +23 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +23 -1
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/http-response-telemetry.d.ts +2 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +5 -1
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/package.json +2 -2
- package/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/src/agent/engine/anthropic-engine.ts +20 -3
- package/src/agent/engine/builder-engine.ts +56 -9
- package/src/agent/engine/first-event-timeout.ts +64 -0
- package/src/cli/pr-visual-recap-workflow.ts +2 -3
- package/src/client/AssistantChat.tsx +7 -3
- package/src/client/resources/ResourcesPanel.tsx +1 -1
- package/src/integrations/computer-supervision-store.ts +4 -4
- package/src/integrations/pending-tasks-store.ts +1 -1
- package/src/integrations/remote-commands-store.ts +5 -5
- package/src/integrations/remote-devices-store.ts +3 -3
- package/src/integrations/remote-push-store.ts +3 -3
- package/src/observability/traces.ts +38 -1
- package/src/server/action-routes.ts +23 -1
- package/src/server/http-response-telemetry.ts +6 -1
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
IconChevronUp,
|
|
5
5
|
IconCopy,
|
|
6
6
|
IconExternalLink,
|
|
7
|
-
IconGripHorizontal,
|
|
8
7
|
IconLoader2,
|
|
8
|
+
IconMessageCircle,
|
|
9
9
|
IconPlayerPauseFilled,
|
|
10
10
|
IconPlayerPlayFilled,
|
|
11
11
|
IconPlayerStopFilled,
|
|
@@ -50,12 +50,6 @@ export function RecordingPill() {
|
|
|
50
50
|
const [hasTranscriptLines, setHasTranscriptLines] = useState(false);
|
|
51
51
|
const [transcriptCopied, setTranscriptCopied] = useState(false);
|
|
52
52
|
const [preloadedLines, setPreloadedLines] = useState<FinalLine[]>([]);
|
|
53
|
-
const [notes, setNotes] = useState("");
|
|
54
|
-
const [saveError, setSaveError] = useState(false);
|
|
55
|
-
const notesDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
56
|
-
// Latest typed notes, mirrored into a ref so the unmount/blur flush can
|
|
57
|
-
// read the current value without re-subscribing.
|
|
58
|
-
const pendingNotesRef = useRef<string | null>(null);
|
|
59
53
|
const activeMeetingIdRef = useRef<string | null>(null);
|
|
60
54
|
// Detached / "floating" mode — Wispr-style pill that auto-moves to the
|
|
61
55
|
// top-right when the main app loses focus, with a drag handle. Driven by
|
|
@@ -69,19 +63,10 @@ export function RecordingPill() {
|
|
|
69
63
|
const [hovered, setHovered] = useState(false);
|
|
70
64
|
const startedAtRef = useRef<number>(Date.now());
|
|
71
65
|
const tickRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const micLevelRef = useRef(0);
|
|
77
|
-
const sysLevelRef = useRef(0);
|
|
78
|
-
// Track whether we've ever seen a system-audio level event in this
|
|
79
|
-
// session — when present, we render the dual-stream waveform; otherwise
|
|
80
|
-
// we collapse back to a single bar group so dictation-only recordings
|
|
81
|
-
// don't get a dead second row.
|
|
82
|
-
const [hasSystemAudio, setHasSystemAudio] = useState(false);
|
|
83
|
-
const micCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
84
|
-
const sysCanvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
66
|
+
// Mic and system audio share one calm activity meter, matching Granola's
|
|
67
|
+
// single indicator for the combined meeting capture.
|
|
68
|
+
const levelRef = useRef(0);
|
|
69
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
85
70
|
const rafRef = useRef<number | null>(null);
|
|
86
71
|
const stopFallbackRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
87
72
|
const dragStartScreenPointRef = useRef<ScreenPoint | null>(null);
|
|
@@ -118,7 +103,7 @@ export function RecordingPill() {
|
|
|
118
103
|
// (e.g. toggling the popover) while a meeting is already in progress.
|
|
119
104
|
// Only reset session state below when the meeting/mode actually
|
|
120
105
|
// changed — otherwise an in-progress meeting's timer, transcript, and
|
|
121
|
-
//
|
|
106
|
+
// transcript would wipe out on every tray click.
|
|
122
107
|
if (isSameSession) return;
|
|
123
108
|
// Reset timer on new context.
|
|
124
109
|
startedAtRef.current = Date.now();
|
|
@@ -130,21 +115,11 @@ export function RecordingPill() {
|
|
|
130
115
|
// disabled and a stale fallback timer can fire mid-session.
|
|
131
116
|
setStopping(false);
|
|
132
117
|
setError(null);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
setSaveError(false);
|
|
118
|
+
setExpanded(false);
|
|
119
|
+
// Reset transcript state for the new session.
|
|
136
120
|
setPreloadedLines([]);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// clips:meeting-notes-init is the authoritative setter — resetting here
|
|
140
|
-
// would race with that event and could wipe a freshly-set id.
|
|
141
|
-
if (ev.payload?.mode !== "meeting") {
|
|
142
|
-
activeMeetingIdRef.current = null;
|
|
143
|
-
}
|
|
144
|
-
if (notesDebounceRef.current) {
|
|
145
|
-
clearTimeout(notesDebounceRef.current);
|
|
146
|
-
notesDebounceRef.current = null;
|
|
147
|
-
}
|
|
121
|
+
activeMeetingIdRef.current =
|
|
122
|
+
ev.payload?.mode === "meeting" ? (next.meetingId ?? null) : null;
|
|
148
123
|
if (stopFallbackRef.current) {
|
|
149
124
|
clearTimeout(stopFallbackRef.current);
|
|
150
125
|
stopFallbackRef.current = null;
|
|
@@ -166,46 +141,12 @@ export function RecordingPill() {
|
|
|
166
141
|
},
|
|
167
142
|
),
|
|
168
143
|
);
|
|
169
|
-
trackListen(
|
|
170
|
-
listen<{ meetingId: string; initialNotes: string }>(
|
|
171
|
-
"clips:meeting-notes-init",
|
|
172
|
-
(ev) => {
|
|
173
|
-
if (ctxRef.current.meetingId !== ev.payload.meetingId) return;
|
|
174
|
-
activeMeetingIdRef.current = ev.payload.meetingId;
|
|
175
|
-
if (pendingNotesRef.current !== null) {
|
|
176
|
-
// User typed before the async fetch resolved — keep their edits and
|
|
177
|
-
// save them now that we have the meeting id. Don't overwrite with
|
|
178
|
-
// server data.
|
|
179
|
-
emit("clips:save-meeting-notes", {
|
|
180
|
-
meetingId: ev.payload.meetingId,
|
|
181
|
-
notes: pendingNotesRef.current,
|
|
182
|
-
}).catch(() => {});
|
|
183
|
-
} else {
|
|
184
|
-
setNotes(ev.payload.initialNotes ?? "");
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
),
|
|
188
|
-
);
|
|
189
144
|
trackListen(
|
|
190
145
|
listen<{ lines: FinalLine[] }>("clips:transcript-preload", (ev) => {
|
|
191
146
|
const lines = ev.payload?.lines;
|
|
192
147
|
if (lines?.length) setPreloadedLines(lines);
|
|
193
148
|
}),
|
|
194
149
|
);
|
|
195
|
-
// Unified auto-save signal from the popover — fires after either the
|
|
196
|
-
// transcript or the notes are persisted.
|
|
197
|
-
trackListen(
|
|
198
|
-
listen<{ meetingId: string; ts: number }>("clips:meeting-saved", (ev) => {
|
|
199
|
-
if (ev.payload?.meetingId !== activeMeetingIdRef.current) return;
|
|
200
|
-
setSaveError(false);
|
|
201
|
-
pendingNotesRef.current = null;
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
204
|
-
trackListen(
|
|
205
|
-
listen("clips:meeting-save-failed", () => {
|
|
206
|
-
setSaveError(true);
|
|
207
|
-
}),
|
|
208
|
-
);
|
|
209
150
|
trackListen(
|
|
210
151
|
listen<{ error: string }>("pill:error", (ev) => {
|
|
211
152
|
setError(ev.payload?.error ?? "An error occurred.");
|
|
@@ -229,25 +170,15 @@ export function RecordingPill() {
|
|
|
229
170
|
"voice:audio-level",
|
|
230
171
|
(ev) => {
|
|
231
172
|
const lvl = Math.max(0, Math.min(1, ev.payload.level));
|
|
232
|
-
|
|
233
|
-
if (source === "system") {
|
|
234
|
-
sysLevelRef.current = lvl;
|
|
235
|
-
setHasSystemAudio(true);
|
|
236
|
-
} else {
|
|
237
|
-
micLevelRef.current = lvl;
|
|
238
|
-
}
|
|
173
|
+
levelRef.current = lvl;
|
|
239
174
|
},
|
|
240
175
|
),
|
|
241
176
|
);
|
|
242
177
|
// Signal that all listeners are registered. app.tsx listens for this and
|
|
243
|
-
// re-emits
|
|
244
|
-
// fired before React mounted (fresh Tauri window) are not missed.
|
|
178
|
+
// re-emits the pill context and transcript preload for a fresh window.
|
|
245
179
|
emit("clips:pill-ready", {}).catch(() => {});
|
|
246
180
|
return () => {
|
|
247
181
|
stopped = true;
|
|
248
|
-
// Flush any pending note edit before tearing down (e.g. the pill window
|
|
249
|
-
// closing on stop) so the last keystrokes aren't lost.
|
|
250
|
-
flushNotesNow();
|
|
251
182
|
unlistens.forEach((u) => {
|
|
252
183
|
try {
|
|
253
184
|
u();
|
|
@@ -255,10 +186,6 @@ export function RecordingPill() {
|
|
|
255
186
|
// ignore
|
|
256
187
|
}
|
|
257
188
|
});
|
|
258
|
-
if (notesDebounceRef.current) {
|
|
259
|
-
clearTimeout(notesDebounceRef.current);
|
|
260
|
-
notesDebounceRef.current = null;
|
|
261
|
-
}
|
|
262
189
|
if (stopFallbackRef.current) {
|
|
263
190
|
clearTimeout(stopFallbackRef.current);
|
|
264
191
|
stopFallbackRef.current = null;
|
|
@@ -280,140 +207,72 @@ export function RecordingPill() {
|
|
|
280
207
|
};
|
|
281
208
|
}, [ctx.mode, paused]);
|
|
282
209
|
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
// When system-audio hasn't emitted any levels yet (e.g. dictation-only
|
|
286
|
-
// flow), the system canvas is hidden by the JSX below, but the rAF loop
|
|
287
|
-
// still runs over whichever canvas refs are mounted.
|
|
210
|
+
// One combined "dancing bars" meter — a few discrete vertical bars instead
|
|
211
|
+
// of separate mic and system waveforms.
|
|
288
212
|
useEffect(() => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const mount = (
|
|
305
|
-
canvas: HTMLCanvasElement | null,
|
|
306
|
-
levelRef: React.MutableRefObject<number>,
|
|
307
|
-
color: string,
|
|
308
|
-
shadowColor: string,
|
|
309
|
-
gain: number,
|
|
310
|
-
) => {
|
|
311
|
-
if (!canvas) return;
|
|
312
|
-
const dpr = window.devicePixelRatio || 1;
|
|
313
|
-
const W = canvas.clientWidth;
|
|
314
|
-
const H = canvas.clientHeight;
|
|
315
|
-
canvas.width = W * dpr;
|
|
316
|
-
canvas.height = H * dpr;
|
|
317
|
-
const ctx2d = canvas.getContext("2d");
|
|
318
|
-
if (!ctx2d) return;
|
|
319
|
-
ctx2d.scale(dpr, dpr);
|
|
320
|
-
// Derive zero-alpha edge color from the full color to avoid duplicate strings.
|
|
321
|
-
const color0 = color.replace(/[\d.]+\)$/, "0)");
|
|
322
|
-
const grad = ctx2d.createLinearGradient(0, 0, W, 0);
|
|
323
|
-
grad.addColorStop(0, color0);
|
|
324
|
-
grad.addColorStop(0.1, color);
|
|
325
|
-
grad.addColorStop(0.9, color);
|
|
326
|
-
grad.addColorStop(1, color0);
|
|
327
|
-
const centerY = H / 2;
|
|
328
|
-
// Gap takes ~35% of each bar's slot, matching a "dancing bars" density.
|
|
329
|
-
const slot = W / N_BARS;
|
|
330
|
-
const gap = Math.max(1, slot * 0.35);
|
|
331
|
-
const barWidth = Math.max(1, slot - gap);
|
|
332
|
-
setups.push({
|
|
333
|
-
W,
|
|
334
|
-
H,
|
|
335
|
-
centerY,
|
|
336
|
-
ctx2d,
|
|
337
|
-
rng: Array(N_BARS).fill(0.5),
|
|
338
|
-
grad,
|
|
339
|
-
levelRef,
|
|
340
|
-
shadowColor,
|
|
341
|
-
gain,
|
|
342
|
-
barWidth,
|
|
343
|
-
gap,
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
// Mic (top, green — matches the collapsed pill's accent). Sys (bottom, sky
|
|
348
|
-
// blue) with 2× gain — system levels run lower.
|
|
349
|
-
mount(
|
|
350
|
-
micCanvasRef.current,
|
|
351
|
-
micLevelRef,
|
|
352
|
-
"rgba(74, 222, 128, 0.95)",
|
|
353
|
-
"rgba(74, 222, 128, 0.55)",
|
|
354
|
-
1.0,
|
|
355
|
-
);
|
|
356
|
-
mount(
|
|
357
|
-
sysCanvasRef.current,
|
|
358
|
-
sysLevelRef,
|
|
359
|
-
"rgba(125, 211, 252, 0.85)",
|
|
360
|
-
"rgba(125, 211, 252, 0.5)",
|
|
361
|
-
2.0,
|
|
362
|
-
);
|
|
363
|
-
|
|
213
|
+
const canvas = canvasRef.current;
|
|
214
|
+
if (!canvas) return;
|
|
215
|
+
const N_BARS = 3;
|
|
216
|
+
const dpr = window.devicePixelRatio || 1;
|
|
217
|
+
const W = canvas.clientWidth;
|
|
218
|
+
const H = canvas.clientHeight;
|
|
219
|
+
canvas.width = W * dpr;
|
|
220
|
+
canvas.height = H * dpr;
|
|
221
|
+
const ctx2d = canvas.getContext("2d");
|
|
222
|
+
if (!ctx2d) return;
|
|
223
|
+
ctx2d.scale(dpr, dpr);
|
|
224
|
+
const slot = W / N_BARS;
|
|
225
|
+
const gap = Math.max(2, slot * 0.3);
|
|
226
|
+
const barWidth = Math.max(3, slot - gap);
|
|
227
|
+
const centerY = H / 2;
|
|
364
228
|
const startMs = Date.now();
|
|
365
229
|
let lastDrawMs = 0;
|
|
366
|
-
// A bar meter reads the same to the eye well below display refresh rate
|
|
367
|
-
// (60-120Hz); cap the actual draw work to ~20fps while still scheduling
|
|
368
|
-
// via rAF every frame so the loop still pauses when the pill is hidden.
|
|
369
230
|
const FRAME_INTERVAL_MS = 1000 / 20;
|
|
370
231
|
const tick = () => {
|
|
371
232
|
rafRef.current = requestAnimationFrame(tick);
|
|
372
233
|
const nowMs = Date.now();
|
|
373
234
|
if (nowMs - lastDrawMs < FRAME_INTERVAL_MS) return;
|
|
374
235
|
lastDrawMs = nowMs;
|
|
375
|
-
// Modulo prevents float precision loss on long recordings.
|
|
376
236
|
const t = (nowMs - startMs) % 1_000_000;
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const x = i * (s.barWidth + s.gap) + s.gap / 2;
|
|
395
|
-
const y = s.centerY - h / 2;
|
|
396
|
-
const radius = Math.min(s.barWidth / 2, 2);
|
|
397
|
-
s.ctx2d.beginPath();
|
|
398
|
-
if (typeof s.ctx2d.roundRect === "function") {
|
|
399
|
-
s.ctx2d.roundRect(x, y, s.barWidth, h, radius);
|
|
400
|
-
} else {
|
|
401
|
-
s.ctx2d.rect(x, y, s.barWidth, h);
|
|
402
|
-
}
|
|
403
|
-
s.ctx2d.fill();
|
|
237
|
+
const target = Math.min(1, levelRef.current);
|
|
238
|
+
ctx2d.clearRect(0, 0, W, H);
|
|
239
|
+
ctx2d.fillStyle = "rgba(132, 204, 22, 0.98)";
|
|
240
|
+
ctx2d.shadowColor = "rgba(132, 204, 22, 0.48)";
|
|
241
|
+
ctx2d.shadowBlur = 4;
|
|
242
|
+
for (let i = 0; i < N_BARS; i += 1) {
|
|
243
|
+
const phase = t * 0.005 + i * (Math.PI * 0.65);
|
|
244
|
+
const barTarget = 0.2 + Math.sin(phase) * 0.42 * target + target * 0.38;
|
|
245
|
+
const h = Math.max(4, Math.min(1, barTarget) * H * 0.92);
|
|
246
|
+
const x = i * (barWidth + gap) + gap / 2;
|
|
247
|
+
const y = centerY - h / 2;
|
|
248
|
+
const radius = Math.min(barWidth / 2, 3);
|
|
249
|
+
ctx2d.beginPath();
|
|
250
|
+
if (typeof ctx2d.roundRect === "function") {
|
|
251
|
+
ctx2d.roundRect(x, y, barWidth, h, radius);
|
|
252
|
+
} else {
|
|
253
|
+
ctx2d.rect(x, y, barWidth, h);
|
|
404
254
|
}
|
|
405
|
-
|
|
255
|
+
ctx2d.fill();
|
|
406
256
|
}
|
|
257
|
+
ctx2d.shadowBlur = 0;
|
|
407
258
|
};
|
|
408
259
|
tick();
|
|
409
260
|
return () => {
|
|
410
261
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
411
262
|
rafRef.current = null;
|
|
412
263
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
264
|
+
}, [expanded]);
|
|
265
|
+
|
|
266
|
+
// Let the compact chip land first, then reveal the live transcript once per
|
|
267
|
+
// meeting. The delay keeps the indicator from feeling like a sudden panel.
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (ctx.mode !== "meeting" || detached) return;
|
|
270
|
+
const timer = setTimeout(() => {
|
|
271
|
+
setExpanded(true);
|
|
272
|
+
invoke("recording_pill_expand", { expanded: true }).catch(() => {});
|
|
273
|
+
}, 280);
|
|
274
|
+
return () => clearTimeout(timer);
|
|
275
|
+
}, [ctx.mode, detached]);
|
|
417
276
|
|
|
418
277
|
async function toggleExpanded() {
|
|
419
278
|
const next = !expanded;
|
|
@@ -436,9 +295,6 @@ export function RecordingPill() {
|
|
|
436
295
|
async function onStopClick() {
|
|
437
296
|
if (stopping) return;
|
|
438
297
|
setStopping(true);
|
|
439
|
-
// Persist any pending note edit before the stop sequence tears the pill
|
|
440
|
-
// window down.
|
|
441
|
-
flushNotesNow();
|
|
442
298
|
emit("clips:pill-stop", { meetingId: ctx.meetingId ?? null }).catch(
|
|
443
299
|
() => {},
|
|
444
300
|
);
|
|
@@ -447,17 +303,6 @@ export function RecordingPill() {
|
|
|
447
303
|
}, 3_000);
|
|
448
304
|
}
|
|
449
305
|
|
|
450
|
-
// Click on the drag handle (detached mode) un-detaches the pill and
|
|
451
|
-
// re-anchors it bottom-center on the meeting / main app. Re-focuses the
|
|
452
|
-
// main app so the pill mode flips back through the focus listener too.
|
|
453
|
-
async function onHandleClick() {
|
|
454
|
-
try {
|
|
455
|
-
await invoke("recording_pill_set_detached", { detached: false });
|
|
456
|
-
} catch {
|
|
457
|
-
// ignore — best effort
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
306
|
// Stable callback for LiveTranscript to push locked-in lines up. Stable
|
|
462
307
|
// identity matters — it's a dep of an effect inside LiveTranscript.
|
|
463
308
|
const handleTranscriptLines = useCallback((lines: FinalLine[]) => {
|
|
@@ -480,20 +325,6 @@ export function RecordingPill() {
|
|
|
480
325
|
}
|
|
481
326
|
};
|
|
482
327
|
|
|
483
|
-
// Immediately persist any pending (debounced) note edit. Used on blur and on
|
|
484
|
-
// unmount so notes typed in the last ~800ms before stopping aren't dropped.
|
|
485
|
-
const flushNotesNow = () => {
|
|
486
|
-
if (!notesDebounceRef.current) return;
|
|
487
|
-
clearTimeout(notesDebounceRef.current);
|
|
488
|
-
notesDebounceRef.current = null;
|
|
489
|
-
const mid = activeMeetingIdRef.current;
|
|
490
|
-
if (mid && pendingNotesRef.current !== null)
|
|
491
|
-
emit("clips:save-meeting-notes", {
|
|
492
|
-
meetingId: mid,
|
|
493
|
-
notes: pendingNotesRef.current,
|
|
494
|
-
}).catch(() => {});
|
|
495
|
-
};
|
|
496
|
-
|
|
497
328
|
const handlePillMouseDown = (e: React.MouseEvent) => {
|
|
498
329
|
if (e.button !== 0) return;
|
|
499
330
|
const target = e.target as HTMLElement;
|
|
@@ -540,30 +371,7 @@ export function RecordingPill() {
|
|
|
540
371
|
onClick={!expanded && !detached ? handlePillMediaClick : undefined}
|
|
541
372
|
>
|
|
542
373
|
<PillLogo className="pill-logo" />
|
|
543
|
-
{
|
|
544
|
-
<div
|
|
545
|
-
className="pill-wave-dual"
|
|
546
|
-
aria-hidden
|
|
547
|
-
title="Top: you. Bottom: speaker."
|
|
548
|
-
>
|
|
549
|
-
<canvas
|
|
550
|
-
ref={micCanvasRef}
|
|
551
|
-
className="pill-wave-canvas-half"
|
|
552
|
-
aria-label="Microphone level"
|
|
553
|
-
/>
|
|
554
|
-
<canvas
|
|
555
|
-
ref={sysCanvasRef}
|
|
556
|
-
className="pill-wave-canvas-half"
|
|
557
|
-
aria-label="System audio level"
|
|
558
|
-
/>
|
|
559
|
-
</div>
|
|
560
|
-
) : (
|
|
561
|
-
<canvas
|
|
562
|
-
ref={micCanvasRef}
|
|
563
|
-
className="pill-wave-canvas"
|
|
564
|
-
aria-hidden
|
|
565
|
-
/>
|
|
566
|
-
)}
|
|
374
|
+
<canvas ref={canvasRef} className="pill-wave-canvas" aria-hidden />
|
|
567
375
|
</div>
|
|
568
376
|
<div className="pill-controls">
|
|
569
377
|
<span className="pill-timer">
|
|
@@ -614,23 +422,8 @@ export function RecordingPill() {
|
|
|
614
422
|
)}
|
|
615
423
|
</button>
|
|
616
424
|
</div>
|
|
617
|
-
{!expanded && !detached ? (
|
|
618
|
-
<div className="pill-vgrip" aria-hidden>
|
|
619
|
-
<IconGripHorizontal size={14} stroke={2} />
|
|
620
|
-
</div>
|
|
621
|
-
) : null}
|
|
622
425
|
</div>
|
|
623
426
|
|
|
624
|
-
{detached ? (
|
|
625
|
-
<button
|
|
626
|
-
type="button"
|
|
627
|
-
onClick={onHandleClick}
|
|
628
|
-
data-no-drag
|
|
629
|
-
aria-label="Re-attach pill to main window"
|
|
630
|
-
className="pill-drag-handle"
|
|
631
|
-
/>
|
|
632
|
-
) : null}
|
|
633
|
-
|
|
634
427
|
{error ? (
|
|
635
428
|
<div className="pill-error" role="alert">
|
|
636
429
|
{error}
|
|
@@ -650,72 +443,30 @@ export function RecordingPill() {
|
|
|
650
443
|
}
|
|
651
444
|
>
|
|
652
445
|
<div className="pill-divider" />
|
|
653
|
-
|
|
654
|
-
<div className="pill-
|
|
655
|
-
<
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
)}
|
|
672
|
-
</button>
|
|
673
|
-
</div>
|
|
674
|
-
<div className="pill-transcript-area">
|
|
675
|
-
<LiveTranscript
|
|
676
|
-
onLinesChange={handleTranscriptLines}
|
|
677
|
-
initialLines={preloadedLines}
|
|
678
|
-
/>
|
|
679
|
-
</div>
|
|
680
|
-
</div>
|
|
681
|
-
<div className="pill-split-divider" />
|
|
682
|
-
<div className="pill-split-pane">
|
|
683
|
-
<div className="pill-pane-label">Notes</div>
|
|
684
|
-
<div className="pill-notes-area">
|
|
685
|
-
<textarea
|
|
686
|
-
className="pill-notes-textarea"
|
|
687
|
-
placeholder="Jot down notes during the meeting…"
|
|
688
|
-
data-no-drag
|
|
689
|
-
value={notes}
|
|
690
|
-
onChange={(e) => {
|
|
691
|
-
const val = e.target.value;
|
|
692
|
-
setNotes(val);
|
|
693
|
-
pendingNotesRef.current = val;
|
|
694
|
-
if (saveError) setSaveError(false);
|
|
695
|
-
if (notesDebounceRef.current)
|
|
696
|
-
clearTimeout(notesDebounceRef.current);
|
|
697
|
-
notesDebounceRef.current = setTimeout(() => {
|
|
698
|
-
const mid = activeMeetingIdRef.current;
|
|
699
|
-
if (mid)
|
|
700
|
-
emit("clips:save-meeting-notes", {
|
|
701
|
-
meetingId: mid,
|
|
702
|
-
notes: val,
|
|
703
|
-
}).catch(() => {});
|
|
704
|
-
}, 800);
|
|
705
|
-
}}
|
|
706
|
-
onBlur={flushNotesNow}
|
|
707
|
-
/>
|
|
708
|
-
</div>
|
|
709
|
-
</div>
|
|
710
|
-
</div>
|
|
711
|
-
) : (
|
|
712
|
-
<div className="pill-transcript-area">
|
|
713
|
-
<LiveTranscript
|
|
714
|
-
onLinesChange={handleTranscriptLines}
|
|
715
|
-
initialLines={preloadedLines}
|
|
716
|
-
/>
|
|
446
|
+
<div className="pill-transcript-area">
|
|
447
|
+
<div className="pill-pane-label pill-pane-label-row">
|
|
448
|
+
<span>Transcript</span>
|
|
449
|
+
<button
|
|
450
|
+
type="button"
|
|
451
|
+
data-no-drag
|
|
452
|
+
className="pill-copy-btn"
|
|
453
|
+
onClick={handleCopyTranscript}
|
|
454
|
+
disabled={!hasTranscriptLines}
|
|
455
|
+
aria-label="Copy transcript"
|
|
456
|
+
title="Copy transcript"
|
|
457
|
+
>
|
|
458
|
+
{transcriptCopied ? (
|
|
459
|
+
<IconCheck size={12} />
|
|
460
|
+
) : (
|
|
461
|
+
<IconCopy size={12} />
|
|
462
|
+
)}
|
|
463
|
+
</button>
|
|
717
464
|
</div>
|
|
718
|
-
|
|
465
|
+
<LiveTranscript
|
|
466
|
+
onLinesChange={handleTranscriptLines}
|
|
467
|
+
initialLines={preloadedLines}
|
|
468
|
+
/>
|
|
469
|
+
</div>
|
|
719
470
|
{ctx.mode === "meeting" ? (
|
|
720
471
|
<div className="pill-saved-bar">
|
|
721
472
|
<button
|
|
@@ -725,17 +476,33 @@ export function RecordingPill() {
|
|
|
725
476
|
onClick={() => {
|
|
726
477
|
const mid = activeMeetingIdRef.current;
|
|
727
478
|
if (mid)
|
|
728
|
-
emit("clips:open-meeting", {
|
|
729
|
-
|
|
730
|
-
|
|
479
|
+
emit("clips:open-meeting", {
|
|
480
|
+
meetingId: mid,
|
|
481
|
+
openChat: true,
|
|
482
|
+
}).catch(() => {});
|
|
731
483
|
}}
|
|
732
|
-
title="
|
|
484
|
+
title="Chat with transcript"
|
|
733
485
|
>
|
|
734
|
-
<
|
|
735
|
-
|
|
486
|
+
<IconMessageCircle size={12} />
|
|
487
|
+
Chat with transcript
|
|
736
488
|
</button>
|
|
737
489
|
<span className="pill-saved-status">
|
|
738
|
-
|
|
490
|
+
<button
|
|
491
|
+
type="button"
|
|
492
|
+
data-no-drag
|
|
493
|
+
className="pill-open-web-btn"
|
|
494
|
+
onClick={() => {
|
|
495
|
+
const mid = activeMeetingIdRef.current;
|
|
496
|
+
if (mid)
|
|
497
|
+
emit("clips:open-meeting", { meetingId: mid }).catch(
|
|
498
|
+
() => {},
|
|
499
|
+
);
|
|
500
|
+
}}
|
|
501
|
+
title="Open this meeting in the browser"
|
|
502
|
+
>
|
|
503
|
+
<IconExternalLink size={12} />
|
|
504
|
+
Open in browser
|
|
505
|
+
</button>
|
|
739
506
|
</span>
|
|
740
507
|
</div>
|
|
741
508
|
) : null}
|