@agent-native/core 0.90.3 → 0.90.5
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/bin/agent-native.js +25 -3
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +17 -0
- package/corpus/core/bin/agent-native.js +25 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +18 -18
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
- package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
- package/corpus/core/src/agent/engine/index.ts +1 -0
- package/corpus/core/src/agent/engine/registry.ts +63 -2
- package/corpus/core/src/agent/production-agent.ts +22 -11
- package/corpus/core/src/agent/run-manager.ts +17 -6
- package/corpus/core/src/agent/run-store.ts +16 -11
- package/corpus/core/src/cli/create.ts +35 -0
- package/corpus/core/src/cli/design-connect.ts +263 -1
- package/corpus/core/src/cli/index.ts +84 -7
- package/corpus/core/src/cli/workspacify.ts +34 -0
- package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
- package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
- package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
- package/corpus/core/src/client/history/index.ts +3 -0
- package/corpus/core/src/client/history/use-history.ts +28 -0
- package/corpus/core/src/client/index.ts +8 -0
- package/corpus/core/src/client/review/index.ts +5 -0
- package/corpus/core/src/client/review/use-review.ts +40 -2
- package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
- package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
- package/corpus/core/src/history/index.ts +0 -1
- package/corpus/core/src/history/registry.ts +24 -12
- package/corpus/core/src/history/store.ts +84 -53
- package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
- package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
- package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
- package/corpus/core/src/review/index.ts +0 -5
- package/corpus/core/src/review/registry.ts +24 -12
- package/corpus/core/src/review/store.ts +10 -7
- package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
- package/corpus/core/src/server/credential-provider.ts +13 -3
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
- package/corpus/templates/assets/actions/generate-image.ts +64 -28
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
- package/corpus/templates/assets/server/lib/generation.ts +29 -4
- package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
- package/corpus/templates/calendar/AGENTS.md +3 -0
- package/corpus/templates/calendar/actions/create-event.ts +8 -34
- package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
- package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
- package/corpus/templates/calendar/actions/update-event.ts +10 -23
- package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/calendar/app/i18n-data.ts +40 -0
- package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
- package/corpus/templates/calendar/server/handlers/events.ts +1 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
- package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
- package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
- package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
- package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
- package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
- package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
- package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
- package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
- package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
- package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
- package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
- package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
- package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
- package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
- package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
- package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
- package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
- package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
- package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/desktop/src/app.tsx +435 -105
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
- package/corpus/templates/clips/desktop/src/styles.css +167 -2
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
- package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
- package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
- package/corpus/templates/content/actions/_database-utils.ts +2 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
- package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
- package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
- package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/content/app/i18n-data.ts +31 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
- package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
- package/corpus/templates/content/parity/matrix.md +24 -24
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/shared/api.ts +59 -4
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
- package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
- package/corpus/templates/design/actions/connect-localhost.ts +26 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +362 -12
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
- package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +41 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
- package/dist/agent/durable-background.d.ts +12 -12
- package/dist/agent/durable-background.js +18 -18
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +8 -2
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -1
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.d.ts.map +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/index.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +14 -0
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +56 -2
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -11
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +7 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +6 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +15 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +31 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +197 -1
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +76 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/workspacify.d.ts.map +1 -1
- package/dist/cli/workspacify.js +30 -0
- package/dist/cli/workspacify.js.map +1 -1
- package/dist/client/KeepTabOpenNotice.d.ts +9 -10
- package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
- package/dist/client/KeepTabOpenNotice.js +8 -4
- package/dist/client/KeepTabOpenNotice.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +14 -13
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +25 -17
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
- package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
- package/dist/client/history/VersionHistoryPanel.js +12 -5
- package/dist/client/history/VersionHistoryPanel.js.map +1 -1
- package/dist/client/history/index.d.ts +1 -1
- package/dist/client/history/index.d.ts.map +1 -1
- package/dist/client/history/index.js +1 -1
- package/dist/client/history/index.js.map +1 -1
- package/dist/client/history/use-history.d.ts +12 -0
- package/dist/client/history/use-history.d.ts.map +1 -1
- package/dist/client/history/use-history.js +6 -0
- package/dist/client/history/use-history.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/review/index.d.ts +1 -1
- package/dist/client/review/index.d.ts.map +1 -1
- package/dist/client/review/index.js +1 -1
- package/dist/client/review/index.js.map +1 -1
- package/dist/client/review/use-review.d.ts +23 -0
- package/dist/client/review/use-review.d.ts.map +1 -1
- package/dist/client/review/use-review.js +8 -0
- package/dist/client/review/use-review.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +6 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/history/actions/create-resource-version.js +7 -4
- package/dist/history/actions/create-resource-version.js.map +1 -1
- package/dist/history/index.d.ts +1 -1
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +1 -1
- package/dist/history/index.js.map +1 -1
- package/dist/history/registry.d.ts.map +1 -1
- package/dist/history/registry.js +14 -12
- package/dist/history/registry.js.map +1 -1
- package/dist/history/store.d.ts.map +1 -1
- package/dist/history/store.js +74 -49
- package/dist/history/store.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/review/actions/consume-review-feedback.d.ts +1 -0
- package/dist/review/actions/consume-review-feedback.js +8 -2
- package/dist/review/actions/consume-review-feedback.js.map +1 -1
- package/dist/review/actions/delete-review-comment.d.ts +2 -1
- package/dist/review/actions/delete-review-comment.js +11 -3
- package/dist/review/actions/delete-review-comment.js.map +1 -1
- package/dist/review/actions/resolve-review-thread.d.ts +2 -1
- package/dist/review/actions/resolve-review-thread.js +5 -2
- package/dist/review/actions/resolve-review-thread.js.map +1 -1
- package/dist/review/index.d.ts +1 -1
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/index.js +1 -1
- package/dist/review/index.js.map +1 -1
- package/dist/review/registry.d.ts.map +1 -1
- package/dist/review/registry.js +14 -12
- package/dist/review/registry.js.map +1 -1
- package/dist/review/store.d.ts +3 -3
- package/dist/review/store.d.ts.map +1 -1
- package/dist/review/store.js +7 -4
- package/dist/review/store.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +2 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IconAlertTriangle,
|
|
3
3
|
IconArrowLeft,
|
|
4
|
+
IconCalendarEvent,
|
|
4
5
|
IconCircleCheck,
|
|
5
6
|
IconDownload,
|
|
6
7
|
IconExternalLink,
|
|
7
8
|
IconFolderOpen,
|
|
8
9
|
IconPencil,
|
|
9
10
|
IconInfoCircle,
|
|
11
|
+
IconMicrophone2,
|
|
10
12
|
IconRefresh,
|
|
11
13
|
IconTrash,
|
|
12
14
|
IconUpload,
|
|
@@ -16,6 +18,7 @@ import { emit, listen } from "@tauri-apps/api/event";
|
|
|
16
18
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
17
19
|
import { open as openExternal } from "@tauri-apps/plugin-shell";
|
|
18
20
|
import {
|
|
21
|
+
type ReactNode,
|
|
19
22
|
type RefObject,
|
|
20
23
|
useCallback,
|
|
21
24
|
useEffect,
|
|
@@ -26,7 +29,6 @@ import {
|
|
|
26
29
|
import { FeedbackButton } from "./components/FeedbackButton";
|
|
27
30
|
import {
|
|
28
31
|
CamIcon,
|
|
29
|
-
ClockIcon,
|
|
30
32
|
CloseIcon,
|
|
31
33
|
GoogleIcon,
|
|
32
34
|
LibraryIcon,
|
|
@@ -93,14 +95,6 @@ import {
|
|
|
93
95
|
type ScreenMemoryStatus,
|
|
94
96
|
} from "./shared/config";
|
|
95
97
|
|
|
96
|
-
interface RecordingSummary {
|
|
97
|
-
id: string;
|
|
98
|
-
title: string;
|
|
99
|
-
durationMs: number;
|
|
100
|
-
thumbnailUrl: string | null;
|
|
101
|
-
updatedAt: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
98
|
interface PendingNativeUpload {
|
|
105
99
|
kind: "native";
|
|
106
100
|
recordingId: string;
|
|
@@ -121,6 +115,18 @@ interface PendingNativeUpload {
|
|
|
121
115
|
|
|
122
116
|
type PendingDesktopUpload = PendingNativeUpload | PendingBrowserRecordingUpload;
|
|
123
117
|
|
|
118
|
+
type PopoverView = "recorder" | "settings" | "meetings" | "dictation";
|
|
119
|
+
|
|
120
|
+
interface PopoverMeeting {
|
|
121
|
+
id: string;
|
|
122
|
+
title: string;
|
|
123
|
+
scheduledStart: string | null;
|
|
124
|
+
scheduledEnd: string | null;
|
|
125
|
+
joinUrl: string | null;
|
|
126
|
+
platform: string | null;
|
|
127
|
+
transcriptStatus: string | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
interface LocalRecordingNotice {
|
|
125
131
|
folderPath?: string;
|
|
126
132
|
files: LocalExportedFile[];
|
|
@@ -184,25 +190,6 @@ const DEFAULT_URL = import.meta.env.DEV
|
|
|
184
190
|
? "http://localhost:8094"
|
|
185
191
|
: "https://clips.agent-native.com";
|
|
186
192
|
|
|
187
|
-
function resolveDesktopThumbnailUrl(
|
|
188
|
-
rawUrl: string | null | undefined,
|
|
189
|
-
serverUrl: string,
|
|
190
|
-
): string | null {
|
|
191
|
-
if (!rawUrl) return null;
|
|
192
|
-
if (
|
|
193
|
-
rawUrl.startsWith("http://") ||
|
|
194
|
-
rawUrl.startsWith("https://") ||
|
|
195
|
-
rawUrl.startsWith("data:") ||
|
|
196
|
-
rawUrl.startsWith("blob:")
|
|
197
|
-
) {
|
|
198
|
-
return rawUrl;
|
|
199
|
-
}
|
|
200
|
-
if (rawUrl.startsWith("/")) {
|
|
201
|
-
return `${serverUrl.replace(/\/+$/, "")}${rawUrl}`;
|
|
202
|
-
}
|
|
203
|
-
return rawUrl;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
193
|
function normalizeCaptureSource(value: string): CaptureSource {
|
|
207
194
|
if (value === "region" && isMacPlatform()) return "region";
|
|
208
195
|
return value === "window" ? "window" : "full-screen";
|
|
@@ -467,6 +454,78 @@ function formatAgo(iso: string): string {
|
|
|
467
454
|
}
|
|
468
455
|
}
|
|
469
456
|
|
|
457
|
+
function formatMeetingWhen(meeting: PopoverMeeting): string {
|
|
458
|
+
const startMs = Date.parse(meeting.scheduledStart ?? "");
|
|
459
|
+
if (Number.isNaN(startMs)) return "Upcoming";
|
|
460
|
+
|
|
461
|
+
const endMs = Date.parse(meeting.scheduledEnd ?? "");
|
|
462
|
+
const now = Date.now();
|
|
463
|
+
if (startMs <= now && (Number.isNaN(endMs) || endMs >= now)) {
|
|
464
|
+
return "Now";
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (startMs > now && startMs - now < 60 * 60 * 1000) {
|
|
468
|
+
return `in ${Math.max(1, Math.round((startMs - now) / 60000))}m`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const start = new Date(startMs);
|
|
472
|
+
const time = start.toLocaleTimeString([], {
|
|
473
|
+
hour: "numeric",
|
|
474
|
+
minute: "2-digit",
|
|
475
|
+
});
|
|
476
|
+
const today = new Date(now);
|
|
477
|
+
const tomorrow = new Date(now + 24 * 60 * 60 * 1000);
|
|
478
|
+
if (start.toDateString() === today.toDateString()) return `Today ${time}`;
|
|
479
|
+
if (start.toDateString() === tomorrow.toDateString()) {
|
|
480
|
+
return `Tomorrow ${time}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return `${start.toLocaleDateString([], {
|
|
484
|
+
weekday: "short",
|
|
485
|
+
month: "short",
|
|
486
|
+
day: "numeric",
|
|
487
|
+
})} ${time}`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function meetingCanStartNotes(meeting: PopoverMeeting): boolean {
|
|
491
|
+
const startMs = Date.parse(meeting.scheduledStart ?? "");
|
|
492
|
+
if (Number.isNaN(startMs)) return false;
|
|
493
|
+
const endMs = Date.parse(meeting.scheduledEnd ?? "");
|
|
494
|
+
const now = Date.now();
|
|
495
|
+
return (
|
|
496
|
+
startMs <= now + 10 * 60 * 1000 && (Number.isNaN(endMs) || endMs >= now)
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function voiceShortcutLabel(
|
|
501
|
+
shortcut: VoiceShortcutPreference,
|
|
502
|
+
customShortcut: string,
|
|
503
|
+
): string {
|
|
504
|
+
switch (shortcut) {
|
|
505
|
+
case "fn":
|
|
506
|
+
return "Fn";
|
|
507
|
+
case "cmd-shift-space":
|
|
508
|
+
return "Cmd+Shift+Space";
|
|
509
|
+
case "ctrl-shift-space":
|
|
510
|
+
return "Ctrl+Shift+Space";
|
|
511
|
+
case "custom":
|
|
512
|
+
return customShortcut || "Custom shortcut";
|
|
513
|
+
case "both":
|
|
514
|
+
return "Fn, Cmd+Shift+Space, or Ctrl+Shift+Space";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function voiceProviderLabel(provider: VoiceProvider): string {
|
|
519
|
+
if (provider === "whisper") return "Local Whisper";
|
|
520
|
+
if (provider === "builder" || provider === "builder-gemini") {
|
|
521
|
+
return "Builder.io cleanup";
|
|
522
|
+
}
|
|
523
|
+
if (provider === "gemini") return "Google Gemini";
|
|
524
|
+
if (provider === "groq") return "Groq";
|
|
525
|
+
if (provider === "macos-native") return "macOS on-device";
|
|
526
|
+
return "Browser speech";
|
|
527
|
+
}
|
|
528
|
+
|
|
470
529
|
function formatFileSize(bytes: number): string {
|
|
471
530
|
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
472
531
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
@@ -628,7 +687,6 @@ export function App() {
|
|
|
628
687
|
const localRecordingMode: LocalRecordingMode =
|
|
629
688
|
featureConfig?.localRecordingMode ?? "off";
|
|
630
689
|
|
|
631
|
-
const [recordings, setRecordings] = useState<RecordingSummary[]>([]);
|
|
632
690
|
const [pendingUploads, setPendingUploads] = useState<PendingDesktopUpload[]>(
|
|
633
691
|
[],
|
|
634
692
|
);
|
|
@@ -641,11 +699,16 @@ export function App() {
|
|
|
641
699
|
);
|
|
642
700
|
const [localRecordingNotice, setLocalRecordingNotice] =
|
|
643
701
|
useState<LocalRecordingNotice | null>(null);
|
|
644
|
-
const [
|
|
702
|
+
const [popoverView, setPopoverView] = useState<PopoverView>("recorder");
|
|
703
|
+
const [meetings, setMeetings] = useState<PopoverMeeting[]>([]);
|
|
704
|
+
const [meetingsLoading, setMeetingsLoading] = useState(false);
|
|
705
|
+
const [meetingsError, setMeetingsError] = useState<string | null>(null);
|
|
706
|
+
const [meetingStartMessage, setMeetingStartMessage] = useState<string | null>(
|
|
707
|
+
null,
|
|
708
|
+
);
|
|
645
709
|
const [readinessOpen, setReadinessOpen] = useState<boolean>(
|
|
646
710
|
() => !loadBool(READINESS_REVIEWED_KEY, false),
|
|
647
711
|
);
|
|
648
|
-
const [showSettings, setShowSettings] = useState(false);
|
|
649
712
|
const [recorder, setRecorder] = useState<RecorderHandle | null>(null);
|
|
650
713
|
const [recError, setRecError] = useState<string | null>(null);
|
|
651
714
|
const [cameraError, setCameraError] = useState<string | null>(null);
|
|
@@ -1029,6 +1092,58 @@ export function App() {
|
|
|
1029
1092
|
[serverUrl],
|
|
1030
1093
|
);
|
|
1031
1094
|
|
|
1095
|
+
const fetchUpcomingMeetings = useCallback(async () => {
|
|
1096
|
+
if (authStatus !== "authed") {
|
|
1097
|
+
setMeetings([]);
|
|
1098
|
+
setMeetingsError(null);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
setMeetingsLoading(true);
|
|
1103
|
+
setMeetingsError(null);
|
|
1104
|
+
try {
|
|
1105
|
+
const result = await callClipsAction<{ meetings?: unknown[] }>(
|
|
1106
|
+
"list-meetings",
|
|
1107
|
+
{ view: "upcoming", limit: 3, upcomingWithinMin: 24 * 60 },
|
|
1108
|
+
{ method: "GET" },
|
|
1109
|
+
);
|
|
1110
|
+
const list = Array.isArray(result.meetings) ? result.meetings : [];
|
|
1111
|
+
setMeetings(
|
|
1112
|
+
list.slice(0, 3).map((raw) => {
|
|
1113
|
+
const meeting = raw as Partial<PopoverMeeting>;
|
|
1114
|
+
return {
|
|
1115
|
+
id: String(meeting.id ?? ""),
|
|
1116
|
+
title: meeting.title || "Untitled meeting",
|
|
1117
|
+
scheduledStart: meeting.scheduledStart ?? null,
|
|
1118
|
+
scheduledEnd: meeting.scheduledEnd ?? null,
|
|
1119
|
+
joinUrl: meeting.joinUrl ?? null,
|
|
1120
|
+
platform: meeting.platform ?? null,
|
|
1121
|
+
transcriptStatus: meeting.transcriptStatus ?? null,
|
|
1122
|
+
};
|
|
1123
|
+
}),
|
|
1124
|
+
);
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
setMeetings([]);
|
|
1127
|
+
setMeetingsError(
|
|
1128
|
+
err instanceof Error ? err.message : "Could not load meetings.",
|
|
1129
|
+
);
|
|
1130
|
+
} finally {
|
|
1131
|
+
setMeetingsLoading(false);
|
|
1132
|
+
}
|
|
1133
|
+
}, [authStatus, callClipsAction]);
|
|
1134
|
+
|
|
1135
|
+
const startMeetingNotes = useCallback((meeting: PopoverMeeting) => {
|
|
1136
|
+
setMeetingStartMessage(`Starting notes for ${meeting.title}…`);
|
|
1137
|
+
emit("meetings:start-transcription", {
|
|
1138
|
+
meetingId: meeting.id,
|
|
1139
|
+
joinUrl: meeting.joinUrl,
|
|
1140
|
+
reason: "user",
|
|
1141
|
+
}).catch((err) => {
|
|
1142
|
+
console.error("[clips-popover] start meeting notes failed:", err);
|
|
1143
|
+
setMeetingStartMessage("Could not start notes. Try again from Meetings.");
|
|
1144
|
+
});
|
|
1145
|
+
}, []);
|
|
1146
|
+
|
|
1032
1147
|
useMeetingTranscription({
|
|
1033
1148
|
callClipsAction,
|
|
1034
1149
|
serverUrl,
|
|
@@ -1155,7 +1270,7 @@ export function App() {
|
|
|
1155
1270
|
}
|
|
1156
1271
|
clearDesktopAuthToken(serverUrl);
|
|
1157
1272
|
await checkAuth();
|
|
1158
|
-
|
|
1273
|
+
setPopoverView("recorder");
|
|
1159
1274
|
}
|
|
1160
1275
|
|
|
1161
1276
|
// ---- Esc closes the popover --------------------------------------------
|
|
@@ -1529,33 +1644,9 @@ export function App() {
|
|
|
1529
1644
|
const appRef = useRef<HTMLDivElement | null>(null);
|
|
1530
1645
|
usePopoverAutoSize(appRef, {
|
|
1531
1646
|
disabled: !popoverVisible || isRecording || recordingFlowActive,
|
|
1532
|
-
width:
|
|
1647
|
+
width: popoverView === "settings" ? 440 : 360,
|
|
1533
1648
|
});
|
|
1534
1649
|
|
|
1535
|
-
// ---- recent list --------------------------------------------------------
|
|
1536
|
-
|
|
1537
|
-
const fetchRecent = useCallback(async () => {
|
|
1538
|
-
if (authStatus !== "authed") return; // don't bother; would just 401
|
|
1539
|
-
try {
|
|
1540
|
-
const url = `${serverUrl.replace(/\/+$/, "")}/_agent-native/actions/list-recordings?limit=3&sort=recent`;
|
|
1541
|
-
const res = await fetch(url, { credentials: "include" });
|
|
1542
|
-
if (!res.ok) return;
|
|
1543
|
-
const json = await res.json();
|
|
1544
|
-
const list = Array.isArray(json?.recordings) ? json.recordings : [];
|
|
1545
|
-
setRecordings(
|
|
1546
|
-
list.slice(0, 3).map((r: any) => ({
|
|
1547
|
-
id: r.id,
|
|
1548
|
-
title: r.title ?? "Untitled",
|
|
1549
|
-
durationMs: r.durationMs ?? 0,
|
|
1550
|
-
thumbnailUrl: r.thumbnailUrl ?? null,
|
|
1551
|
-
updatedAt: r.updatedAt ?? r.createdAt,
|
|
1552
|
-
})),
|
|
1553
|
-
);
|
|
1554
|
-
} catch {
|
|
1555
|
-
// ignore — server may be unreachable, we still render the chrome
|
|
1556
|
-
}
|
|
1557
|
-
}, [serverUrl, authStatus]);
|
|
1558
|
-
|
|
1559
1650
|
const loadPendingUploads = useCallback(async () => {
|
|
1560
1651
|
try {
|
|
1561
1652
|
const nativeList = await invoke<Omit<PendingNativeUpload, "kind">[]>(
|
|
@@ -1580,8 +1671,10 @@ export function App() {
|
|
|
1580
1671
|
}, []);
|
|
1581
1672
|
|
|
1582
1673
|
useEffect(() => {
|
|
1583
|
-
|
|
1584
|
-
|
|
1674
|
+
if (popoverView === "meetings" && popoverVisible) {
|
|
1675
|
+
void fetchUpcomingMeetings();
|
|
1676
|
+
}
|
|
1677
|
+
}, [fetchUpcomingMeetings, popoverView, popoverVisible]);
|
|
1585
1678
|
|
|
1586
1679
|
useEffect(() => {
|
|
1587
1680
|
loadPendingUploads();
|
|
@@ -1651,7 +1744,6 @@ export function App() {
|
|
|
1651
1744
|
});
|
|
1652
1745
|
}
|
|
1653
1746
|
await loadPendingUploads();
|
|
1654
|
-
await fetchRecent();
|
|
1655
1747
|
await openExternal(`${targetServerUrl}/r/${upload.recordingId}`);
|
|
1656
1748
|
getCurrentWindow()
|
|
1657
1749
|
.hide()
|
|
@@ -2008,7 +2100,7 @@ export function App() {
|
|
|
2008
2100
|
return;
|
|
2009
2101
|
}
|
|
2010
2102
|
|
|
2011
|
-
|
|
2103
|
+
setPopoverView("recorder");
|
|
2012
2104
|
if (authStatus === "anon" && localRecordingMode === "off") {
|
|
2013
2105
|
setRecError("Sign in to Clips before using the recording shortcut.");
|
|
2014
2106
|
invoke("show_popover").catch(() => {});
|
|
@@ -2148,9 +2240,6 @@ export function App() {
|
|
|
2148
2240
|
.catch(() => {});
|
|
2149
2241
|
emit("clips:popover-visible", false).catch(() => {});
|
|
2150
2242
|
}
|
|
2151
|
-
if (!stopResult?.localOnly) {
|
|
2152
|
-
fetchRecent();
|
|
2153
|
-
}
|
|
2154
2243
|
}
|
|
2155
2244
|
}
|
|
2156
2245
|
}),
|
|
@@ -2210,7 +2299,7 @@ export function App() {
|
|
|
2210
2299
|
});
|
|
2211
2300
|
unlisteners.length = 0;
|
|
2212
2301
|
};
|
|
2213
|
-
}, [recorder,
|
|
2302
|
+
}, [recorder, loadPendingUploads]);
|
|
2214
2303
|
|
|
2215
2304
|
// Auto-hide on blur is handled on the Rust side (tauri::WindowEvent::Focused).
|
|
2216
2305
|
|
|
@@ -2224,7 +2313,7 @@ export function App() {
|
|
|
2224
2313
|
// we just render the normal pre-record panel so the user at least knows
|
|
2225
2314
|
// where they are. No recording-only UI lives here.
|
|
2226
2315
|
|
|
2227
|
-
if (
|
|
2316
|
+
if (popoverView === "settings") {
|
|
2228
2317
|
return (
|
|
2229
2318
|
<div className="app app-settings" ref={appRef}>
|
|
2230
2319
|
<Setup
|
|
@@ -2250,9 +2339,48 @@ export function App() {
|
|
|
2250
2339
|
onConnect={(url) => {
|
|
2251
2340
|
saveString(STORAGE_KEY, url.replace(/\/+$/, ""));
|
|
2252
2341
|
setServerUrl(url.replace(/\/+$/, ""));
|
|
2253
|
-
|
|
2342
|
+
setPopoverView("recorder");
|
|
2254
2343
|
}}
|
|
2255
|
-
onCancel={() =>
|
|
2344
|
+
onCancel={() => setPopoverView("recorder")}
|
|
2345
|
+
/>
|
|
2346
|
+
</div>
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
if (popoverView === "meetings") {
|
|
2351
|
+
return (
|
|
2352
|
+
<div className="app app-popover-view" ref={appRef}>
|
|
2353
|
+
<MeetingsPopoverView
|
|
2354
|
+
meetings={meetings}
|
|
2355
|
+
loading={meetingsLoading}
|
|
2356
|
+
error={meetingsError}
|
|
2357
|
+
startMessage={meetingStartMessage}
|
|
2358
|
+
meetingsEnabled={featureConfig?.meetingsEnabled !== false}
|
|
2359
|
+
onBack={() => setPopoverView("recorder")}
|
|
2360
|
+
onRefresh={fetchUpcomingMeetings}
|
|
2361
|
+
onOpenMeetings={() => openInBrowser("/meetings")}
|
|
2362
|
+
onOpenMeeting={(meetingId) =>
|
|
2363
|
+
openInBrowser(`/meetings/${encodeURIComponent(meetingId)}`)
|
|
2364
|
+
}
|
|
2365
|
+
onOpenSettings={() => setPopoverView("settings")}
|
|
2366
|
+
onStartNotes={startMeetingNotes}
|
|
2367
|
+
/>
|
|
2368
|
+
</div>
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (popoverView === "dictation") {
|
|
2373
|
+
return (
|
|
2374
|
+
<div className="app app-popover-view" ref={appRef}>
|
|
2375
|
+
<DictationPopoverView
|
|
2376
|
+
voiceEnabled={voiceDictationEnabled}
|
|
2377
|
+
voiceShortcut={voiceShortcut}
|
|
2378
|
+
voiceCustomShortcut={voiceCustomShortcut}
|
|
2379
|
+
voiceMode={voiceMode}
|
|
2380
|
+
voiceProvider={voiceProvider}
|
|
2381
|
+
onBack={() => setPopoverView("recorder")}
|
|
2382
|
+
onOpenDictate={() => openInBrowser("/dictate")}
|
|
2383
|
+
onOpenSettings={() => setPopoverView("settings")}
|
|
2256
2384
|
/>
|
|
2257
2385
|
</div>
|
|
2258
2386
|
);
|
|
@@ -2309,7 +2437,7 @@ export function App() {
|
|
|
2309
2437
|
</>
|
|
2310
2438
|
)}
|
|
2311
2439
|
<div className="footer">
|
|
2312
|
-
<a className="footer-link" onClick={() =>
|
|
2440
|
+
<a className="footer-link" onClick={() => setPopoverView("settings")}>
|
|
2313
2441
|
Settings
|
|
2314
2442
|
</a>
|
|
2315
2443
|
</div>
|
|
@@ -2472,45 +2600,22 @@ export function App() {
|
|
|
2472
2600
|
onClick={() => openInBrowser("/")}
|
|
2473
2601
|
/>
|
|
2474
2602
|
<BottomButton
|
|
2475
|
-
icon="
|
|
2476
|
-
label="
|
|
2477
|
-
onClick={() =>
|
|
2603
|
+
icon="meetings"
|
|
2604
|
+
label="Meetings"
|
|
2605
|
+
onClick={() => setPopoverView("meetings")}
|
|
2478
2606
|
/>
|
|
2479
2607
|
<BottomButton
|
|
2480
|
-
icon="
|
|
2481
|
-
label="
|
|
2608
|
+
icon="dictation"
|
|
2609
|
+
label="Dictate"
|
|
2482
2610
|
badge={undefined}
|
|
2483
|
-
onClick={() =>
|
|
2611
|
+
onClick={() => setPopoverView("dictation")}
|
|
2612
|
+
/>
|
|
2613
|
+
<BottomButton
|
|
2614
|
+
icon="settings"
|
|
2615
|
+
label="Settings"
|
|
2616
|
+
onClick={() => setPopoverView("settings")}
|
|
2484
2617
|
/>
|
|
2485
2618
|
</div>
|
|
2486
|
-
|
|
2487
|
-
{showRecent && recordings.length > 0 ? (
|
|
2488
|
-
<div className="recent-list">
|
|
2489
|
-
{recordings.map((r) => (
|
|
2490
|
-
<button
|
|
2491
|
-
key={r.id}
|
|
2492
|
-
className="recent-item"
|
|
2493
|
-
onClick={() => openInBrowser(`/r/${r.id}`)}
|
|
2494
|
-
>
|
|
2495
|
-
{r.thumbnailUrl ? (
|
|
2496
|
-
<img
|
|
2497
|
-
className="thumb"
|
|
2498
|
-
src={
|
|
2499
|
-
resolveDesktopThumbnailUrl(r.thumbnailUrl, serverUrl) ?? ""
|
|
2500
|
-
}
|
|
2501
|
-
alt=""
|
|
2502
|
-
/>
|
|
2503
|
-
) : (
|
|
2504
|
-
<div className="thumb thumb-placeholder" />
|
|
2505
|
-
)}
|
|
2506
|
-
<div className="recent-meta">
|
|
2507
|
-
<div className="recent-title">{r.title}</div>
|
|
2508
|
-
<div className="recent-sub">{formatAgo(r.updatedAt)}</div>
|
|
2509
|
-
</div>
|
|
2510
|
-
</button>
|
|
2511
|
-
))}
|
|
2512
|
-
</div>
|
|
2513
|
-
) : null}
|
|
2514
2619
|
</div>
|
|
2515
2620
|
);
|
|
2516
2621
|
}
|
|
@@ -3126,13 +3231,236 @@ function SignInForm({
|
|
|
3126
3231
|
);
|
|
3127
3232
|
}
|
|
3128
3233
|
|
|
3234
|
+
function PopoverSubViewHeader({
|
|
3235
|
+
title,
|
|
3236
|
+
onBack,
|
|
3237
|
+
action,
|
|
3238
|
+
}: {
|
|
3239
|
+
title: string;
|
|
3240
|
+
onBack: () => void;
|
|
3241
|
+
action?: ReactNode;
|
|
3242
|
+
}) {
|
|
3243
|
+
return (
|
|
3244
|
+
<div className="setup-header popover-view-header">
|
|
3245
|
+
<button
|
|
3246
|
+
type="button"
|
|
3247
|
+
className="setup-back"
|
|
3248
|
+
onClick={onBack}
|
|
3249
|
+
aria-label="Back"
|
|
3250
|
+
>
|
|
3251
|
+
<IconArrowLeft size={18} stroke={1.75} />
|
|
3252
|
+
</button>
|
|
3253
|
+
<h2>{title}</h2>
|
|
3254
|
+
<div className="popover-view-header-spacer" />
|
|
3255
|
+
{action}
|
|
3256
|
+
</div>
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
function MeetingsPopoverView({
|
|
3261
|
+
meetings,
|
|
3262
|
+
loading,
|
|
3263
|
+
error,
|
|
3264
|
+
startMessage,
|
|
3265
|
+
meetingsEnabled,
|
|
3266
|
+
onBack,
|
|
3267
|
+
onRefresh,
|
|
3268
|
+
onOpenMeetings,
|
|
3269
|
+
onOpenMeeting,
|
|
3270
|
+
onOpenSettings,
|
|
3271
|
+
onStartNotes,
|
|
3272
|
+
}: {
|
|
3273
|
+
meetings: PopoverMeeting[];
|
|
3274
|
+
loading: boolean;
|
|
3275
|
+
error: string | null;
|
|
3276
|
+
startMessage: string | null;
|
|
3277
|
+
meetingsEnabled: boolean;
|
|
3278
|
+
onBack: () => void;
|
|
3279
|
+
onRefresh: () => void;
|
|
3280
|
+
onOpenMeetings: () => void;
|
|
3281
|
+
onOpenMeeting: (meetingId: string) => void;
|
|
3282
|
+
onOpenSettings: () => void;
|
|
3283
|
+
onStartNotes: (meeting: PopoverMeeting) => void;
|
|
3284
|
+
}) {
|
|
3285
|
+
return (
|
|
3286
|
+
<div className="setup popover-view">
|
|
3287
|
+
<PopoverSubViewHeader
|
|
3288
|
+
title="Meetings"
|
|
3289
|
+
onBack={onBack}
|
|
3290
|
+
action={
|
|
3291
|
+
<button
|
|
3292
|
+
type="button"
|
|
3293
|
+
className="link-button popover-view-link"
|
|
3294
|
+
onClick={onOpenMeetings}
|
|
3295
|
+
>
|
|
3296
|
+
Open web
|
|
3297
|
+
</button>
|
|
3298
|
+
}
|
|
3299
|
+
/>
|
|
3300
|
+
|
|
3301
|
+
<div className="setup-section">
|
|
3302
|
+
<p className="setup-hint">
|
|
3303
|
+
Start Granola-style live notes from calendar meetings without hunting
|
|
3304
|
+
through Settings.
|
|
3305
|
+
</p>
|
|
3306
|
+
</div>
|
|
3307
|
+
|
|
3308
|
+
{!meetingsEnabled ? (
|
|
3309
|
+
<div className="popover-empty-card">
|
|
3310
|
+
<strong>Meeting notes are off</strong>
|
|
3311
|
+
<p>Turn them on to show reminders and start live transcription.</p>
|
|
3312
|
+
<button type="button" className="secondary" onClick={onOpenSettings}>
|
|
3313
|
+
Open meeting settings
|
|
3314
|
+
</button>
|
|
3315
|
+
</div>
|
|
3316
|
+
) : error ? (
|
|
3317
|
+
<div className="popover-empty-card">
|
|
3318
|
+
<strong>Could not load meetings</strong>
|
|
3319
|
+
<p>{error}</p>
|
|
3320
|
+
<button type="button" className="secondary" onClick={onRefresh}>
|
|
3321
|
+
Try again
|
|
3322
|
+
</button>
|
|
3323
|
+
</div>
|
|
3324
|
+
) : loading ? (
|
|
3325
|
+
<div className="popover-empty-card">
|
|
3326
|
+
<strong>Loading meetings…</strong>
|
|
3327
|
+
<p>Checking your connected calendar.</p>
|
|
3328
|
+
</div>
|
|
3329
|
+
) : meetings.length === 0 ? (
|
|
3330
|
+
<div className="popover-empty-card">
|
|
3331
|
+
<strong>No meetings ready</strong>
|
|
3332
|
+
<p>Connect Google Calendar or open the Meetings page to see setup.</p>
|
|
3333
|
+
<button type="button" className="secondary" onClick={onOpenMeetings}>
|
|
3334
|
+
Open Meetings
|
|
3335
|
+
</button>
|
|
3336
|
+
</div>
|
|
3337
|
+
) : (
|
|
3338
|
+
<div className="popover-list">
|
|
3339
|
+
{meetings.map((meeting) => {
|
|
3340
|
+
const canStart = meetingCanStartNotes(meeting);
|
|
3341
|
+
return (
|
|
3342
|
+
<div className="popover-list-item" key={meeting.id}>
|
|
3343
|
+
<div className="popover-list-icon">
|
|
3344
|
+
<IconCalendarEvent size={17} stroke={1.75} />
|
|
3345
|
+
</div>
|
|
3346
|
+
<div className="popover-list-main">
|
|
3347
|
+
<div className="popover-list-title">{meeting.title}</div>
|
|
3348
|
+
<div className="popover-list-sub">
|
|
3349
|
+
{formatMeetingWhen(meeting)}
|
|
3350
|
+
{meeting.platform ? ` · ${meeting.platform}` : ""}
|
|
3351
|
+
</div>
|
|
3352
|
+
</div>
|
|
3353
|
+
{canStart ? (
|
|
3354
|
+
<button
|
|
3355
|
+
type="button"
|
|
3356
|
+
className="popover-list-action popover-list-action-primary"
|
|
3357
|
+
onClick={() => onStartNotes(meeting)}
|
|
3358
|
+
>
|
|
3359
|
+
Start notes
|
|
3360
|
+
</button>
|
|
3361
|
+
) : (
|
|
3362
|
+
<button
|
|
3363
|
+
type="button"
|
|
3364
|
+
className="popover-list-action"
|
|
3365
|
+
onClick={() => onOpenMeeting(meeting.id)}
|
|
3366
|
+
>
|
|
3367
|
+
Open
|
|
3368
|
+
</button>
|
|
3369
|
+
)}
|
|
3370
|
+
</div>
|
|
3371
|
+
);
|
|
3372
|
+
})}
|
|
3373
|
+
</div>
|
|
3374
|
+
)}
|
|
3375
|
+
|
|
3376
|
+
{startMessage ? <p className="setup-success">{startMessage}</p> : null}
|
|
3377
|
+
</div>
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
function DictationPopoverView({
|
|
3382
|
+
voiceEnabled,
|
|
3383
|
+
voiceShortcut,
|
|
3384
|
+
voiceCustomShortcut,
|
|
3385
|
+
voiceMode,
|
|
3386
|
+
voiceProvider,
|
|
3387
|
+
onBack,
|
|
3388
|
+
onOpenDictate,
|
|
3389
|
+
onOpenSettings,
|
|
3390
|
+
}: {
|
|
3391
|
+
voiceEnabled: boolean;
|
|
3392
|
+
voiceShortcut: VoiceShortcutPreference;
|
|
3393
|
+
voiceCustomShortcut: string;
|
|
3394
|
+
voiceMode: VoiceMode;
|
|
3395
|
+
voiceProvider: VoiceProvider;
|
|
3396
|
+
onBack: () => void;
|
|
3397
|
+
onOpenDictate: () => void;
|
|
3398
|
+
onOpenSettings: () => void;
|
|
3399
|
+
}) {
|
|
3400
|
+
const shortcut = voiceShortcutLabel(voiceShortcut, voiceCustomShortcut);
|
|
3401
|
+
return (
|
|
3402
|
+
<div className="setup popover-view">
|
|
3403
|
+
<PopoverSubViewHeader
|
|
3404
|
+
title="Dictate"
|
|
3405
|
+
onBack={onBack}
|
|
3406
|
+
action={
|
|
3407
|
+
<button
|
|
3408
|
+
type="button"
|
|
3409
|
+
className="link-button popover-view-link"
|
|
3410
|
+
onClick={onOpenDictate}
|
|
3411
|
+
>
|
|
3412
|
+
Open web
|
|
3413
|
+
</button>
|
|
3414
|
+
}
|
|
3415
|
+
/>
|
|
3416
|
+
|
|
3417
|
+
<div className="popover-empty-card">
|
|
3418
|
+
<div className="popover-card-heading">
|
|
3419
|
+
<IconMicrophone2 size={18} stroke={1.75} />
|
|
3420
|
+
<strong>
|
|
3421
|
+
{voiceEnabled ? "Ready to dictate" : "Dictation is off"}
|
|
3422
|
+
</strong>
|
|
3423
|
+
</div>
|
|
3424
|
+
{voiceEnabled ? (
|
|
3425
|
+
<>
|
|
3426
|
+
<p>
|
|
3427
|
+
{voiceMode === "toggle"
|
|
3428
|
+
? "Press once to start, then press again to stop."
|
|
3429
|
+
: "Hold the shortcut while speaking; release to paste."}
|
|
3430
|
+
</p>
|
|
3431
|
+
<div className="popover-kv">
|
|
3432
|
+
<span>Shortcut</span>
|
|
3433
|
+
<strong>{shortcut}</strong>
|
|
3434
|
+
</div>
|
|
3435
|
+
<div className="popover-kv">
|
|
3436
|
+
<span>Provider</span>
|
|
3437
|
+
<strong>{voiceProviderLabel(voiceProvider)}</strong>
|
|
3438
|
+
</div>
|
|
3439
|
+
</>
|
|
3440
|
+
) : (
|
|
3441
|
+
<p>Turn on voice dictation to speak to type anywhere on your Mac.</p>
|
|
3442
|
+
)}
|
|
3443
|
+
</div>
|
|
3444
|
+
|
|
3445
|
+
<div className="setup-button-row">
|
|
3446
|
+
<button type="button" className="secondary" onClick={onOpenDictate}>
|
|
3447
|
+
Open Dictate history
|
|
3448
|
+
</button>
|
|
3449
|
+
<button type="button" className="secondary" onClick={onOpenSettings}>
|
|
3450
|
+
Dictation settings
|
|
3451
|
+
</button>
|
|
3452
|
+
</div>
|
|
3453
|
+
</div>
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3129
3457
|
function BottomButton({
|
|
3130
3458
|
icon,
|
|
3131
3459
|
label,
|
|
3132
3460
|
badge,
|
|
3133
3461
|
onClick,
|
|
3134
3462
|
}: {
|
|
3135
|
-
icon: "library" | "settings" | "
|
|
3463
|
+
icon: "library" | "settings" | "meetings" | "dictation";
|
|
3136
3464
|
label: string;
|
|
3137
3465
|
badge?: string;
|
|
3138
3466
|
onClick: () => void;
|
|
@@ -3144,8 +3472,10 @@ function BottomButton({
|
|
|
3144
3472
|
<LibraryIcon />
|
|
3145
3473
|
) : icon === "settings" ? (
|
|
3146
3474
|
<SettingsIcon />
|
|
3475
|
+
) : icon === "meetings" ? (
|
|
3476
|
+
<IconCalendarEvent size={18} stroke={1.75} />
|
|
3147
3477
|
) : (
|
|
3148
|
-
<
|
|
3478
|
+
<IconMicrophone2 size={18} stroke={1.75} />
|
|
3149
3479
|
)}
|
|
3150
3480
|
{badge ? <span className="badge">{badge}</span> : null}
|
|
3151
3481
|
</span>
|