@agent-native/core 0.130.1 → 0.130.2
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 +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/server/email-template.ts +43 -13
- package/corpus/core/src/sharing/actions/share-resource.ts +26 -4
- package/corpus/core/src/sharing/index.ts +1 -0
- package/corpus/core/src/sharing/registry.ts +23 -0
- package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +3 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/meetings/day-header.tsx +1 -1
- package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +1 -22
- package/corpus/templates/clips/app/components/workspace/branding-editor.tsx +26 -1
- package/corpus/templates/clips/app/components/workspace/organization-identity-card.tsx +107 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -4
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -10
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +23 -51
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +1 -8
- package/corpus/templates/clips/changelog/2026-07-29-clearing-all-recording-edits-no-longer-leaves-the-editor-unr.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-clip-share-emails-now-open-with-who-shared-the-clip-add-a-su.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-finished-meeting-pills-now-have-a-clear-close-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-meeting-cards-no-longer-flicker-or-disappear-when-hovered-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-organization-name-logo-brand-color-and-default-visibility-no.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-transcript-lines-on-the-meeting-page-no-longer-jump-to-the-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-when-a-meeting-wraps-up-the-recording-pill-now-shows-a-green.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +6 -3
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +112 -32
- package/corpus/templates/clips/desktop/src/styles.css +42 -2
- package/corpus/templates/clips/server/db/index.ts +6 -0
- package/corpus/templates/clips/server/lib/share-email-hero.ts +33 -0
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +0 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/email-template.d.ts +4 -0
- package/dist/server/email-template.d.ts.map +1 -1
- package/dist/server/email-template.js +25 -8
- package/dist/server/email-template.js.map +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +23 -4
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/index.d.ts +1 -1
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js.map +1 -1
- package/dist/sharing/registry.d.ts +23 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/package.json +1 -1
- package/src/server/email-template.ts +43 -13
- package/src/sharing/actions/share-resource.ts +26 -4
- package/src/sharing/index.ts +1 -0
- package/src/sharing/registry.ts +23 -0
- package/corpus/templates/clips/app/routes/_app.settings.organization.tsx +0 -232
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
appApiPath,
|
|
4
4
|
} from "@agent-native/core/client/api-path";
|
|
5
5
|
import { ChangelogSettingsCard } from "@agent-native/core/client/changelog";
|
|
6
|
-
import {
|
|
6
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
7
7
|
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
8
|
-
import {
|
|
8
|
+
import { useOrg, useSwitchOrg } from "@agent-native/core/client/org";
|
|
9
9
|
import {
|
|
10
10
|
AccountSettingsCard,
|
|
11
11
|
useBuilderConnectFlow,
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
IconLoader2,
|
|
31
31
|
IconServer,
|
|
32
32
|
IconTrash,
|
|
33
|
-
IconUser,
|
|
34
33
|
IconUsersGroup,
|
|
35
34
|
} from "@tabler/icons-react";
|
|
36
35
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -71,6 +70,7 @@ import {
|
|
|
71
70
|
SelectValue,
|
|
72
71
|
} from "@/components/ui/select";
|
|
73
72
|
import { Switch } from "@/components/ui/switch";
|
|
73
|
+
import { OrganizationIdentityCard } from "@/components/workspace/organization-identity-card";
|
|
74
74
|
import { useVideoStorageStatus } from "@/hooks/use-video-storage-status";
|
|
75
75
|
import enMessages from "@/i18n/en-US";
|
|
76
76
|
import { cn } from "@/lib/utils";
|
|
@@ -166,7 +166,6 @@ const AI_PROVIDER_FIELDS = [
|
|
|
166
166
|
interface ClipsUserSettings {
|
|
167
167
|
defaultPlaybackSpeed?: string;
|
|
168
168
|
emailNotifications?: boolean;
|
|
169
|
-
displayName?: string;
|
|
170
169
|
transcriptCleanupEnabled?: boolean;
|
|
171
170
|
includeFullVideoInAi?: boolean;
|
|
172
171
|
}
|
|
@@ -393,12 +392,29 @@ async function saveRegisteredSecret(key: string, value: string): Promise<void> {
|
|
|
393
392
|
}
|
|
394
393
|
|
|
395
394
|
export default function SettingsIndexRoute() {
|
|
396
|
-
const { session } = useSession();
|
|
397
395
|
const t = useT();
|
|
398
396
|
const agentSettingsTabs = useAgentSettingsTabs();
|
|
397
|
+
// Organization identity (name, logo, brand color) belongs with membership,
|
|
398
|
+
// so it rides on the framework's Organization tab rather than a second one.
|
|
399
|
+
const settingsTabs = useMemo(
|
|
400
|
+
() =>
|
|
401
|
+
agentSettingsTabs.map((tab) =>
|
|
402
|
+
tab.id === "organization"
|
|
403
|
+
? {
|
|
404
|
+
...tab,
|
|
405
|
+
content: (
|
|
406
|
+
<div className="mx-auto w-full max-w-2xl space-y-6">
|
|
407
|
+
<OrganizationIdentityCard />
|
|
408
|
+
{tab.content}
|
|
409
|
+
</div>
|
|
410
|
+
),
|
|
411
|
+
}
|
|
412
|
+
: tab,
|
|
413
|
+
),
|
|
414
|
+
[agentSettingsTabs],
|
|
415
|
+
);
|
|
399
416
|
const { data: org } = useOrg();
|
|
400
417
|
const switchOrg = useSwitchOrg();
|
|
401
|
-
const email = session?.email ?? "";
|
|
402
418
|
const storageStatus = useVideoStorageStatus();
|
|
403
419
|
const builderStatus = useBuilderStatus();
|
|
404
420
|
const builderConnect = useBuilderConnectFlow({
|
|
@@ -430,7 +446,6 @@ export default function SettingsIndexRoute() {
|
|
|
430
446
|
useState<SlackInstallation | null>(null);
|
|
431
447
|
const [defaultSpeed, setDefaultSpeed] = useState("1.2");
|
|
432
448
|
const [emailNotifications, setEmailNotifications] = useState(true);
|
|
433
|
-
const [displayName, setDisplayName] = useState("");
|
|
434
449
|
const [transcriptCleanupEnabled, setTranscriptCleanupEnabled] =
|
|
435
450
|
useState(true);
|
|
436
451
|
const [s3Values, setS3Values] = useState<Record<string, string>>({});
|
|
@@ -496,7 +511,6 @@ export default function SettingsIndexRoute() {
|
|
|
496
511
|
if (cancelled) return;
|
|
497
512
|
setDefaultSpeed(v.defaultPlaybackSpeed ?? "1.2");
|
|
498
513
|
setEmailNotifications(v.emailNotifications ?? true);
|
|
499
|
-
setDisplayName(v.displayName ?? "");
|
|
500
514
|
setTranscriptCleanupEnabled(v.transcriptCleanupEnabled !== false);
|
|
501
515
|
setLoading(false);
|
|
502
516
|
});
|
|
@@ -531,7 +545,6 @@ export default function SettingsIndexRoute() {
|
|
|
531
545
|
await saveSettings({
|
|
532
546
|
defaultPlaybackSpeed: defaultSpeed,
|
|
533
547
|
emailNotifications,
|
|
534
|
-
displayName: displayName.trim() || undefined,
|
|
535
548
|
transcriptCleanupEnabled,
|
|
536
549
|
});
|
|
537
550
|
toast.success(t("settings.saved"));
|
|
@@ -801,12 +814,6 @@ export default function SettingsIndexRoute() {
|
|
|
801
814
|
"ai provider api key anthropic openai gemini groq openrouter builder",
|
|
802
815
|
hash: "ai-providers",
|
|
803
816
|
},
|
|
804
|
-
{
|
|
805
|
-
id: "clips-profile",
|
|
806
|
-
label: t("settings.profile"),
|
|
807
|
-
keywords: "profile email display name",
|
|
808
|
-
hash: "profile",
|
|
809
|
-
},
|
|
810
817
|
{
|
|
811
818
|
id: "clips-playback",
|
|
812
819
|
label: t("settings.playback"),
|
|
@@ -839,7 +846,7 @@ export default function SettingsIndexRoute() {
|
|
|
839
846
|
<SettingsTabsPage
|
|
840
847
|
account={<AccountSettingsCard />}
|
|
841
848
|
whatsNewLabel={t("settings.whatsNew")}
|
|
842
|
-
extraTabs={
|
|
849
|
+
extraTabs={settingsTabs}
|
|
843
850
|
generalSearchEntries={generalSearchEntries}
|
|
844
851
|
general={
|
|
845
852
|
<div className="mx-auto w-full max-w-4xl space-y-6">
|
|
@@ -1477,33 +1484,6 @@ export default function SettingsIndexRoute() {
|
|
|
1477
1484
|
</CardContent>
|
|
1478
1485
|
</Card>
|
|
1479
1486
|
|
|
1480
|
-
<Card id="profile" className="scroll-mt-16">
|
|
1481
|
-
<CardHeader>
|
|
1482
|
-
<CardTitle className="text-base flex items-center gap-2">
|
|
1483
|
-
<IconUser className="size-4 text-primary" />
|
|
1484
|
-
{t("settings.profile")}
|
|
1485
|
-
</CardTitle>
|
|
1486
|
-
</CardHeader>
|
|
1487
|
-
<CardContent className="space-y-4">
|
|
1488
|
-
<div className="space-y-1.5">
|
|
1489
|
-
<Label htmlFor="email">{t("settings.email")}</Label>
|
|
1490
|
-
<Input id="email" value={email} readOnly disabled />
|
|
1491
|
-
</div>
|
|
1492
|
-
<div className="space-y-1.5">
|
|
1493
|
-
<Label htmlFor="display-name">
|
|
1494
|
-
{t("settings.displayName")}
|
|
1495
|
-
</Label>
|
|
1496
|
-
<Input
|
|
1497
|
-
id="display-name"
|
|
1498
|
-
value={displayName}
|
|
1499
|
-
onChange={(e) => setDisplayName(e.target.value)}
|
|
1500
|
-
placeholder={t("settings.displayNamePlaceholder")}
|
|
1501
|
-
disabled={loading}
|
|
1502
|
-
/>
|
|
1503
|
-
</div>
|
|
1504
|
-
</CardContent>
|
|
1505
|
-
</Card>
|
|
1506
|
-
|
|
1507
1487
|
<Card id="playback" className="scroll-mt-16">
|
|
1508
1488
|
<CardHeader>
|
|
1509
1489
|
<CardTitle className="text-base">
|
|
@@ -1605,14 +1585,6 @@ export default function SettingsIndexRoute() {
|
|
|
1605
1585
|
</div>
|
|
1606
1586
|
</div>
|
|
1607
1587
|
}
|
|
1608
|
-
team={
|
|
1609
|
-
<div className="mx-auto w-full max-w-3xl">
|
|
1610
|
-
<TeamPage
|
|
1611
|
-
showTitle={false}
|
|
1612
|
-
createOrgDescription={t("organizationSettings.description")}
|
|
1613
|
-
/>
|
|
1614
|
-
</div>
|
|
1615
|
-
}
|
|
1616
1588
|
whatsNew={
|
|
1617
1589
|
<div className="mx-auto w-full max-w-3xl">
|
|
1618
1590
|
<ChangelogSettingsCard
|
|
@@ -427,8 +427,6 @@ export default function ShareMeetingRoute() {
|
|
|
427
427
|
<TranscriptBubbles
|
|
428
428
|
segments={transcript.segments}
|
|
429
429
|
isLive={false}
|
|
430
|
-
recordingId={null}
|
|
431
|
-
onSeek={() => {}}
|
|
432
430
|
/>
|
|
433
431
|
</div>
|
|
434
432
|
) : transcript.fullText ? (
|
|
@@ -437,12 +435,7 @@ export default function ShareMeetingRoute() {
|
|
|
437
435
|
</div>
|
|
438
436
|
) : (
|
|
439
437
|
<div className="flex min-h-40 flex-col overflow-hidden rounded-lg border border-border">
|
|
440
|
-
<TranscriptBubbles
|
|
441
|
-
segments={[]}
|
|
442
|
-
isLive={false}
|
|
443
|
-
recordingId={null}
|
|
444
|
-
onSeek={() => {}}
|
|
445
|
-
/>
|
|
438
|
+
<TranscriptBubbles segments={[]} isLive={false} />
|
|
446
439
|
</div>
|
|
447
440
|
)}
|
|
448
441
|
</section>
|
|
@@ -207,14 +207,17 @@ export function useMeetingTranscription({
|
|
|
207
207
|
if (reason !== "app-quit") await finalizePromise;
|
|
208
208
|
}
|
|
209
209
|
// Keep completed notes in Clips instead of interrupting the user by
|
|
210
|
-
// opening a browser tab.
|
|
211
|
-
//
|
|
210
|
+
// opening a browser tab. On a normal stop the pill stays up and
|
|
211
|
+
// switches to its finished banner off `meetings:transcription-stopped`;
|
|
212
|
+
// it hides itself once the user opens the meeting or dismisses it.
|
|
212
213
|
// Guard the shared Rust-side state writes and sessionRef null-out by
|
|
213
214
|
// identity. App quit and other callers can still race a stop against a
|
|
214
215
|
// new start that slips in between awaits, and stale teardown must not
|
|
215
216
|
// clobber the session that has since taken over.
|
|
216
217
|
if (sessionRef.current === session) {
|
|
217
|
-
|
|
218
|
+
if (reason === "app-quit" || reason === "replaced") {
|
|
219
|
+
await invoke("recording_pill_hide").catch(() => {});
|
|
220
|
+
}
|
|
218
221
|
await invoke("set_recording_state", { active: false }).catch(
|
|
219
222
|
() => {},
|
|
220
223
|
);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
IconPlayerPauseFilled,
|
|
10
10
|
IconPlayerPlayFilled,
|
|
11
11
|
IconPlayerStopFilled,
|
|
12
|
+
IconX,
|
|
12
13
|
} from "@tabler/icons-react";
|
|
13
14
|
import { invoke } from "@tauri-apps/api/core";
|
|
14
15
|
import { emit, listen } from "@tauri-apps/api/event";
|
|
@@ -46,6 +47,10 @@ export function RecordingPill() {
|
|
|
46
47
|
const [ctx, setCtx] = useState<PillContext>({ mode: "clip" });
|
|
47
48
|
const ctxRef = useRef<PillContext>({ mode: "clip" });
|
|
48
49
|
const [stopping, setStopping] = useState(false);
|
|
50
|
+
const [finishedMeetingId, setFinishedMeetingId] = useState<string | null>(
|
|
51
|
+
null,
|
|
52
|
+
);
|
|
53
|
+
const finished = finishedMeetingId !== null;
|
|
49
54
|
const [error, setError] = useState<string | null>(null);
|
|
50
55
|
const transcriptLinesRef = useRef<FinalLine[]>([]);
|
|
51
56
|
const [hasTranscriptLines, setHasTranscriptLines] = useState(false);
|
|
@@ -113,6 +118,7 @@ export function RecordingPill() {
|
|
|
113
118
|
// new recording session begins, otherwise the Stop button stays
|
|
114
119
|
// disabled and a stale fallback timer can fire mid-session.
|
|
115
120
|
setStopping(false);
|
|
121
|
+
setFinishedMeetingId(null);
|
|
116
122
|
setError(null);
|
|
117
123
|
setExpanded(false);
|
|
118
124
|
// Reset transcript state for the new session.
|
|
@@ -146,6 +152,21 @@ export function RecordingPill() {
|
|
|
146
152
|
if (lines?.length) setPreloadedLines(lines);
|
|
147
153
|
}),
|
|
148
154
|
);
|
|
155
|
+
trackListen(
|
|
156
|
+
listen<{ meetingId?: string | null; reason?: string }>(
|
|
157
|
+
"meetings:transcription-stopped",
|
|
158
|
+
(ev) => {
|
|
159
|
+
const reason = ev.payload?.reason;
|
|
160
|
+
// "replaced" hands straight over to the next session and "app-quit"
|
|
161
|
+
// is tearing the window down — neither has a user left to read this.
|
|
162
|
+
if (reason === "replaced" || reason === "app-quit") return;
|
|
163
|
+
if (ctxRef.current.mode !== "meeting") return;
|
|
164
|
+
const meetingId = ev.payload?.meetingId ?? activeMeetingIdRef.current;
|
|
165
|
+
if (!meetingId) return;
|
|
166
|
+
showFinished(meetingId);
|
|
167
|
+
},
|
|
168
|
+
),
|
|
169
|
+
);
|
|
149
170
|
trackListen(
|
|
150
171
|
listen<{ error: string }>("pill:error", (ev) => {
|
|
151
172
|
setError(ev.payload?.error ?? "An error occurred.");
|
|
@@ -187,7 +208,7 @@ export function RecordingPill() {
|
|
|
187
208
|
useEffect(() => {
|
|
188
209
|
// Clip recordings already broadcast their pause-aware elapsed time every
|
|
189
210
|
// 500ms. Keep the local wall clock only for meeting mode.
|
|
190
|
-
if (paused || ctx.mode === "clip") return;
|
|
211
|
+
if (paused || finished || ctx.mode === "clip") return;
|
|
191
212
|
tickRef.current = setInterval(() => {
|
|
192
213
|
setElapsed(Math.floor((Date.now() - startedAtRef.current) / 1000));
|
|
193
214
|
}, 500);
|
|
@@ -195,7 +216,7 @@ export function RecordingPill() {
|
|
|
195
216
|
if (tickRef.current) clearInterval(tickRef.current);
|
|
196
217
|
tickRef.current = null;
|
|
197
218
|
};
|
|
198
|
-
}, [ctx.mode, paused]);
|
|
219
|
+
}, [ctx.mode, finished, paused]);
|
|
199
220
|
|
|
200
221
|
async function toggleExpanded() {
|
|
201
222
|
const next = !expanded;
|
|
@@ -215,12 +236,32 @@ export function RecordingPill() {
|
|
|
215
236
|
);
|
|
216
237
|
}
|
|
217
238
|
|
|
239
|
+
function showFinished(meetingId: string) {
|
|
240
|
+
if (stopFallbackRef.current) {
|
|
241
|
+
clearTimeout(stopFallbackRef.current);
|
|
242
|
+
stopFallbackRef.current = null;
|
|
243
|
+
}
|
|
244
|
+
setStopping(false);
|
|
245
|
+
setFinishedMeetingId(meetingId);
|
|
246
|
+
setExpanded(true);
|
|
247
|
+
invoke("recording_pill_expand", { expanded: true }).catch(() => {});
|
|
248
|
+
}
|
|
249
|
+
|
|
218
250
|
async function onStopClick() {
|
|
219
251
|
if (stopping) return;
|
|
220
|
-
|
|
252
|
+
const meetingId = ctx.meetingId ?? activeMeetingIdRef.current;
|
|
221
253
|
emit("clips:pill-stop", { meetingId: ctx.meetingId ?? null }).catch(
|
|
222
254
|
() => {},
|
|
223
255
|
);
|
|
256
|
+
// Meetings keep the pill up and switch it to the finished banner right
|
|
257
|
+
// away. Teardown (final flush, finalize) runs for seconds afterwards, so
|
|
258
|
+
// waiting on `meetings:transcription-stopped` would leave the pill looking
|
|
259
|
+
// stuck; the banner is what the user acts on, not the save.
|
|
260
|
+
if (ctxRef.current.mode === "meeting" && meetingId) {
|
|
261
|
+
showFinished(meetingId);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
setStopping(true);
|
|
224
265
|
stopFallbackRef.current = setTimeout(() => {
|
|
225
266
|
invoke("recording_pill_hide").catch(() => {});
|
|
226
267
|
}, 3_000);
|
|
@@ -288,6 +329,15 @@ export function RecordingPill() {
|
|
|
288
329
|
});
|
|
289
330
|
};
|
|
290
331
|
|
|
332
|
+
const closeFinished = (openMeeting: boolean) => {
|
|
333
|
+
if (openMeeting && finishedMeetingId) {
|
|
334
|
+
emit("clips:open-meeting", { meetingId: finishedMeetingId }).catch(
|
|
335
|
+
() => {},
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
invoke("recording_pill_hide").catch(() => {});
|
|
339
|
+
};
|
|
340
|
+
|
|
291
341
|
const mm = String(Math.floor(elapsed / 60)).padStart(2, "0");
|
|
292
342
|
const ss = String(elapsed % 60).padStart(2, "0");
|
|
293
343
|
const stopLabel =
|
|
@@ -323,7 +373,7 @@ export function RecordingPill() {
|
|
|
323
373
|
<span className="pill-timer">
|
|
324
374
|
{mm}:{ss}
|
|
325
375
|
</span>
|
|
326
|
-
{expanded ? (
|
|
376
|
+
{expanded && !finished ? (
|
|
327
377
|
<button
|
|
328
378
|
type="button"
|
|
329
379
|
onClick={onPauseClick}
|
|
@@ -339,34 +389,49 @@ export function RecordingPill() {
|
|
|
339
389
|
)}
|
|
340
390
|
</button>
|
|
341
391
|
) : null}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
392
|
+
{!finished ? (
|
|
393
|
+
<button
|
|
394
|
+
type="button"
|
|
395
|
+
onClick={onStopClick}
|
|
396
|
+
disabled={stopping}
|
|
397
|
+
data-no-drag
|
|
398
|
+
className="pill-stop-btn"
|
|
399
|
+
aria-label={stopping ? "Stopping" : stopLabel}
|
|
400
|
+
title={stopping ? "Stopping..." : stopLabel}
|
|
401
|
+
>
|
|
402
|
+
{stopping ? (
|
|
403
|
+
<IconLoader2 className="pill-spinner" size={14} />
|
|
404
|
+
) : (
|
|
405
|
+
<IconPlayerStopFilled size={14} />
|
|
406
|
+
)}
|
|
407
|
+
</button>
|
|
408
|
+
) : null}
|
|
409
|
+
{finished ? (
|
|
410
|
+
<button
|
|
411
|
+
type="button"
|
|
412
|
+
onClick={() => closeFinished(false)}
|
|
413
|
+
data-no-drag
|
|
414
|
+
className="pill-close-btn"
|
|
415
|
+
aria-label="Dismiss"
|
|
416
|
+
title="Dismiss"
|
|
417
|
+
>
|
|
418
|
+
<IconX size={16} />
|
|
419
|
+
</button>
|
|
420
|
+
) : (
|
|
421
|
+
<button
|
|
422
|
+
type="button"
|
|
423
|
+
onClick={toggleExpanded}
|
|
424
|
+
data-no-drag
|
|
425
|
+
className="pill-expand-btn"
|
|
426
|
+
aria-label={expanded ? "Collapse" : "Expand"}
|
|
427
|
+
>
|
|
428
|
+
{expanded ? (
|
|
429
|
+
<IconChevronUp size={16} />
|
|
430
|
+
) : (
|
|
431
|
+
<IconChevronDown size={16} />
|
|
432
|
+
)}
|
|
433
|
+
</button>
|
|
434
|
+
)}
|
|
370
435
|
</div>
|
|
371
436
|
</div>
|
|
372
437
|
|
|
@@ -389,6 +454,21 @@ export function RecordingPill() {
|
|
|
389
454
|
}
|
|
390
455
|
>
|
|
391
456
|
<div className="pill-divider" />
|
|
457
|
+
{finished ? (
|
|
458
|
+
<div className="pill-finished-banner" role="status">
|
|
459
|
+
<span className="pill-finished-text">
|
|
460
|
+
Meeting finished — notes are ready
|
|
461
|
+
</span>
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
data-no-drag
|
|
465
|
+
className="pill-finished-open"
|
|
466
|
+
onClick={() => closeFinished(true)}
|
|
467
|
+
>
|
|
468
|
+
Open meeting
|
|
469
|
+
</button>
|
|
470
|
+
</div>
|
|
471
|
+
) : null}
|
|
392
472
|
<div className="pill-transcript-area">
|
|
393
473
|
<div className="pill-pane-label pill-pane-label-row">
|
|
394
474
|
<span>Transcript</span>
|
|
@@ -5652,7 +5652,8 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5652
5652
|
opacity: 0.6;
|
|
5653
5653
|
}
|
|
5654
5654
|
|
|
5655
|
-
.pill-expand-btn
|
|
5655
|
+
.pill-expand-btn,
|
|
5656
|
+
.pill-close-btn {
|
|
5656
5657
|
margin-left: 4px;
|
|
5657
5658
|
display: inline-flex;
|
|
5658
5659
|
height: 28px;
|
|
@@ -5667,7 +5668,8 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5667
5668
|
transition: background 120ms;
|
|
5668
5669
|
}
|
|
5669
5670
|
|
|
5670
|
-
.pill-expand-btn:hover
|
|
5671
|
+
.pill-expand-btn:hover,
|
|
5672
|
+
.pill-close-btn:hover {
|
|
5671
5673
|
background: rgba(255, 255, 255, 0.1);
|
|
5672
5674
|
}
|
|
5673
5675
|
|
|
@@ -5862,6 +5864,44 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5862
5864
|
background: rgba(255, 255, 255, 0.1);
|
|
5863
5865
|
}
|
|
5864
5866
|
|
|
5867
|
+
.pill-finished-banner {
|
|
5868
|
+
display: flex;
|
|
5869
|
+
align-items: center;
|
|
5870
|
+
gap: 8px;
|
|
5871
|
+
flex-shrink: 0;
|
|
5872
|
+
margin: 8px 12px 2px;
|
|
5873
|
+
padding: 8px 10px;
|
|
5874
|
+
border: 1px solid rgba(34, 197, 94, 0.45);
|
|
5875
|
+
border-radius: 8px;
|
|
5876
|
+
background: rgba(34, 197, 94, 0.16);
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
.pill-finished-text {
|
|
5880
|
+
min-width: 0;
|
|
5881
|
+
flex: 1;
|
|
5882
|
+
font-size: 12px;
|
|
5883
|
+
font-weight: 600;
|
|
5884
|
+
line-height: 1.35;
|
|
5885
|
+
color: #86efac;
|
|
5886
|
+
}
|
|
5887
|
+
|
|
5888
|
+
.pill-finished-open {
|
|
5889
|
+
flex-shrink: 0;
|
|
5890
|
+
padding: 5px 9px;
|
|
5891
|
+
border: none;
|
|
5892
|
+
border-radius: 6px;
|
|
5893
|
+
background: #22c55e;
|
|
5894
|
+
color: #052e16;
|
|
5895
|
+
font-size: 11px;
|
|
5896
|
+
font-weight: 700;
|
|
5897
|
+
cursor: pointer;
|
|
5898
|
+
transition: background 120ms;
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
.pill-finished-open:hover {
|
|
5902
|
+
background: #4ade80;
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5865
5905
|
.pill-transcript-area {
|
|
5866
5906
|
min-height: 0;
|
|
5867
5907
|
flex: 1;
|
|
@@ -5,6 +5,7 @@ import { eq } from "drizzle-orm";
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
absoluteUrl,
|
|
8
|
+
recordingShareEmailExtras,
|
|
8
9
|
recordingShareHeroHtml,
|
|
9
10
|
} from "../lib/share-email-hero.js";
|
|
10
11
|
import * as schema from "./schema.js";
|
|
@@ -58,6 +59,11 @@ registerShareableResource({
|
|
|
58
59
|
replyTo: ctx.sender.email,
|
|
59
60
|
}),
|
|
60
61
|
getHeroHtml: (recording, ctx) => recordingShareHeroHtml(recording, ctx),
|
|
62
|
+
getShareEmailExtras: (_recording, ctx) =>
|
|
63
|
+
recordingShareEmailExtras({
|
|
64
|
+
href: ctx.href,
|
|
65
|
+
senderEmail: ctx.sender.email,
|
|
66
|
+
}),
|
|
61
67
|
getDb,
|
|
62
68
|
ownerAccessIgnoresOrg: true,
|
|
63
69
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
emailLink,
|
|
2
3
|
getAppProductionUrl,
|
|
3
4
|
withConfiguredAppBasePath,
|
|
4
5
|
} from "@agent-native/core/server";
|
|
6
|
+
import type { ShareEmailExtras } from "@agent-native/core/sharing";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Origin plus the configured mount path. Deployments served under
|
|
@@ -52,6 +54,37 @@ function isHttpUrl(value: string): boolean {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Clips-specific body for the share-notification email: the AI companion CTA,
|
|
59
|
+
* the copyable agent link, and the closing note that routes replies to the
|
|
60
|
+
* person who shared the clip.
|
|
61
|
+
*/
|
|
62
|
+
export function recordingShareEmailExtras(ctx: {
|
|
63
|
+
href: string;
|
|
64
|
+
senderEmail: string;
|
|
65
|
+
}): ShareEmailExtras {
|
|
66
|
+
const summarizeUrl = new URL(ctx.href);
|
|
67
|
+
summarizeUrl.searchParams.set("panel", "agent");
|
|
68
|
+
return {
|
|
69
|
+
// The heading already says who shared what, so the clip thumbnail follows
|
|
70
|
+
// it directly.
|
|
71
|
+
paragraphs: [],
|
|
72
|
+
secondaryCta: { label: "Summarize with AI", url: summarizeUrl.toString() },
|
|
73
|
+
linkBlock: {
|
|
74
|
+
intro: "Copy and paste this link for your own AI agent to summarize:",
|
|
75
|
+
url: ctx.href,
|
|
76
|
+
placement: "after-cta",
|
|
77
|
+
},
|
|
78
|
+
closingParagraphs: [
|
|
79
|
+
"Clips is a 100% free, open-source, Agent-Native app for sharing screengrabs with friends and colleagues. No download required.",
|
|
80
|
+
`Just reply to this email if you want to get back to ${emailLink(
|
|
81
|
+
ctx.senderEmail,
|
|
82
|
+
`mailto:${ctx.senderEmail}`,
|
|
83
|
+
)} directly.`,
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
55
88
|
export type ShareHeroRecording = {
|
|
56
89
|
thumbnailUrl?: string | null;
|
|
57
90
|
animatedThumbnailUrl?: string | null;
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,10 +77,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
|
86
86
|
//# sourceMappingURL=awareness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa
|
|
1
|
+
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAChC,IAAI,CAON;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,cAAc,GACrB,IAAI,CAgBN;AAoBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,MAAmB,GAC7B,IAAI,CAON;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE1E;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAkCD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAwDa,MAAM;eAAS,MAAM;;GAoB1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAYM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|