@agent-native/core 0.114.13 → 0.114.16
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 +35 -14
- package/bin/launcher.js +34 -0
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/bin/agent-native.js +35 -14
- package/corpus/core/bin/launcher.js +34 -0
- package/corpus/core/docs/content/deployment.mdx +1 -1
- package/corpus/core/docs/content/frames.mdx +1 -1
- package/corpus/core/docs/content/integrations.mdx +27 -27
- package/corpus/core/docs/content/pr-visual-recap.mdx +17 -13
- package/corpus/core/docs/content/workspace-management.mdx +1 -1
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +4 -0
- package/corpus/core/src/client/AgentPanel.tsx +10 -4
- package/corpus/core/src/client/ConnectBuilderCard.tsx +5 -1
- package/corpus/core/src/client/chat/markdown-renderer.tsx +3 -5
- package/corpus/core/src/client/error-format.ts +2 -1
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +2 -18
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +43 -32
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +4 -2
- package/corpus/core/src/client/settings/BackgroundAgentSection.tsx +5 -1
- package/corpus/core/src/client/settings/SettingsPanel.tsx +4 -4
- package/corpus/core/src/client/settings/useBuilderStatus.ts +3 -0
- package/corpus/core/src/localization/default-messages.ts +1 -7
- package/corpus/core/src/server/builder-browser.ts +4 -0
- package/corpus/core/src/server/builder-design-systems.ts +6 -1
- package/corpus/core/src/server/fusion-app.ts +5 -1
- package/corpus/core/src/shared/builder-link-tracking.ts +41 -0
- package/corpus/core/src/shared/index.ts +1 -0
- package/corpus/templates/analytics/AGENTS.md +10 -1
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +10 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +5 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +20 -32
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +62 -0
- package/corpus/templates/analytics/changelog/2026-07-21-chart-series-controls-stay-open-while-moving-from-the-legend.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-21-dashboard-email-captures-complete-large-dashboards-in-chunks.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +4 -4
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -8
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +39 -7
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +341 -251
- package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +114 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +84 -6
- package/corpus/templates/analytics/server/plugins/db.ts +13 -0
- package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +25 -8
- package/corpus/templates/clips/app/components/library/library-grid.tsx +118 -9
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +10 -8
- package/corpus/templates/clips/app/components/player/video-player.tsx +10 -4
- package/corpus/templates/clips/app/i18n/en-US.ts +6 -0
- package/corpus/templates/clips/app/routes/_app.trash.tsx +16 -1
- package/corpus/templates/clips/changelog/2026-07-21-choose-which-whisper-model-to-use-in-settings.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-21-fixed-organization-recording-visibility-default.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-21-meeting-notes-stop-when-calls-end.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +90 -78
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +2 -0
- package/corpus/templates/clips/desktop/src/hooks/useWhisperSettings.ts +172 -0
- package/corpus/templates/clips/desktop/src/lib/meeting-call-app.ts +41 -0
- package/corpus/templates/clips/desktop/src/lib/silence-events.ts +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +80 -7
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +32 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +268 -20
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_model.rs +285 -164
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +18 -7
- package/corpus/templates/clips/learnings.defaults.md +1 -1
- package/corpus/templates/clips/server/plugins/db.ts +11 -0
- package/corpus/templates/clips/shared/builder-credits.ts +1 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +20 -38
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +40 -1
- package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +9 -1
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +5 -1
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +9 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +4 -0
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -1
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
- package/dist/client/chat/markdown-renderer.js +3 -2
- package/dist/client/chat/markdown-renderer.js.map +1 -1
- package/dist/client/error-format.d.ts +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +1 -1
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +3 -4
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +21 -7
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +2 -2
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.d.ts.map +1 -1
- package/dist/client/settings/BackgroundAgentSection.js +5 -1
- package/dist/client/settings/BackgroundAgentSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +2 -0
- package/dist/client/settings/useBuilderStatus.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/localization/default-messages.d.ts +0 -6
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +1 -7
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +4 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +6 -1
- package/dist/server/builder-design-systems.js.map +1 -1
- package/dist/server/fusion-app.d.ts.map +1 -1
- package/dist/server/fusion-app.js +2 -1
- package/dist/server/fusion-app.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/builder-link-tracking.d.ts +14 -0
- package/dist/shared/builder-link-tracking.d.ts.map +1 -0
- package/dist/shared/builder-link-tracking.js +26 -0
- package/dist/shared/builder-link-tracking.js.map +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/docs/content/deployment.mdx +1 -1
- package/docs/content/frames.mdx +1 -1
- package/docs/content/integrations.mdx +27 -27
- package/docs/content/pr-visual-recap.mdx +17 -13
- package/docs/content/workspace-management.mdx +1 -1
- package/package.json +3 -2
- package/src/agent/engine/builder-engine.ts +4 -0
- package/src/client/AgentPanel.tsx +10 -4
- package/src/client/ConnectBuilderCard.tsx +5 -1
- package/src/client/chat/markdown-renderer.tsx +3 -5
- package/src/client/error-format.ts +2 -1
- package/src/client/index.ts +1 -0
- package/src/client/integrations/IntegrationsPanel.tsx +2 -18
- package/src/client/resources/McpIntegrationDialog.tsx +43 -32
- package/src/client/resources/mcp-integration-catalog.ts +4 -2
- package/src/client/settings/BackgroundAgentSection.tsx +5 -1
- package/src/client/settings/SettingsPanel.tsx +4 -4
- package/src/client/settings/useBuilderStatus.ts +3 -0
- package/src/localization/default-messages.ts +1 -7
- package/src/server/builder-browser.ts +4 -0
- package/src/server/builder-design-systems.ts +6 -1
- package/src/server/fusion-app.ts +5 -1
- package/src/shared/builder-link-tracking.ts +41 -0
- package/src/shared/index.ts +1 -0
|
@@ -46,6 +46,7 @@ export default function TrashRoute() {
|
|
|
46
46
|
const t = useT();
|
|
47
47
|
const [sort, setSort] = useState<SortKey>("recent");
|
|
48
48
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
49
|
+
const [lastSelectedId, setLastSelectedId] = useState<string | null>(null);
|
|
49
50
|
const [confirmPurge, setConfirmPurge] = useState(false);
|
|
50
51
|
const [singlePurgeId, setSinglePurgeId] = useState<string | null>(null);
|
|
51
52
|
const [isBulkPending, setIsBulkPending] = useState(false);
|
|
@@ -60,13 +61,26 @@ export default function TrashRoute() {
|
|
|
60
61
|
"delete-recording-permanent",
|
|
61
62
|
);
|
|
62
63
|
|
|
63
|
-
const toggleSelect = (id: string) => {
|
|
64
|
+
const toggleSelect = (id: string, shiftKey = false) => {
|
|
64
65
|
setSelected((prev) => {
|
|
66
|
+
if (shiftKey && lastSelectedId && lastSelectedId !== id) {
|
|
67
|
+
const ids = recordings.map((r) => r.id);
|
|
68
|
+
const fromIndex = ids.indexOf(lastSelectedId);
|
|
69
|
+
const toIndex = ids.indexOf(id);
|
|
70
|
+
if (fromIndex !== -1 && toIndex !== -1) {
|
|
71
|
+
const [start, end] =
|
|
72
|
+
fromIndex < toIndex ? [fromIndex, toIndex] : [toIndex, fromIndex];
|
|
73
|
+
const next = new Set(prev);
|
|
74
|
+
for (let i = start; i <= end; i++) next.add(ids[i]);
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
65
78
|
const next = new Set(prev);
|
|
66
79
|
if (next.has(id)) next.delete(id);
|
|
67
80
|
else next.add(id);
|
|
68
81
|
return next;
|
|
69
82
|
});
|
|
83
|
+
setLastSelectedId(id);
|
|
70
84
|
};
|
|
71
85
|
|
|
72
86
|
const restoreAll = async (ids: string[]) => {
|
|
@@ -170,6 +184,7 @@ export default function TrashRoute() {
|
|
|
170
184
|
? new Set()
|
|
171
185
|
: new Set(recordings.map((r) => r.id)),
|
|
172
186
|
);
|
|
187
|
+
setLastSelectedId(null);
|
|
173
188
|
};
|
|
174
189
|
|
|
175
190
|
return (
|
|
@@ -51,6 +51,7 @@ import { UpdateBanner } from "./components/UpdateBanner";
|
|
|
51
51
|
import { useMediaDevices } from "./hooks/useMediaDevices";
|
|
52
52
|
import { useMeetingTranscription } from "./hooks/useMeetingTranscription";
|
|
53
53
|
import { stopAllMicMeters } from "./hooks/useMicMeter";
|
|
54
|
+
import { useWhisperSettings } from "./hooks/useWhisperSettings";
|
|
54
55
|
import { startBubbleFramePump } from "./lib/bubble-pump";
|
|
55
56
|
import {
|
|
56
57
|
startBubbleWebrtc,
|
|
@@ -571,6 +572,21 @@ function openPrivacySettings(pane: MacosPrivacyPane): void {
|
|
|
571
572
|
}
|
|
572
573
|
}
|
|
573
574
|
|
|
575
|
+
// Same explicit-drag pattern the toolbar/bubble overlays use —
|
|
576
|
+
// `data-tauri-drag-region` has been unreliable, so we call `startDragging()`
|
|
577
|
+
// directly on mousedown. Clicks on buttons/inputs still reach their handlers
|
|
578
|
+
// since we only start a drag when the mousedown target isn't inside one.
|
|
579
|
+
function handlePopoverHeaderMouseDown(event: React.MouseEvent) {
|
|
580
|
+
if (event.button !== 0) return;
|
|
581
|
+
const target = event.target as HTMLElement;
|
|
582
|
+
if (target.closest("button, a, input, select, textarea")) return;
|
|
583
|
+
getCurrentWindow()
|
|
584
|
+
.startDragging()
|
|
585
|
+
.catch((err) => {
|
|
586
|
+
console.warn("[clips-popover] startDragging failed:", err);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
574
590
|
function nativeVoiceProvider(): VoiceProvider {
|
|
575
591
|
return isMacPlatform() ? "macos-native" : "browser";
|
|
576
592
|
}
|
|
@@ -4247,7 +4263,10 @@ function Header({
|
|
|
4247
4263
|
// close button lives top-right as an absolute-positioned sibling, so the
|
|
4248
4264
|
// tabs aren't offset by the close button's width.
|
|
4249
4265
|
return (
|
|
4250
|
-
<div
|
|
4266
|
+
<div
|
|
4267
|
+
className="header header-centered"
|
|
4268
|
+
onMouseDown={handlePopoverHeaderMouseDown}
|
|
4269
|
+
>
|
|
4251
4270
|
<FeedbackButton submitterEmail={submitterEmail} />
|
|
4252
4271
|
<div
|
|
4253
4272
|
className="mode-toggle"
|
|
@@ -4443,7 +4462,10 @@ function PopoverSubViewHeader({
|
|
|
4443
4462
|
action?: ReactNode;
|
|
4444
4463
|
}) {
|
|
4445
4464
|
return (
|
|
4446
|
-
<div
|
|
4465
|
+
<div
|
|
4466
|
+
className="setup-header popover-view-header"
|
|
4467
|
+
onMouseDown={handlePopoverHeaderMouseDown}
|
|
4468
|
+
>
|
|
4447
4469
|
<button
|
|
4448
4470
|
type="button"
|
|
4449
4471
|
className="setup-back"
|
|
@@ -4940,17 +4962,20 @@ function Setup({
|
|
|
4940
4962
|
featureConfig?.meetingTranscriptionMode ?? "ask";
|
|
4941
4963
|
const showMeetingWidgetEnabled =
|
|
4942
4964
|
featureConfig?.showMeetingWidgetEnabled !== false;
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
downloadedMb: number;
|
|
4949
|
-
totalMb: number;
|
|
4950
|
-
}
|
|
4951
|
-
const [whisperStatus, setWhisperStatus] = useState<WhisperModelStatus | null>(
|
|
4952
|
-
null,
|
|
4965
|
+
const whisper = useWhisperSettings(
|
|
4966
|
+
featureConfig,
|
|
4967
|
+
voiceProvider,
|
|
4968
|
+
onVoiceProviderChange,
|
|
4969
|
+
nativeVoiceProvider,
|
|
4953
4970
|
);
|
|
4971
|
+
const {
|
|
4972
|
+
catalog: whisperModels,
|
|
4973
|
+
status: whisperStatus,
|
|
4974
|
+
enabled: whisperModelEnabled,
|
|
4975
|
+
modelId: whisperModelId,
|
|
4976
|
+
selectedModel: selectedWhisperModel,
|
|
4977
|
+
deletableModels,
|
|
4978
|
+
} = whisper;
|
|
4954
4979
|
const [screenMemoryStatus, setScreenMemoryStatus] =
|
|
4955
4980
|
useState<ScreenMemoryStatus | null>(null);
|
|
4956
4981
|
const screenMemoryStatusRefreshVersionRef = useRef(0);
|
|
@@ -5059,24 +5084,6 @@ function Setup({
|
|
|
5059
5084
|
);
|
|
5060
5085
|
}
|
|
5061
5086
|
|
|
5062
|
-
function triggerWhisperDownload() {
|
|
5063
|
-
invoke("whisper_model_download").catch(() => {});
|
|
5064
|
-
}
|
|
5065
|
-
|
|
5066
|
-
function setWhisperModelEnabled(enabled: boolean) {
|
|
5067
|
-
if (!featureConfig) return;
|
|
5068
|
-
invoke("set_feature_config", {
|
|
5069
|
-
config: { ...featureConfig, whisperModelEnabled: enabled },
|
|
5070
|
-
}).catch((err) =>
|
|
5071
|
-
console.error("[settings] set_feature_config failed", err),
|
|
5072
|
-
);
|
|
5073
|
-
if (enabled) {
|
|
5074
|
-
triggerWhisperDownload();
|
|
5075
|
-
} else if (voiceProvider === "whisper") {
|
|
5076
|
-
onVoiceProviderChange(nativeVoiceProvider());
|
|
5077
|
-
}
|
|
5078
|
-
}
|
|
5079
|
-
|
|
5080
5087
|
function setLaunchAtLoginEnabled(enabled: boolean) {
|
|
5081
5088
|
if (!featureConfig) return;
|
|
5082
5089
|
invoke("set_feature_config", {
|
|
@@ -5480,47 +5487,6 @@ function Setup({
|
|
|
5480
5487
|
};
|
|
5481
5488
|
}, [refreshScreenMemoryStatus]);
|
|
5482
5489
|
|
|
5483
|
-
// Load model status on mount and keep it current via events.
|
|
5484
|
-
useEffect(() => {
|
|
5485
|
-
let cancelled = false;
|
|
5486
|
-
const refresh = () => {
|
|
5487
|
-
invoke<WhisperModelStatus>("whisper_model_status")
|
|
5488
|
-
.then((s) => {
|
|
5489
|
-
if (!cancelled) setWhisperStatus(s);
|
|
5490
|
-
})
|
|
5491
|
-
.catch(() => {});
|
|
5492
|
-
};
|
|
5493
|
-
refresh();
|
|
5494
|
-
const unlistens: Array<() => void> = [];
|
|
5495
|
-
const track = (p: Promise<() => void>) => {
|
|
5496
|
-
p.then((u) => {
|
|
5497
|
-
if (cancelled) {
|
|
5498
|
-
try {
|
|
5499
|
-
u();
|
|
5500
|
-
} catch {
|
|
5501
|
-
/* ignore */
|
|
5502
|
-
}
|
|
5503
|
-
return;
|
|
5504
|
-
}
|
|
5505
|
-
unlistens.push(u);
|
|
5506
|
-
}).catch(() => {});
|
|
5507
|
-
};
|
|
5508
|
-
track(listen("whisper:model-progress", () => refresh()));
|
|
5509
|
-
track(listen("whisper:model-ready", () => refresh()));
|
|
5510
|
-
track(listen("whisper:model-error", () => refresh()));
|
|
5511
|
-
track(listen("whisper:model-enabled-changed", () => refresh()));
|
|
5512
|
-
return () => {
|
|
5513
|
-
cancelled = true;
|
|
5514
|
-
unlistens.forEach((u) => {
|
|
5515
|
-
try {
|
|
5516
|
-
u();
|
|
5517
|
-
} catch {
|
|
5518
|
-
/* ignore */
|
|
5519
|
-
}
|
|
5520
|
-
});
|
|
5521
|
-
};
|
|
5522
|
-
}, []);
|
|
5523
|
-
|
|
5524
5490
|
useEffect(() => {
|
|
5525
5491
|
const base = (serverUrl ?? initial ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
5526
5492
|
let cancelled = false;
|
|
@@ -5606,7 +5572,7 @@ function Setup({
|
|
|
5606
5572
|
onVoiceProviderChange(nativeVoiceProvider());
|
|
5607
5573
|
} else if (mode === "whisper") {
|
|
5608
5574
|
onVoiceProviderChange("whisper");
|
|
5609
|
-
if (!whisperModelEnabled)
|
|
5575
|
+
if (!whisperModelEnabled) whisper.setEnabled(true);
|
|
5610
5576
|
} else if (mode === "builder") {
|
|
5611
5577
|
onVoiceProviderChange("builder-gemini");
|
|
5612
5578
|
} else {
|
|
@@ -6431,7 +6397,7 @@ function Setup({
|
|
|
6431
6397
|
|
|
6432
6398
|
return (
|
|
6433
6399
|
<div className="setup">
|
|
6434
|
-
<div className="setup-header">
|
|
6400
|
+
<div className="setup-header" onMouseDown={handlePopoverHeaderMouseDown}>
|
|
6435
6401
|
{onCancel ? (
|
|
6436
6402
|
<button
|
|
6437
6403
|
type="button"
|
|
@@ -7192,15 +7158,58 @@ function Setup({
|
|
|
7192
7158
|
/>
|
|
7193
7159
|
<Switch
|
|
7194
7160
|
on={whisperModelEnabled}
|
|
7195
|
-
onChange={
|
|
7161
|
+
onChange={whisper.setEnabled}
|
|
7196
7162
|
label="Enable Whisper model"
|
|
7197
7163
|
/>
|
|
7198
7164
|
</div>
|
|
7165
|
+
<SettingLabel
|
|
7166
|
+
label="Model"
|
|
7167
|
+
hint="Larger models can improve transcription accuracy but use more storage and may run more slowly."
|
|
7168
|
+
htmlFor="whisper-model"
|
|
7169
|
+
/>
|
|
7170
|
+
<select
|
|
7171
|
+
id="whisper-model"
|
|
7172
|
+
className="setup-select"
|
|
7173
|
+
value={whisperModelId}
|
|
7174
|
+
onChange={(event) => whisper.setModelId(event.target.value)}
|
|
7175
|
+
disabled={
|
|
7176
|
+
whisperModels.length === 0 || whisperStatus?.state === "downloading"
|
|
7177
|
+
}
|
|
7178
|
+
>
|
|
7179
|
+
{whisperModels.map((model) => (
|
|
7180
|
+
<option key={model.id} value={model.id}>
|
|
7181
|
+
{model.title} · {model.sizeMb} MB — {model.description}
|
|
7182
|
+
</option>
|
|
7183
|
+
))}
|
|
7184
|
+
</select>
|
|
7185
|
+
{selectedWhisperModel ? (
|
|
7186
|
+
<p className="setup-hint">{selectedWhisperModel.description}</p>
|
|
7187
|
+
) : null}
|
|
7199
7188
|
<WhisperModelStatusRow
|
|
7200
7189
|
status={whisperStatus}
|
|
7201
7190
|
enabled={whisperModelEnabled}
|
|
7202
|
-
onDownload={
|
|
7191
|
+
onDownload={whisper.triggerDownload}
|
|
7203
7192
|
/>
|
|
7193
|
+
{deletableModels.length > 0 ? (
|
|
7194
|
+
<div className="whisper-other-models">
|
|
7195
|
+
<p className="setup-hint">Other downloaded models</p>
|
|
7196
|
+
{deletableModels.map((model) => (
|
|
7197
|
+
<div key={model.id} className="whisper-other-model-row">
|
|
7198
|
+
<span className="whisper-other-model-name">
|
|
7199
|
+
{model.title} · {model.sizeMb} MB
|
|
7200
|
+
</span>
|
|
7201
|
+
<button
|
|
7202
|
+
type="button"
|
|
7203
|
+
className="whisper-delete-btn"
|
|
7204
|
+
onClick={() => whisper.deleteModel(model.id)}
|
|
7205
|
+
>
|
|
7206
|
+
<IconTrash size={13} />
|
|
7207
|
+
Delete
|
|
7208
|
+
</button>
|
|
7209
|
+
</div>
|
|
7210
|
+
))}
|
|
7211
|
+
</div>
|
|
7212
|
+
) : null}
|
|
7204
7213
|
</div>
|
|
7205
7214
|
|
|
7206
7215
|
<div className="setup-section-heading">Dictation</div>
|
|
@@ -7528,9 +7537,12 @@ function WhisperModelStatusRow({
|
|
|
7528
7537
|
<span className="whisper-progress-label">
|
|
7529
7538
|
Downloading… {status.downloadedMb} / {status.totalMb} MB ({pct}%)
|
|
7530
7539
|
</span>
|
|
7531
|
-
<
|
|
7532
|
-
|
|
7533
|
-
|
|
7540
|
+
<progress
|
|
7541
|
+
className="whisper-progress-bar"
|
|
7542
|
+
value={pct}
|
|
7543
|
+
max={100}
|
|
7544
|
+
aria-label={`Whisper model download ${pct}% complete`}
|
|
7545
|
+
/>
|
|
7534
7546
|
</div>
|
|
7535
7547
|
);
|
|
7536
7548
|
}
|
|
@@ -3,6 +3,7 @@ import { emit, listen } from "@tauri-apps/api/event";
|
|
|
3
3
|
import { open as openExternal } from "@tauri-apps/plugin-shell";
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
5
5
|
|
|
6
|
+
import { callAppBundleIdsForJoinUrl } from "../lib/meeting-call-app";
|
|
6
7
|
import {
|
|
7
8
|
appendFinalTranscript,
|
|
8
9
|
onFinalTranscript,
|
|
@@ -414,6 +415,7 @@ export function useMeetingTranscription({
|
|
|
414
415
|
silenceThreshold: 0.05,
|
|
415
416
|
silenceMs: 15 * 60 * 1000,
|
|
416
417
|
callEndedMs: 2 * 60 * 1000,
|
|
418
|
+
callAppBundleIds: callAppBundleIdsForJoinUrl(payload.joinUrl),
|
|
417
419
|
scheduledEndMs,
|
|
418
420
|
watchSleep: true,
|
|
419
421
|
watchCallEnded: true,
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { invoke } from "@tauri-apps/api/core";
|
|
2
|
+
import { listen } from "@tauri-apps/api/event";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import type { VoiceProvider } from "../lib/voice-dictation";
|
|
6
|
+
import type { FeatureConfig } from "../shared/config";
|
|
7
|
+
|
|
8
|
+
export interface WhisperModelOption {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
url: string;
|
|
13
|
+
filename: string;
|
|
14
|
+
sha256: string;
|
|
15
|
+
sizeBytes: number;
|
|
16
|
+
sizeMb: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type WhisperModelState =
|
|
20
|
+
| "disabled"
|
|
21
|
+
| "missing"
|
|
22
|
+
| "downloading"
|
|
23
|
+
| "ready";
|
|
24
|
+
|
|
25
|
+
export interface WhisperModelStatus {
|
|
26
|
+
state: WhisperModelState;
|
|
27
|
+
path: string;
|
|
28
|
+
downloadedMb: number;
|
|
29
|
+
totalMb: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const WHISPER_STATUS_EVENTS = [
|
|
33
|
+
"whisper:model-progress",
|
|
34
|
+
"whisper:model-error",
|
|
35
|
+
"whisper:model-enabled-changed",
|
|
36
|
+
"whisper:model-selection-changed",
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export function useWhisperSettings(
|
|
40
|
+
featureConfig: FeatureConfig | null,
|
|
41
|
+
voiceProvider: VoiceProvider,
|
|
42
|
+
onVoiceProviderChange: (provider: VoiceProvider) => void,
|
|
43
|
+
nativeVoiceProvider: () => VoiceProvider,
|
|
44
|
+
) {
|
|
45
|
+
const [catalog, setCatalog] = useState<WhisperModelOption[]>([]);
|
|
46
|
+
const [downloadedModelIds, setDownloadedModelIds] = useState<string[]>([]);
|
|
47
|
+
const [status, setStatus] = useState<WhisperModelStatus | null>(null);
|
|
48
|
+
|
|
49
|
+
const enabled = featureConfig?.whisperModelEnabled !== false;
|
|
50
|
+
const modelId = featureConfig?.whisperModelId ?? "base";
|
|
51
|
+
const selectedModel = catalog.find((m) => m.id === modelId) ?? null;
|
|
52
|
+
const deletableModels = catalog.filter(
|
|
53
|
+
(m) => m.id !== modelId && downloadedModelIds.includes(m.id),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
invoke<WhisperModelOption[]>("whisper_models")
|
|
58
|
+
.then(setCatalog)
|
|
59
|
+
.catch(() => {});
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const refreshDownloaded = useCallback(() => {
|
|
63
|
+
invoke<string[]>("whisper_downloaded_models")
|
|
64
|
+
.then(setDownloadedModelIds)
|
|
65
|
+
.catch(() => {});
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
const refreshStatus = () => {
|
|
71
|
+
invoke<WhisperModelStatus>("whisper_model_status")
|
|
72
|
+
.then((s) => {
|
|
73
|
+
if (!cancelled) setStatus(s);
|
|
74
|
+
})
|
|
75
|
+
.catch(() => {});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
refreshStatus();
|
|
79
|
+
refreshDownloaded();
|
|
80
|
+
|
|
81
|
+
const unlistens: Array<() => void> = [];
|
|
82
|
+
const track = (p: Promise<() => void>) => {
|
|
83
|
+
p.then((u) => {
|
|
84
|
+
if (cancelled) {
|
|
85
|
+
try {
|
|
86
|
+
u();
|
|
87
|
+
} catch {
|
|
88
|
+
/* ignore */
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
unlistens.push(u);
|
|
93
|
+
}).catch(() => {});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
for (const event of WHISPER_STATUS_EVENTS) {
|
|
97
|
+
track(listen(event, () => refreshStatus()));
|
|
98
|
+
}
|
|
99
|
+
track(
|
|
100
|
+
listen("whisper:model-ready", () => {
|
|
101
|
+
refreshStatus();
|
|
102
|
+
refreshDownloaded();
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
track(listen("whisper:model-deleted", () => refreshDownloaded()));
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
cancelled = true;
|
|
109
|
+
unlistens.forEach((u) => {
|
|
110
|
+
try {
|
|
111
|
+
u();
|
|
112
|
+
} catch {
|
|
113
|
+
/* ignore */
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
}, [refreshDownloaded]);
|
|
118
|
+
|
|
119
|
+
function triggerDownload() {
|
|
120
|
+
invoke("whisper_model_download").catch(() => {});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function setEnabled(next: boolean) {
|
|
124
|
+
if (!featureConfig) return;
|
|
125
|
+
invoke("set_feature_config", {
|
|
126
|
+
config: { ...featureConfig, whisperModelEnabled: next },
|
|
127
|
+
})
|
|
128
|
+
.then(() => {
|
|
129
|
+
if (next) {
|
|
130
|
+
triggerDownload();
|
|
131
|
+
} else if (voiceProvider === "whisper") {
|
|
132
|
+
onVoiceProviderChange(nativeVoiceProvider());
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
.catch((err) =>
|
|
136
|
+
console.error("[whisper] set_feature_config failed", err),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setModelId(next: string) {
|
|
141
|
+
if (!featureConfig) return;
|
|
142
|
+
invoke("set_feature_config", {
|
|
143
|
+
config: { ...featureConfig, whisperModelId: next },
|
|
144
|
+
})
|
|
145
|
+
.then(() => {
|
|
146
|
+
if (enabled) triggerDownload();
|
|
147
|
+
})
|
|
148
|
+
.catch((err) =>
|
|
149
|
+
console.error("[whisper] set_feature_config failed", err),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function deleteModel(modelIdToDelete: string) {
|
|
154
|
+
invoke("whisper_model_delete", { modelId: modelIdToDelete })
|
|
155
|
+
.then(() => refreshDownloaded())
|
|
156
|
+
.catch(() => {});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
catalog,
|
|
161
|
+
downloadedModelIds,
|
|
162
|
+
status,
|
|
163
|
+
enabled,
|
|
164
|
+
modelId,
|
|
165
|
+
selectedModel,
|
|
166
|
+
deletableModels,
|
|
167
|
+
triggerDownload,
|
|
168
|
+
setEnabled,
|
|
169
|
+
setModelId,
|
|
170
|
+
deleteModel,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const NATIVE_CALL_APP_BUNDLE_IDS = [
|
|
2
|
+
"us.zoom.xos",
|
|
3
|
+
"us.zoom.ZoomClips",
|
|
4
|
+
"com.microsoft.teams2",
|
|
5
|
+
"com.microsoft.teams",
|
|
6
|
+
] as const;
|
|
7
|
+
|
|
8
|
+
const BROWSER_CALL_APP_BUNDLE_IDS = [
|
|
9
|
+
"com.google.Chrome",
|
|
10
|
+
"company.thebrowser.Browser",
|
|
11
|
+
"com.apple.Safari",
|
|
12
|
+
"org.mozilla.firefox",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
function isHost(hostname: string, host: string): boolean {
|
|
16
|
+
return hostname === host || hostname.endsWith(`.${host}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Limit microphone-use monitoring to the app that can own this meeting.
|
|
21
|
+
* Browser bundles are included only when the calendar join URL identifies a
|
|
22
|
+
* browser-hosted provider, so another tab using the microphone cannot end a
|
|
23
|
+
* native Zoom or Teams recording.
|
|
24
|
+
*/
|
|
25
|
+
export function callAppBundleIdsForJoinUrl(joinUrl?: string | null): string[] {
|
|
26
|
+
if (!joinUrl) return [...NATIVE_CALL_APP_BUNDLE_IDS];
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const hostname = new URL(joinUrl).hostname.toLowerCase();
|
|
30
|
+
if (isHost(hostname, "meet.google.com")) {
|
|
31
|
+
return [...BROWSER_CALL_APP_BUNDLE_IDS];
|
|
32
|
+
}
|
|
33
|
+
if (isHost(hostname, "teams.microsoft.com")) {
|
|
34
|
+
return [...NATIVE_CALL_APP_BUNDLE_IDS, ...BROWSER_CALL_APP_BUNDLE_IDS];
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Native Zoom and Teams remain the safe fallback for malformed URLs.
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...NATIVE_CALL_APP_BUNDLE_IDS];
|
|
41
|
+
}
|
|
@@ -5600,23 +5600,39 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5600
5600
|
}
|
|
5601
5601
|
|
|
5602
5602
|
.whisper-progress-bar {
|
|
5603
|
-
height: 4px;
|
|
5604
5603
|
width: 100%;
|
|
5604
|
+
height: 4px;
|
|
5605
|
+
margin-top: 4px;
|
|
5606
|
+
border: none;
|
|
5605
5607
|
border-radius: 999px;
|
|
5606
|
-
|
|
5608
|
+
appearance: none;
|
|
5607
5609
|
overflow: hidden;
|
|
5608
|
-
|
|
5610
|
+
background: var(--surface-strong);
|
|
5611
|
+
color: #16a34a;
|
|
5609
5612
|
}
|
|
5610
5613
|
|
|
5611
|
-
.whisper-progress-
|
|
5612
|
-
|
|
5614
|
+
.whisper-progress-bar::-webkit-progress-bar {
|
|
5615
|
+
border-radius: inherit;
|
|
5616
|
+
background: var(--surface-strong);
|
|
5617
|
+
}
|
|
5618
|
+
|
|
5619
|
+
.whisper-progress-bar::-webkit-progress-value {
|
|
5620
|
+
border-radius: inherit;
|
|
5621
|
+
background: #16a34a;
|
|
5622
|
+
}
|
|
5623
|
+
|
|
5624
|
+
.whisper-progress-bar::-moz-progress-bar {
|
|
5613
5625
|
border-radius: inherit;
|
|
5614
5626
|
background: #16a34a;
|
|
5615
|
-
transition: width 300ms ease;
|
|
5616
5627
|
}
|
|
5617
5628
|
|
|
5618
5629
|
@media (prefers-color-scheme: dark) {
|
|
5619
|
-
.whisper-progress-
|
|
5630
|
+
.whisper-progress-bar {
|
|
5631
|
+
color: #4ade80;
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5634
|
+
.whisper-progress-bar::-webkit-progress-value,
|
|
5635
|
+
.whisper-progress-bar::-moz-progress-bar {
|
|
5620
5636
|
background: #4ade80;
|
|
5621
5637
|
}
|
|
5622
5638
|
}
|
|
@@ -5644,6 +5660,63 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
5644
5660
|
background: var(--surface-hover);
|
|
5645
5661
|
}
|
|
5646
5662
|
|
|
5663
|
+
.whisper-other-models {
|
|
5664
|
+
display: flex;
|
|
5665
|
+
flex-direction: column;
|
|
5666
|
+
gap: 4px;
|
|
5667
|
+
margin-top: 6px;
|
|
5668
|
+
}
|
|
5669
|
+
|
|
5670
|
+
.whisper-other-model-row {
|
|
5671
|
+
display: flex;
|
|
5672
|
+
align-items: center;
|
|
5673
|
+
justify-content: space-between;
|
|
5674
|
+
gap: 8px;
|
|
5675
|
+
padding: 5px 8px;
|
|
5676
|
+
border: 1px solid var(--border);
|
|
5677
|
+
border-radius: var(--radius-sm);
|
|
5678
|
+
background: var(--surface);
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
.whisper-other-model-name {
|
|
5682
|
+
font-size: 11px;
|
|
5683
|
+
color: var(--fg-muted);
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
.whisper-delete-btn {
|
|
5687
|
+
display: inline-flex;
|
|
5688
|
+
align-items: center;
|
|
5689
|
+
gap: 4px;
|
|
5690
|
+
flex-shrink: 0;
|
|
5691
|
+
height: 22px;
|
|
5692
|
+
padding: 0 8px;
|
|
5693
|
+
border: 1px solid var(--border-strong);
|
|
5694
|
+
border-radius: var(--radius-sm);
|
|
5695
|
+
background: var(--surface);
|
|
5696
|
+
color: var(--fg-muted);
|
|
5697
|
+
cursor: pointer;
|
|
5698
|
+
font-size: 11px;
|
|
5699
|
+
font-weight: 600;
|
|
5700
|
+
transition:
|
|
5701
|
+
background 120ms,
|
|
5702
|
+
color 120ms,
|
|
5703
|
+
border-color 120ms;
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
.whisper-delete-btn:hover {
|
|
5707
|
+
background: rgba(239, 68, 68, 0.1);
|
|
5708
|
+
border-color: rgba(239, 68, 68, 0.4);
|
|
5709
|
+
color: #ef4444;
|
|
5710
|
+
}
|
|
5711
|
+
|
|
5712
|
+
@media (prefers-color-scheme: dark) {
|
|
5713
|
+
.whisper-delete-btn:hover {
|
|
5714
|
+
background: rgba(239, 68, 68, 0.15);
|
|
5715
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
5716
|
+
color: #f87171;
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5647
5720
|
/* ---- */
|
|
5648
5721
|
|
|
5649
5722
|
.pill-error {
|