@agent-native/core 0.94.0 → 0.94.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 +12 -0
- package/corpus/core/docs/content/cloneable-saas.mdx +15 -15
- package/corpus/core/docs/content/creating-templates.mdx +1 -1
- package/corpus/core/docs/content/extensions.mdx +1 -1
- package/corpus/core/docs/content/local-file-mode.mdx +6 -6
- package/corpus/core/docs/content/locales/ar-SA/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/de-DE/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/voice-input.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/voice-input.mdx +2 -2
- package/corpus/core/docs/content/sharing.mdx +4 -4
- package/corpus/core/docs/content/template-analytics.mdx +2 -2
- package/corpus/core/docs/content/template-calendar.mdx +4 -4
- package/corpus/core/docs/content/template-clips.mdx +2 -2
- package/corpus/core/docs/content/template-content.mdx +12 -11
- package/corpus/core/docs/content/template-mail.mdx +1 -1
- package/corpus/core/docs/content/template-slides.mdx +2 -2
- package/corpus/core/docs/content/voice-input.mdx +2 -2
- package/corpus/core/docs/content/workspace.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +75 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +19 -1
- package/corpus/core/src/client/agent-chat.ts +6 -0
- package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +72 -2
- package/corpus/core/src/client/composer/realtime-voice-audio-level.ts +64 -0
- package/corpus/core/src/client/composer/realtime-voice-transcript.ts +134 -0
- package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +220 -6
- package/corpus/core/src/client/use-chat-threads.ts +33 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +98 -1
- package/corpus/templates/clips/changelog/2026-07-10-comment-urls-open-as-clickable-links.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-desktop-recording-uploads-now-show-a-brief-uploaded-state-wi.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-desktop-recordings-now-retry-saving-their-locally-captured-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-desktop-update-prompts-are-more-compact-and-easier-to-dismiss.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-quiet-recordings-now-finish-saving-instead-of-getting-stuck.md +6 -0
- package/corpus/templates/clips/desktop/src/components/UpdateBanner.tsx +7 -4
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +284 -85
- package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +148 -40
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +13 -13
- package/corpus/templates/clips/desktop/src/styles.css +83 -13
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -28
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +130 -65
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +10 -4
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +82 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +82 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +57 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +9 -0
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts +3 -1
- package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/RealtimeVoiceMode.js +32 -3
- package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/client/composer/realtime-voice-audio-level.d.ts +14 -0
- package/dist/client/composer/realtime-voice-audio-level.d.ts.map +1 -0
- package/dist/client/composer/realtime-voice-audio-level.js +47 -0
- package/dist/client/composer/realtime-voice-audio-level.js.map +1 -0
- package/dist/client/composer/realtime-voice-transcript.d.ts +26 -0
- package/dist/client/composer/realtime-voice-transcript.d.ts.map +1 -0
- package/dist/client/composer/realtime-voice-transcript.js +103 -0
- package/dist/client/composer/realtime-voice-transcript.js.map +1 -0
- package/dist/client/composer/useRealtimeVoiceMode.d.ts +14 -0
- package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/client/composer/useRealtimeVoiceMode.js +177 -6
- package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +26 -6
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
- package/docs/content/cloneable-saas.mdx +15 -15
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/extensions.mdx +1 -1
- package/docs/content/local-file-mode.mdx +6 -6
- package/docs/content/locales/ar-SA/voice-input.mdx +2 -2
- package/docs/content/locales/de-DE/voice-input.mdx +2 -2
- package/docs/content/locales/es-ES/voice-input.mdx +2 -2
- package/docs/content/locales/fr-FR/voice-input.mdx +2 -2
- package/docs/content/locales/hi-IN/voice-input.mdx +2 -2
- package/docs/content/locales/ja-JP/voice-input.mdx +2 -2
- package/docs/content/locales/ko-KR/voice-input.mdx +2 -2
- package/docs/content/locales/pt-BR/voice-input.mdx +2 -2
- package/docs/content/locales/zh-CN/voice-input.mdx +2 -2
- package/docs/content/locales/zh-TW/voice-input.mdx +2 -2
- package/docs/content/sharing.mdx +4 -4
- package/docs/content/template-analytics.mdx +2 -2
- package/docs/content/template-calendar.mdx +4 -4
- package/docs/content/template-clips.mdx +2 -2
- package/docs/content/template-content.mdx +12 -11
- package/docs/content/template-mail.mdx +1 -1
- package/docs/content/template-slides.mdx +2 -2
- package/docs/content/voice-input.mdx +2 -2
- package/docs/content/workspace.mdx +1 -1
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconAlertCircle,
|
|
3
|
+
IconCheck,
|
|
4
|
+
IconExternalLink,
|
|
5
|
+
IconX,
|
|
6
|
+
} from "@tabler/icons-react";
|
|
1
7
|
import { invoke } from "@tauri-apps/api/core";
|
|
2
8
|
import { listen } from "@tauri-apps/api/event";
|
|
3
9
|
import { open as openExternal } from "@tauri-apps/plugin-shell";
|
|
@@ -13,6 +19,7 @@ type NativeUploadProgress = {
|
|
|
13
19
|
type ProcessingProgress = {
|
|
14
20
|
stage?: string;
|
|
15
21
|
progress?: number | null;
|
|
22
|
+
viewUrl?: string;
|
|
16
23
|
};
|
|
17
24
|
|
|
18
25
|
type NativeUploadFinished = {
|
|
@@ -23,15 +30,40 @@ type NativeUploadFinished = {
|
|
|
23
30
|
localFilePath?: string | null;
|
|
24
31
|
};
|
|
25
32
|
|
|
33
|
+
const FINALIZING_RESULT_STORAGE_KEY = "clips-finalizing-result";
|
|
34
|
+
|
|
35
|
+
function takePersistedFinalizingResult(): NativeUploadFinished | null {
|
|
36
|
+
try {
|
|
37
|
+
const raw = window.localStorage.getItem(FINALIZING_RESULT_STORAGE_KEY);
|
|
38
|
+
if (!raw) return null;
|
|
39
|
+
window.localStorage.removeItem(FINALIZING_RESULT_STORAGE_KEY);
|
|
40
|
+
const parsed: unknown = JSON.parse(raw);
|
|
41
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
42
|
+
const payload = parsed as Record<string, unknown>;
|
|
43
|
+
if (
|
|
44
|
+
typeof payload.recordingId !== "string" ||
|
|
45
|
+
typeof payload.viewUrl !== "string" ||
|
|
46
|
+
typeof payload.ok !== "boolean"
|
|
47
|
+
) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
recordingId: payload.recordingId,
|
|
52
|
+
viewUrl: payload.viewUrl,
|
|
53
|
+
ok: payload.ok,
|
|
54
|
+
error: typeof payload.error === "string" ? payload.error : null,
|
|
55
|
+
localFilePath: null,
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* The window ignores cursor events on the Rust side, so the compact
|
|
33
|
-
* bottom-left card does not block the user's screen while compression or
|
|
34
|
-
* upload runs.
|
|
63
|
+
* Compact bottom-left feedback window. Rendered the moment the user clicks
|
|
64
|
+
* Stop and kept visible while the desktop finishes its durable backup and
|
|
65
|
+
* first upload/finalize attempt. The browser can open `/r/:id` earlier so the
|
|
66
|
+
* page shows live progress.
|
|
35
67
|
*/
|
|
36
68
|
export function Finalizing() {
|
|
37
69
|
const [progress, setProgress] = useState<ProcessingProgress>({
|
|
@@ -45,6 +77,9 @@ export function Finalizing() {
|
|
|
45
77
|
let unlistenFinished: (() => void) | null = null;
|
|
46
78
|
let completionTimer: ReturnType<typeof window.setTimeout> | null = null;
|
|
47
79
|
let openingWatchdog: ReturnType<typeof window.setTimeout> | null = null;
|
|
80
|
+
const hardWatchdog = window.setTimeout(() => {
|
|
81
|
+
void invoke("hide_finalizing").catch(() => {});
|
|
82
|
+
}, 120_000);
|
|
48
83
|
let finishedHandled = false;
|
|
49
84
|
const clearCompletionTimer = () => {
|
|
50
85
|
if (completionTimer) {
|
|
@@ -59,6 +94,7 @@ export function Finalizing() {
|
|
|
59
94
|
}
|
|
60
95
|
};
|
|
61
96
|
listen<NativeUploadProgress>("clips:native-upload-progress", (event) => {
|
|
97
|
+
if (finishedHandled) return;
|
|
62
98
|
const payload = event.payload ?? {};
|
|
63
99
|
if (payload.stage === "opening" && payload.progress === 1) {
|
|
64
100
|
clearOpeningWatchdog();
|
|
@@ -99,21 +135,36 @@ export function Finalizing() {
|
|
|
99
135
|
const handleFinished = (payload: NativeUploadFinished) => {
|
|
100
136
|
if (disposed || finishedHandled) return;
|
|
101
137
|
finishedHandled = true;
|
|
138
|
+
try {
|
|
139
|
+
window.localStorage.removeItem(FINALIZING_RESULT_STORAGE_KEY);
|
|
140
|
+
} catch {
|
|
141
|
+
// Storage is a best-effort event-race fallback only.
|
|
142
|
+
}
|
|
102
143
|
clearCompletionTimer();
|
|
103
144
|
clearOpeningWatchdog();
|
|
104
145
|
if (payload.ok && payload.viewUrl) {
|
|
105
|
-
setProgress({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
setProgress({
|
|
147
|
+
stage: "uploaded",
|
|
148
|
+
progress: 1,
|
|
149
|
+
viewUrl: payload.viewUrl,
|
|
150
|
+
});
|
|
151
|
+
void claimNativeOpen(payload.recordingId).then((claimed) => {
|
|
152
|
+
if (!claimed || disposed) return;
|
|
153
|
+
void openExternal(payload.viewUrl as string).catch((err) => {
|
|
154
|
+
console.error("[clips-finalizing] open clip failed:", err);
|
|
111
155
|
});
|
|
112
|
-
}
|
|
156
|
+
});
|
|
157
|
+
completionTimer = window.setTimeout(() => {
|
|
158
|
+
void invoke("hide_finalizing").catch(() => {});
|
|
159
|
+
}, 2500);
|
|
113
160
|
return;
|
|
114
161
|
}
|
|
115
162
|
|
|
116
|
-
setProgress({
|
|
163
|
+
setProgress({
|
|
164
|
+
stage: "failed",
|
|
165
|
+
progress: 1,
|
|
166
|
+
viewUrl: payload.viewUrl,
|
|
167
|
+
});
|
|
117
168
|
completionTimer = window.setTimeout(() => {
|
|
118
169
|
void invoke("show_popover").catch(() => {});
|
|
119
170
|
void invoke("hide_finalizing").catch(() => {});
|
|
@@ -129,6 +180,11 @@ export function Finalizing() {
|
|
|
129
180
|
return;
|
|
130
181
|
}
|
|
131
182
|
unlistenFinished = u;
|
|
183
|
+
const persisted = takePersistedFinalizingResult();
|
|
184
|
+
if (persisted) {
|
|
185
|
+
handleFinished(persisted);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
132
188
|
void invoke<NativeUploadFinished | null>(
|
|
133
189
|
"native_fullscreen_take_upload_finished",
|
|
134
190
|
)
|
|
@@ -142,6 +198,7 @@ export function Finalizing() {
|
|
|
142
198
|
disposed = true;
|
|
143
199
|
clearCompletionTimer();
|
|
144
200
|
clearOpeningWatchdog();
|
|
201
|
+
window.clearTimeout(hardWatchdog);
|
|
145
202
|
unlisten?.();
|
|
146
203
|
unlistenFinished?.();
|
|
147
204
|
};
|
|
@@ -152,38 +209,89 @@ export function Finalizing() {
|
|
|
152
209
|
? Math.round(progress.progress * 100)
|
|
153
210
|
: null;
|
|
154
211
|
const caption =
|
|
155
|
-
progress.stage === "
|
|
156
|
-
|
|
157
|
-
progress.stage === "opening"
|
|
158
|
-
? "Uploading clip..."
|
|
212
|
+
progress.stage === "uploaded"
|
|
213
|
+
? "Uploaded"
|
|
159
214
|
: progress.stage === "failed"
|
|
160
215
|
? "Upload paused"
|
|
161
|
-
: "
|
|
216
|
+
: progress.stage === "uploading" ||
|
|
217
|
+
progress.stage === "processing" ||
|
|
218
|
+
progress.stage === "opening"
|
|
219
|
+
? "Uploading clip..."
|
|
220
|
+
: "Optimizing clip...";
|
|
221
|
+
const finished = progress.stage === "uploaded" || progress.stage === "failed";
|
|
222
|
+
|
|
223
|
+
const dismiss = () => {
|
|
224
|
+
void invoke("hide_finalizing").catch(() => {});
|
|
225
|
+
};
|
|
226
|
+
const openClip = () => {
|
|
227
|
+
if (!progress.viewUrl) return;
|
|
228
|
+
void openExternal(progress.viewUrl).catch((err) => {
|
|
229
|
+
console.error("[clips-finalizing] open clip failed:", err);
|
|
230
|
+
});
|
|
231
|
+
};
|
|
162
232
|
|
|
163
233
|
return (
|
|
164
234
|
<div className="finalizing-root">
|
|
165
235
|
<div className="finalizing-card">
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
aria-valuemax={100}
|
|
176
|
-
aria-valuenow={percent ?? undefined}
|
|
177
|
-
>
|
|
178
|
-
<div
|
|
179
|
-
className={
|
|
180
|
-
percent === null
|
|
181
|
-
? "finalizing-progress-fill finalizing-progress-fill-indeterminate"
|
|
182
|
-
: "finalizing-progress-fill"
|
|
183
|
-
}
|
|
184
|
-
style={percent === null ? undefined : { width: `${percent}%` }}
|
|
236
|
+
{progress.stage === "uploaded" ? (
|
|
237
|
+
<IconCheck
|
|
238
|
+
className="finalizing-status-icon finalizing-status-icon-success"
|
|
239
|
+
aria-hidden="true"
|
|
240
|
+
/>
|
|
241
|
+
) : progress.stage === "failed" ? (
|
|
242
|
+
<IconAlertCircle
|
|
243
|
+
className="finalizing-status-icon finalizing-status-icon-failed"
|
|
244
|
+
aria-hidden="true"
|
|
185
245
|
/>
|
|
246
|
+
) : (
|
|
247
|
+
<div className="finalizing-spinner" aria-hidden="true" />
|
|
248
|
+
)}
|
|
249
|
+
<div className="finalizing-caption" aria-live="polite">
|
|
250
|
+
{caption}
|
|
251
|
+
</div>
|
|
252
|
+
<div className="finalizing-actions">
|
|
253
|
+
{progress.viewUrl ? (
|
|
254
|
+
<button
|
|
255
|
+
type="button"
|
|
256
|
+
className="finalizing-action"
|
|
257
|
+
onClick={openClip}
|
|
258
|
+
aria-label="Open clip in browser"
|
|
259
|
+
title="Open clip in browser"
|
|
260
|
+
>
|
|
261
|
+
<IconExternalLink aria-hidden="true" />
|
|
262
|
+
</button>
|
|
263
|
+
) : null}
|
|
264
|
+
<button
|
|
265
|
+
type="button"
|
|
266
|
+
className="finalizing-action"
|
|
267
|
+
onClick={dismiss}
|
|
268
|
+
aria-label="Dismiss upload status"
|
|
269
|
+
title="Dismiss"
|
|
270
|
+
>
|
|
271
|
+
<IconX aria-hidden="true" />
|
|
272
|
+
</button>
|
|
186
273
|
</div>
|
|
274
|
+
{!finished ? (
|
|
275
|
+
<div
|
|
276
|
+
className="finalizing-progress"
|
|
277
|
+
aria-label={
|
|
278
|
+
percent === null ? caption : `${caption} ${percent}% complete`
|
|
279
|
+
}
|
|
280
|
+
role="progressbar"
|
|
281
|
+
aria-valuemin={0}
|
|
282
|
+
aria-valuemax={100}
|
|
283
|
+
aria-valuenow={percent ?? undefined}
|
|
284
|
+
>
|
|
285
|
+
<div
|
|
286
|
+
className={
|
|
287
|
+
percent === null
|
|
288
|
+
? "finalizing-progress-fill finalizing-progress-fill-indeterminate"
|
|
289
|
+
: "finalizing-progress-fill"
|
|
290
|
+
}
|
|
291
|
+
style={percent === null ? undefined : { width: `${percent}%` }}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
) : null}
|
|
187
295
|
</div>
|
|
188
296
|
</div>
|
|
189
297
|
);
|
|
@@ -326,19 +326,19 @@ export function MeetingNotification() {
|
|
|
326
326
|
const secondaryLabel = hasJoin ? "& open Clips" : null;
|
|
327
327
|
|
|
328
328
|
return (
|
|
329
|
-
<div
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
<div className="meeting-notification-root">
|
|
330
|
+
<div
|
|
331
|
+
className="meeting-notification"
|
|
332
|
+
onMouseEnter={() => {
|
|
333
|
+
setShowClose(true);
|
|
334
|
+
clearAutoHide();
|
|
335
|
+
}}
|
|
336
|
+
onMouseLeave={() => {
|
|
337
|
+
setShowClose(false);
|
|
338
|
+
setMenuOpen(false);
|
|
339
|
+
resumeAutoHide();
|
|
340
|
+
}}
|
|
341
|
+
>
|
|
342
342
|
<div
|
|
343
343
|
className={`meeting-notification-bar ${isCalendar ? "meeting-notification-bar-calendar" : "meeting-notification-bar-adhoc"}`}
|
|
344
344
|
/>
|
|
@@ -2527,12 +2527,9 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
2527
2527
|
/* ------------------------------------------------------------------------- */
|
|
2528
2528
|
|
|
2529
2529
|
/*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
* compact card tucked into the bottom-left with a spinner and caption.
|
|
2534
|
-
* The window also has ignore_cursor_events(true), so clicks pass through
|
|
2535
|
-
* to whatever is beneath and the progress card stays out of the way.
|
|
2530
|
+
* Compact transparent window shown after the user clicks Stop. Keeping the OS
|
|
2531
|
+
* window close to the card's bounds lets its open/dismiss controls stay
|
|
2532
|
+
* interactive without blocking the rest of the screen.
|
|
2536
2533
|
*/
|
|
2537
2534
|
.finalizing-root {
|
|
2538
2535
|
position: fixed;
|
|
@@ -2540,17 +2537,16 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
2540
2537
|
display: flex;
|
|
2541
2538
|
align-items: flex-end;
|
|
2542
2539
|
justify-content: flex-start;
|
|
2543
|
-
padding:
|
|
2540
|
+
padding: 10px;
|
|
2544
2541
|
background: transparent;
|
|
2545
|
-
pointer-events: none;
|
|
2546
2542
|
}
|
|
2547
2543
|
|
|
2548
2544
|
.finalizing-card {
|
|
2549
2545
|
display: grid;
|
|
2550
|
-
grid-template-columns: 20px minmax(0, 1fr);
|
|
2546
|
+
grid-template-columns: 20px minmax(0, 1fr) auto;
|
|
2551
2547
|
align-items: center;
|
|
2552
2548
|
gap: 7px 10px;
|
|
2553
|
-
width:
|
|
2549
|
+
width: 100%;
|
|
2554
2550
|
padding: 12px 14px;
|
|
2555
2551
|
border-radius: 16px;
|
|
2556
2552
|
background: rgba(20, 22, 28, 0.94);
|
|
@@ -2577,6 +2573,20 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
2577
2573
|
animation: finalizing-spin 0.9s linear infinite;
|
|
2578
2574
|
}
|
|
2579
2575
|
|
|
2576
|
+
.finalizing-status-icon {
|
|
2577
|
+
width: 18px;
|
|
2578
|
+
height: 18px;
|
|
2579
|
+
stroke-width: 2.2;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
.finalizing-status-icon-success {
|
|
2583
|
+
color: #8ce99a;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
.finalizing-status-icon-failed {
|
|
2587
|
+
color: #ffb4a8;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2580
2590
|
@keyframes finalizing-spin {
|
|
2581
2591
|
to {
|
|
2582
2592
|
transform: rotate(360deg);
|
|
@@ -2592,6 +2602,45 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
2592
2602
|
text-align: left;
|
|
2593
2603
|
}
|
|
2594
2604
|
|
|
2605
|
+
.finalizing-actions {
|
|
2606
|
+
display: flex;
|
|
2607
|
+
align-items: center;
|
|
2608
|
+
gap: 2px;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
.finalizing-action {
|
|
2612
|
+
display: inline-flex;
|
|
2613
|
+
width: 26px;
|
|
2614
|
+
height: 26px;
|
|
2615
|
+
align-items: center;
|
|
2616
|
+
justify-content: center;
|
|
2617
|
+
padding: 0;
|
|
2618
|
+
border: 0;
|
|
2619
|
+
border-radius: 8px;
|
|
2620
|
+
background: transparent;
|
|
2621
|
+
color: rgba(255, 255, 255, 0.64);
|
|
2622
|
+
cursor: pointer;
|
|
2623
|
+
transition:
|
|
2624
|
+
color 140ms ease-out,
|
|
2625
|
+
background-color 140ms ease-out;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
.finalizing-action:hover {
|
|
2629
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2630
|
+
color: white;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.finalizing-action:focus-visible {
|
|
2634
|
+
outline: 2px solid rgba(255, 255, 255, 0.9);
|
|
2635
|
+
outline-offset: 1px;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
.finalizing-action svg {
|
|
2639
|
+
width: 15px;
|
|
2640
|
+
height: 15px;
|
|
2641
|
+
stroke-width: 2;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2595
2644
|
.finalizing-progress {
|
|
2596
2645
|
grid-column: 1 / -1;
|
|
2597
2646
|
width: 100%;
|
|
@@ -3400,14 +3449,14 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
3400
3449
|
* Slots into the top of the popover when an update is in flight or ready.
|
|
3401
3450
|
* Two visual states:
|
|
3402
3451
|
* - pending: subtle inline row, just keeps the user informed.
|
|
3403
|
-
* - ready: accent color + actions (
|
|
3452
|
+
* - ready: accent color + actions (dismiss / Restart) since clicking
|
|
3404
3453
|
* Restart is the thing we actually want them to do.
|
|
3405
3454
|
*/
|
|
3406
3455
|
.update-banner {
|
|
3407
3456
|
display: flex;
|
|
3408
3457
|
align-items: center;
|
|
3409
3458
|
gap: 8px;
|
|
3410
|
-
margin: 0
|
|
3459
|
+
margin: 0;
|
|
3411
3460
|
padding: 8px 10px;
|
|
3412
3461
|
border-radius: 8px;
|
|
3413
3462
|
font-size: 12px;
|
|
@@ -3424,7 +3473,7 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
3424
3473
|
background: var(--surface-strong);
|
|
3425
3474
|
color: var(--fg);
|
|
3426
3475
|
border: 1px solid var(--border-strong, var(--border));
|
|
3427
|
-
flex-wrap:
|
|
3476
|
+
flex-wrap: nowrap;
|
|
3428
3477
|
}
|
|
3429
3478
|
|
|
3430
3479
|
.update-banner--error {
|
|
@@ -3466,6 +3515,27 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
3466
3515
|
color: var(--fg);
|
|
3467
3516
|
}
|
|
3468
3517
|
|
|
3518
|
+
.update-banner-btn--dismiss {
|
|
3519
|
+
width: 28px;
|
|
3520
|
+
height: 28px;
|
|
3521
|
+
display: inline-flex;
|
|
3522
|
+
align-items: center;
|
|
3523
|
+
justify-content: center;
|
|
3524
|
+
padding: 0;
|
|
3525
|
+
background: transparent;
|
|
3526
|
+
color: var(--fg-muted);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
.update-banner-btn--dismiss:hover {
|
|
3530
|
+
background: var(--surface-hover, var(--surface));
|
|
3531
|
+
color: var(--fg);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
.update-banner-btn--dismiss:focus-visible {
|
|
3535
|
+
outline: none;
|
|
3536
|
+
box-shadow: 0 0 0 2px var(--brand-ring);
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3469
3539
|
.update-banner-btn--primary {
|
|
3470
3540
|
background: var(--brand);
|
|
3471
3541
|
color: var(--bg);
|
|
@@ -458,42 +458,57 @@ fn stop_countdown_control_tracking() {
|
|
|
458
458
|
COUNTDOWN_CONTROL_TRACKING.store(false, Ordering::SeqCst);
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
///
|
|
462
|
-
///
|
|
463
|
-
///
|
|
464
|
-
///
|
|
465
|
-
/// cursor events so the progress card does not block the screen. Marked
|
|
466
|
-
/// non-sharable for consistency with the other Clips overlays, even though
|
|
467
|
-
/// the recording has already ended by the time this appears.
|
|
461
|
+
/// Compact bottom-left status window shown while the recorder flushes its
|
|
462
|
+
/// final chunks and awaits the server finalize. Keeping the native window near
|
|
463
|
+
/// the card's bounds makes its open/dismiss controls clickable without placing
|
|
464
|
+
/// an input-blocking transparent window over the whole monitor.
|
|
468
465
|
#[tauri::command]
|
|
469
466
|
pub async fn show_finalizing(app: AppHandle) -> Result<(), String> {
|
|
470
467
|
dlog!("[clips-tray] show_finalizing invoked");
|
|
471
468
|
if let Some(existing) = app.get_webview_window(FINALIZING_LABEL) {
|
|
472
469
|
let _ = existing.close();
|
|
473
470
|
}
|
|
474
|
-
let (mx, my,
|
|
475
|
-
|
|
476
|
-
let
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
471
|
+
let (mx, my, _mw, mh) = tray_monitor_physical_rect(&app);
|
|
472
|
+
let scale = overlay_scale_factor(&app);
|
|
473
|
+
let content_w: u32 = (336.0 * scale).round() as u32;
|
|
474
|
+
let content_h: u32 = (86.0 * scale).round() as u32;
|
|
475
|
+
let margin: i32 = (14.0 * scale).round() as i32;
|
|
476
|
+
let gutter = overlay_shadow_gutter_physical(&app);
|
|
477
|
+
let w = content_w + gutter * 2;
|
|
478
|
+
let h = content_h + gutter * 2;
|
|
479
|
+
let x = (mx + margin - gutter as i32).max(mx);
|
|
480
|
+
let y = (my + mh as i32 - h as i32 - margin).max(my);
|
|
481
|
+
dlog!("[clips-tray] finalizing target size {}x{} physical", w, h);
|
|
482
|
+
#[allow(unused_mut)]
|
|
483
|
+
let mut builder =
|
|
484
|
+
WebviewWindowBuilder::new(&app, FINALIZING_LABEL, build_overlay_url("finalizing"))
|
|
485
|
+
.title("Finalizing")
|
|
486
|
+
.decorations(false)
|
|
487
|
+
.transparent(true)
|
|
488
|
+
.always_on_top(true)
|
|
489
|
+
.skip_taskbar(true)
|
|
490
|
+
.resizable(false)
|
|
491
|
+
.shadow(false)
|
|
492
|
+
.visible(false)
|
|
493
|
+
// Don't steal focus — same rationale as the countdown overlay.
|
|
494
|
+
.focused(false);
|
|
495
|
+
// This window deliberately stays non-activating, but its Open and Dismiss
|
|
496
|
+
// controls must receive the activating click on macOS instead of requiring
|
|
497
|
+
// a second click after the window becomes key.
|
|
498
|
+
#[cfg(target_os = "macos")]
|
|
499
|
+
{
|
|
500
|
+
builder = builder.accept_first_mouse(true);
|
|
501
|
+
}
|
|
502
|
+
let win = builder.build().map_err(|e| {
|
|
503
|
+
eprintln!("[clips-tray] finalizing build failed: {}", e);
|
|
504
|
+
e.to_string()
|
|
505
|
+
})?;
|
|
506
|
+
let _ = win.set_size(tauri::Size::Physical(PhysicalSize::new(w, h)));
|
|
507
|
+
let _ = win.set_position(PhysicalPosition::new(x, y));
|
|
508
|
+
let _ = win.set_ignore_cursor_events(false);
|
|
494
509
|
set_capture_excluded(&win);
|
|
495
510
|
configure_overlay_behavior(&win);
|
|
496
|
-
|
|
511
|
+
crate::util::show_without_activation(&win);
|
|
497
512
|
dlog!("[clips-tray] finalizing shown");
|
|
498
513
|
Ok(())
|
|
499
514
|
}
|