@agent-native/core 0.136.4 → 0.137.0
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/clips/.agents/skills/recording/SKILL.md +53 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +7 -0
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +24 -4
- package/corpus/templates/clips/app/components/import-menu.tsx +109 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
- package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
- package/corpus/templates/clips/changelog/2026-08-03-restart-during-a-recording-now-immediately-starts-a-fresh-ta.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +81 -30
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +436 -158
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +14 -1
- package/corpus/templates/clips/server/plugins/auth.ts +9 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +13 -0
- package/corpus/templates/content/actions/_database-utils.ts +6 -0
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
- package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
- package/corpus/templates/content/actions/configure-document-property.ts +113 -32
- package/corpus/templates/content/actions/delete-content-database.ts +5 -7
- package/corpus/templates/content/actions/delete-document-property.ts +109 -62
- package/corpus/templates/content/actions/delete-document.ts +178 -36
- package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
- package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
- package/corpus/templates/content/actions/remove-database-items.ts +13 -0
- package/corpus/templates/content/actions/set-document-property.ts +69 -1
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +24 -0
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
- package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
- package/corpus/templates/design/app/i18n-data.ts +8 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
- package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
- package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
- package/corpus/templates/factory/.env.example +17 -0
- package/corpus/templates/factory/AGENTS.md +27 -17
- package/corpus/templates/factory/README.md +19 -9
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
- package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
- package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
- package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
- package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
- package/corpus/templates/factory/app/routes/factory.tsx +462 -9
- package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
- package/corpus/templates/factory/netlify.toml +2 -2
- package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
- package/corpus/templates/factory/server/connectors/slack.ts +133 -5
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
- package/corpus/templates/factory/server/plugins/auth.ts +1 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
- package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
- package/corpus/templates/factory/server/triage/contracts.ts +14 -1
- package/corpus/templates/factory/server/triage/github-client.ts +404 -0
- package/corpus/templates/factory/server/triage/metadata.ts +44 -0
- package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
- package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
- package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
- package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
- package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
- package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
- package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
- package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
- package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
- package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
- package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +17 -2
- package/dist/cli/skills-content/canvas.d.ts +3 -3
- package/dist/cli/skills-content/canvas.js +16 -5
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +27 -0
- package/dist/cli/skills-content/wireframe.d.ts +2 -2
- package/dist/cli/skills-content/wireframe.js +7 -0
- package/dist/client/AgentPanel.js +23 -8
- package/dist/client/api-path.d.ts +6 -0
- package/dist/client/api-path.js +55 -0
- package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
- package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
- package/dist/client/onboarding/first-run-enabled.js +9 -0
- package/dist/client/onboarding/index.d.ts +2 -1
- package/dist/client/onboarding/index.js +1 -0
- package/dist/client/onboarding/use-onboarding.d.ts +6 -1
- package/dist/client/onboarding/use-onboarding.js +32 -1
- package/dist/client/route-state.js +10 -0
- package/dist/client/settings/SecretsSection.js +32 -8
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +4 -4
- package/dist/connections/catalog.js +3 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.js +5 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.d.ts +4 -0
- package/dist/onboarding/app-profile.js +378 -0
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +1 -0
- package/dist/onboarding/plugin.d.ts +2 -0
- package/dist/onboarding/plugin.js +21 -1
- package/dist/onboarding/types.d.ts +19 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/secrets/routes.d.ts +2 -2
- package/dist/secrets/routes.js +41 -17
- package/dist/server/auth.js +21 -1
- package/dist/server/better-auth-instance.d.ts +3 -2
- package/dist/server/better-auth-instance.js +9 -7
- package/dist/server/email-markdown.js +3 -8
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/first-run-onboarding.d.ts +3 -0
- package/dist/shared/first-run-onboarding.js +3 -0
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +5 -0
- package/docs/content/cross-app-sso.mdx +26 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -189,6 +189,59 @@ When mode is `screen+camera`, "the bubble" is two different things:
|
|
|
189
189
|
|
|
190
190
|
Because that draw loop runs continuously for the whole recording, keep its CPU/GPU cost bounded: a Worker-based timer drives the draw loop at the capture frame rate (`SCREEN_CAPTURE_FRAME_RATE`, 24fps — falling back to `requestAnimationFrame` only if Worker creation fails, e.g. under a strict CSP), the canvas is hard-capped to 1080p-class dimensions (1920px on its longest edge) even if the source display track is Retina/4K, and the bubble's drop shadow is pre-rendered into a small cached sprite (keyed by bubble size) instead of re-blurring with `shadowBlur` every frame.
|
|
191
191
|
|
|
192
|
+
## Restart (desktop)
|
|
193
|
+
|
|
194
|
+
Restart throws the current take away and immediately starts another one. It
|
|
195
|
+
must never re-acquire the screen: the toolbar click reaches the popover through
|
|
196
|
+
async Tauri IPC, which carries no user activation, so a second `getDisplayMedia`
|
|
197
|
+
would throw. Instead the dying session hands its live display and mic streams to
|
|
198
|
+
the replacement — `RecorderHandle.discardForRestart()` returns a `RestartHandoff`
|
|
199
|
+
that `startRecording` accepts as `preAcquiredDisplayStream` /
|
|
200
|
+
`preAcquiredAudioStream`.
|
|
201
|
+
|
|
202
|
+
Ownership is the opposite of the camera's. The popover owns the camera stream for
|
|
203
|
+
the whole session and re-hands it unchanged, so restart must not bump
|
|
204
|
+
`bubbleSessionEpoch` or clear `bubbleStreamTransferredToRecorder`. The handed-off
|
|
205
|
+
display and mic streams belong to the **recorder**, so the new session stops them
|
|
206
|
+
on its own stop/cancel, and whoever asked for the restart must stop them if the
|
|
207
|
+
new session never comes up.
|
|
208
|
+
|
|
209
|
+
`cancel()` and `discardForRestart()` share one `discardTake(forRestart)` per
|
|
210
|
+
backend, and the difference is which teardown they run. Cancel ends the
|
|
211
|
+
*session*: `hide_overlays` (which destroys the camera bubble window too) and
|
|
212
|
+
`clearRecordingState()`. A restart ends only the *take*, so it uses
|
|
213
|
+
`hide_recording_chrome`, which spares the bubble, and leaves the recording state
|
|
214
|
+
active.
|
|
215
|
+
|
|
216
|
+
Stop, cancel and restart are mutually exclusive terminal transitions, so each
|
|
217
|
+
gets its own promise slot — never reuse `cancelPromise` for a discard, or a
|
|
218
|
+
cancel arriving mid-restart returns the take-level teardown and the session
|
|
219
|
+
never ends. A cancel that lands after a discard still owes the session half,
|
|
220
|
+
plus stopping the capture the retake never took ownership of. On the app side
|
|
221
|
+
`restartInFlightRef` latches synchronously so stop and cancel events cannot act
|
|
222
|
+
on the recorder a restart is already tearing down.
|
|
223
|
+
|
|
224
|
+
Native full-screen backends re-acquire capture in Rust and hand off nothing.
|
|
225
|
+
`resolveRestartHandoff` vets the inherited streams before anything is acquired,
|
|
226
|
+
and it treats the two kinds of capture differently: an ended display share is
|
|
227
|
+
fatal and fails with `RESTART_CAPTURE_ENDED_MESSAGE`, because re-acquiring it
|
|
228
|
+
would surface an activation error that names the wrong cause, while an ended
|
|
229
|
+
microphone is just dropped and re-acquired normally. A restart also has to wait
|
|
230
|
+
for the old take's `transcriptionCapture.cancel()` — the engine is process-global
|
|
231
|
+
(`audio_transcription_stop` / `native_speech_stop`), so a late cancel would stop
|
|
232
|
+
the replacement's engine. And `stop()` after a discard must throw rather than
|
|
233
|
+
answer with the discarded take's id, or an aborted recording gets published as a
|
|
234
|
+
finished one. The
|
|
235
|
+
recording-flow latches (`recordingFlowGateRef`, `recordingFlowActive`,
|
|
236
|
+
`clipsForceAlive`, `set_recording_state`) stay held across the restart; releasing
|
|
237
|
+
them the way cancel does lets the popover blur auto-hide fire mid-restart.
|
|
238
|
+
|
|
239
|
+
Holding those latches has a catch. The `show_toolbar` effect is keyed on
|
|
240
|
+
`isRecording || recordingFlowActive`, so it does not re-run when the flow never
|
|
241
|
+
leaves — but the discard already closed the toolbar window. Restart therefore
|
|
242
|
+
bumps `recordingChromeEpoch` to rebuild it. The countdown needs no such nudge;
|
|
243
|
+
the recorder recreates it on every start.
|
|
244
|
+
|
|
192
245
|
## Error recovery
|
|
193
246
|
|
|
194
247
|
| Failure | Handling |
|
|
@@ -387,11 +387,18 @@ export default defineAction({
|
|
|
387
387
|
await writeAppState("navigate", { view: "recording", recordingId: id });
|
|
388
388
|
|
|
389
389
|
try {
|
|
390
|
+
console.log("[import-loom-recording] dispatching loom-import job", {
|
|
391
|
+
recordingId: id,
|
|
392
|
+
provider: providerId,
|
|
393
|
+
});
|
|
390
394
|
await dispatchPostFinalizeJob({
|
|
391
395
|
recordingId: id,
|
|
392
396
|
kind: "loom-import",
|
|
393
397
|
requireAccepted: true,
|
|
394
398
|
});
|
|
399
|
+
console.log("[import-loom-recording] loom-import job accepted", {
|
|
400
|
+
recordingId: id,
|
|
401
|
+
});
|
|
395
402
|
} catch (err) {
|
|
396
403
|
const failureReason = `Could not start the Loom import: ${
|
|
397
404
|
err instanceof Error ? err.message : String(err)
|
|
@@ -55,6 +55,11 @@ export async function failLoomImport(
|
|
|
55
55
|
failureReason: string,
|
|
56
56
|
claimId?: string,
|
|
57
57
|
): Promise<LoomImportJobResult> {
|
|
58
|
+
console.error("[loom-import] failed", {
|
|
59
|
+
recordingId,
|
|
60
|
+
claimId,
|
|
61
|
+
failureReason,
|
|
62
|
+
});
|
|
58
63
|
const now = new Date().toISOString();
|
|
59
64
|
const [updated] = await getDb()
|
|
60
65
|
.update(schema.recordings)
|
|
@@ -140,9 +145,16 @@ export async function runLoomImportJob({
|
|
|
140
145
|
);
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
console.log("[loom-import] job started", { recordingId, claimId });
|
|
149
|
+
|
|
143
150
|
let media: Awaited<ReturnType<typeof downloadLoomVideo>>;
|
|
144
151
|
try {
|
|
145
152
|
media = await downloadLoomVideo({ loomId, shareUrl });
|
|
153
|
+
console.log("[loom-import] download complete", {
|
|
154
|
+
recordingId,
|
|
155
|
+
bytes: media.sizeBytes,
|
|
156
|
+
mimeType: media.mimeType,
|
|
157
|
+
});
|
|
146
158
|
} catch (err) {
|
|
147
159
|
return failLoomImport(
|
|
148
160
|
recordingId,
|
|
@@ -167,6 +179,10 @@ export async function runLoomImportJob({
|
|
|
167
179
|
claimId,
|
|
168
180
|
);
|
|
169
181
|
}
|
|
182
|
+
console.log("[loom-import] reupload complete", {
|
|
183
|
+
recordingId,
|
|
184
|
+
videoUrl: upload.url,
|
|
185
|
+
});
|
|
170
186
|
|
|
171
187
|
const now = new Date().toISOString();
|
|
172
188
|
const [mediaReady] = await db
|
|
@@ -187,11 +203,15 @@ export async function runLoomImportJob({
|
|
|
187
203
|
)
|
|
188
204
|
.returning({ id: schema.recordings.id });
|
|
189
205
|
if (!mediaReady) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
const failureReason =
|
|
207
|
+
"The Loom import lease was lost before media was saved.";
|
|
208
|
+
console.warn("[loom-import] lease lost before ready", {
|
|
209
|
+
recordingId,
|
|
210
|
+
claimId,
|
|
211
|
+
});
|
|
212
|
+
return { status: "failed", failureReason };
|
|
194
213
|
}
|
|
214
|
+
console.log("[loom-import] recording ready", { recordingId });
|
|
195
215
|
|
|
196
216
|
void queueBuilderMediaCompression({
|
|
197
217
|
recordingId,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { IconChevronDown, IconLink, IconUpload } from "@tabler/icons-react";
|
|
3
|
+
import { Link } from "react-router";
|
|
4
|
+
|
|
5
|
+
import { Button, type ButtonProps } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from "@/components/ui/dropdown-menu";
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipTrigger,
|
|
16
|
+
} from "@/components/ui/tooltip";
|
|
17
|
+
import { cn } from "@/lib/utils";
|
|
18
|
+
|
|
19
|
+
type MenuSide = "top" | "right" | "bottom" | "left";
|
|
20
|
+
type MenuAlign = "start" | "center" | "end";
|
|
21
|
+
|
|
22
|
+
export interface ImportMenuProps {
|
|
23
|
+
uploadHref?: string;
|
|
24
|
+
onUpload?: () => void;
|
|
25
|
+
importLoomHref?: string;
|
|
26
|
+
className?: string;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
iconOnly?: boolean;
|
|
29
|
+
menuAlign?: MenuAlign;
|
|
30
|
+
menuSide?: MenuSide;
|
|
31
|
+
size?: ButtonProps["size"];
|
|
32
|
+
variant?: ButtonProps["variant"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ImportMenu({
|
|
36
|
+
uploadHref,
|
|
37
|
+
onUpload,
|
|
38
|
+
importLoomHref,
|
|
39
|
+
className,
|
|
40
|
+
disabled,
|
|
41
|
+
iconOnly = false,
|
|
42
|
+
menuAlign = "center",
|
|
43
|
+
menuSide,
|
|
44
|
+
size = iconOnly ? "icon" : "default",
|
|
45
|
+
variant = "outline",
|
|
46
|
+
}: ImportMenuProps) {
|
|
47
|
+
const t = useT();
|
|
48
|
+
|
|
49
|
+
if (!uploadHref && !onUpload && !importLoomHref) return null;
|
|
50
|
+
|
|
51
|
+
const trigger = (
|
|
52
|
+
<Button
|
|
53
|
+
type="button"
|
|
54
|
+
variant={variant}
|
|
55
|
+
size={size}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
aria-label={t("preRecord.import")}
|
|
58
|
+
className={cn(!iconOnly && "gap-2", className)}
|
|
59
|
+
>
|
|
60
|
+
<IconUpload />
|
|
61
|
+
{!iconOnly ? (
|
|
62
|
+
<>
|
|
63
|
+
{t("preRecord.import")}
|
|
64
|
+
<IconChevronDown />
|
|
65
|
+
</>
|
|
66
|
+
) : null}
|
|
67
|
+
</Button>
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<DropdownMenu>
|
|
72
|
+
{iconOnly ? (
|
|
73
|
+
<Tooltip>
|
|
74
|
+
<TooltipTrigger asChild>
|
|
75
|
+
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
|
76
|
+
</TooltipTrigger>
|
|
77
|
+
<TooltipContent side={menuSide ?? "right"}>
|
|
78
|
+
{t("preRecord.import")}
|
|
79
|
+
</TooltipContent>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
) : (
|
|
82
|
+
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
|
83
|
+
)}
|
|
84
|
+
<DropdownMenuContent align={menuAlign} side={menuSide} className="w-56">
|
|
85
|
+
{uploadHref ? (
|
|
86
|
+
<DropdownMenuItem asChild>
|
|
87
|
+
<Link to={uploadHref}>
|
|
88
|
+
<IconUpload />
|
|
89
|
+
{t("preRecord.uploadVideo")}
|
|
90
|
+
</Link>
|
|
91
|
+
</DropdownMenuItem>
|
|
92
|
+
) : onUpload ? (
|
|
93
|
+
<DropdownMenuItem onSelect={onUpload}>
|
|
94
|
+
<IconUpload />
|
|
95
|
+
{t("preRecord.uploadVideo")}
|
|
96
|
+
</DropdownMenuItem>
|
|
97
|
+
) : null}
|
|
98
|
+
{importLoomHref ? (
|
|
99
|
+
<DropdownMenuItem asChild>
|
|
100
|
+
<Link to={importLoomHref}>
|
|
101
|
+
<IconLink />
|
|
102
|
+
{t("preRecord.importLoom")}
|
|
103
|
+
</Link>
|
|
104
|
+
</DropdownMenuItem>
|
|
105
|
+
) : null}
|
|
106
|
+
</DropdownMenuContent>
|
|
107
|
+
</DropdownMenu>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -8,13 +8,12 @@ import {
|
|
|
8
8
|
IconChevronLeft,
|
|
9
9
|
IconChevronRight,
|
|
10
10
|
IconPlayerRecord,
|
|
11
|
-
IconUpload,
|
|
12
|
-
IconLink,
|
|
13
11
|
} from "@tabler/icons-react";
|
|
14
12
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
15
13
|
import { NavLink } from "react-router";
|
|
16
14
|
import { toast } from "sonner";
|
|
17
15
|
|
|
16
|
+
import { ImportMenu } from "@/components/import-menu";
|
|
18
17
|
import { CreateFolderDialog } from "@/components/library/create-folder-dialog";
|
|
19
18
|
import { ShareRecordingDialog } from "@/components/player/share-dialog";
|
|
20
19
|
import { Button } from "@/components/ui/button";
|
|
@@ -101,22 +100,13 @@ function NewRecordingTile({
|
|
|
101
100
|
{t("navigation.newRecording")}
|
|
102
101
|
</NavLink>
|
|
103
102
|
</Button>
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
</NavLink>
|
|
112
|
-
<NavLink
|
|
113
|
-
to={importHref}
|
|
114
|
-
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground"
|
|
115
|
-
>
|
|
116
|
-
<IconLink className="h-3.5 w-3.5" />
|
|
117
|
-
{t("preRecord.importLoom")}
|
|
118
|
-
</NavLink>
|
|
119
|
-
</div>
|
|
103
|
+
<ImportMenu
|
|
104
|
+
uploadHref={uploadHref}
|
|
105
|
+
importLoomHref={importHref}
|
|
106
|
+
size="sm"
|
|
107
|
+
variant="ghost"
|
|
108
|
+
className="h-auto px-0 text-xs font-medium text-muted-foreground hover:bg-transparent hover:text-foreground"
|
|
109
|
+
/>
|
|
120
110
|
</div>
|
|
121
111
|
);
|
|
122
112
|
}
|
|
@@ -32,8 +32,6 @@ import {
|
|
|
32
32
|
IconShare,
|
|
33
33
|
IconSettings,
|
|
34
34
|
IconSearch,
|
|
35
|
-
IconUpload,
|
|
36
|
-
IconLink,
|
|
37
35
|
} from "@tabler/icons-react";
|
|
38
36
|
import { ReactNode, useEffect, useMemo, useState } from "react";
|
|
39
37
|
import { NavLink, useLocation, useParams } from "react-router";
|
|
@@ -43,6 +41,7 @@ import {
|
|
|
43
41
|
CaptureInstallButton,
|
|
44
42
|
CaptureInstallInlineLink,
|
|
45
43
|
} from "@/components/capture-install-options";
|
|
44
|
+
import { ImportMenu } from "@/components/import-menu";
|
|
46
45
|
import {
|
|
47
46
|
AlertDialog,
|
|
48
47
|
AlertDialogAction,
|
|
@@ -400,34 +399,14 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
400
399
|
</div>
|
|
401
400
|
|
|
402
401
|
<div className="flex flex-col items-center gap-1 px-2">
|
|
403
|
-
<
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
</NavLink>
|
|
412
|
-
</TooltipTrigger>
|
|
413
|
-
<TooltipContent side="right">
|
|
414
|
-
{t("preRecord.uploadVideo")}
|
|
415
|
-
</TooltipContent>
|
|
416
|
-
</Tooltip>
|
|
417
|
-
<Tooltip>
|
|
418
|
-
<TooltipTrigger asChild>
|
|
419
|
-
<NavLink
|
|
420
|
-
to="/import"
|
|
421
|
-
aria-label={t("preRecord.importLoom")}
|
|
422
|
-
className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground"
|
|
423
|
-
>
|
|
424
|
-
<IconLink className="h-4 w-4" />
|
|
425
|
-
</NavLink>
|
|
426
|
-
</TooltipTrigger>
|
|
427
|
-
<TooltipContent side="right">
|
|
428
|
-
{t("preRecord.importLoom")}
|
|
429
|
-
</TooltipContent>
|
|
430
|
-
</Tooltip>
|
|
402
|
+
<ImportMenu
|
|
403
|
+
uploadHref="/record?autoUpload=1"
|
|
404
|
+
importLoomHref="/import"
|
|
405
|
+
iconOnly
|
|
406
|
+
variant="ghost"
|
|
407
|
+
menuSide="right"
|
|
408
|
+
menuAlign="start"
|
|
409
|
+
/>
|
|
431
410
|
</div>
|
|
432
411
|
|
|
433
412
|
<nav className="mt-3 flex flex-col items-center gap-1 px-2">
|
|
@@ -463,28 +442,13 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
463
442
|
{t("navigation.newRecording")}
|
|
464
443
|
</NavLink>
|
|
465
444
|
</Button>
|
|
466
|
-
<
|
|
467
|
-
|
|
445
|
+
<ImportMenu
|
|
446
|
+
uploadHref="/record?autoUpload=1"
|
|
447
|
+
importLoomHref="/import"
|
|
468
448
|
size="sm"
|
|
469
449
|
variant="ghost"
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
<NavLink to="/record?autoUpload=1">
|
|
473
|
-
<IconUpload className="h-4 w-4" />
|
|
474
|
-
{t("preRecord.uploadVideo")}
|
|
475
|
-
</NavLink>
|
|
476
|
-
</Button>
|
|
477
|
-
<Button
|
|
478
|
-
className="mt-1.5 w-full gap-1.5 text-muted-foreground"
|
|
479
|
-
size="sm"
|
|
480
|
-
variant="ghost"
|
|
481
|
-
asChild
|
|
482
|
-
>
|
|
483
|
-
<NavLink to="/import">
|
|
484
|
-
<IconLink className="h-4 w-4" />
|
|
485
|
-
{t("preRecord.importLoom")}
|
|
486
|
-
</NavLink>
|
|
487
|
-
</Button>
|
|
450
|
+
className="mt-1.5 w-full"
|
|
451
|
+
/>
|
|
488
452
|
</div>
|
|
489
453
|
|
|
490
454
|
<nav className="mt-3 space-y-0.5 px-2">
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
DropdownMenuItem,
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
} from "@/components/ui/dropdown-menu";
|
|
10
|
+
import {
|
|
11
|
+
Tooltip,
|
|
12
|
+
TooltipContent,
|
|
13
|
+
TooltipTrigger,
|
|
14
|
+
} from "@/components/ui/tooltip";
|
|
10
15
|
|
|
11
16
|
export type SortKey = "recent" | "views" | "oldest";
|
|
12
17
|
|
|
@@ -24,12 +29,21 @@ interface SortMenuProps {
|
|
|
24
29
|
export function SortMenu({ value, onChange }: SortMenuProps) {
|
|
25
30
|
return (
|
|
26
31
|
<DropdownMenu>
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
<Tooltip>
|
|
33
|
+
<TooltipTrigger asChild>
|
|
34
|
+
<DropdownMenuTrigger asChild>
|
|
35
|
+
<Button
|
|
36
|
+
variant="outline"
|
|
37
|
+
size="icon"
|
|
38
|
+
className="h-9 w-9"
|
|
39
|
+
aria-label={LABELS[value]}
|
|
40
|
+
>
|
|
41
|
+
<IconArrowsSort className="h-3.5 w-3.5" />
|
|
42
|
+
</Button>
|
|
43
|
+
</DropdownMenuTrigger>
|
|
44
|
+
</TooltipTrigger>
|
|
45
|
+
<TooltipContent>{LABELS[value]}</TooltipContent>
|
|
46
|
+
</Tooltip>
|
|
33
47
|
<DropdownMenuContent align="end" className="w-44">
|
|
34
48
|
{(Object.keys(LABELS) as SortKey[]).map((key) => (
|
|
35
49
|
<DropdownMenuItem
|
|
@@ -7,16 +7,14 @@ import {
|
|
|
7
7
|
IconChevronDown,
|
|
8
8
|
IconDeviceDesktop,
|
|
9
9
|
IconDeviceScreen,
|
|
10
|
-
IconLink,
|
|
11
10
|
IconMicrophone,
|
|
12
11
|
IconPlayerRecord,
|
|
13
|
-
IconUpload,
|
|
14
12
|
IconVideo,
|
|
15
13
|
} from "@tabler/icons-react";
|
|
16
14
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
17
|
-
import { Link } from "react-router";
|
|
18
15
|
|
|
19
16
|
import { CaptureInstallInlineLink } from "@/components/capture-install-options";
|
|
17
|
+
import { ImportMenu } from "@/components/import-menu";
|
|
20
18
|
import { Button } from "@/components/ui/button";
|
|
21
19
|
import {
|
|
22
20
|
Collapsible,
|
|
@@ -66,15 +64,14 @@ export interface PreRecordPanelProps {
|
|
|
66
64
|
initialDisplaySurface?: DisplaySurface | null;
|
|
67
65
|
/** Called when the user picks a local video file to upload. */
|
|
68
66
|
onUpload?: (file: File) => void;
|
|
69
|
-
/** When set,
|
|
70
|
-
* Loom-import page instead of an inline import form. */
|
|
67
|
+
/** When set, includes an "Import Loom" option in the shared import menu. */
|
|
71
68
|
importLoomHref?: string;
|
|
72
69
|
onCancel?: () => void;
|
|
73
70
|
busy?: boolean;
|
|
74
71
|
cameraSize?: CameraBubbleSize;
|
|
75
72
|
onCameraSizeChange?: (size: CameraBubbleSize) => void;
|
|
76
73
|
/** Opens the file picker once on mount, e.g. when arriving from a
|
|
77
|
-
* dedicated
|
|
74
|
+
* dedicated upload entry point elsewhere in the app. */
|
|
78
75
|
autoOpenUpload?: boolean;
|
|
79
76
|
}
|
|
80
77
|
|
|
@@ -924,34 +921,13 @@ export function PreRecordPanel({
|
|
|
924
921
|
|
|
925
922
|
{(onUpload || importLoomHref) && (
|
|
926
923
|
<>
|
|
927
|
-
<
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
<button
|
|
935
|
-
type="button"
|
|
936
|
-
disabled={busy}
|
|
937
|
-
onClick={() => fileInputRef.current?.click()}
|
|
938
|
-
className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60"
|
|
939
|
-
>
|
|
940
|
-
<IconUpload className="h-4 w-4" />
|
|
941
|
-
{t("preRecord.uploadVideo")}
|
|
942
|
-
</button>
|
|
943
|
-
) : null}
|
|
944
|
-
|
|
945
|
-
{importLoomHref ? (
|
|
946
|
-
<Link
|
|
947
|
-
to={importLoomHref}
|
|
948
|
-
className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
949
|
-
>
|
|
950
|
-
<IconLink className="h-4 w-4" />
|
|
951
|
-
{t("preRecord.importLoom")}
|
|
952
|
-
</Link>
|
|
953
|
-
) : null}
|
|
954
|
-
</div>
|
|
924
|
+
<ImportMenu
|
|
925
|
+
onUpload={
|
|
926
|
+
onUpload ? () => fileInputRef.current?.click() : undefined
|
|
927
|
+
}
|
|
928
|
+
importLoomHref={importLoomHref}
|
|
929
|
+
disabled={busy}
|
|
930
|
+
/>
|
|
955
931
|
|
|
956
932
|
{onUpload ? (
|
|
957
933
|
<input
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
-
import { IconPlayerRecord } from "@tabler/icons-react";
|
|
3
2
|
import { NavLink } from "react-router";
|
|
4
3
|
|
|
5
4
|
import { LibraryGrid } from "@/components/library/library-grid";
|
|
@@ -38,7 +37,6 @@ export default function LibraryIndexRoute() {
|
|
|
38
37
|
asChild
|
|
39
38
|
>
|
|
40
39
|
<NavLink to="/record" aria-label={t("navigation.newRecording")}>
|
|
41
|
-
<IconPlayerRecord className="h-4 w-4" />
|
|
42
40
|
<span className="hidden sm:inline">
|
|
43
41
|
{t("navigation.newRecording")}
|
|
44
42
|
</span>
|