@agent-native/core 0.157.2 → 0.157.4
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/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +40 -1
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -7
- package/corpus/templates/clips/actions/approve-recording-access-request.ts +147 -0
- package/corpus/templates/clips/actions/request-recording-access.ts +121 -23
- package/corpus/templates/clips/actions/view-screen.ts +1 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +34 -53
- package/corpus/templates/clips/app/components/player/request-access-dialog.tsx +104 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +36 -45
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +26 -1
- package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +123 -37
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +41 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -0
- package/corpus/templates/clips/server/lib/emails.ts +40 -11
- package/corpus/templates/clips/shared/recording-link.ts +32 -1
- package/corpus/templates/clips/shared/share-attribution.ts +13 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/content/actions/add-database-item.ts +9 -0
- package/corpus/templates/content/actions/describe-content-database.ts +92 -0
- package/corpus/templates/content/actions/list-content-databases.ts +213 -72
- package/corpus/templates/content/actions/set-document-property.ts +9 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +9 -0
- package/corpus/templates/content/actions/update-document.ts +9 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +22 -10
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +2 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +245 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +192 -134
- package/corpus/templates/content/app/components/editor/database/settings.tsx +7 -0
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +61 -45
- package/corpus/templates/content/app/components/editor/extensions/ImageNode.ts +6 -0
- package/corpus/templates/content/app/components/editor/image-upload.ts +137 -5
- package/corpus/templates/content/app/global.css +8 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +91 -11
- package/corpus/templates/content/changelog/2026-08-12-image-uploads-now-confirm-the-image-can-be-displayed-before-.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-fill-the-page-width-by-default-and-remain-visible-whe.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-inserted-from-the-slash-command-stay-saved-after-uplo.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-svg-files-can-be-selected-reliably-from-the-image-picker.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +7 -1
- package/corpus/templates/content/server/plugins/agent-chat.ts +3 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -1
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +25 -30
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +124 -180
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +1 -0
- package/corpus/templates/slides/app/global.css +99 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/chat-threads/schema.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +318 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +41 -3
- package/dist/client/AssistantChat.d.ts +9 -1
- package/dist/client/AssistantChat.js +26 -13
- package/dist/client/MultiTabAssistantChat.js +18 -5
- package/dist/client/agent-chat.d.ts +8 -0
- package/dist/client/agent-chat.js +15 -0
- package/dist/client/app-chat-sidebar.d.ts +23 -0
- package/dist/client/app-chat-sidebar.js +53 -0
- package/dist/client/app-providers.js +31 -2
- package/dist/client/frame-protocol.d.ts +20 -2
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/sharing/AgentShareSection.d.ts +8 -0
- package/dist/client/sharing/AgentShareSection.js +50 -0
- package/dist/client/sharing/ShareButton.js +2 -1
- package/dist/client/sharing/ShareDialog.d.ts +2 -2
- package/dist/client/sharing/ShareDialog.js +21 -29
- package/dist/client/sharing/index.d.ts +1 -0
- package/dist/client/sharing/index.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +2 -1
- package/dist/client/theme.d.ts +21 -0
- package/dist/client/theme.js +60 -0
- package/dist/client/ui/index.d.ts +1 -1
- package/dist/client/ui/index.js +1 -1
- package/dist/client/use-session.js +23 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/dashboard-storage/schema.d.ts +1 -1
- package/dist/data-programs/schema.d.ts +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +8 -5
- package/dist/server/identity-sso-store.d.ts +6 -1
- package/dist/server/identity-sso-store.js +57 -14
- package/dist/server/identity-sso.d.ts +7 -8
- package/dist/server/identity-sso.js +15 -12
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +1 -0
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/sharing/schema.js +4 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/cross-app-sso.mdx +24 -17
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { callAction, useSession } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { buildSignInReturnHref } from "@agent-native/core/client/ui";
|
|
4
|
+
import { IconAlertTriangle, IconCheck, IconLock } from "@tabler/icons-react";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { Link, useSearchParams } from "react-router";
|
|
7
|
+
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
10
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
11
|
+
import enMessages from "@/i18n/en-US";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
recordingAccessApprovalContinuationPath,
|
|
15
|
+
recordingAccessApprovalSessionKey,
|
|
16
|
+
} from "../../shared/recording-link";
|
|
17
|
+
|
|
18
|
+
interface ApprovalResult {
|
|
19
|
+
ok: true;
|
|
20
|
+
alreadyAllowed: boolean;
|
|
21
|
+
requesterEmail: string;
|
|
22
|
+
recordingId: string;
|
|
23
|
+
recordingTitle: string;
|
|
24
|
+
shareId: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ApprovalState =
|
|
29
|
+
| { kind: "loading" }
|
|
30
|
+
| { kind: "sign-in" }
|
|
31
|
+
| { kind: "success"; result: ApprovalResult }
|
|
32
|
+
| { kind: "error"; message: string };
|
|
33
|
+
|
|
34
|
+
export function meta() {
|
|
35
|
+
return [{ title: enMessages.sharePage.privateClip }];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function ApproveRecordingAccessRequestRoute() {
|
|
39
|
+
const t = useT();
|
|
40
|
+
const [searchParams] = useSearchParams();
|
|
41
|
+
const { session, isLoading: sessionLoading } = useSession();
|
|
42
|
+
const recordingId = searchParams.get("recordingId") ?? "";
|
|
43
|
+
const approvalTokenFromUrl = searchParams.get("token") ?? "";
|
|
44
|
+
const approvalTokenStorageKey =
|
|
45
|
+
recordingAccessApprovalSessionKey(recordingId);
|
|
46
|
+
const [approvalToken, setApprovalToken] = useState(() => {
|
|
47
|
+
if (approvalTokenFromUrl) return approvalTokenFromUrl;
|
|
48
|
+
if (typeof window === "undefined" || !recordingId) return "";
|
|
49
|
+
try {
|
|
50
|
+
return sessionStorage.getItem(approvalTokenStorageKey) ?? "";
|
|
51
|
+
} catch {
|
|
52
|
+
// coercion-ok: unavailable tab storage is an absent continuation.
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const [state, setState] = useState<ApprovalState>({ kind: "loading" });
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!recordingId) {
|
|
60
|
+
setApprovalToken("");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!approvalTokenFromUrl) {
|
|
64
|
+
try {
|
|
65
|
+
setApprovalToken(sessionStorage.getItem(approvalTokenStorageKey) ?? "");
|
|
66
|
+
} catch {
|
|
67
|
+
setApprovalToken("");
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
sessionStorage.setItem(approvalTokenStorageKey, approvalTokenFromUrl);
|
|
73
|
+
} catch {
|
|
74
|
+
// coercion-ok: never fall back to putting the bearer token in the URL.
|
|
75
|
+
// Session storage may be unavailable in hardened browser contexts.
|
|
76
|
+
}
|
|
77
|
+
setApprovalToken(approvalTokenFromUrl);
|
|
78
|
+
|
|
79
|
+
// The email link must contain the capability, but it should not remain in
|
|
80
|
+
// the address bar or be copied into the sign-in continuation URL.
|
|
81
|
+
const params = new URLSearchParams(window.location.search);
|
|
82
|
+
params.delete("token");
|
|
83
|
+
const nextSearch = params.toString();
|
|
84
|
+
window.history.replaceState(
|
|
85
|
+
window.history.state,
|
|
86
|
+
"",
|
|
87
|
+
`${window.location.pathname}${nextSearch ? `?${nextSearch}` : ""}${window.location.hash}`,
|
|
88
|
+
);
|
|
89
|
+
}, [approvalTokenFromUrl, approvalTokenStorageKey, recordingId]);
|
|
90
|
+
|
|
91
|
+
const signInReturnTo = useMemo(() => {
|
|
92
|
+
return recordingAccessApprovalContinuationPath(recordingId);
|
|
93
|
+
}, [recordingId]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (sessionLoading) return;
|
|
97
|
+
|
|
98
|
+
let cancelled = false;
|
|
99
|
+
if (!session?.email) {
|
|
100
|
+
setState({ kind: "sign-in" });
|
|
101
|
+
return () => {
|
|
102
|
+
cancelled = true;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!recordingId || !approvalToken) {
|
|
106
|
+
setState({
|
|
107
|
+
kind: "error",
|
|
108
|
+
message: t("sharePage.accessApprovalInvalid"),
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
cancelled = true;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setState({ kind: "loading" });
|
|
116
|
+
void callAction<ApprovalResult>("approve-recording-access-request", {
|
|
117
|
+
recordingId,
|
|
118
|
+
approvalToken,
|
|
119
|
+
})
|
|
120
|
+
.then((result) => {
|
|
121
|
+
try {
|
|
122
|
+
sessionStorage.removeItem(approvalTokenStorageKey);
|
|
123
|
+
} catch {
|
|
124
|
+
// coercion-ok: cleanup failure cannot authorize or expose the token.
|
|
125
|
+
// Session storage may be unavailable in hardened browser contexts.
|
|
126
|
+
}
|
|
127
|
+
if (!cancelled) setState({ kind: "success", result });
|
|
128
|
+
})
|
|
129
|
+
.catch((error: unknown) => {
|
|
130
|
+
if (cancelled) return;
|
|
131
|
+
setState({
|
|
132
|
+
kind: "error",
|
|
133
|
+
message:
|
|
134
|
+
error instanceof Error
|
|
135
|
+
? error.message
|
|
136
|
+
: t("sharePage.accessApprovalInvalid"),
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
cancelled = true;
|
|
142
|
+
};
|
|
143
|
+
}, [
|
|
144
|
+
approvalToken,
|
|
145
|
+
approvalTokenStorageKey,
|
|
146
|
+
recordingId,
|
|
147
|
+
session?.email,
|
|
148
|
+
sessionLoading,
|
|
149
|
+
t,
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
const signInHref = buildSignInReturnHref({ returnTo: signInReturnTo });
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div className="flex min-h-screen items-center justify-center bg-background px-6 py-12 text-foreground">
|
|
156
|
+
<Card className="w-full max-w-md">
|
|
157
|
+
<CardHeader>
|
|
158
|
+
<CardTitle className="flex items-center gap-2">
|
|
159
|
+
<IconLock
|
|
160
|
+
className="size-5 text-muted-foreground"
|
|
161
|
+
aria-hidden="true"
|
|
162
|
+
/>
|
|
163
|
+
{state.kind === "success"
|
|
164
|
+
? state.result.alreadyAllowed
|
|
165
|
+
? t("sharePage.accessApprovalAlreadyTitle")
|
|
166
|
+
: t("sharePage.accessApprovalTitle")
|
|
167
|
+
: state.kind === "error"
|
|
168
|
+
? t("sharePage.accessApprovalErrorTitle")
|
|
169
|
+
: state.kind === "sign-in"
|
|
170
|
+
? t("sharePage.accessApprovalSignInTitle")
|
|
171
|
+
: t("sharePage.accessApprovalLoading")}
|
|
172
|
+
</CardTitle>
|
|
173
|
+
</CardHeader>
|
|
174
|
+
<CardContent aria-live="polite">
|
|
175
|
+
{state.kind === "loading" ? (
|
|
176
|
+
<div className="space-y-3 py-2">
|
|
177
|
+
<Skeleton className="h-4 w-full" />
|
|
178
|
+
<Skeleton className="h-4 w-4/5" />
|
|
179
|
+
</div>
|
|
180
|
+
) : state.kind === "sign-in" ? (
|
|
181
|
+
<div className="space-y-4">
|
|
182
|
+
<p className="text-sm text-muted-foreground">
|
|
183
|
+
{t("sharePage.accessApprovalSignInMessage")}
|
|
184
|
+
</p>
|
|
185
|
+
<Button asChild>
|
|
186
|
+
<a
|
|
187
|
+
href={signInHref}
|
|
188
|
+
onClick={() => {
|
|
189
|
+
if (!approvalToken) return;
|
|
190
|
+
try {
|
|
191
|
+
sessionStorage.setItem(
|
|
192
|
+
approvalTokenStorageKey,
|
|
193
|
+
approvalToken,
|
|
194
|
+
);
|
|
195
|
+
} catch {
|
|
196
|
+
// coercion-ok: never fall back to putting the bearer token in the URL.
|
|
197
|
+
// Session storage may be unavailable in hardened browser contexts.
|
|
198
|
+
}
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
{t("sharePage.accessApprovalSignIn")}
|
|
202
|
+
</a>
|
|
203
|
+
</Button>
|
|
204
|
+
</div>
|
|
205
|
+
) : state.kind === "error" ? (
|
|
206
|
+
<div className="space-y-4">
|
|
207
|
+
<div className="flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-sm text-destructive">
|
|
208
|
+
<IconAlertTriangle
|
|
209
|
+
className="mt-0.5 size-4 shrink-0"
|
|
210
|
+
aria-hidden="true"
|
|
211
|
+
/>
|
|
212
|
+
<span>{state.message}</span>
|
|
213
|
+
</div>
|
|
214
|
+
{recordingId ? (
|
|
215
|
+
<Button asChild variant="outline">
|
|
216
|
+
<Link to={`/r/${encodeURIComponent(recordingId)}`}>
|
|
217
|
+
{t("sharePage.accessApprovalOpenClip")}
|
|
218
|
+
</Link>
|
|
219
|
+
</Button>
|
|
220
|
+
) : null}
|
|
221
|
+
</div>
|
|
222
|
+
) : (
|
|
223
|
+
<div className="space-y-4">
|
|
224
|
+
<div className="flex items-start gap-3 rounded-md border border-primary/30 bg-primary/10 p-3 text-sm">
|
|
225
|
+
<IconCheck
|
|
226
|
+
className="mt-0.5 size-4 shrink-0 text-primary"
|
|
227
|
+
aria-hidden="true"
|
|
228
|
+
/>
|
|
229
|
+
<span>
|
|
230
|
+
{state.result.alreadyAllowed
|
|
231
|
+
? t("sharePage.accessApprovalAlreadyMessage", {
|
|
232
|
+
email: state.result.requesterEmail,
|
|
233
|
+
})
|
|
234
|
+
: t("sharePage.accessApprovalMessage", {
|
|
235
|
+
email: state.result.requesterEmail,
|
|
236
|
+
})}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
<Button asChild variant="outline">
|
|
240
|
+
<Link to={`/r/${encodeURIComponent(state.result.recordingId)}`}>
|
|
241
|
+
{t("sharePage.accessApprovalOpenClip")}
|
|
242
|
+
</Link>
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
</CardContent>
|
|
247
|
+
</Card>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
isHumanReadableDocumentTitle,
|
|
17
17
|
normalizeDocumentTitle,
|
|
18
18
|
} from "@agent-native/core/shared";
|
|
19
|
-
import { ShareTrigger } from "@agent-native/toolkit/sharing";
|
|
19
|
+
import { ShareCopyRow, ShareTrigger } from "@agent-native/toolkit/sharing";
|
|
20
20
|
import {
|
|
21
21
|
BUILDER_CREDITS_UPGRADE_URL,
|
|
22
22
|
type BuilderCreditsStatus,
|
|
@@ -277,7 +277,6 @@ export default function RecordingPage() {
|
|
|
277
277
|
const [processingTimeout, setProcessingTimeout] = useState(false);
|
|
278
278
|
const [retryingFinalize, setRetryingFinalize] = useState(false);
|
|
279
279
|
const [downloading, setDownloading] = useState(false);
|
|
280
|
-
const [pendingLinkCopied, setPendingLinkCopied] = useState(false);
|
|
281
280
|
const browserTabId = useMemo(() => getBrowserTabId(), []);
|
|
282
281
|
const recordingScope = useMemo(
|
|
283
282
|
() =>
|
|
@@ -445,14 +444,6 @@ export default function RecordingPage() {
|
|
|
445
444
|
if (!recordingId || typeof window === "undefined") return "";
|
|
446
445
|
return recordingShareUrl(recordingId, shareViaId);
|
|
447
446
|
}, [recordingId, shareViaId]);
|
|
448
|
-
const copyPendingShareLink = useCallback(async () => {
|
|
449
|
-
if (!pendingShareUrl) return;
|
|
450
|
-
// The full Share popover remains available when clipboard permission is
|
|
451
|
-
// unavailable, so a denied clipboard write does not block the page.
|
|
452
|
-
if (!(await writeClipboardText(pendingShareUrl))) return;
|
|
453
|
-
setPendingLinkCopied(true);
|
|
454
|
-
window.setTimeout(() => setPendingLinkCopied(false), 1400);
|
|
455
|
-
}, [pendingShareUrl]);
|
|
456
447
|
useEffect(() => {
|
|
457
448
|
if (!recording?.id) return;
|
|
458
449
|
const now = Date.now();
|
|
@@ -1033,28 +1024,14 @@ export default function RecordingPage() {
|
|
|
1033
1024
|
/>
|
|
1034
1025
|
</div>
|
|
1035
1026
|
) : null}
|
|
1036
|
-
<
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
</div>
|
|
1045
|
-
<Button
|
|
1046
|
-
type="button"
|
|
1047
|
-
variant="outline"
|
|
1048
|
-
size="sm"
|
|
1049
|
-
className="shrink-0 gap-1.5"
|
|
1050
|
-
onClick={() => void copyPendingShareLink()}
|
|
1051
|
-
>
|
|
1052
|
-
<IconClipboardCopy className="size-4" />
|
|
1053
|
-
{pendingLinkCopied
|
|
1054
|
-
? t("bugReportRoute.copied")
|
|
1055
|
-
: t("shareUi.copy")}
|
|
1056
|
-
</Button>
|
|
1057
|
-
</div>
|
|
1027
|
+
<ShareCopyRow
|
|
1028
|
+
value={pendingShareUrl}
|
|
1029
|
+
label={t("shareDialog.shareLink")}
|
|
1030
|
+
copyLabel={t("shareUi.copy")}
|
|
1031
|
+
copiedLabel={t("bugReportRoute.copied")}
|
|
1032
|
+
onCopy={writeClipboardText}
|
|
1033
|
+
className="rounded-lg border border-border bg-muted/30 p-2 ps-3"
|
|
1034
|
+
/>
|
|
1058
1035
|
</div>
|
|
1059
1036
|
</main>
|
|
1060
1037
|
</div>
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useMemo,
|
|
33
33
|
useRef,
|
|
34
34
|
useState,
|
|
35
|
+
type FormEvent,
|
|
35
36
|
type ReactNode,
|
|
36
37
|
} from "react";
|
|
37
38
|
import {
|
|
@@ -55,6 +56,7 @@ import { RecordingOptionsMenu } from "@/components/player/delete-recording-menu"
|
|
|
55
56
|
import { InsightsPanel } from "@/components/player/insights-panel";
|
|
56
57
|
import { ReactionsTray } from "@/components/player/reactions-tray";
|
|
57
58
|
import { RecordingViewsBadge } from "@/components/player/recording-views-badge";
|
|
59
|
+
import { RequestAccessDialog } from "@/components/player/request-access-dialog";
|
|
58
60
|
import { ShareRecordingPopover } from "@/components/player/share-dialog";
|
|
59
61
|
import { SignInPromptDialog } from "@/components/player/sign-in-prompt-dialog";
|
|
60
62
|
import { TranscriptPanel } from "@/components/player/transcript-panel";
|
|
@@ -97,6 +99,7 @@ import {
|
|
|
97
99
|
CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
|
|
98
100
|
} from "../../shared/recording-link";
|
|
99
101
|
import {
|
|
102
|
+
buildShareContinuationQuery,
|
|
100
103
|
buildSignupAttributionQuery,
|
|
101
104
|
readShareAttribution,
|
|
102
105
|
} from "../../shared/share-attribution";
|
|
@@ -242,14 +245,12 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
242
245
|
if (!access) {
|
|
243
246
|
const status = userEmail ? 403 : 401;
|
|
244
247
|
const deniedData = emptyLoaderData(url, status);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
});
|
|
252
|
-
}
|
|
248
|
+
deniedData.accessRequestToken = signScopedAgentAccessToken({
|
|
249
|
+
resourceKind: CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
250
|
+
resourceId: id,
|
|
251
|
+
...(userEmail ? { viewerEmail: userEmail } : {}),
|
|
252
|
+
ttlSeconds: CLIPS_ACCESS_REQUEST_TOKEN_TTL_SECONDS,
|
|
253
|
+
});
|
|
253
254
|
return privateShareLoaderData(deniedData, status);
|
|
254
255
|
}
|
|
255
256
|
}
|
|
@@ -453,7 +454,11 @@ export default function ShareRoute() {
|
|
|
453
454
|
notifiedOwner: boolean;
|
|
454
455
|
ok: true;
|
|
455
456
|
},
|
|
456
|
-
{
|
|
457
|
+
{
|
|
458
|
+
accessRequestToken?: string;
|
|
459
|
+
recordingId: string;
|
|
460
|
+
requesterEmail?: string;
|
|
461
|
+
}
|
|
457
462
|
>("request-recording-access");
|
|
458
463
|
const [signInIntent, setSignInIntent] = useState<"comment" | "react" | null>(
|
|
459
464
|
null,
|
|
@@ -467,6 +472,14 @@ export default function ShareRoute() {
|
|
|
467
472
|
const [downloading, setDownloading] = useState(false);
|
|
468
473
|
const [downloadMenuOpen, setDownloadMenuOpen] = useState(false);
|
|
469
474
|
const [accessRequestSent, setAccessRequestSent] = useState(false);
|
|
475
|
+
const [accessRequestError, setAccessRequestError] = useState<string | null>(
|
|
476
|
+
null,
|
|
477
|
+
);
|
|
478
|
+
const [requestAccessDialogOpen, setRequestAccessDialogOpen] = useState(false);
|
|
479
|
+
const [requesterEmail, setRequesterEmail] = useState("");
|
|
480
|
+
const [requestAccessDialogError, setRequestAccessDialogError] = useState<
|
|
481
|
+
string | null
|
|
482
|
+
>(null);
|
|
470
483
|
const agentAccessToken = useMemo(() => {
|
|
471
484
|
if (typeof window === "undefined") return "";
|
|
472
485
|
return (
|
|
@@ -476,6 +489,71 @@ export default function ShareRoute() {
|
|
|
476
489
|
);
|
|
477
490
|
}, []);
|
|
478
491
|
|
|
492
|
+
const shareReturnTo = useMemo(() => {
|
|
493
|
+
const path = `/share/${encodeURIComponent(recordingId)}`;
|
|
494
|
+
if (typeof window === "undefined") return path;
|
|
495
|
+
const query = buildShareContinuationQuery(attribution);
|
|
496
|
+
return query ? `${path}?${query}` : path;
|
|
497
|
+
}, [attribution, recordingId]);
|
|
498
|
+
const signInHref = buildSignInReturnHref({ returnTo: shareReturnTo });
|
|
499
|
+
|
|
500
|
+
const submitAccessRequest = useCallback(
|
|
501
|
+
(email?: string) => {
|
|
502
|
+
if (!shareId || accessRequestSent || requestAccess.isPending) return;
|
|
503
|
+
const normalizedEmail = email?.trim() || undefined;
|
|
504
|
+
setAccessRequestError(null);
|
|
505
|
+
setRequestAccessDialogError(null);
|
|
506
|
+
requestAccess.mutate(
|
|
507
|
+
{
|
|
508
|
+
accessRequestToken: loaderData.accessRequestToken,
|
|
509
|
+
recordingId: shareId,
|
|
510
|
+
...(normalizedEmail ? { requesterEmail: normalizedEmail } : {}),
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
onSuccess: () => {
|
|
514
|
+
setAccessRequestSent(true);
|
|
515
|
+
setRequestAccessDialogOpen(false);
|
|
516
|
+
toast.success(
|
|
517
|
+
normalizedEmail
|
|
518
|
+
? t("sharePage.accessRequestSentWithEmail", {
|
|
519
|
+
email: normalizedEmail,
|
|
520
|
+
})
|
|
521
|
+
: t("sharePage.accessRequestSent"),
|
|
522
|
+
);
|
|
523
|
+
},
|
|
524
|
+
onError: (error: unknown) => {
|
|
525
|
+
const message =
|
|
526
|
+
error instanceof Error && error.message
|
|
527
|
+
? error.message
|
|
528
|
+
: t("sharePage.accessRequestFailed");
|
|
529
|
+
setAccessRequestError(message);
|
|
530
|
+
setRequestAccessDialogError(message);
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
},
|
|
535
|
+
[
|
|
536
|
+
accessRequestSent,
|
|
537
|
+
loaderData.accessRequestToken,
|
|
538
|
+
requestAccess,
|
|
539
|
+
shareId,
|
|
540
|
+
t,
|
|
541
|
+
],
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
const submitGuestAccessRequest = useCallback(
|
|
545
|
+
(event: FormEvent<HTMLFormElement>) => {
|
|
546
|
+
event.preventDefault();
|
|
547
|
+
const email = requesterEmail.trim();
|
|
548
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
549
|
+
setRequestAccessDialogError(t("sharePage.requestAccessEmailRequired"));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
submitAccessRequest(email);
|
|
553
|
+
},
|
|
554
|
+
[requesterEmail, submitAccessRequest, t],
|
|
555
|
+
);
|
|
556
|
+
|
|
479
557
|
const dataQ = useQuery({
|
|
480
558
|
queryKey: ["public-recording", shareId, password, agentAccessToken],
|
|
481
559
|
queryFn: async () => {
|
|
@@ -815,29 +893,13 @@ export default function ShareRoute() {
|
|
|
815
893
|
? "sharePage.privateClipMessage"
|
|
816
894
|
: "sharePage.privateClipSignedOutMessage",
|
|
817
895
|
)}
|
|
896
|
+
error={canRequestAccess ? accessRequestError : null}
|
|
818
897
|
action={
|
|
819
898
|
canRequestAccess ? (
|
|
820
899
|
<Button
|
|
821
900
|
size="sm"
|
|
822
901
|
disabled={requestAccess.isPending || requestSent}
|
|
823
|
-
onClick={() =>
|
|
824
|
-
if (!shareId || requestSent) return;
|
|
825
|
-
requestAccess.mutate(
|
|
826
|
-
{
|
|
827
|
-
accessRequestToken: loaderData.accessRequestToken,
|
|
828
|
-
recordingId: shareId,
|
|
829
|
-
},
|
|
830
|
-
{
|
|
831
|
-
onSuccess: () => {
|
|
832
|
-
setAccessRequestSent(true);
|
|
833
|
-
toast.success(t("sharePage.accessRequestSent"));
|
|
834
|
-
},
|
|
835
|
-
onError: () => {
|
|
836
|
-
toast.error(t("sharePage.accessRequestFailed"));
|
|
837
|
-
},
|
|
838
|
-
},
|
|
839
|
-
);
|
|
840
|
-
}}
|
|
902
|
+
onClick={() => submitAccessRequest()}
|
|
841
903
|
>
|
|
842
904
|
{requestSent
|
|
843
905
|
? t("sharePage.accessRequested")
|
|
@@ -846,20 +908,34 @@ export default function ShareRoute() {
|
|
|
846
908
|
: t("sharePage.requestAccess")}
|
|
847
909
|
</Button>
|
|
848
910
|
) : shareId ? (
|
|
849
|
-
<Button
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
</a>
|
|
911
|
+
<Button
|
|
912
|
+
size="sm"
|
|
913
|
+
onClick={() => {
|
|
914
|
+
setAccessRequestError(null);
|
|
915
|
+
setRequestAccessDialogError(null);
|
|
916
|
+
setRequestAccessDialogOpen(true);
|
|
917
|
+
}}
|
|
918
|
+
>
|
|
919
|
+
{t("sharePage.requestAccess")}
|
|
859
920
|
</Button>
|
|
860
921
|
) : null
|
|
861
922
|
}
|
|
862
923
|
/>
|
|
924
|
+
{!canRequestAccess && shareId ? (
|
|
925
|
+
<RequestAccessDialog
|
|
926
|
+
open={requestAccessDialogOpen}
|
|
927
|
+
onOpenChange={setRequestAccessDialogOpen}
|
|
928
|
+
signInHref={signInHref}
|
|
929
|
+
email={requesterEmail}
|
|
930
|
+
onEmailChange={(value) => {
|
|
931
|
+
setRequesterEmail(value);
|
|
932
|
+
setRequestAccessDialogError(null);
|
|
933
|
+
}}
|
|
934
|
+
onSubmit={submitGuestAccessRequest}
|
|
935
|
+
isSubmitting={requestAccess.isPending}
|
|
936
|
+
error={requestAccessDialogError}
|
|
937
|
+
/>
|
|
938
|
+
) : null}
|
|
863
939
|
</>
|
|
864
940
|
);
|
|
865
941
|
}
|
|
@@ -1464,11 +1540,13 @@ function EndState({
|
|
|
1464
1540
|
icon,
|
|
1465
1541
|
title,
|
|
1466
1542
|
message,
|
|
1543
|
+
error,
|
|
1467
1544
|
action,
|
|
1468
1545
|
}: {
|
|
1469
1546
|
icon?: ReactNode;
|
|
1470
1547
|
title: string;
|
|
1471
1548
|
message: string;
|
|
1549
|
+
error?: string | null;
|
|
1472
1550
|
action?: ReactNode;
|
|
1473
1551
|
}) {
|
|
1474
1552
|
const t = useT();
|
|
@@ -1484,6 +1562,14 @@ function EndState({
|
|
|
1484
1562
|
<p className="mb-6 max-w-md text-center text-sm text-muted-foreground">
|
|
1485
1563
|
{message}
|
|
1486
1564
|
</p>
|
|
1565
|
+
{error ? (
|
|
1566
|
+
<p
|
|
1567
|
+
className="mb-6 max-w-md text-center text-sm text-destructive"
|
|
1568
|
+
role="alert"
|
|
1569
|
+
>
|
|
1570
|
+
{error}
|
|
1571
|
+
</p>
|
|
1572
|
+
) : null}
|
|
1487
1573
|
<div className="flex flex-wrap items-center justify-center gap-2">
|
|
1488
1574
|
{action}
|
|
1489
1575
|
<Button asChild variant="ghost" size="sm">
|
|
@@ -45,6 +45,11 @@ import {
|
|
|
45
45
|
import { getDb, schema } from "../../server/db";
|
|
46
46
|
import { CLIPS_MEETING_AGENT_RESOURCE_KIND } from "../../shared/meeting-agent-access";
|
|
47
47
|
import { privateShareLoaderData } from "../../shared/share-loader-response";
|
|
48
|
+
import {
|
|
49
|
+
normalizeTranscriptSegments,
|
|
50
|
+
parseTranscriptSegments,
|
|
51
|
+
} from "../../shared/transcript-segments";
|
|
52
|
+
import { resolveTranscriptPresentation } from "../../shared/transcript-status";
|
|
48
53
|
|
|
49
54
|
type LoaderData = { meeting: PublicMeeting | null };
|
|
50
55
|
|
|
@@ -84,6 +89,8 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
84
89
|
actualStart: schema.meetings.actualStart,
|
|
85
90
|
actualEnd: schema.meetings.actualEnd,
|
|
86
91
|
transcriptStatus: schema.meetings.transcriptStatus,
|
|
92
|
+
recordingId: schema.meetings.recordingId,
|
|
93
|
+
shareTranscript: schema.meetings.shareTranscript,
|
|
87
94
|
visibility: schema.meetings.visibility,
|
|
88
95
|
})
|
|
89
96
|
.from(schema.meetings)
|
|
@@ -104,7 +111,7 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
104
111
|
return shareMeetingLoaderData({ meeting: null }, hasAgentAccessToken);
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
const [participants, actionItems] = await Promise.all([
|
|
114
|
+
const [participants, actionItems, transcriptRows] = await Promise.all([
|
|
108
115
|
getDb()
|
|
109
116
|
.select({
|
|
110
117
|
email: schema.meetingParticipants.email,
|
|
@@ -122,8 +129,33 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
122
129
|
})
|
|
123
130
|
.from(schema.meetingActionItems)
|
|
124
131
|
.where(eq(schema.meetingActionItems.meetingId, meetingId)),
|
|
132
|
+
meeting.shareTranscript && meeting.recordingId
|
|
133
|
+
? getDb()
|
|
134
|
+
.select({
|
|
135
|
+
status: schema.recordingTranscripts.status,
|
|
136
|
+
language: schema.recordingTranscripts.language,
|
|
137
|
+
fullText: schema.recordingTranscripts.fullText,
|
|
138
|
+
failureReason: schema.recordingTranscripts.failureReason,
|
|
139
|
+
segmentsJson: schema.recordingTranscripts.segmentsJson,
|
|
140
|
+
updatedAt: schema.recordingTranscripts.updatedAt,
|
|
141
|
+
})
|
|
142
|
+
.from(schema.recordingTranscripts)
|
|
143
|
+
.where(
|
|
144
|
+
eq(schema.recordingTranscripts.recordingId, meeting.recordingId),
|
|
145
|
+
)
|
|
146
|
+
.limit(1)
|
|
147
|
+
: Promise.resolve([]),
|
|
125
148
|
]);
|
|
126
149
|
|
|
150
|
+
const transcript = transcriptRows[0] ?? null;
|
|
151
|
+
const transcriptPresentation = resolveTranscriptPresentation(transcript);
|
|
152
|
+
const transcriptSegments = transcript
|
|
153
|
+
? normalizeTranscriptSegments({
|
|
154
|
+
segments: parseTranscriptSegments(transcript.segmentsJson),
|
|
155
|
+
fullText: transcript.fullText,
|
|
156
|
+
})
|
|
157
|
+
: [];
|
|
158
|
+
|
|
127
159
|
let bullets: PublicMeeting["bullets"] = [];
|
|
128
160
|
try {
|
|
129
161
|
const parsed = JSON.parse(meeting.bulletsJson);
|
|
@@ -150,6 +182,14 @@ export async function loader({ params, url }: LoaderFunctionArgs) {
|
|
|
150
182
|
actualStart: meeting.actualStart,
|
|
151
183
|
actualEnd: meeting.actualEnd,
|
|
152
184
|
transcriptStatus: meeting.transcriptStatus,
|
|
185
|
+
transcript: transcript
|
|
186
|
+
? {
|
|
187
|
+
status: transcriptPresentation.status ?? transcript.status,
|
|
188
|
+
language: transcript.language,
|
|
189
|
+
fullText: transcript.fullText,
|
|
190
|
+
segments: transcriptSegments,
|
|
191
|
+
}
|
|
192
|
+
: null,
|
|
153
193
|
},
|
|
154
194
|
},
|
|
155
195
|
hasAgentAccessToken,
|