@agent-native/core-corpus 0.222.1 → 0.222.2-nightly-20261029182928
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/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/brain/server/plugins/auth.ts +1 -1
- package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +1 -7
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/app/components/clips-command-menu.tsx +12 -7
- package/corpus/templates/clips/app/components/import-menu.tsx +4 -1
- package/corpus/templates/clips/app/components/library/library-grid.tsx +15 -8
- package/corpus/templates/clips/app/components/library/library-layout.tsx +10 -2
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +4 -7
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.screenshots.tsx +16 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +12 -1
- package/corpus/templates/clips/changelog/2026-10-28-fixed-the-share-advanced-popover-so-the-save-buttons-fit-and.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-29-screenshots-are-available-when-enabled-in-labs.md +6 -0
- package/corpus/templates/clips/server/lib/content-clip-peer.ts +158 -28
- package/corpus/templates/clips/shared/content-clip-contract.ts +23 -1
- package/corpus/templates/clips/shared/labs.ts +9 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -0
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/content/actions/_clip-embed-access.ts +31 -1
- package/corpus/templates/content/actions/add-clip-comment.ts +95 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +91 -4
- package/corpus/templates/content/actions/list-clip-comments.ts +68 -30
- package/corpus/templates/content/actions/resolve-clip-comment.ts +56 -0
- package/corpus/templates/content/app/clip-embed-messages.ts +22 -0
- package/corpus/templates/content/app/components/editor/CommentStack.tsx +3 -2
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +188 -62
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +87 -10
- package/corpus/templates/content/app/components/editor/StaticDocumentBody.tsx +36 -0
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +7 -2
- package/corpus/templates/content/app/components/editor/clip-embed/ClipEmbedBlock.tsx +113 -9
- package/corpus/templates/content/app/components/editor/clip-embed/ClipPlayer.tsx +33 -1
- package/corpus/templates/content/app/components/editor/clip-embed/clip-comments.ts +397 -0
- package/corpus/templates/content/app/components/editor/clip-embed/player-controls.tsx +17 -3
- package/corpus/templates/content/app/components/editor/clip-embed/scrubber-position.ts +12 -0
- package/corpus/templates/content/app/components/editor/clip-embed/scrubber.tsx +8 -13
- package/corpus/templates/content/app/components/editor/clip-embed/use-clip-comment-writes.ts +348 -0
- package/corpus/templates/content/app/components/editor/clip-embed/use-clip-playback.ts +22 -17
- package/corpus/templates/content/app/components/editor/comment-block-anchors.ts +13 -4
- package/corpus/templates/content/app/components/editor/comment-stacks.ts +6 -1
- package/corpus/templates/content/app/components/editor/discussion-panel.tsx +8 -5
- package/corpus/templates/content/app/components/editor/extensions/TablePresentation.ts +20 -2
- package/corpus/templates/content/app/components/editor/table-sizing.ts +749 -0
- package/corpus/templates/content/app/components/editor/table-view.ts +61 -84
- package/corpus/templates/content/app/global.css +119 -6
- package/corpus/templates/content/app/hooks/use-comments.ts +26 -7
- package/corpus/templates/content/app/lib/private-link-redirect.ts +68 -6
- package/corpus/templates/content/app/root.tsx +13 -0
- package/corpus/templates/content/app/routes/p.$id.tsx +6 -3
- package/corpus/templates/content/changelog/2026-10-22-shareable-local-file-copies-open-inline-with-document-editin.md +6 -0
- package/corpus/templates/content/changelog/2026-10-27-a-public-page-s-link-opens-a-read-only-copy-for-people-who-ar.md +6 -0
- package/corpus/templates/content/changelog/2026-10-27-two-collections-connected-to-the-same-builder-model-can-no-l.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +7 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +11 -5
- package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +23 -20
- package/corpus/templates/content/server/lib/clips-peer.ts +67 -13
- package/corpus/templates/content/shared/clips-peer-contract.ts +40 -3
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.agents/skills/hubspot-email/SKILL.md +2 -2
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.generated/bridge/native-builtin-metadata.generated.ts +326 -0
- package/corpus/templates/design/.generated/bridge/native-worker-source.generated.ts +6 -0
- package/corpus/templates/design/README.md +14 -0
- package/corpus/templates/design/actions/enroll-first-output-experiment.ts +99 -0
- package/corpus/templates/design/actions/generate-design.ts +19 -16
- package/corpus/templates/design/actions/visual-edit-browser-request.ts +23 -1
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +13 -0
- package/corpus/templates/design/app/lib/agent-run-status.ts +24 -0
- package/corpus/templates/design/app/lib/design-funnel-tracking.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +59 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-generation-and-access.ts +6 -0
- package/corpus/templates/design/app/pages/design-editor/view/first-output-next-step.tsx +95 -15
- package/corpus/templates/design/changelog/2026-10-22-generation-sessions-and-coding-handoffs-open-the-design-inli.md +6 -0
- package/corpus/templates/design/server/db/schema.ts +11 -0
- package/corpus/templates/design/server/lib/builder-identity-enabled.ts +23 -0
- package/corpus/templates/design/server/lib/builder-identity.ts +2 -21
- package/corpus/templates/design/server/lib/chatgpt-directory-tools.ts +16 -0
- package/corpus/templates/design/server/plugins/builder-host-embed-headers.ts +2 -7
- package/corpus/templates/design/server/plugins/db.ts +11 -0
- package/corpus/templates/design/shared/builder-host-origins.ts +28 -0
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +37 -8
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/changelog/2026-10-29-deck-change-notifications-are-now-only-sent-to-people-who-ca.md +6 -0
- package/corpus/templates/slides/server/handlers/decks.ts +70 -24
- package/corpus/templates/slides-2/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","content-metrics-collection-jobs","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","auth","creative-context","db","labs","onboarding","otel"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","auth","brain-jobs","core-routes","db","otel"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -13,12 +13,6 @@ declare global {
|
|
|
13
13
|
export const BRAIN_QUEUE_SWEEP_LIMIT = 25;
|
|
14
14
|
export const BRAIN_MAX_DISTILLATIONS_PER_SWEEP = 2;
|
|
15
15
|
|
|
16
|
-
function productionLike() {
|
|
17
|
-
return (
|
|
18
|
-
process.env.NODE_ENV === "production" || process.env.NETLIFY === "true"
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
function scheduledFunctionRuntime() {
|
|
23
17
|
return globalThis.__AGENT_NATIVE_BRAIN_SCHEDULED_RUNTIME__ === true;
|
|
24
18
|
}
|
|
@@ -35,7 +29,7 @@ function headerMatchesSecret(header: string | undefined, secret: string) {
|
|
|
35
29
|
export default defineEventHandler(async (event) => {
|
|
36
30
|
const secret = process.env.BRAIN_JOBS_SECRET?.trim(); // guard:allow-env-credential — deployment-level scheduler route secret
|
|
37
31
|
const scheduled = scheduledFunctionRuntime();
|
|
38
|
-
if (!secret &&
|
|
32
|
+
if (!secret && !scheduled) {
|
|
39
33
|
throw createError({
|
|
40
34
|
statusCode: 503,
|
|
41
35
|
statusMessage: "BRAIN_JOBS_SECRET is required",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","auth","core-routes","db","event-rules","oauth-refresh","otel","transactional-emails"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","auth","otel"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","auth","calendar-jobs","config","core-routes","db","feature-flags","labs","onboarding","onboarding-calendar","otel","transactional-email-catalog"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { useLab } from "@agent-native/core/client/labs";
|
|
2
3
|
import { useOrgRole } from "@agent-native/core/client/org";
|
|
3
4
|
import { docsUrl } from "@agent-native/core/shared";
|
|
4
5
|
import { useCommandMenuShortcut } from "@agent-native/toolkit/app/shared";
|
|
5
6
|
import { CommandMenu } from "@agent-native/toolkit/app/shared";
|
|
7
|
+
import { CLIPS_SCREENSHOTS } from "@shared/labs";
|
|
6
8
|
import {
|
|
7
9
|
IconArchive,
|
|
8
10
|
IconPhoto,
|
|
@@ -393,6 +395,7 @@ export function ClipsCommandMenu({
|
|
|
393
395
|
const location = useLocation();
|
|
394
396
|
const navigate = useNavigate();
|
|
395
397
|
const t = useT();
|
|
398
|
+
const screenshotsEnabled = useLab(CLIPS_SCREENSHOTS);
|
|
396
399
|
const { canManageOrg } = useOrgRole();
|
|
397
400
|
const context = useMemo(
|
|
398
401
|
() => commandRouteContext(location.pathname),
|
|
@@ -614,13 +617,15 @@ export function ClipsCommandMenu({
|
|
|
614
617
|
{t("navigation.dictate")}
|
|
615
618
|
<CommandMenu.Shortcut>G D</CommandMenu.Shortcut>
|
|
616
619
|
</CommandMenu.Item>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
620
|
+
{screenshotsEnabled && (
|
|
621
|
+
<CommandMenu.Item
|
|
622
|
+
onSelect={() => void navigate("/screenshots")}
|
|
623
|
+
keywords={["screenshots", "screenshot", "images", "stills"]}
|
|
624
|
+
>
|
|
625
|
+
<IconPhoto size={16} />
|
|
626
|
+
{t("navigation.screenshots")}
|
|
627
|
+
</CommandMenu.Item>
|
|
628
|
+
)}
|
|
624
629
|
<CommandMenu.Item
|
|
625
630
|
onSelect={() => void navigate("/archive")}
|
|
626
631
|
keywords={["archive"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { useLab } from "@agent-native/core/client/labs";
|
|
3
|
+
import { CLIPS_SCREENSHOTS } from "@shared/labs";
|
|
2
4
|
import { IconChevronDown, IconUpload } from "@tabler/icons-react";
|
|
3
5
|
import { useState } from "react";
|
|
4
6
|
|
|
@@ -63,6 +65,7 @@ export function ImportMenu({
|
|
|
63
65
|
variant = "outline",
|
|
64
66
|
}: ImportMenuProps) {
|
|
65
67
|
const t = useT();
|
|
68
|
+
const screenshotsEnabled = useLab(CLIPS_SCREENSHOTS);
|
|
66
69
|
const { input, openUploadPicker } = useUploadVideoPicker();
|
|
67
70
|
const [loomDialogOpen, setLoomDialogOpen] = useState(false);
|
|
68
71
|
|
|
@@ -118,7 +121,7 @@ export function ImportMenu({
|
|
|
118
121
|
{t("preRecord.uploadVideo")}
|
|
119
122
|
</DropdownMenuItem>
|
|
120
123
|
) : null}
|
|
121
|
-
{onScreenshot ? (
|
|
124
|
+
{onScreenshot && screenshotsEnabled ? (
|
|
122
125
|
<DropdownMenuItem
|
|
123
126
|
disabled={screenshotPending}
|
|
124
127
|
onSelect={(event) => {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
7
|
import { normalizeDocumentTitle } from "@agent-native/core/shared";
|
|
8
8
|
import { FileStorageSetupPopover } from "@agent-native/toolkit/app/chat/FileStorageSetupPopover";
|
|
9
|
-
import { IconAlertTriangle } from "@tabler/icons-react";
|
|
9
|
+
import { IconAlertTriangle, IconPhoto } from "@tabler/icons-react";
|
|
10
10
|
import {
|
|
11
11
|
type DragEvent,
|
|
12
12
|
type ReactElement,
|
|
@@ -59,7 +59,7 @@ import { resolveVideoMimeType } from "@/lib/video-metadata";
|
|
|
59
59
|
|
|
60
60
|
import { BulkActionToolbar, type BulkMoveTarget } from "./bulk-action-toolbar";
|
|
61
61
|
import { DroppedUploadCard } from "./dropped-upload-card";
|
|
62
|
-
import { EmptyState } from "./empty-state";
|
|
62
|
+
import { AppEmptyState, EmptyState } from "./empty-state";
|
|
63
63
|
import { FilterChips, type FilterChip } from "./filter-chips";
|
|
64
64
|
import { FolderCard } from "./folder-card";
|
|
65
65
|
import { buildLibraryActionHrefs } from "./library-action-hrefs";
|
|
@@ -757,7 +757,7 @@ export function LibraryGrid({
|
|
|
757
757
|
/>
|
|
758
758
|
)}
|
|
759
759
|
<div className="ms-auto flex shrink-0 items-center gap-2 lg:col-start-3 lg:ms-0 lg:justify-self-end">
|
|
760
|
-
{!isEmptyState && extraActions}
|
|
760
|
+
{(!isEmptyState || kind === "image") && extraActions}
|
|
761
761
|
{!isEmptyState && (
|
|
762
762
|
<SortMenu value={sort} onChange={handleSortChange} />
|
|
763
763
|
)}
|
|
@@ -834,11 +834,18 @@ export function LibraryGrid({
|
|
|
834
834
|
) : recordings.length === 0 &&
|
|
835
835
|
visibleFolders.length === 0 &&
|
|
836
836
|
uploads.length === 0 ? (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
837
|
+
kind === "image" ? (
|
|
838
|
+
<AppEmptyState
|
|
839
|
+
icon={IconPhoto}
|
|
840
|
+
title={t("navigation.screenshots")}
|
|
841
|
+
/>
|
|
842
|
+
) : (
|
|
843
|
+
<EmptyState
|
|
844
|
+
kind={resolvedEmptyKind}
|
|
845
|
+
spaceId={spaceId}
|
|
846
|
+
folderId={folderId}
|
|
847
|
+
/>
|
|
848
|
+
)
|
|
842
849
|
) : (
|
|
843
850
|
<div className="flex flex-col gap-8">
|
|
844
851
|
{visibleFolders.length > 0 && (
|
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
AppSidebarFooter,
|
|
13
13
|
AppSidebarHeader,
|
|
14
14
|
} from "@agent-native/toolkit/app/shared";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
CLIPS_MEETINGS,
|
|
17
|
+
CLIPS_SCREENSHOTS,
|
|
18
|
+
CLIPS_VOICE_DICTATION,
|
|
19
|
+
} from "@shared/labs";
|
|
16
20
|
import {
|
|
17
21
|
IconInbox,
|
|
18
22
|
IconPhoto,
|
|
@@ -195,6 +199,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
195
199
|
const t = useT();
|
|
196
200
|
const isRecordingRoute = location.pathname.startsWith("/r/");
|
|
197
201
|
const meetingsLabEnabled = useLab(CLIPS_MEETINGS);
|
|
202
|
+
const screenshotsLabEnabled = useLab(CLIPS_SCREENSHOTS);
|
|
198
203
|
const voiceDictationLabEnabled = useLab(CLIPS_VOICE_DICTATION);
|
|
199
204
|
const recordingScope = useMemo(() => {
|
|
200
205
|
const match = location.pathname.match(/^\/(?:r|share)\/([^/]+)/);
|
|
@@ -564,7 +569,10 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
564
569
|
];
|
|
565
570
|
|
|
566
571
|
const primaryNavItems = navItems.filter(
|
|
567
|
-
({ to }) =>
|
|
572
|
+
({ to }) =>
|
|
573
|
+
to !== "/archive" &&
|
|
574
|
+
to !== "/trash" &&
|
|
575
|
+
(to !== "/screenshots" || screenshotsLabEnabled),
|
|
568
576
|
);
|
|
569
577
|
const lifecycleNavItems = navItems.filter(
|
|
570
578
|
({ to }) => to === "/archive" || to === "/trash",
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
PopoverTrigger,
|
|
65
65
|
PopoverContent,
|
|
66
66
|
} from "@/components/ui/popover";
|
|
67
|
+
import { Switch } from "@/components/ui/switch";
|
|
67
68
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
68
69
|
import { cn } from "@/lib/utils";
|
|
69
70
|
|
|
@@ -755,7 +756,7 @@ function RecordingAdvancedAccess({
|
|
|
755
756
|
>
|
|
756
757
|
{t("embedRoute.passwordRequired")}
|
|
757
758
|
</Label>
|
|
758
|
-
<
|
|
759
|
+
<Switch
|
|
759
760
|
id="share-password-required"
|
|
760
761
|
checked={passwordEnabled}
|
|
761
762
|
disabled={!canEdit || updateRecording.isPending}
|
|
@@ -765,7 +766,7 @@ function RecordingAdvancedAccess({
|
|
|
765
766
|
|
|
766
767
|
{passwordEnabled ? (
|
|
767
768
|
<>
|
|
768
|
-
<div className="
|
|
769
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-2">
|
|
769
770
|
<Input
|
|
770
771
|
size="sm"
|
|
771
772
|
type="text"
|
|
@@ -778,12 +779,10 @@ function RecordingAdvancedAccess({
|
|
|
778
779
|
? t("playerSettings.passwordSetPlaceholder")
|
|
779
780
|
: t("playerSettings.passwordInputPlaceholder")
|
|
780
781
|
}
|
|
781
|
-
className="min-w-0"
|
|
782
782
|
/>
|
|
783
783
|
<Button
|
|
784
784
|
type="button"
|
|
785
785
|
size="sm"
|
|
786
|
-
className="shrink-0"
|
|
787
786
|
disabled={
|
|
788
787
|
!canEdit || updateRecording.isPending || !password.trim()
|
|
789
788
|
}
|
|
@@ -815,7 +814,7 @@ function RecordingAdvancedAccess({
|
|
|
815
814
|
<Label htmlFor="share-expiry" className="text-sm font-normal">
|
|
816
815
|
{t("shareDialog.linkExpires")}
|
|
817
816
|
</Label>
|
|
818
|
-
<div className="
|
|
817
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-2">
|
|
819
818
|
<Input
|
|
820
819
|
id="share-expiry"
|
|
821
820
|
size="sm"
|
|
@@ -825,12 +824,10 @@ function RecordingAdvancedAccess({
|
|
|
825
824
|
onChange={(event) =>
|
|
826
825
|
setExpiryDraft(fromDatetimeLocal(event.target.value))
|
|
827
826
|
}
|
|
828
|
-
className="min-w-0"
|
|
829
827
|
/>
|
|
830
828
|
<Button
|
|
831
829
|
type="button"
|
|
832
830
|
size="sm"
|
|
833
|
-
className="shrink-0"
|
|
834
831
|
disabled={!canEdit || updateRecording.isPending}
|
|
835
832
|
onClick={() => {
|
|
836
833
|
const nextExpiry = expiryDraft || null;
|
|
@@ -92,7 +92,7 @@ export function ShareAdvancedPopover({
|
|
|
92
92
|
align="start"
|
|
93
93
|
sideOffset={6}
|
|
94
94
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
95
|
-
className="w-
|
|
95
|
+
className="w-80 max-w-[calc(100vw-2rem)] space-y-3 p-3"
|
|
96
96
|
>
|
|
97
97
|
{children}
|
|
98
98
|
</PopoverContent>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { useLabState } from "@agent-native/core/client/labs";
|
|
3
|
+
import { CLIPS_SCREENSHOTS } from "@shared/labs";
|
|
4
|
+
import { Navigate } from "react-router";
|
|
2
5
|
|
|
3
6
|
import { LibraryGrid } from "@/components/library/library-grid";
|
|
4
7
|
import { ScreenshotPrimaryAction } from "@/components/library/library-primary-actions";
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
5
9
|
import enMessages from "@/i18n/en-US";
|
|
6
10
|
|
|
7
11
|
export function meta() {
|
|
@@ -16,6 +20,18 @@ export function meta() {
|
|
|
16
20
|
*/
|
|
17
21
|
export default function ScreenshotsRoute() {
|
|
18
22
|
const t = useT();
|
|
23
|
+
const lab = useLabState(CLIPS_SCREENSHOTS.key);
|
|
24
|
+
if (lab.isLoading) return null;
|
|
25
|
+
if (lab.isError) {
|
|
26
|
+
return (
|
|
27
|
+
<div role="alert" className="flex flex-col items-center gap-4 py-20">
|
|
28
|
+
<p>{t("sharePage.somethingWentWrong")}</p>
|
|
29
|
+
<Button onClick={lab.refetch}>{t("libraryGrid.retry")}</Button>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
if (!lab.isSuccess) return null;
|
|
34
|
+
if (!lab.enabled) return <Navigate replace to="/library" />;
|
|
19
35
|
return (
|
|
20
36
|
<LibraryGrid
|
|
21
37
|
view="library"
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
SettingsTabsPage,
|
|
5
5
|
useAgentSettingsTabs,
|
|
6
6
|
} from "@agent-native/toolkit/app/settings";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CLIPS_LABS,
|
|
9
|
+
CLIPS_SCREENSHOTS,
|
|
10
|
+
CLIPS_VOICE_DICTATION,
|
|
11
|
+
} from "@shared/labs";
|
|
8
12
|
import { useMemo } from "react";
|
|
9
13
|
|
|
10
14
|
import { useClipsSettingsRedesign } from "@/components/settings/clips-settings-redesign";
|
|
@@ -54,6 +58,13 @@ export default function SettingsIndexRoute() {
|
|
|
54
58
|
description: t("settings.labLookbackContextDescription"),
|
|
55
59
|
};
|
|
56
60
|
}
|
|
61
|
+
if (lab.key === CLIPS_SCREENSHOTS.key) {
|
|
62
|
+
return {
|
|
63
|
+
...lab,
|
|
64
|
+
displayName: t("navigation.screenshots"),
|
|
65
|
+
description: t("preRecord.takeScreenshot"),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
57
68
|
if (lab.key === CLIPS_VOICE_DICTATION.key) {
|
|
58
69
|
return {
|
|
59
70
|
...lab,
|
|
@@ -13,7 +13,18 @@ import {
|
|
|
13
13
|
} from "@agent-native/core/server";
|
|
14
14
|
import { ForbiddenError, resolveAccess } from "@agent-native/core/sharing";
|
|
15
15
|
import { isEmailDerivedName } from "@agent-native/core/user-profile";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
and,
|
|
18
|
+
asc,
|
|
19
|
+
count,
|
|
20
|
+
eq,
|
|
21
|
+
inArray,
|
|
22
|
+
isNotNull,
|
|
23
|
+
isNull,
|
|
24
|
+
sql,
|
|
25
|
+
type SQL,
|
|
26
|
+
} from "drizzle-orm";
|
|
27
|
+
import { alias } from "drizzle-orm/pg-core";
|
|
17
28
|
import { getQuery, getRequestHeader, getRouterParam, type H3Event } from "h3";
|
|
18
29
|
import { z } from "zod";
|
|
19
30
|
|
|
@@ -29,8 +40,13 @@ import {
|
|
|
29
40
|
CONTENT_CLIP_CLAIMS,
|
|
30
41
|
CONTENT_CLIP_COMMENT_AUTHOR_CHARS,
|
|
31
42
|
CONTENT_CLIP_COMMENT_PREVIEW_CHARS,
|
|
43
|
+
CONTENT_CLIP_COMMENT_TEXT_CHARS,
|
|
44
|
+
CONTENT_CLIP_MAX_COMMENT_CHARS,
|
|
32
45
|
CONTENT_CLIP_MAX_COMMENT_THREADS,
|
|
46
|
+
CONTENT_CLIP_MAX_LISTED_REPLIES,
|
|
33
47
|
CONTENT_CLIP_MAX_TOKEN_SECONDS,
|
|
48
|
+
CONTENT_CLIP_MAX_VIDEO_TIMESTAMP_MS,
|
|
49
|
+
CONTENT_CLIP_OPEN_THREADS_VIEW,
|
|
34
50
|
CONTENT_CLIP_PUBLIC_READER,
|
|
35
51
|
CONTENT_CLIP_RECORDING_TITLE_CHARS,
|
|
36
52
|
CONTENT_CLIP_SCOPES,
|
|
@@ -92,10 +108,8 @@ const MAX_DOCUMENT_ID_CHARS = 256;
|
|
|
92
108
|
const MAX_ISSUER_CHARS = 2048;
|
|
93
109
|
const MAX_VIEWER_NAME_CHARS = 200;
|
|
94
110
|
const ISSUED_AT_SKEW_SECONDS = 60;
|
|
95
|
-
const MAX_COMMENT_CHARS = 10_000;
|
|
96
111
|
const MAX_COMMENT_ID_CHARS = 128;
|
|
97
112
|
const MAX_EMOJI_CHARS = 32;
|
|
98
|
-
const MAX_VIDEO_TIMESTAMP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
99
113
|
const MAX_BODY_BYTES = 64 * 1024;
|
|
100
114
|
const viewerEmailSchema = z.string().email().max(320);
|
|
101
115
|
|
|
@@ -556,50 +570,152 @@ const commentListQuerySchema = z.object({
|
|
|
556
570
|
.transform(Number)
|
|
557
571
|
.pipe(z.number().int().min(1).max(CONTENT_CLIP_MAX_COMMENT_THREADS))
|
|
558
572
|
.default(CONTENT_CLIP_MAX_COMMENT_THREADS),
|
|
573
|
+
view: z.literal(CONTENT_CLIP_OPEN_THREADS_VIEW).optional(),
|
|
559
574
|
});
|
|
560
575
|
|
|
561
576
|
/**
|
|
562
577
|
* The first `limit` thread starts by video time, as `list-comments` orders
|
|
563
|
-
* them, reading one more to tell a full page from the whole list.
|
|
564
|
-
*
|
|
565
|
-
*
|
|
578
|
+
* them, reading one more to tell a full page from the whole list. The
|
|
579
|
+
* open-threads view lists only open threads, each with its replies up to the
|
|
580
|
+
* contract's bound. Text and names are cut to the contract's lengths. A
|
|
581
|
+
* signed-out reader's list carries no commenter's email, nor a name made
|
|
582
|
+
* from one.
|
|
566
583
|
*/
|
|
567
|
-
async function listCommentThreads(
|
|
584
|
+
async function listCommentThreads(
|
|
585
|
+
peer: ContentClipPeer,
|
|
586
|
+
limit: number,
|
|
587
|
+
openThreads: boolean,
|
|
588
|
+
) {
|
|
568
589
|
const { recordingId } = peer;
|
|
569
590
|
const signedOut = peer.viewerEmail === null;
|
|
570
591
|
await assertCommentAccess(recordingId, "read");
|
|
571
592
|
const comments = schema.recordingComments;
|
|
593
|
+
const text = sql<string>`left(${comments.content}, ${
|
|
594
|
+
openThreads
|
|
595
|
+
? CONTENT_CLIP_COMMENT_TEXT_CHARS
|
|
596
|
+
: CONTENT_CLIP_COMMENT_PREVIEW_CHARS
|
|
597
|
+
})`;
|
|
572
598
|
const rows = await getDb()
|
|
573
599
|
.select({
|
|
574
600
|
id: comments.id,
|
|
575
601
|
authorEmail: comments.authorEmail,
|
|
576
602
|
authorName: comments.authorName,
|
|
577
|
-
content:
|
|
603
|
+
content: text,
|
|
578
604
|
videoTimestampMs: comments.videoTimestampMs,
|
|
605
|
+
createdAt: comments.createdAt,
|
|
579
606
|
})
|
|
580
607
|
.from(comments)
|
|
581
608
|
.where(
|
|
582
|
-
and(
|
|
609
|
+
and(
|
|
610
|
+
eq(comments.recordingId, recordingId),
|
|
611
|
+
isNull(comments.parentId),
|
|
612
|
+
openThreads ? eq(comments.resolved, false) : undefined,
|
|
613
|
+
),
|
|
614
|
+
)
|
|
615
|
+
.orderBy(
|
|
616
|
+
asc(comments.videoTimestampMs),
|
|
617
|
+
asc(comments.createdAt),
|
|
618
|
+
asc(comments.id),
|
|
583
619
|
)
|
|
584
|
-
.orderBy(asc(comments.videoTimestampMs), asc(comments.createdAt))
|
|
585
620
|
.limit(limit + 1);
|
|
621
|
+
const author = <
|
|
622
|
+
Row extends { authorEmail: string; authorName: string | null },
|
|
623
|
+
>(
|
|
624
|
+
row: Row,
|
|
625
|
+
) => {
|
|
626
|
+
const authorName =
|
|
627
|
+
signedOut && isEmailDerivedName(row.authorName, row.authorEmail)
|
|
628
|
+
? null
|
|
629
|
+
: row.authorName;
|
|
630
|
+
return {
|
|
631
|
+
...row,
|
|
632
|
+
authorEmail: signedOut
|
|
633
|
+
? null
|
|
634
|
+
: row.authorEmail.slice(0, CONTENT_CLIP_COMMENT_AUTHOR_CHARS),
|
|
635
|
+
authorName:
|
|
636
|
+
authorName?.slice(0, CONTENT_CLIP_COMMENT_AUTHOR_CHARS) ?? null,
|
|
637
|
+
};
|
|
638
|
+
};
|
|
586
639
|
const threads = await hydrateCommentAuthorNames(rows.slice(0, limit));
|
|
640
|
+
const truncated = rows.length > limit;
|
|
641
|
+
if (!openThreads) {
|
|
642
|
+
return {
|
|
643
|
+
comments: threads.map(({ createdAt: _createdAt, ...thread }) =>
|
|
644
|
+
author(thread),
|
|
645
|
+
),
|
|
646
|
+
truncated,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const threadIds = threads.map((thread) => thread.id);
|
|
651
|
+
const replies = threadIds.length
|
|
652
|
+
? await listThreadReplies(recordingId, threadIds, text)
|
|
653
|
+
: { rows: [], counts: new Map<string, number>() };
|
|
654
|
+
const repliesOf = new Map<string, unknown[]>();
|
|
655
|
+
for (const { threadId, ...reply } of await hydrateCommentAuthorNames(
|
|
656
|
+
replies.rows,
|
|
657
|
+
)) {
|
|
658
|
+
repliesOf.set(threadId, [
|
|
659
|
+
...(repliesOf.get(threadId) ?? []),
|
|
660
|
+
author(reply),
|
|
661
|
+
]);
|
|
662
|
+
}
|
|
587
663
|
return {
|
|
588
|
-
comments: threads.map((thread) => {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
664
|
+
comments: threads.map((thread) => ({
|
|
665
|
+
...author(thread),
|
|
666
|
+
replyCount: replies.counts.get(thread.id) ?? 0,
|
|
667
|
+
replies: repliesOf.get(thread.id) ?? [],
|
|
668
|
+
})),
|
|
669
|
+
truncated,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* The listed threads' replies, oldest first, from the earliest thread on and
|
|
675
|
+
* no more than the contract's bound, with how many each thread has.
|
|
676
|
+
*/
|
|
677
|
+
async function listThreadReplies(
|
|
678
|
+
recordingId: string,
|
|
679
|
+
threadIds: string[],
|
|
680
|
+
text: SQL<string>,
|
|
681
|
+
) {
|
|
682
|
+
const comments = schema.recordingComments;
|
|
683
|
+
const root = alias(comments, "thread_start");
|
|
684
|
+
const inThreads = and(
|
|
685
|
+
eq(comments.recordingId, recordingId),
|
|
686
|
+
inArray(comments.threadId, threadIds),
|
|
687
|
+
isNotNull(comments.parentId),
|
|
688
|
+
);
|
|
689
|
+
const [rows, counts] = await Promise.all([
|
|
690
|
+
getDb()
|
|
691
|
+
.select({
|
|
692
|
+
id: comments.id,
|
|
693
|
+
threadId: comments.threadId,
|
|
694
|
+
authorEmail: comments.authorEmail,
|
|
695
|
+
authorName: comments.authorName,
|
|
696
|
+
content: text,
|
|
697
|
+
createdAt: comments.createdAt,
|
|
698
|
+
})
|
|
699
|
+
.from(comments)
|
|
700
|
+
.innerJoin(root, eq(root.id, comments.threadId))
|
|
701
|
+
.where(inThreads)
|
|
702
|
+
.orderBy(
|
|
703
|
+
asc(root.videoTimestampMs),
|
|
704
|
+
asc(root.createdAt),
|
|
705
|
+
asc(root.id),
|
|
706
|
+
asc(comments.createdAt),
|
|
707
|
+
asc(comments.id),
|
|
708
|
+
)
|
|
709
|
+
.limit(CONTENT_CLIP_MAX_LISTED_REPLIES),
|
|
710
|
+
getDb()
|
|
711
|
+
.select({ threadId: comments.threadId, replies: count() })
|
|
712
|
+
.from(comments)
|
|
713
|
+
.where(inThreads)
|
|
714
|
+
.groupBy(comments.threadId),
|
|
715
|
+
]);
|
|
716
|
+
return {
|
|
717
|
+
rows,
|
|
718
|
+
counts: new Map(counts.map((row) => [row.threadId, Number(row.replies)])),
|
|
603
719
|
};
|
|
604
720
|
}
|
|
605
721
|
|
|
@@ -629,21 +745,35 @@ export async function handleContentClipComments(
|
|
|
629
745
|
);
|
|
630
746
|
}
|
|
631
747
|
try {
|
|
632
|
-
return json(
|
|
748
|
+
return json(
|
|
749
|
+
await listCommentThreads(
|
|
750
|
+
peer,
|
|
751
|
+
query.data.limit,
|
|
752
|
+
query.data.view === CONTENT_CLIP_OPEN_THREADS_VIEW,
|
|
753
|
+
),
|
|
754
|
+
);
|
|
633
755
|
} catch (error) {
|
|
634
756
|
return forbiddenResponse(error);
|
|
635
757
|
}
|
|
636
758
|
});
|
|
637
759
|
}
|
|
638
760
|
|
|
639
|
-
const commentText = z
|
|
761
|
+
const commentText = z
|
|
762
|
+
.string()
|
|
763
|
+
.trim()
|
|
764
|
+
.min(1)
|
|
765
|
+
.max(CONTENT_CLIP_MAX_COMMENT_CHARS);
|
|
640
766
|
const commentId = z.string().trim().min(1).max(MAX_COMMENT_ID_CHARS);
|
|
641
767
|
const commentWriteSchema = z.discriminatedUnion("operation", [
|
|
642
768
|
z
|
|
643
769
|
.object({
|
|
644
770
|
operation: z.literal("add"),
|
|
645
771
|
content: commentText,
|
|
646
|
-
videoTimestampMs: z
|
|
772
|
+
videoTimestampMs: z
|
|
773
|
+
.number()
|
|
774
|
+
.int()
|
|
775
|
+
.min(0)
|
|
776
|
+
.max(CONTENT_CLIP_MAX_VIDEO_TIMESTAMP_MS),
|
|
647
777
|
})
|
|
648
778
|
.strict(),
|
|
649
779
|
z
|