@agent-native/core 0.120.1 → 0.120.2
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 +9 -0
- package/corpus/core/docs/content/extensions.mdx +23 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +4 -0
- package/corpus/core/src/agent/run-manager.ts +36 -10
- package/corpus/core/src/agent/types.ts +2 -0
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +16 -3
- package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
- package/corpus/core/src/deploy/build.ts +3 -0
- package/corpus/core/src/server/analytics.ts +154 -25
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/core/src/templates/chat/netlify.toml +1 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/corpus/core/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
- package/corpus/templates/analytics/AGENTS.md +20 -8
- package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
- package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
- package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
- package/corpus/templates/analytics/actions/view-screen.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +103 -502
- package/corpus/templates/analytics/app/global.css +30 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
- package/corpus/templates/analytics/app/i18n-data.ts +80 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -53
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +23 -25
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
- package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/server/db/schema.ts +4 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
- package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
- package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
- package/corpus/templates/analytics/server/plugins/db.ts +10 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
- package/corpus/templates/assets/actions/generate-image.ts +28 -1
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +46 -8
- package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
- package/corpus/templates/assets/netlify.toml +1 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -8
- package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/brain/netlify.toml +1 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +43 -5
- package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/calendar/netlify.toml +1 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/chat/netlify.toml +1 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +52 -6
- package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
- package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
- package/corpus/templates/clips/desktop/src/styles.css +35 -3
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
- package/corpus/templates/clips/netlify.toml +1 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -7
- package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/content/netlify.toml +1 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -17
- package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/design/netlify.toml +1 -0
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +1 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +41 -45
- package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/forms/netlify.toml +1 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +36 -5
- package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/macros/netlify.toml +1 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -9
- package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/mail/netlify.toml +1 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +38 -5
- package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/plan/netlify.toml +1 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +47 -4
- package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
- package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
- package/corpus/templates/tasks/netlify.toml +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +25 -10
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +5 -3
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
- package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
- package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
- package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +10 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/deploy/build.js +1 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/server/analytics.d.ts +9 -4
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +111 -22
- package/dist/server/analytics.js.map +1 -1
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/dist/templates/chat/netlify.toml +1 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/extensions.mdx +23 -4
- package/package.json +1 -1
- package/src/agent/production-agent.ts +4 -0
- package/src/agent/run-manager.ts +36 -10
- package/src/agent/types.ts +2 -0
- package/src/client/extensions/ExtensionsListPage.tsx +16 -3
- package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/src/client/settings/SettingsPanel.tsx +10 -0
- package/src/deploy/build.ts +3 -0
- package/src/server/analytics.ts +154 -25
- package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
- package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/src/templates/chat/netlify.toml +1 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
- package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
} from "@agent-native/core/client/agent-chat";
|
|
5
5
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
6
6
|
import { DevDatabaseLink } from "@agent-native/core/client/db-admin";
|
|
7
|
-
import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
8
7
|
import { getBrowserTabId } from "@agent-native/core/client/hooks";
|
|
9
8
|
import { useT } from "@agent-native/core/client/i18n";
|
|
10
9
|
import {
|
|
@@ -241,9 +240,17 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
241
240
|
icon: IconTrash,
|
|
242
241
|
match: (p) => p.startsWith("/trash"),
|
|
243
242
|
},
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
const bottomNavItems: {
|
|
246
|
+
to: string;
|
|
247
|
+
label: string;
|
|
248
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
249
|
+
match: (path: string) => boolean;
|
|
250
|
+
}[] = [
|
|
244
251
|
{
|
|
245
252
|
to: "/agent",
|
|
246
|
-
label: t("
|
|
253
|
+
label: t("settings.agentTitle"),
|
|
247
254
|
icon: IconHierarchy2,
|
|
248
255
|
match: (p) => p.startsWith("/agent"),
|
|
249
256
|
},
|
|
@@ -519,6 +526,49 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
519
526
|
)}
|
|
520
527
|
</div>
|
|
521
528
|
|
|
529
|
+
<div className="shrink-0">
|
|
530
|
+
{showCollapsedSidebar ? (
|
|
531
|
+
<nav className="flex flex-col items-center gap-1 px-2 py-1">
|
|
532
|
+
{bottomNavItems.map(({ to, label, icon: Icon, match }) => (
|
|
533
|
+
<Tooltip key={to}>
|
|
534
|
+
<TooltipTrigger asChild>
|
|
535
|
+
<NavLink
|
|
536
|
+
to={to}
|
|
537
|
+
aria-label={label}
|
|
538
|
+
className={cn(
|
|
539
|
+
"flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground",
|
|
540
|
+
match(location.pathname) &&
|
|
541
|
+
"bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary",
|
|
542
|
+
)}
|
|
543
|
+
>
|
|
544
|
+
<Icon className="h-4 w-4" />
|
|
545
|
+
</NavLink>
|
|
546
|
+
</TooltipTrigger>
|
|
547
|
+
<TooltipContent side="right">{label}</TooltipContent>
|
|
548
|
+
</Tooltip>
|
|
549
|
+
))}
|
|
550
|
+
</nav>
|
|
551
|
+
) : (
|
|
552
|
+
<nav className="space-y-0.5 border-t border-border px-2 py-1">
|
|
553
|
+
{bottomNavItems.map(({ to, label, icon: Icon, match }) => (
|
|
554
|
+
<NavLink
|
|
555
|
+
key={to}
|
|
556
|
+
to={to}
|
|
557
|
+
className={cn(
|
|
558
|
+
"flex items-center gap-2 rounded px-2 py-1.5 text-xs",
|
|
559
|
+
match(location.pathname)
|
|
560
|
+
? "bg-primary/10 font-medium text-primary"
|
|
561
|
+
: "text-foreground hover:bg-accent/60",
|
|
562
|
+
)}
|
|
563
|
+
>
|
|
564
|
+
<Icon className="h-4 w-4" />
|
|
565
|
+
<span className="flex-1 truncate">{label}</span>
|
|
566
|
+
</NavLink>
|
|
567
|
+
))}
|
|
568
|
+
</nav>
|
|
569
|
+
)}
|
|
570
|
+
</div>
|
|
571
|
+
|
|
522
572
|
{!showCollapsedSidebar && (
|
|
523
573
|
<>
|
|
524
574
|
<div className="shrink-0 space-y-1.5 px-2 py-1.5">
|
|
@@ -539,10 +589,6 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
539
589
|
{(isMobile || !pageHasHeaderSearch) && <SearchBar />}
|
|
540
590
|
</div>
|
|
541
591
|
|
|
542
|
-
<div className="shrink-0 px-1 py-1">
|
|
543
|
-
<ExtensionsSidebarSection />
|
|
544
|
-
</div>
|
|
545
|
-
|
|
546
592
|
<div className="shrink-0 space-y-2 px-3 py-2">
|
|
547
593
|
<OrgSwitcher settingsPath="/settings/organization" />
|
|
548
594
|
<DevDatabaseLink />
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
-
|
|
4
|
-
import { PageHeader } from "@/components/library/page-header";
|
|
1
|
+
import { Navigate } from "react-router";
|
|
5
2
|
|
|
6
3
|
export default function ExtensionsRoute() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<>
|
|
11
|
-
<PageHeader>
|
|
12
|
-
<h1 className="text-base font-semibold tracking-tight truncate">
|
|
13
|
-
{t("navigation.extensions")}
|
|
14
|
-
</h1>
|
|
15
|
-
</PageHeader>
|
|
16
|
-
<ExtensionsListPage />
|
|
17
|
-
</>
|
|
18
|
-
);
|
|
4
|
+
return <Navigate to="/settings#extensions" replace />;
|
|
19
5
|
}
|
|
@@ -88,7 +88,7 @@ export function sampleLevels(
|
|
|
88
88
|
const levels: number[] = [];
|
|
89
89
|
for (let i = 0; i < barCount; i++) {
|
|
90
90
|
const idx = Math.min(usable - 1, Math.floor((i / barCount) * usable));
|
|
91
|
-
levels.push(data[idx] / 255);
|
|
91
|
+
levels.push(Math.min(1, (data[idx] / 255) * 1.65));
|
|
92
92
|
}
|
|
93
93
|
return levels;
|
|
94
94
|
}
|
|
@@ -3,6 +3,7 @@ import { invoke } from "@tauri-apps/api/core";
|
|
|
3
3
|
export interface DismissibleMeetingNotification {
|
|
4
4
|
type: "calendar" | "adhoc";
|
|
5
5
|
meetingId: string;
|
|
6
|
+
joinUrl?: string | null;
|
|
6
7
|
platform?: string | null;
|
|
7
8
|
scheduledStart?: string | null;
|
|
8
9
|
scheduledEnd?: string | null;
|
|
@@ -21,6 +22,7 @@ export async function dismissMeetingNotification(
|
|
|
21
22
|
await nativeInvoke("dismiss_meeting_notification", {
|
|
22
23
|
meetingId: notification.meetingId,
|
|
23
24
|
notificationType: notification.type,
|
|
25
|
+
joinUrl: notification.joinUrl ?? null,
|
|
24
26
|
platform: notification.platform ?? null,
|
|
25
27
|
scheduledStart: notification.scheduledStart ?? null,
|
|
26
28
|
scheduledEnd: notification.scheduledEnd ?? null,
|
|
@@ -1139,7 +1139,7 @@ export function installDesktopVoiceDictation(
|
|
|
1139
1139
|
* `stop()` works the same way as for the browser path.
|
|
1140
1140
|
*
|
|
1141
1141
|
* No `getUserMedia()` here — the audio engine handles the mic on the Rust
|
|
1142
|
-
* side
|
|
1142
|
+
* side and emits the real level events consumed by the flow bar.
|
|
1143
1143
|
*/
|
|
1144
1144
|
const startNative = async (
|
|
1145
1145
|
cleanupProvider?: ServerVoiceProvider,
|
|
@@ -1191,7 +1191,6 @@ export function installDesktopVoiceDictation(
|
|
|
1191
1191
|
};
|
|
1192
1192
|
session = next;
|
|
1193
1193
|
startInFlight = false;
|
|
1194
|
-
startSyntheticMeter(next);
|
|
1195
1194
|
try {
|
|
1196
1195
|
// Bias the recognizer toward the user's learned vocabulary. Stage
|
|
1197
1196
|
// the list via a separate command so meeting capture can pass mic
|
|
@@ -1299,7 +1298,6 @@ export function installDesktopVoiceDictation(
|
|
|
1299
1298
|
};
|
|
1300
1299
|
session = next;
|
|
1301
1300
|
startInFlight = false;
|
|
1302
|
-
startSyntheticMeter(next);
|
|
1303
1301
|
if (stopRequestedBeforeReady) {
|
|
1304
1302
|
stop();
|
|
1305
1303
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { listen } from "@tauri-apps/api/event";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
const BAR_SHAPES = [0.52, 1, 0.68];
|
|
5
|
+
const LEVEL_DECAY = 0.78;
|
|
6
|
+
const EVENT_ATTACK_DECAY = 0.52;
|
|
7
|
+
|
|
8
|
+
interface LiveAudioBarsProps {
|
|
9
|
+
className?: string;
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Small Granola-style meter shared by the compact and expanded overlays. */
|
|
14
|
+
export function LiveAudioBars({
|
|
15
|
+
className,
|
|
16
|
+
compact = false,
|
|
17
|
+
}: LiveAudioBarsProps) {
|
|
18
|
+
const [level, setLevel] = useState(0);
|
|
19
|
+
const levelRef = useRef(0);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let stopped = false;
|
|
23
|
+
let unlisten: (() => void) | null = null;
|
|
24
|
+
|
|
25
|
+
const decayTimer = window.setInterval(() => {
|
|
26
|
+
const next = levelRef.current * LEVEL_DECAY;
|
|
27
|
+
levelRef.current = next < 0.01 ? 0 : next;
|
|
28
|
+
setLevel(levelRef.current);
|
|
29
|
+
}, 60);
|
|
30
|
+
|
|
31
|
+
listen<{ level?: number }>("voice:audio-level", (event) => {
|
|
32
|
+
const incoming = Number(event.payload?.level);
|
|
33
|
+
if (!Number.isFinite(incoming)) return;
|
|
34
|
+
const next = Math.max(
|
|
35
|
+
levelRef.current * EVENT_ATTACK_DECAY,
|
|
36
|
+
Math.max(0, Math.min(1, incoming)),
|
|
37
|
+
);
|
|
38
|
+
levelRef.current = next;
|
|
39
|
+
setLevel(next);
|
|
40
|
+
})
|
|
41
|
+
.then((cleanup) => {
|
|
42
|
+
if (stopped) {
|
|
43
|
+
cleanup();
|
|
44
|
+
} else {
|
|
45
|
+
unlisten = cleanup;
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.catch(() => {});
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
stopped = true;
|
|
52
|
+
window.clearInterval(decayTimer);
|
|
53
|
+
unlisten?.();
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
// Peak levels from speech taps are often quiet even when speech is clear.
|
|
58
|
+
// A gentle curve keeps the meter responsive without turning background noise
|
|
59
|
+
// into a full-height signal.
|
|
60
|
+
const visualLevel = level > 0 ? Math.min(1, Math.pow(level, 0.52) * 1.08) : 0;
|
|
61
|
+
const rootClassName = [
|
|
62
|
+
"live-audio-bars",
|
|
63
|
+
compact ? "live-audio-bars-compact" : null,
|
|
64
|
+
className,
|
|
65
|
+
]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(" ");
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<span className={rootClassName} aria-hidden="true">
|
|
71
|
+
{BAR_SHAPES.map((shape, index) => {
|
|
72
|
+
const idleHeight = index === 1 ? 0.2 : 0.14;
|
|
73
|
+
const height = Math.round(
|
|
74
|
+
(idleHeight + visualLevel * shape * 0.8) * 100,
|
|
75
|
+
);
|
|
76
|
+
return (
|
|
77
|
+
<span
|
|
78
|
+
className="live-audio-bar"
|
|
79
|
+
key={index}
|
|
80
|
+
style={{ height: `${Math.max(12, height)}%` }}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</span>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -39,6 +39,7 @@ interface TranscriptionStatusPayload {
|
|
|
39
39
|
|
|
40
40
|
const SNOOZE_MS = 5 * 60_000;
|
|
41
41
|
const FALLBACK_AUTO_HIDE_MS = 6 * 60_000;
|
|
42
|
+
const DISMISSAL_TOMBSTONE_MS = 30 * 60_000;
|
|
42
43
|
// Card is up to 440px wide; the extra width leaves room for the drop shadow
|
|
43
44
|
// (~32px each side) so it isn't clipped by the transparent window edges.
|
|
44
45
|
const NOTIFICATION_WINDOW_WIDTH = 504;
|
|
@@ -128,6 +129,7 @@ export function MeetingNotification() {
|
|
|
128
129
|
const [pending, setPending] = useState(false);
|
|
129
130
|
const autoHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
130
131
|
const dataRef = useRef<NotificationData | null>(null);
|
|
132
|
+
const dismissedKeysRef = useRef(new Map<string, number>());
|
|
131
133
|
// Real DOM hover only fires while this overlay window is key, which macOS
|
|
132
134
|
// won't grant it without a click (`show_without_activation` never
|
|
133
135
|
// activates). `polledHovered` mirrors the Rust-side global cursor poll
|
|
@@ -139,6 +141,21 @@ export function MeetingNotification() {
|
|
|
139
141
|
const hovered = domHovered || polledHovered;
|
|
140
142
|
const prevHoveredRef = useRef(false);
|
|
141
143
|
|
|
144
|
+
function notificationKey(payload: NotificationData): string {
|
|
145
|
+
return [payload.type, payload.meetingId, payload.scheduledStart ?? ""].join(
|
|
146
|
+
"|",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isDismissed(payload: NotificationData): boolean {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const dismissed = dismissedKeysRef.current;
|
|
153
|
+
for (const [key, expiresAt] of dismissed) {
|
|
154
|
+
if (expiresAt <= now) dismissed.delete(key);
|
|
155
|
+
}
|
|
156
|
+
return dismissed.has(notificationKey(payload));
|
|
157
|
+
}
|
|
158
|
+
|
|
142
159
|
useEffect(() => {
|
|
143
160
|
dataRef.current = data;
|
|
144
161
|
}, [data]);
|
|
@@ -188,6 +205,7 @@ export function MeetingNotification() {
|
|
|
188
205
|
payload: NotificationData,
|
|
189
206
|
options?: { hydrated?: boolean },
|
|
190
207
|
) {
|
|
208
|
+
if (isDismissed(payload)) return;
|
|
191
209
|
setData(payload);
|
|
192
210
|
setError(null);
|
|
193
211
|
setMenuOpen(false);
|
|
@@ -311,6 +329,12 @@ export function MeetingNotification() {
|
|
|
311
329
|
|
|
312
330
|
function dismissNotification() {
|
|
313
331
|
const current = dataRef.current;
|
|
332
|
+
if (current) {
|
|
333
|
+
dismissedKeysRef.current.set(
|
|
334
|
+
notificationKey(current),
|
|
335
|
+
Date.now() + DISMISSAL_TOMBSTONE_MS,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
314
338
|
hideNotification();
|
|
315
339
|
if (current) {
|
|
316
340
|
void dismissMeetingNotification(current);
|
|
@@ -17,6 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
17
17
|
|
|
18
18
|
import { isDirectPillClick, type ScreenPoint } from "../lib/pill-interaction";
|
|
19
19
|
import { speakerFor } from "../lib/transcription-engine";
|
|
20
|
+
import { LiveAudioBars } from "./live-audio-bars";
|
|
20
21
|
import { LiveTranscript, type FinalLine } from "./live-transcript";
|
|
21
22
|
import { PillLogo } from "./pill-logo";
|
|
22
23
|
|
|
@@ -65,9 +66,6 @@ export function RecordingPill() {
|
|
|
65
66
|
const tickRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
66
67
|
// Mic and system audio share one calm activity meter, matching Granola's
|
|
67
68
|
// single indicator for the combined meeting capture.
|
|
68
|
-
const levelRef = useRef(0);
|
|
69
|
-
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
70
|
-
const rafRef = useRef<number | null>(null);
|
|
71
69
|
const stopFallbackRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
72
70
|
const dragStartScreenPointRef = useRef<ScreenPoint | null>(null);
|
|
73
71
|
|
|
@@ -165,15 +163,6 @@ export function RecordingPill() {
|
|
|
165
163
|
if (ev.payload?.detached) setExpanded(false);
|
|
166
164
|
}),
|
|
167
165
|
);
|
|
168
|
-
trackListen(
|
|
169
|
-
listen<{ level: number; source?: "mic" | "system" }>(
|
|
170
|
-
"voice:audio-level",
|
|
171
|
-
(ev) => {
|
|
172
|
-
const lvl = Math.max(0, Math.min(1, ev.payload.level));
|
|
173
|
-
levelRef.current = lvl;
|
|
174
|
-
},
|
|
175
|
-
),
|
|
176
|
-
);
|
|
177
166
|
// Signal that all listeners are registered. app.tsx listens for this and
|
|
178
167
|
// re-emits the pill context and transcript preload for a fresh window.
|
|
179
168
|
emit("clips:pill-ready", {}).catch(() => {});
|
|
@@ -207,62 +196,6 @@ export function RecordingPill() {
|
|
|
207
196
|
};
|
|
208
197
|
}, [ctx.mode, paused]);
|
|
209
198
|
|
|
210
|
-
// One combined "dancing bars" meter — a few discrete vertical bars instead
|
|
211
|
-
// of separate mic and system waveforms.
|
|
212
|
-
useEffect(() => {
|
|
213
|
-
const canvas = canvasRef.current;
|
|
214
|
-
if (!canvas) return;
|
|
215
|
-
const N_BARS = 3;
|
|
216
|
-
const dpr = window.devicePixelRatio || 1;
|
|
217
|
-
const W = canvas.clientWidth;
|
|
218
|
-
const H = canvas.clientHeight;
|
|
219
|
-
canvas.width = W * dpr;
|
|
220
|
-
canvas.height = H * dpr;
|
|
221
|
-
const ctx2d = canvas.getContext("2d");
|
|
222
|
-
if (!ctx2d) return;
|
|
223
|
-
ctx2d.scale(dpr, dpr);
|
|
224
|
-
const slot = W / N_BARS;
|
|
225
|
-
const gap = Math.max(2, slot * 0.3);
|
|
226
|
-
const barWidth = Math.max(3, slot - gap);
|
|
227
|
-
const centerY = H / 2;
|
|
228
|
-
const startMs = Date.now();
|
|
229
|
-
let lastDrawMs = 0;
|
|
230
|
-
const FRAME_INTERVAL_MS = 1000 / 20;
|
|
231
|
-
const tick = () => {
|
|
232
|
-
rafRef.current = requestAnimationFrame(tick);
|
|
233
|
-
const nowMs = Date.now();
|
|
234
|
-
if (nowMs - lastDrawMs < FRAME_INTERVAL_MS) return;
|
|
235
|
-
lastDrawMs = nowMs;
|
|
236
|
-
const t = (nowMs - startMs) % 1_000_000;
|
|
237
|
-
const target = Math.min(1, levelRef.current);
|
|
238
|
-
ctx2d.clearRect(0, 0, W, H);
|
|
239
|
-
ctx2d.fillStyle = "rgba(132, 204, 22, 0.98)";
|
|
240
|
-
ctx2d.shadowColor = "rgba(132, 204, 22, 0.48)";
|
|
241
|
-
ctx2d.shadowBlur = 4;
|
|
242
|
-
for (let i = 0; i < N_BARS; i += 1) {
|
|
243
|
-
const phase = t * 0.005 + i * (Math.PI * 0.65);
|
|
244
|
-
const barTarget = 0.2 + Math.sin(phase) * 0.42 * target + target * 0.38;
|
|
245
|
-
const h = Math.max(4, Math.min(1, barTarget) * H * 0.92);
|
|
246
|
-
const x = i * (barWidth + gap) + gap / 2;
|
|
247
|
-
const y = centerY - h / 2;
|
|
248
|
-
const radius = Math.min(barWidth / 2, 3);
|
|
249
|
-
ctx2d.beginPath();
|
|
250
|
-
if (typeof ctx2d.roundRect === "function") {
|
|
251
|
-
ctx2d.roundRect(x, y, barWidth, h, radius);
|
|
252
|
-
} else {
|
|
253
|
-
ctx2d.rect(x, y, barWidth, h);
|
|
254
|
-
}
|
|
255
|
-
ctx2d.fill();
|
|
256
|
-
}
|
|
257
|
-
ctx2d.shadowBlur = 0;
|
|
258
|
-
};
|
|
259
|
-
tick();
|
|
260
|
-
return () => {
|
|
261
|
-
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
262
|
-
rafRef.current = null;
|
|
263
|
-
};
|
|
264
|
-
}, [expanded]);
|
|
265
|
-
|
|
266
199
|
// Let the compact chip land first, then reveal the live transcript once per
|
|
267
200
|
// meeting. The delay keeps the indicator from feeling like a sudden panel.
|
|
268
201
|
useEffect(() => {
|
|
@@ -380,7 +313,10 @@ export function RecordingPill() {
|
|
|
380
313
|
onClick={!expanded && !detached ? handlePillMediaClick : undefined}
|
|
381
314
|
>
|
|
382
315
|
<PillLogo className="pill-logo" />
|
|
383
|
-
<
|
|
316
|
+
<LiveAudioBars
|
|
317
|
+
compact={!expanded && !detached}
|
|
318
|
+
className="pill-wave-meter"
|
|
319
|
+
/>
|
|
384
320
|
</div>
|
|
385
321
|
<div className="pill-controls">
|
|
386
322
|
<span className="pill-timer">
|
|
@@ -805,13 +805,45 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
805
805
|
fill: none;
|
|
806
806
|
/* Matches the recording pill's mic waveform accent. */
|
|
807
807
|
stroke: rgba(74, 222, 128, 0.95);
|
|
808
|
-
stroke-width: 1.
|
|
808
|
+
stroke-width: 1.25;
|
|
809
809
|
stroke-linecap: round;
|
|
810
810
|
stroke-linejoin: round;
|
|
811
811
|
/* Keep the line crisp regardless of the non-uniform x-stretch. */
|
|
812
812
|
vector-effect: non-scaling-stroke;
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
+
.live-audio-bars {
|
|
816
|
+
display: inline-flex;
|
|
817
|
+
width: 48px;
|
|
818
|
+
height: 26px;
|
|
819
|
+
align-items: center;
|
|
820
|
+
justify-content: center;
|
|
821
|
+
gap: 4px;
|
|
822
|
+
flex-shrink: 0;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.live-audio-bars-compact {
|
|
826
|
+
width: 22px;
|
|
827
|
+
height: 22px;
|
|
828
|
+
gap: 3px;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.live-audio-bar {
|
|
832
|
+
display: block;
|
|
833
|
+
width: 2px;
|
|
834
|
+
min-height: 3px;
|
|
835
|
+
border-radius: 999px;
|
|
836
|
+
background: rgba(132, 204, 22, 0.98);
|
|
837
|
+
box-shadow: 0 0 5px rgba(132, 204, 22, 0.35);
|
|
838
|
+
transition: height 70ms ease-out;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@media (prefers-reduced-motion: reduce) {
|
|
842
|
+
.live-audio-bar {
|
|
843
|
+
transition: none;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
815
847
|
/* ------------------------------------------------------------------------- */
|
|
816
848
|
/* Primary CTA */
|
|
817
849
|
/* ------------------------------------------------------------------------- */
|
|
@@ -5534,7 +5566,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5534
5566
|
display: none;
|
|
5535
5567
|
}
|
|
5536
5568
|
|
|
5537
|
-
.pill-vertical .pill-wave-
|
|
5569
|
+
.pill-vertical .pill-wave-meter {
|
|
5538
5570
|
height: 22px;
|
|
5539
5571
|
width: 22px;
|
|
5540
5572
|
}
|
|
@@ -5569,7 +5601,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5569
5601
|
font-variant-numeric: tabular-nums;
|
|
5570
5602
|
}
|
|
5571
5603
|
|
|
5572
|
-
.pill-wave-
|
|
5604
|
+
.pill-wave-meter {
|
|
5573
5605
|
height: 26px;
|
|
5574
5606
|
width: 48px;
|
|
5575
5607
|
flex-shrink: 0;
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
//!
|
|
3
3
|
//! Polls the frontmost macOS app every few seconds. When Zoom or Teams stays
|
|
4
4
|
//! frontmost for a short dwell window, creates a meeting row via
|
|
5
|
-
//! `create-meeting
|
|
6
|
-
//!
|
|
5
|
+
//! `create-meeting`, and silently starts transcription when the user has
|
|
6
|
+
//! explicitly selected Auto mode. Ask/manual mode stays quiet because a
|
|
7
|
+
//! frontmost Zoom window is not reliable evidence of an active call.
|
|
7
8
|
//!
|
|
8
9
|
//! Reuses `MeetingsWatcherState` session (server URL + cookie + auth token)
|
|
9
10
|
//! so the popover only needs to push credentials once.
|
|
@@ -157,7 +158,7 @@ async fn tick_macos(
|
|
|
157
158
|
let front = crate::util::frontmost_bundle_id();
|
|
158
159
|
let matched = front.as_deref().and_then(match_vc_bundle);
|
|
159
160
|
|
|
160
|
-
let Some((platform,
|
|
161
|
+
let Some((platform, _title)) = matched else {
|
|
161
162
|
// VC app left front — clear session dedupe so a later re-focus can
|
|
162
163
|
// fire again (cooldown still applies).
|
|
163
164
|
clear_session_if_left(app, None);
|
|
@@ -173,9 +174,7 @@ async fn tick_macos(
|
|
|
173
174
|
return Ok(());
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
if config.meeting_transcription_mode
|
|
177
|
-
&& !config.show_meeting_widget_enabled
|
|
178
|
-
{
|
|
177
|
+
if config.meeting_transcription_mode != MeetingTranscriptionMode::Auto {
|
|
179
178
|
reset_dwell(app);
|
|
180
179
|
return Ok(());
|
|
181
180
|
}
|
|
@@ -250,44 +249,14 @@ async fn tick_macos(
|
|
|
250
249
|
}
|
|
251
250
|
};
|
|
252
251
|
|
|
253
|
-
let
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
let title_clone = title.to_string();
|
|
262
|
-
let platform_clone = platform.to_string();
|
|
263
|
-
let scheduled_start = chrono::Utc::now().to_rfc3339();
|
|
264
|
-
tauri::async_runtime::spawn(async move {
|
|
265
|
-
let _ = crate::notifications::notify_meeting_starting(
|
|
266
|
-
app_clone,
|
|
267
|
-
id_clone,
|
|
268
|
-
title_clone,
|
|
269
|
-
0,
|
|
270
|
-
None,
|
|
271
|
-
Some(scheduled_start),
|
|
272
|
-
None,
|
|
273
|
-
Some(platform_clone),
|
|
274
|
-
Some(auto_start),
|
|
275
|
-
Some("adhoc".to_string()),
|
|
276
|
-
)
|
|
277
|
-
.await;
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if auto_start {
|
|
282
|
-
let _ = app.emit(
|
|
283
|
-
"meetings:start-transcription",
|
|
284
|
-
serde_json::json!({
|
|
285
|
-
"meetingId": meeting_id,
|
|
286
|
-
"joinUrl": null,
|
|
287
|
-
"reason": "adhoc-auto",
|
|
288
|
-
}),
|
|
289
|
-
);
|
|
290
|
-
}
|
|
252
|
+
let _ = app.emit(
|
|
253
|
+
"meetings:start-transcription",
|
|
254
|
+
serde_json::json!({
|
|
255
|
+
"meetingId": meeting_id,
|
|
256
|
+
"joinUrl": null,
|
|
257
|
+
"reason": "adhoc-auto",
|
|
258
|
+
}),
|
|
259
|
+
);
|
|
291
260
|
|
|
292
261
|
Ok(())
|
|
293
262
|
}
|