@agent-native/core 0.114.5 → 0.114.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +6 -4
- package/corpus/core/src/cli/mcp.ts +124 -0
- package/corpus/core/src/cli/skills-content/help.ts +4 -3
- package/corpus/core/src/cli/skills-content/index.ts +1 -0
- package/corpus/core/src/cli/skills-content/rewind-skill.ts +52 -0
- package/corpus/core/src/cli/skills.ts +115 -13
- package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
- package/corpus/core/src/mcp/screen-memory-stdio.ts +1360 -104
- package/corpus/core/src/mcp-client/index.ts +9 -0
- package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
- package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
- package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
- package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
- package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
- package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
- package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
- package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
- package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
- package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
- package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
- package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +1 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
- package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
- package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
- package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
- package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
- package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
- package/corpus/templates/clips/desktop/src/app.tsx +2478 -223
- package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
- package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
- package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
- package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
- package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
- package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
- package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
- package/corpus/templates/clips/desktop/src/styles.css +956 -134
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
- package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
- package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
- package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
- package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
- package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
- package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
- package/corpus/templates/clips/shared/meeting-join-url.ts +0 -5
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
- package/corpus/templates/content/AGENTS.md +4 -1
- package/corpus/templates/content/actions/_content-spaces.ts +6 -1
- package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
- package/corpus/templates/content/actions/_database-utils.ts +83 -15
- package/corpus/templates/content/actions/add-database-item.ts +3 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
- package/corpus/templates/content/actions/delete-content-database.ts +12 -9
- package/corpus/templates/content/actions/delete-database-items.ts +3 -2
- package/corpus/templates/content/actions/delete-document.ts +233 -6
- package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +4 -1
- package/corpus/templates/content/actions/list-content-databases.ts +1 -0
- package/corpus/templates/content/actions/list-documents.ts +1 -0
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
- package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
- package/corpus/templates/content/actions/pull-document.ts +15 -2
- package/corpus/templates/content/actions/restore-content-database.ts +48 -11
- package/corpus/templates/content/actions/restore-document.ts +28 -0
- package/corpus/templates/content/actions/search-documents.ts +2 -1
- package/corpus/templates/content/actions/view-screen.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
- package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
- package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
- package/corpus/templates/content/app/i18n-data.ts +83 -2
- package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
- package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +3 -0
- package/corpus/templates/content/server/db/schema.ts +2 -0
- package/corpus/templates/content/server/lib/public-documents.ts +8 -2
- package/corpus/templates/content/server/plugins/db.ts +52 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
- package/corpus/templates/content/shared/api.ts +10 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/ui/progress.tsx +3 -2
- package/dist/cli/mcp-config-writers.d.ts +2 -2
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +6 -6
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +79 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/skills-content/help.d.ts +1 -1
- package/dist/cli/skills-content/help.d.ts.map +1 -1
- package/dist/cli/skills-content/help.js +4 -3
- package/dist/cli/skills-content/help.js.map +1 -1
- package/dist/cli/skills-content/index.d.ts +1 -0
- package/dist/cli/skills-content/index.d.ts.map +1 -1
- package/dist/cli/skills-content/index.js +1 -0
- package/dist/cli/skills-content/index.js.map +1 -1
- package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
- package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
- package/dist/cli/skills-content/rewind-skill.js +53 -0
- package/dist/cli/skills-content/rewind-skill.js.map +1 -0
- package/dist/cli/skills.d.ts +6 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +90 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +3 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +4 -4
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +55 -6
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +365 -0
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp/screen-memory-stdio.js +1014 -91
- package/dist/mcp/screen-memory-stdio.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/screen-memory-local.d.ts +90 -0
- package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
- package/dist/mcp-client/screen-memory-local.js +590 -12
- package/dist/mcp-client/screen-memory-local.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
- package/src/cli/mcp-config-writers.ts +6 -4
- package/src/cli/mcp.ts +124 -0
- package/src/cli/skills-content/help.ts +4 -3
- package/src/cli/skills-content/index.ts +1 -0
- package/src/cli/skills-content/rewind-skill.ts +52 -0
- package/src/cli/skills.ts +115 -13
- package/src/client/resources/ResourceTree.tsx +11 -5
- package/src/client/resources/ResourcesPanel.tsx +141 -18
- package/src/mcp/screen-memory-stdio.ts +1360 -104
- package/src/mcp-client/index.ts +9 -0
- package/src/mcp-client/screen-memory-local.ts +847 -13
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
IconDownload,
|
|
17
17
|
IconLoader2,
|
|
18
18
|
IconTrash,
|
|
19
|
+
IconHistory,
|
|
19
20
|
} from "@tabler/icons-react";
|
|
20
21
|
import { useState } from "react";
|
|
21
22
|
import { toast } from "sonner";
|
|
@@ -84,6 +85,10 @@ export interface EditorToolbarProps {
|
|
|
84
85
|
onOpenThumbnailPicker: () => void;
|
|
85
86
|
onOpenChapters: () => void;
|
|
86
87
|
onOpenStitch: () => void;
|
|
88
|
+
onOpenRewind: () => void;
|
|
89
|
+
rewindAlreadyAdded?: boolean;
|
|
90
|
+
rewindAvailable?: boolean;
|
|
91
|
+
rewindRequiresPrivate?: boolean;
|
|
87
92
|
chaptersOpen?: boolean;
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -103,6 +108,10 @@ export function EditorToolbar({
|
|
|
103
108
|
onOpenThumbnailPicker,
|
|
104
109
|
onOpenChapters,
|
|
105
110
|
onOpenStitch,
|
|
111
|
+
onOpenRewind,
|
|
112
|
+
rewindAlreadyAdded,
|
|
113
|
+
rewindAvailable = true,
|
|
114
|
+
rewindRequiresPrivate = false,
|
|
106
115
|
chaptersOpen,
|
|
107
116
|
}: EditorToolbarProps) {
|
|
108
117
|
const t = useT();
|
|
@@ -482,6 +491,19 @@ export function EditorToolbar({
|
|
|
482
491
|
<IconPuzzle className="mr-2 h-4 w-4" />
|
|
483
492
|
{t("editorToolbar.stitchClips")}
|
|
484
493
|
</DropdownMenuItem>
|
|
494
|
+
{rewindAvailable ? (
|
|
495
|
+
<DropdownMenuItem
|
|
496
|
+
disabled={rewindAlreadyAdded}
|
|
497
|
+
onSelect={onOpenRewind}
|
|
498
|
+
>
|
|
499
|
+
<IconHistory className="mr-2 h-4 w-4" />
|
|
500
|
+
{rewindAlreadyAdded
|
|
501
|
+
? "Rewind history added"
|
|
502
|
+
: rewindRequiresPrivate
|
|
503
|
+
? "Make private and add Rewind history…"
|
|
504
|
+
: "Add what happened before…"}
|
|
505
|
+
</DropdownMenuItem>
|
|
506
|
+
) : null}
|
|
485
507
|
<DropdownMenuSeparator />
|
|
486
508
|
<DropdownMenuItem onSelect={() => setClearOpen(true)}>
|
|
487
509
|
<IconTrash className="mr-2 h-4 w-4" />
|
|
@@ -490,6 +512,20 @@ export function EditorToolbar({
|
|
|
490
512
|
</DropdownMenuContent>
|
|
491
513
|
</DropdownMenu>
|
|
492
514
|
|
|
515
|
+
{rewindAvailable && !rewindAlreadyAdded ? (
|
|
516
|
+
<Button
|
|
517
|
+
size="sm"
|
|
518
|
+
variant="ghost"
|
|
519
|
+
className="hidden shrink-0 gap-1.5 xl:inline-flex"
|
|
520
|
+
onClick={onOpenRewind}
|
|
521
|
+
>
|
|
522
|
+
<IconHistory className="h-4 w-4" />
|
|
523
|
+
{rewindRequiresPrivate
|
|
524
|
+
? "Make private + add earlier"
|
|
525
|
+
: "Add earlier…"}
|
|
526
|
+
</Button>
|
|
527
|
+
) : null}
|
|
528
|
+
|
|
493
529
|
<div className="min-w-3 flex-1" />
|
|
494
530
|
|
|
495
531
|
<Separator orientation="vertical" className="mx-1 h-6" />
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agentNativePath,
|
|
3
|
+
appBasePath,
|
|
4
|
+
} from "@agent-native/core/client/api-path";
|
|
5
|
+
import { useActionMutation } from "@agent-native/core/client/hooks";
|
|
6
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
7
|
+
import { IconHistory, IconLoader2 } from "@tabler/icons-react";
|
|
8
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogContent,
|
|
15
|
+
DialogDescription,
|
|
16
|
+
DialogFooter,
|
|
17
|
+
DialogHeader,
|
|
18
|
+
DialogTitle,
|
|
19
|
+
} from "@/components/ui/dialog";
|
|
20
|
+
import { exportConcat } from "@/lib/ffmpeg-export";
|
|
21
|
+
import { uploadFileClient } from "@/lib/upload-file-client";
|
|
22
|
+
|
|
23
|
+
interface RewindExtensionRequest {
|
|
24
|
+
requestId: string;
|
|
25
|
+
recordingId: string;
|
|
26
|
+
seconds: 30 | 300;
|
|
27
|
+
status:
|
|
28
|
+
| "pending"
|
|
29
|
+
| "processing"
|
|
30
|
+
| "ready"
|
|
31
|
+
| "applying"
|
|
32
|
+
| "applied"
|
|
33
|
+
| "failed";
|
|
34
|
+
preRollRecordingId?: string;
|
|
35
|
+
actualDurationMs?: number;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface RewindExtensionDialogProps {
|
|
40
|
+
open: boolean;
|
|
41
|
+
onOpenChange: (open: boolean) => void;
|
|
42
|
+
recordingId: string;
|
|
43
|
+
durationMs: number;
|
|
44
|
+
videoFormat: "webm" | "mp4";
|
|
45
|
+
hasAudio: boolean;
|
|
46
|
+
visibility: "private" | "org" | "public";
|
|
47
|
+
onVisibilityChanged: () => void | Promise<void>;
|
|
48
|
+
onApplied: () => void | Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function wait(ms: number): Promise<void> {
|
|
52
|
+
return new Promise((resolve) => window.setTimeout(resolve, ms));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readRequest(
|
|
56
|
+
recordingId: string,
|
|
57
|
+
): Promise<RewindExtensionRequest | null> {
|
|
58
|
+
const params = new URLSearchParams({ recordingId });
|
|
59
|
+
const response = await fetch(
|
|
60
|
+
agentNativePath(
|
|
61
|
+
`/_agent-native/actions/get-rewind-extension-request?${params}`,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
const json = await response.json().catch(() => null);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(json?.error || json?.message || "Could not check Rewind.");
|
|
67
|
+
}
|
|
68
|
+
return (json?.result ?? json)?.request ?? null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function RewindExtensionDialog({
|
|
72
|
+
open,
|
|
73
|
+
onOpenChange,
|
|
74
|
+
recordingId,
|
|
75
|
+
durationMs,
|
|
76
|
+
videoFormat,
|
|
77
|
+
hasAudio,
|
|
78
|
+
visibility,
|
|
79
|
+
onVisibilityChanged,
|
|
80
|
+
onApplied,
|
|
81
|
+
}: RewindExtensionDialogProps) {
|
|
82
|
+
const t = useT();
|
|
83
|
+
const makePrivateForRewind = useActionMutation(
|
|
84
|
+
"make-recording-private-for-rewind",
|
|
85
|
+
);
|
|
86
|
+
const requestExtension = useActionMutation("request-rewind-extension");
|
|
87
|
+
const applyExtension = useActionMutation("apply-rewind-extension");
|
|
88
|
+
const requestTranscript = useActionMutation("request-transcript" as any);
|
|
89
|
+
const [busy, setBusy] = useState(false);
|
|
90
|
+
const [status, setStatus] = useState<string | null>(null);
|
|
91
|
+
const [progress, setProgress] = useState(0);
|
|
92
|
+
const [privacyConfirmed, setPrivacyConfirmed] = useState(false);
|
|
93
|
+
const mounted = useRef(true);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
return () => {
|
|
97
|
+
mounted.current = false;
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!open && !busy) {
|
|
103
|
+
setStatus(null);
|
|
104
|
+
setProgress(0);
|
|
105
|
+
setPrivacyConfirmed(false);
|
|
106
|
+
}
|
|
107
|
+
}, [busy, open]);
|
|
108
|
+
|
|
109
|
+
const makePrivate = useCallback(async () => {
|
|
110
|
+
setBusy(true);
|
|
111
|
+
setStatus("Making this Clip private…");
|
|
112
|
+
try {
|
|
113
|
+
await makePrivateForRewind.mutateAsync({ recordingId });
|
|
114
|
+
await onVisibilityChanged();
|
|
115
|
+
setPrivacyConfirmed(true);
|
|
116
|
+
setStatus(null);
|
|
117
|
+
toast.success(t("rewindExtension.privateReady"));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
120
|
+
setStatus(null);
|
|
121
|
+
} finally {
|
|
122
|
+
setBusy(false);
|
|
123
|
+
}
|
|
124
|
+
}, [makePrivateForRewind, onVisibilityChanged, recordingId, t]);
|
|
125
|
+
|
|
126
|
+
const addFromRewind = useCallback(
|
|
127
|
+
async (seconds: 30 | 300) => {
|
|
128
|
+
setBusy(true);
|
|
129
|
+
setProgress(0);
|
|
130
|
+
try {
|
|
131
|
+
setStatus("Asking Clips Alpha for local Rewind history…");
|
|
132
|
+
const created = (await requestExtension.mutateAsync({
|
|
133
|
+
recordingId,
|
|
134
|
+
seconds,
|
|
135
|
+
})) as RewindExtensionRequest;
|
|
136
|
+
let request = created;
|
|
137
|
+
for (let attempt = 0; attempt < 160; attempt += 1) {
|
|
138
|
+
if (request.status === "failed") {
|
|
139
|
+
throw new Error(
|
|
140
|
+
request.error || "Clips Alpha could not read Rewind.",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
request.status === "ready" &&
|
|
145
|
+
request.preRollRecordingId &&
|
|
146
|
+
request.actualDurationMs
|
|
147
|
+
) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
setStatus(
|
|
151
|
+
request.status === "processing"
|
|
152
|
+
? "Clips Alpha is preparing that local interval…"
|
|
153
|
+
: "Waiting for Clips Alpha…",
|
|
154
|
+
);
|
|
155
|
+
await wait(1_500);
|
|
156
|
+
request = (await readRequest(recordingId)) ?? request;
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
request.status !== "ready" ||
|
|
160
|
+
!request.preRollRecordingId ||
|
|
161
|
+
!request.actualDurationMs
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
"Clips Alpha did not finish the Rewind request in time.",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setStatus("Combining the selected history with this Clip…");
|
|
169
|
+
const blob = await exportConcat(
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
url: `${appBasePath()}/api/video/${encodeURIComponent(request.preRollRecordingId)}`,
|
|
173
|
+
format: "mp4",
|
|
174
|
+
hasAudio,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
url: `${appBasePath()}/api/video/${encodeURIComponent(recordingId)}`,
|
|
178
|
+
format: videoFormat,
|
|
179
|
+
hasAudio,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
(next) => mounted.current && setProgress(next.progress),
|
|
183
|
+
);
|
|
184
|
+
setStatus("Saving the longer Clip…");
|
|
185
|
+
const upload = await uploadFileClient(
|
|
186
|
+
blob,
|
|
187
|
+
`rewind-${recordingId}.mp4`,
|
|
188
|
+
);
|
|
189
|
+
if (!upload?.url) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"Connect video storage before adding Rewind history.",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
await applyExtension.mutateAsync({
|
|
195
|
+
recordingId,
|
|
196
|
+
requestId: request.requestId,
|
|
197
|
+
preRollRecordingId: request.preRollRecordingId,
|
|
198
|
+
videoUrl: upload.url,
|
|
199
|
+
durationMs: durationMs + request.actualDurationMs,
|
|
200
|
+
addedMs: request.actualDurationMs,
|
|
201
|
+
});
|
|
202
|
+
if (hasAudio) {
|
|
203
|
+
void requestTranscript
|
|
204
|
+
.mutateAsync({ recordingId, force: true, regenerate: true })
|
|
205
|
+
.catch(() => {
|
|
206
|
+
toast.info(
|
|
207
|
+
"The longer Clip was saved; its transcript can be regenerated later.",
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
await onApplied();
|
|
212
|
+
toast.success(
|
|
213
|
+
`${request.actualDurationMs >= 60_000 ? "Five minutes" : "Thirty seconds"} added from Rewind.`,
|
|
214
|
+
);
|
|
215
|
+
onOpenChange(false);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
218
|
+
setStatus(null);
|
|
219
|
+
} finally {
|
|
220
|
+
if (mounted.current) {
|
|
221
|
+
setBusy(false);
|
|
222
|
+
setProgress(0);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
[
|
|
227
|
+
applyExtension,
|
|
228
|
+
durationMs,
|
|
229
|
+
hasAudio,
|
|
230
|
+
onApplied,
|
|
231
|
+
onOpenChange,
|
|
232
|
+
recordingId,
|
|
233
|
+
requestExtension,
|
|
234
|
+
requestTranscript,
|
|
235
|
+
videoFormat,
|
|
236
|
+
],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<Dialog open={open} onOpenChange={(next) => !busy && onOpenChange(next)}>
|
|
241
|
+
<DialogContent className="sm:max-w-md">
|
|
242
|
+
<DialogHeader>
|
|
243
|
+
<DialogTitle className="flex items-center gap-2">
|
|
244
|
+
<IconHistory className="h-4 w-4 text-primary" />
|
|
245
|
+
{t("rewindExtension.title")}
|
|
246
|
+
</DialogTitle>
|
|
247
|
+
<DialogDescription>
|
|
248
|
+
{t("rewindExtension.description")}
|
|
249
|
+
</DialogDescription>
|
|
250
|
+
</DialogHeader>
|
|
251
|
+
|
|
252
|
+
{busy ? (
|
|
253
|
+
<div
|
|
254
|
+
className="rounded-lg border bg-muted/30 p-4 text-sm"
|
|
255
|
+
role="status"
|
|
256
|
+
aria-live="polite"
|
|
257
|
+
>
|
|
258
|
+
<div className="flex items-center gap-2 font-medium">
|
|
259
|
+
<IconLoader2
|
|
260
|
+
className="h-4 w-4 animate-spin"
|
|
261
|
+
aria-hidden="true"
|
|
262
|
+
/>
|
|
263
|
+
{status}
|
|
264
|
+
</div>
|
|
265
|
+
{progress > 0 ? (
|
|
266
|
+
<div className="mt-3 h-1.5 overflow-hidden rounded-full bg-muted">
|
|
267
|
+
<div
|
|
268
|
+
className="h-full bg-primary transition-[width]"
|
|
269
|
+
role="progressbar"
|
|
270
|
+
aria-label={t("rewindExtension.progressLabel")}
|
|
271
|
+
aria-valuemin={0}
|
|
272
|
+
aria-valuemax={100}
|
|
273
|
+
aria-valuenow={Math.round(progress * 100)}
|
|
274
|
+
style={{ width: `${Math.round(progress * 100)}%` }}
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
) : null}
|
|
278
|
+
</div>
|
|
279
|
+
) : visibility !== "private" && !privacyConfirmed ? (
|
|
280
|
+
<div className="grid gap-3">
|
|
281
|
+
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-sm">
|
|
282
|
+
<strong className="block">
|
|
283
|
+
{t("rewindExtension.privateFirstTitle")}
|
|
284
|
+
</strong>
|
|
285
|
+
<span className="mt-1 block text-muted-foreground">
|
|
286
|
+
{t("rewindExtension.privateFirstDescription")}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
<Button onClick={() => void makePrivate()}>
|
|
290
|
+
{t("rewindExtension.makePrivateContinue")}
|
|
291
|
+
</Button>
|
|
292
|
+
</div>
|
|
293
|
+
) : (
|
|
294
|
+
<div className="grid gap-2">
|
|
295
|
+
<Button
|
|
296
|
+
variant="outline"
|
|
297
|
+
className="h-auto justify-start py-3 text-left"
|
|
298
|
+
onClick={() => void addFromRewind(30)}
|
|
299
|
+
>
|
|
300
|
+
<span>
|
|
301
|
+
<strong className="block">
|
|
302
|
+
{t("rewindExtension.add30Seconds")}
|
|
303
|
+
</strong>
|
|
304
|
+
<span className="text-xs font-normal text-muted-foreground">
|
|
305
|
+
Useful when you clicked Record just after the important bit.
|
|
306
|
+
</span>
|
|
307
|
+
</span>
|
|
308
|
+
</Button>
|
|
309
|
+
<Button
|
|
310
|
+
variant="outline"
|
|
311
|
+
className="h-auto justify-start py-3 text-left"
|
|
312
|
+
onClick={() => void addFromRewind(300)}
|
|
313
|
+
>
|
|
314
|
+
<span>
|
|
315
|
+
<strong className="block">
|
|
316
|
+
{t("rewindExtension.add5Minutes")}
|
|
317
|
+
</strong>
|
|
318
|
+
<span className="text-xs font-normal text-muted-foreground">
|
|
319
|
+
{t("rewindExtension.add5MinutesDescription")}
|
|
320
|
+
</span>
|
|
321
|
+
</span>
|
|
322
|
+
</Button>
|
|
323
|
+
</div>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
<DialogFooter>
|
|
327
|
+
<Button
|
|
328
|
+
variant="ghost"
|
|
329
|
+
disabled={busy}
|
|
330
|
+
onClick={() => onOpenChange(false)}
|
|
331
|
+
>
|
|
332
|
+
Cancel
|
|
333
|
+
</Button>
|
|
334
|
+
</DialogFooter>
|
|
335
|
+
</DialogContent>
|
|
336
|
+
</Dialog>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
@@ -24,24 +24,9 @@ import {
|
|
|
24
24
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
25
25
|
import { exportConcat } from "@/lib/ffmpeg-export";
|
|
26
26
|
import { formatMs } from "@/lib/timestamp-mapping";
|
|
27
|
+
import { uploadFileClient } from "@/lib/upload-file-client";
|
|
27
28
|
import { cn } from "@/lib/utils";
|
|
28
29
|
|
|
29
|
-
/** Client-side upload via the framework's auto-mounted `/file-upload` route. */
|
|
30
|
-
async function uploadFileClient(
|
|
31
|
-
blob: Blob,
|
|
32
|
-
filename: string,
|
|
33
|
-
): Promise<{ url: string } | null> {
|
|
34
|
-
const form = new FormData();
|
|
35
|
-
form.append("file", blob, filename);
|
|
36
|
-
const res = await fetch(agentNativePath("/_agent-native/file-upload"), {
|
|
37
|
-
method: "POST",
|
|
38
|
-
body: form,
|
|
39
|
-
});
|
|
40
|
-
if (!res.ok) return null;
|
|
41
|
-
const json = await res.json();
|
|
42
|
-
return json?.url ? { url: json.url as string } : null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
30
|
export interface StitchManagerProps {
|
|
46
31
|
open: boolean;
|
|
47
32
|
onOpenChange: (open: boolean) => void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
1
2
|
import { useMemo } from "react";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -21,6 +22,8 @@ export interface TimelineProps {
|
|
|
21
22
|
chapters?: TimelineChapter[];
|
|
22
23
|
excludedRanges?: Array<{ startMs: number; endMs: number }>;
|
|
23
24
|
splitPoints?: number[];
|
|
25
|
+
/** Countdown-complete start after explicit Rewind history was prepended. */
|
|
26
|
+
originalStartMs?: number;
|
|
24
27
|
onSeek?: (originalMs: number) => void;
|
|
25
28
|
onClickChapter?: (chapter: TimelineChapter) => void;
|
|
26
29
|
className?: string;
|
|
@@ -44,10 +47,12 @@ export function Timeline({
|
|
|
44
47
|
chapters = [],
|
|
45
48
|
excludedRanges = [],
|
|
46
49
|
splitPoints = [],
|
|
50
|
+
originalStartMs,
|
|
47
51
|
onSeek,
|
|
48
52
|
onClickChapter,
|
|
49
53
|
className,
|
|
50
54
|
}: TimelineProps) {
|
|
55
|
+
const t = useT();
|
|
51
56
|
const ticks = useMemo(() => {
|
|
52
57
|
if (durationMs <= 0) return [];
|
|
53
58
|
// Target ~1 tick per 100px at the current zoom, rounded to a human interval.
|
|
@@ -133,6 +138,27 @@ export function Timeline({
|
|
|
133
138
|
);
|
|
134
139
|
})}
|
|
135
140
|
|
|
141
|
+
{typeof originalStartMs === "number" && originalStartMs > 0 ? (
|
|
142
|
+
<Tooltip>
|
|
143
|
+
<TooltipTrigger asChild>
|
|
144
|
+
<div
|
|
145
|
+
className="absolute top-0 z-10 h-full w-0.5 cursor-help bg-amber-500"
|
|
146
|
+
style={{
|
|
147
|
+
left: (originalStartMs / Math.max(durationMs, 1)) * width - 1,
|
|
148
|
+
}}
|
|
149
|
+
aria-label={`Original Clip start at ${formatMs(originalStartMs)}`}
|
|
150
|
+
>
|
|
151
|
+
<span className="absolute left-1 top-0 whitespace-nowrap rounded-sm bg-amber-500 px-1 text-[9px] font-semibold text-black">
|
|
152
|
+
{t("timeline.clipStartedHere")}
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
</TooltipTrigger>
|
|
156
|
+
<TooltipContent>
|
|
157
|
+
Recording began after the countdown · {formatMs(originalStartMs)}
|
|
158
|
+
</TooltipContent>
|
|
159
|
+
</Tooltip>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
136
162
|
{/* Playhead */}
|
|
137
163
|
<div
|
|
138
164
|
className="absolute top-0 h-full pointer-events-none"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
-
import { resolveNativeMeetingJoinUrl } from "@shared/meeting-join-url";
|
|
3
2
|
import {
|
|
4
3
|
IconCheck,
|
|
5
4
|
IconClock,
|
|
@@ -285,7 +284,7 @@ export function UpcomingMeetingCard({
|
|
|
285
284
|
<div className="flex items-center gap-1.5">
|
|
286
285
|
{meeting.joinUrl && (
|
|
287
286
|
<a
|
|
288
|
-
href={
|
|
287
|
+
href={meeting.joinUrl}
|
|
289
288
|
target="_blank"
|
|
290
289
|
rel="noopener noreferrer"
|
|
291
290
|
onClick={(e) => e.stopPropagation()}
|
|
@@ -1493,6 +1493,24 @@ All notable user-facing changes to Clips are documented here. Open it any time f
|
|
|
1493
1493
|
guideStartDescription:
|
|
1494
1494
|
"Use the desktop reminder or the menu-bar Start Meeting Notes item when the call begins.",
|
|
1495
1495
|
},
|
|
1496
|
+
rewindExtension: {
|
|
1497
|
+
title: "Add what happened before",
|
|
1498
|
+
description:
|
|
1499
|
+
"Pull a specific interval from local Rewind and add it to the start of this Clip. Nothing is added automatically.",
|
|
1500
|
+
progressLabel: "Rewind history processing progress",
|
|
1501
|
+
privateFirstTitle: "Make this Clip private first",
|
|
1502
|
+
privateFirstDescription:
|
|
1503
|
+
"Local Rewind history can contain context from before you chose to record. This changes the Clip to private. If anyone still has direct access, Clips will stop here so you can remove them in Share first.",
|
|
1504
|
+
makePrivateContinue: "Make private and continue",
|
|
1505
|
+
add30Seconds: "Add the previous 30 seconds",
|
|
1506
|
+
add5Minutes: "Add the previous 5 minutes",
|
|
1507
|
+
add5MinutesDescription:
|
|
1508
|
+
"Good for recovering the lead-in to a longer explanation.",
|
|
1509
|
+
privateReady: "This Clip is private. You can now add local Rewind history.",
|
|
1510
|
+
},
|
|
1511
|
+
timeline: {
|
|
1512
|
+
clipStartedHere: "Clip started here",
|
|
1513
|
+
},
|
|
1496
1514
|
};
|
|
1497
1515
|
|
|
1498
1516
|
export default messages;
|
|
@@ -349,7 +349,11 @@ export async function exportGif(
|
|
|
349
349
|
* before calling the `stitch-recordings` action with the uploaded URL.
|
|
350
350
|
*/
|
|
351
351
|
export async function exportConcat(
|
|
352
|
-
sources: Array<{
|
|
352
|
+
sources: Array<{
|
|
353
|
+
url: string;
|
|
354
|
+
format?: "webm" | "mp4";
|
|
355
|
+
hasAudio?: boolean;
|
|
356
|
+
}>,
|
|
353
357
|
onProgress?: (p: ExportProgress) => void,
|
|
354
358
|
): Promise<Blob> {
|
|
355
359
|
if (sources.length < 2) {
|
|
@@ -377,38 +381,41 @@ export async function exportConcat(
|
|
|
377
381
|
"-i",
|
|
378
382
|
`src${i}.${s.format ?? "webm"}`,
|
|
379
383
|
]);
|
|
384
|
+
const includesAudio = sources.every((source) => source.hasAudio !== false);
|
|
380
385
|
const filterParts: string[] = [];
|
|
381
386
|
const concatInputs: string[] = [];
|
|
382
387
|
for (let i = 0; i < sources.length; i++) {
|
|
383
388
|
filterParts.push(`[${i}:v]setpts=PTS-STARTPTS[v${i}]`);
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
if (includesAudio) {
|
|
390
|
+
filterParts.push(`[${i}:a]asetpts=PTS-STARTPTS[a${i}]`);
|
|
391
|
+
concatInputs.push(`[v${i}][a${i}]`);
|
|
392
|
+
} else {
|
|
393
|
+
concatInputs.push(`[v${i}]`);
|
|
394
|
+
}
|
|
386
395
|
}
|
|
387
396
|
filterParts.push(
|
|
388
|
-
`${concatInputs.join("")}concat=n=${sources.length}:v=1:a
|
|
397
|
+
`${concatInputs.join("")}concat=n=${sources.length}:v=1:a=${includesAudio ? 1 : 0}[outv]${includesAudio ? "[outa]" : ""}`,
|
|
389
398
|
);
|
|
390
|
-
|
|
399
|
+
const outputArgs = [
|
|
391
400
|
...inputArgs,
|
|
392
401
|
"-filter_complex",
|
|
393
402
|
filterParts.join(";"),
|
|
394
403
|
"-map",
|
|
395
404
|
"[outv]",
|
|
396
|
-
"-map",
|
|
397
|
-
"[outa]",
|
|
398
405
|
"-c:v",
|
|
399
406
|
"libx264",
|
|
400
407
|
"-preset",
|
|
401
408
|
"veryfast",
|
|
402
409
|
"-crf",
|
|
403
410
|
"22",
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
"128k",
|
|
411
|
+
...(includesAudio
|
|
412
|
+
? ["-map", "[outa]", "-c:a", "aac", "-b:a", "128k"]
|
|
413
|
+
: []),
|
|
408
414
|
"-movflags",
|
|
409
415
|
"+faststart",
|
|
410
416
|
"stitched.mp4",
|
|
411
|
-
]
|
|
417
|
+
];
|
|
418
|
+
await ffmpeg.exec(outputArgs);
|
|
412
419
|
|
|
413
420
|
const data = (await ffmpeg.readFile("stitched.mp4")) as Uint8Array;
|
|
414
421
|
const blob = new Blob([data as BlobPart], { type: "video/mp4" });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ClipVisibility = "private" | "org" | "public";
|
|
2
|
+
|
|
3
|
+
export function isPrivateClip(
|
|
4
|
+
visibility: ClipVisibility | string | null | undefined,
|
|
5
|
+
): boolean {
|
|
6
|
+
return visibility === "private";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function canAddRewindHistory(
|
|
10
|
+
role: string | null | undefined,
|
|
11
|
+
visibility: ClipVisibility | string | null | undefined,
|
|
12
|
+
): boolean {
|
|
13
|
+
return role === "owner" && isPrivateClip(visibility);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function canOfferRewindHistory(
|
|
17
|
+
role: string | null | undefined,
|
|
18
|
+
): boolean {
|
|
19
|
+
return role === "owner";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rewindHistoryUnavailableReason(
|
|
23
|
+
role: string | null | undefined,
|
|
24
|
+
visibility: ClipVisibility | string | null | undefined,
|
|
25
|
+
): string | undefined {
|
|
26
|
+
if (role !== "owner") return "Only the owner can add Rewind history";
|
|
27
|
+
if (visibility !== "private") {
|
|
28
|
+
return "Make this Clip private before adding Rewind history";
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
@@ -45,6 +45,8 @@ export interface EditsJson {
|
|
|
45
45
|
thumbnail?: ThumbnailSpec | null;
|
|
46
46
|
/** Provenance: source recording IDs when this recording was created via stitch-recordings. */
|
|
47
47
|
stitchedFrom?: string[];
|
|
48
|
+
/** Original countdown-complete boundary after an explicit Rewind pre-roll was prepended. */
|
|
49
|
+
rewindOriginalStartMs?: number;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export const DEFAULT_EDITS: EditsJson = {
|
|
@@ -73,6 +75,11 @@ export function parseEdits(raw: string | null | undefined): EditsJson {
|
|
|
73
75
|
...(Array.isArray(j.stitchedFrom)
|
|
74
76
|
? { stitchedFrom: j.stitchedFrom as string[] }
|
|
75
77
|
: {}),
|
|
78
|
+
...(typeof j.rewindOriginalStartMs === "number" &&
|
|
79
|
+
Number.isFinite(j.rewindOriginalStartMs) &&
|
|
80
|
+
j.rewindOriginalStartMs > 0
|
|
81
|
+
? { rewindOriginalStartMs: Math.round(j.rewindOriginalStartMs) }
|
|
82
|
+
: {}),
|
|
76
83
|
};
|
|
77
84
|
} catch {
|
|
78
85
|
return { ...DEFAULT_EDITS };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { agentNativePath } from "@agent-native/core/client/api-path";
|
|
2
|
+
|
|
3
|
+
/** Upload a browser-produced file through Clips' authenticated file route. */
|
|
4
|
+
export async function uploadFileClient(
|
|
5
|
+
blob: Blob,
|
|
6
|
+
filename: string,
|
|
7
|
+
): Promise<{ url: string } | null> {
|
|
8
|
+
const form = new FormData();
|
|
9
|
+
form.append("file", blob, filename);
|
|
10
|
+
const response = await fetch(agentNativePath("/_agent-native/file-upload"), {
|
|
11
|
+
method: "POST",
|
|
12
|
+
body: form,
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) return null;
|
|
15
|
+
const json = await response.json();
|
|
16
|
+
return json?.url ? { url: json.url as string } : null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: fixed
|
|
3
|
+
date: 2026-07-17
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Active Clips now keep Rewind's shared screen and audio capture stable until the Clip ends. Rewind's on/off, pause, and capture-mode controls visibly lock during recording, with a native safeguard for non-UI callers, so changing Rewind can no longer strand a Clip in optimization with a coverage gap.
|
|
7
|
+
|
|
8
|
+
Rewind Settings also use application exclusions as the single privacy model, return Back through the surface that opened them, describe local-memory maintenance before asking someone to act, and speak plainly about local agents finding moments on request.
|
|
9
|
+
|
|
10
|
+
The recording toolbar's timer now opens Clips without stopping the active recording, and Dock, tray, and shortcut opens all restore the active popover. Rewind-derived hosted Clips now create the resumable storage session they need before recording, and exact-range encoding uses an interactive-speed preset so Stop no longer appears frozen for roughly the Clip's full duration. The compact Rewind settings rows also reserve separate label and control columns to prevent overlap.
|
|
11
|
+
|
|
12
|
+
Recorder Home now keeps its bottom navigation fixed while the content region adapts to the available popover height. After Rewind's first-time setup, its single Home switch means remembering or paused; the duplicate Pause button and active-recording lock paragraph are gone. Full disable and capture-mode setup remain in Rewind Settings, while the active-Clip switch uses an unmistakable disabled treatment until recording ends.
|