@agent-native/core 0.175.0 → 0.175.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/assets/app/root.tsx +5 -2
- package/corpus/templates/clips/actions/create-recording.ts +8 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +3 -1
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +11 -0
- package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +2 -41
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +48 -4
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -2
- package/corpus/templates/clips/app/lib/fmp4-seek.ts +274 -0
- package/corpus/templates/clips/app/lib/fmp4.ts +169 -0
- package/corpus/templates/clips/app/lib/mse-video-loader.ts +351 -109
- package/corpus/templates/clips/app/root.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-08-25-seeking-into-a-long-recording-no-longer-sticks-on-buffering.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +103 -13
- package/corpus/templates/clips/server/lib/resumable-upload-provider.ts +2 -2
- package/corpus/templates/clips/server/lib/video-storage.ts +2 -2
- package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +162 -157
- package/corpus/templates/tasks/app/root.tsx +5 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/file-upload/builder.d.ts +2 -2
- package/dist/file-upload/builder.js +28 -26
- package/dist/file-upload/registry.js +11 -8
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-api-auth.d.ts +31 -0
- package/dist/server/builder-api-auth.js +62 -0
- package/dist/server/builder-oauth.d.ts +5 -3
- package/dist/server/builder-oauth.js +40 -12
- package/dist/server/core-routes-plugin.js +5 -2
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +3 -3
|
@@ -12,6 +12,7 @@ use super::*;
|
|
|
12
12
|
use screencapturekit::error::SCError;
|
|
13
13
|
use screencapturekit::stream::delegate_trait::SCStreamDelegateTrait;
|
|
14
14
|
use std::io::{Seek, SeekFrom, Write};
|
|
15
|
+
use std::sync::RwLock;
|
|
15
16
|
|
|
16
17
|
/// `AVAssetWriter.status` raw value for `.completed`.
|
|
17
18
|
const AV_WRITER_STATUS_COMPLETED: i64 = 2;
|
|
@@ -1011,6 +1012,13 @@ unsafe impl Send for CustomScreenCaptureWriterState {}
|
|
|
1011
1012
|
/// keeps receiving samples over the whole recording.
|
|
1012
1013
|
struct CustomScreenCaptureOutputHandler {
|
|
1013
1014
|
writer: CustomScreenCaptureWriter,
|
|
1015
|
+
/// Only callbacks from this stream generation may reach the writer. A
|
|
1016
|
+
/// ScreenCaptureKit stream can still have callbacks queued after stop; a
|
|
1017
|
+
/// replacement must discard those stale samples before they corrupt the
|
|
1018
|
+
/// writer's timeline.
|
|
1019
|
+
stream_generation: u64,
|
|
1020
|
+
active_stream_generation: Arc<AtomicU64>,
|
|
1021
|
+
callback_admission: Arc<RwLock<()>>,
|
|
1014
1022
|
/// At most one temporary Clips writer may mirror this physical producer.
|
|
1015
1023
|
/// It deliberately lives beside the callback handler rather than the
|
|
1016
1024
|
/// audio bus: the bus has one producer contract and must never publish a
|
|
@@ -1283,12 +1291,61 @@ impl Clone for CustomScreenCaptureWriter {
|
|
|
1283
1291
|
}
|
|
1284
1292
|
}
|
|
1285
1293
|
|
|
1294
|
+
impl CustomScreenCaptureOutputHandler {
|
|
1295
|
+
fn replacement_stream(&self) -> Self {
|
|
1296
|
+
let _admission = self
|
|
1297
|
+
.callback_admission
|
|
1298
|
+
.write()
|
|
1299
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
1300
|
+
let stream_generation = self.active_stream_generation.fetch_add(1, Ordering::SeqCst) + 1;
|
|
1301
|
+
Self {
|
|
1302
|
+
writer: self.writer.clone(),
|
|
1303
|
+
stream_generation,
|
|
1304
|
+
active_stream_generation: Arc::clone(&self.active_stream_generation),
|
|
1305
|
+
callback_admission: Arc::clone(&self.callback_admission),
|
|
1306
|
+
clip_sink: Arc::clone(&self.clip_sink),
|
|
1307
|
+
recording_enabled: Arc::clone(&self.recording_enabled),
|
|
1308
|
+
mic_ready: self.mic_ready.clone(),
|
|
1309
|
+
watch: Arc::clone(&self.watch),
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
fn invalidate_stream(&self) {
|
|
1314
|
+
let _admission = self
|
|
1315
|
+
.callback_admission
|
|
1316
|
+
.write()
|
|
1317
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
1318
|
+
self.active_stream_generation.fetch_add(1, Ordering::SeqCst);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
fn is_current(&self) -> bool {
|
|
1322
|
+
stream_generation_is_current(
|
|
1323
|
+
self.active_stream_generation.load(Ordering::SeqCst),
|
|
1324
|
+
self.stream_generation,
|
|
1325
|
+
)
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
fn stream_generation_is_current(active_generation: u64, sample_generation: u64) -> bool {
|
|
1330
|
+
active_generation == sample_generation
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
fn sample_pts_advances(previous: Option<f64>, current: f64) -> bool {
|
|
1334
|
+
previous.is_none_or(|last| current > last)
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1286
1337
|
impl SCStreamOutputTrait for CustomScreenCaptureOutputHandler {
|
|
1287
1338
|
fn did_output_sample_buffer(
|
|
1288
1339
|
&self,
|
|
1289
1340
|
sample_buffer: screencapturekit::cm::CMSampleBuffer,
|
|
1290
1341
|
of_type: SCStreamOutputType,
|
|
1291
1342
|
) {
|
|
1343
|
+
let Ok(_admission) = self.callback_admission.read() else {
|
|
1344
|
+
return;
|
|
1345
|
+
};
|
|
1346
|
+
if !self.is_current() {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1292
1349
|
if matches!(of_type, SCStreamOutputType::Microphone) {
|
|
1293
1350
|
if let Some(mic_ready) = &self.mic_ready {
|
|
1294
1351
|
mic_ready.store(true, Ordering::Relaxed);
|
|
@@ -2038,21 +2095,20 @@ impl CustomScreenCaptureWriter {
|
|
|
2038
2095
|
}
|
|
2039
2096
|
return;
|
|
2040
2097
|
}
|
|
2041
|
-
// Record the timeline BEFORE the append so a failure report describes
|
|
2042
|
-
// the sample that was actually rejected, not the last good one.
|
|
2043
2098
|
let stats = guard.append_stats.entry(track).or_default();
|
|
2044
2099
|
let previous_pts = stats.last_pts_seconds;
|
|
2045
2100
|
if let Some(pts) = pts_seconds {
|
|
2046
|
-
if previous_pts
|
|
2101
|
+
if !sample_pts_advances(previous_pts, pts) {
|
|
2047
2102
|
stats.pts_regressions += 1;
|
|
2048
2103
|
let regressions = stats.pts_regressions;
|
|
2049
2104
|
if regressions == 1 || regressions % 100 == 0 {
|
|
2050
2105
|
crate::logfile::diagnostic(&format!(
|
|
2051
|
-
"[capture-health] {track}
|
|
2106
|
+
"[capture-health] dropped {track} sample with non-advancing PTS: {:.6}s after {:.6}s ({regressions} so far)",
|
|
2052
2107
|
pts,
|
|
2053
2108
|
previous_pts.unwrap_or(f64::NAN)
|
|
2054
2109
|
));
|
|
2055
2110
|
}
|
|
2111
|
+
return;
|
|
2056
2112
|
}
|
|
2057
2113
|
stats.last_pts_seconds = Some(pts);
|
|
2058
2114
|
}
|
|
@@ -3755,6 +3811,7 @@ impl CustomCaptureResume {
|
|
|
3755
3811
|
/// hold so it never reads the silence as a stall and rebuilds.
|
|
3756
3812
|
pub(crate) fn pause(&self) {
|
|
3757
3813
|
self.watch.set_paused(true);
|
|
3814
|
+
self.handler.invalidate_stream();
|
|
3758
3815
|
if let Ok(guard) = self.stream.lock() {
|
|
3759
3816
|
let _ = guard.stop_capture();
|
|
3760
3817
|
}
|
|
@@ -3774,7 +3831,8 @@ impl CustomCaptureResume {
|
|
|
3774
3831
|
// timeline. The session stays paused and can be retried — and a retry
|
|
3775
3832
|
// re-measures `paused_for` from the same (uncleared) pause instant, so
|
|
3776
3833
|
// advancing the offset here would compound on every failed attempt.
|
|
3777
|
-
let
|
|
3834
|
+
let replacement_handler = self.handler.replacement_stream();
|
|
3835
|
+
let new_stream = build_custom_scstream(&self.params, &replacement_handler, &self.watch)?;
|
|
3778
3836
|
|
|
3779
3837
|
// Apply the pause gap just before the stream starts delivering, so the
|
|
3780
3838
|
// first rebased frame already skips it. Roll back if startup fails so
|
|
@@ -4130,6 +4188,7 @@ fn spawn_capture_watchdog(
|
|
|
4130
4188
|
// Stop the dead/wedged stream before starting a replacement so two
|
|
4131
4189
|
// streams never feed the writer at once. Slow build/start work is
|
|
4132
4190
|
// done without holding the stream lock.
|
|
4191
|
+
let replacement_handler = handler.replacement_stream();
|
|
4133
4192
|
if let Ok(guard) = stream.lock() {
|
|
4134
4193
|
let _ = guard.stop_capture();
|
|
4135
4194
|
}
|
|
@@ -4137,21 +4196,35 @@ fn spawn_capture_watchdog(
|
|
|
4137
4196
|
return;
|
|
4138
4197
|
}
|
|
4139
4198
|
|
|
4140
|
-
match build_custom_scstream(¶ms, &
|
|
4199
|
+
match build_custom_scstream(¶ms, &replacement_handler, &watch).and_then(|s| {
|
|
4141
4200
|
s.start_capture()
|
|
4142
4201
|
.map(|()| s)
|
|
4143
4202
|
.map_err(|e| format!("start_capture failed: {e:?}"))
|
|
4144
4203
|
}) {
|
|
4145
4204
|
Ok(new_stream) => {
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4205
|
+
let mut pending_stream = Some(new_stream);
|
|
4206
|
+
let install = if let Ok(mut guard) = stream.lock() {
|
|
4207
|
+
if shutdown.load(Ordering::SeqCst)
|
|
4208
|
+
|| writer.appends_closed.load(Ordering::SeqCst)
|
|
4209
|
+
|| watch.is_paused()
|
|
4210
|
+
|| !replacement_handler.is_current()
|
|
4211
|
+
{
|
|
4212
|
+
false
|
|
4213
|
+
} else {
|
|
4214
|
+
*guard = pending_stream
|
|
4215
|
+
.take()
|
|
4216
|
+
.expect("pending watchdog stream is available");
|
|
4217
|
+
true
|
|
4218
|
+
}
|
|
4219
|
+
} else {
|
|
4220
|
+
false
|
|
4221
|
+
};
|
|
4222
|
+
if !install {
|
|
4223
|
+
if let Some(stream) = pending_stream {
|
|
4224
|
+
let _ = stream.stop_capture();
|
|
4225
|
+
}
|
|
4150
4226
|
return;
|
|
4151
4227
|
}
|
|
4152
|
-
if let Ok(mut guard) = stream.lock() {
|
|
4153
|
-
*guard = new_stream;
|
|
4154
|
-
}
|
|
4155
4228
|
// Discard any stop note the deliberate teardown of the old
|
|
4156
4229
|
// stream raised; otherwise a later poll would consume it as
|
|
4157
4230
|
// a fresh failure and rebuild the healthy stream again.
|
|
@@ -4256,6 +4329,9 @@ pub(crate) fn start_custom_screencapturekit_backend_at(
|
|
|
4256
4329
|
let watch = Arc::new(CaptureWatch::new());
|
|
4257
4330
|
let handler = CustomScreenCaptureOutputHandler {
|
|
4258
4331
|
writer: writer.clone(),
|
|
4332
|
+
stream_generation: 0,
|
|
4333
|
+
active_stream_generation: Arc::new(AtomicU64::new(0)),
|
|
4334
|
+
callback_admission: Arc::new(RwLock::new(())),
|
|
4259
4335
|
clip_sink: Arc::clone(&clip_sink),
|
|
4260
4336
|
recording_enabled: Arc::clone(&recording_enabled),
|
|
4261
4337
|
mic_ready: mic_ready.clone(),
|
|
@@ -4491,6 +4567,20 @@ mod fragment_fence_tests {
|
|
|
4491
4567
|
assert_eq!(mixer.compute_safe_end(false), 0);
|
|
4492
4568
|
}
|
|
4493
4569
|
|
|
4570
|
+
#[test]
|
|
4571
|
+
fn replacement_stream_rejects_callbacks_from_the_prior_generation() {
|
|
4572
|
+
assert!(stream_generation_is_current(1, 1));
|
|
4573
|
+
assert!(!stream_generation_is_current(1, 0));
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
#[test]
|
|
4577
|
+
fn timestamp_admission_rejects_equal_and_regressing_samples() {
|
|
4578
|
+
assert!(sample_pts_advances(None, 1.0));
|
|
4579
|
+
assert!(sample_pts_advances(Some(1.0), 1.01));
|
|
4580
|
+
assert!(!sample_pts_advances(Some(1.0), 1.0));
|
|
4581
|
+
assert!(!sample_pts_advances(Some(1.0), 0.99));
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4494
4584
|
#[test]
|
|
4495
4585
|
fn clip_sink_gate_orders_prepare_activate_and_logical_close() {
|
|
4496
4586
|
let gate = ClipSinkGate::new();
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
listFileUploadProviders,
|
|
5
5
|
type FileUploadProvider,
|
|
6
6
|
} from "@agent-native/core/file-upload";
|
|
7
|
-
import {
|
|
7
|
+
import { hasBuilderApiCredentialCustody } from "@agent-native/core/server";
|
|
8
8
|
|
|
9
9
|
async function isConfiguredForRequest(
|
|
10
10
|
provider: FileUploadProvider,
|
|
@@ -34,7 +34,7 @@ export async function resolveResumableUploadProvider(
|
|
|
34
34
|
|
|
35
35
|
if (providerId === builderFileUploadProvider.id) {
|
|
36
36
|
try {
|
|
37
|
-
if (await
|
|
37
|
+
if (await hasBuilderApiCredentialCustody()) {
|
|
38
38
|
return builderFileUploadProvider;
|
|
39
39
|
}
|
|
40
40
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { listFileUploadProviders } from "@agent-native/core/file-upload";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
hasBuilderApiCredentialCustody,
|
|
4
4
|
runWithRequestContext,
|
|
5
5
|
} from "@agent-native/core/server";
|
|
6
6
|
|
|
@@ -51,7 +51,7 @@ export async function hasRequestVideoStorage(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
try {
|
|
54
|
-
return await
|
|
54
|
+
return await hasBuilderApiCredentialCustody();
|
|
55
55
|
} catch {
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
@@ -68,173 +68,178 @@ export function FieldRenderer({
|
|
|
68
68
|
<p className="text-xs text-muted-foreground">{field.description}</p>
|
|
69
69
|
)}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
71
|
+
<div
|
|
72
|
+
inert={preview ? true : undefined}
|
|
73
|
+
className={preview ? "pointer-events-none" : undefined}
|
|
74
|
+
>
|
|
75
|
+
{field.type === "text" && (
|
|
76
|
+
<Input
|
|
77
|
+
placeholder={field.placeholder || ""}
|
|
78
|
+
value={(value as string) || ""}
|
|
79
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{field.type === "email" && (
|
|
85
|
+
<Input
|
|
86
|
+
type="email"
|
|
87
|
+
placeholder={field.placeholder || "you@example.com"}
|
|
88
|
+
value={(value as string) || ""}
|
|
89
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{field.type === "number" && (
|
|
95
|
+
<Input
|
|
96
|
+
type="number"
|
|
97
|
+
placeholder={field.placeholder || ""}
|
|
98
|
+
value={(value as string) || ""}
|
|
99
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
100
|
+
min={field.validation?.min}
|
|
101
|
+
max={field.validation?.max}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{field.type === "textarea" && (
|
|
107
|
+
<Textarea
|
|
108
|
+
placeholder={field.placeholder || ""}
|
|
109
|
+
value={(value as string) || ""}
|
|
110
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
111
|
+
rows={4}
|
|
112
|
+
disabled={disabled}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
{field.type === "select" && (
|
|
117
|
+
<Select
|
|
118
|
+
value={(value as string) || ""}
|
|
119
|
+
onValueChange={handleChange}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
>
|
|
122
|
+
<SelectTrigger>
|
|
123
|
+
<SelectValue placeholder={field.placeholder || "Select..."} />
|
|
124
|
+
</SelectTrigger>
|
|
125
|
+
<SelectContent>
|
|
126
|
+
{dedupeRenderableOptions(field.options).map((opt, i) => (
|
|
127
|
+
<SelectItem key={`${opt}-${i}`} value={opt}>
|
|
128
|
+
{opt}
|
|
129
|
+
</SelectItem>
|
|
130
|
+
))}
|
|
131
|
+
</SelectContent>
|
|
132
|
+
</Select>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{field.type === "multiselect" && (
|
|
136
|
+
<div className="space-y-1">
|
|
137
|
+
{dedupeRenderableOptions(field.options).map((opt, i) => {
|
|
138
|
+
const selected = Array.isArray(value) ? value : [];
|
|
139
|
+
return (
|
|
140
|
+
<label
|
|
141
|
+
key={`${opt}-${i}`}
|
|
142
|
+
className="flex items-center gap-2.5 text-sm min-h-[44px] py-1"
|
|
143
|
+
>
|
|
144
|
+
<Checkbox
|
|
145
|
+
checked={selected.includes(opt)}
|
|
146
|
+
onCheckedChange={(checked) => {
|
|
147
|
+
const next = checked
|
|
148
|
+
? [...selected, opt]
|
|
149
|
+
: selected.filter((s: string) => s !== opt);
|
|
150
|
+
handleChange(next);
|
|
151
|
+
}}
|
|
152
|
+
disabled={disabled}
|
|
153
|
+
/>
|
|
154
|
+
{opt}
|
|
155
|
+
</label>
|
|
156
|
+
);
|
|
157
|
+
})}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
111
160
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</
|
|
121
|
-
|
|
161
|
+
{field.type === "checkbox" && (
|
|
162
|
+
<label className="flex items-center gap-2.5 text-sm min-h-[44px]">
|
|
163
|
+
<Checkbox
|
|
164
|
+
checked={!!value}
|
|
165
|
+
onCheckedChange={handleChange}
|
|
166
|
+
disabled={disabled}
|
|
167
|
+
/>
|
|
168
|
+
{field.placeholder || field.label}
|
|
169
|
+
</label>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
{field.type === "radio" && (
|
|
173
|
+
<RadioGroup
|
|
174
|
+
value={(value as string) || ""}
|
|
175
|
+
onValueChange={handleChange}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
>
|
|
122
178
|
{dedupeRenderableOptions(field.options).map((opt, i) => (
|
|
123
|
-
<
|
|
124
|
-
{opt}
|
|
125
|
-
</SelectItem>
|
|
126
|
-
))}
|
|
127
|
-
</SelectContent>
|
|
128
|
-
</Select>
|
|
129
|
-
)}
|
|
130
|
-
|
|
131
|
-
{field.type === "multiselect" && (
|
|
132
|
-
<div className="space-y-1">
|
|
133
|
-
{dedupeRenderableOptions(field.options).map((opt, i) => {
|
|
134
|
-
const selected = Array.isArray(value) ? value : [];
|
|
135
|
-
return (
|
|
136
|
-
<label
|
|
179
|
+
<div
|
|
137
180
|
key={`${opt}-${i}`}
|
|
138
|
-
className="flex items-center gap-2.5
|
|
181
|
+
className="flex items-center gap-2.5 min-h-[44px]"
|
|
139
182
|
>
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{field.type === "checkbox" && (
|
|
158
|
-
<label className="flex items-center gap-2.5 text-sm min-h-[44px]">
|
|
159
|
-
<Checkbox
|
|
160
|
-
checked={!!value}
|
|
161
|
-
onCheckedChange={handleChange}
|
|
183
|
+
<RadioGroupItem value={opt} id={`${field.id}-${opt}-${i}`} />
|
|
184
|
+
<Label
|
|
185
|
+
htmlFor={`${field.id}-${opt}-${i}`}
|
|
186
|
+
className="font-normal"
|
|
187
|
+
>
|
|
188
|
+
{opt}
|
|
189
|
+
</Label>
|
|
190
|
+
</div>
|
|
191
|
+
))}
|
|
192
|
+
</RadioGroup>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
{field.type === "date" && (
|
|
196
|
+
<Input
|
|
197
|
+
type="date"
|
|
198
|
+
value={(value as string) || ""}
|
|
199
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
162
200
|
disabled={disabled}
|
|
163
201
|
/>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
key={`${opt}-${i}`}
|
|
177
|
-
className="flex items-center gap-2.5 min-h-[44px]"
|
|
178
|
-
>
|
|
179
|
-
<RadioGroupItem value={opt} id={`${field.id}-${opt}-${i}`} />
|
|
180
|
-
<Label
|
|
181
|
-
htmlFor={`${field.id}-${opt}-${i}`}
|
|
182
|
-
className="font-normal"
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{field.type === "rating" && (
|
|
205
|
+
<div className="flex gap-0.5 sm:gap-1">
|
|
206
|
+
{[1, 2, 3, 4, 5].map((star) => (
|
|
207
|
+
<button
|
|
208
|
+
key={star}
|
|
209
|
+
type="button"
|
|
210
|
+
onClick={() => !disabled && handleChange(star)}
|
|
211
|
+
className="flex size-11 cursor-pointer items-center justify-center rounded-md p-2 transition-[color,transform] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100 disabled:cursor-not-allowed sm:size-10 sm:p-1.5"
|
|
212
|
+
disabled={disabled}
|
|
213
|
+
aria-label={`${star} star${star !== 1 ? "s" : ""}`}
|
|
183
214
|
>
|
|
184
|
-
{
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{[1, 2, 3, 4, 5].map((star) => (
|
|
203
|
-
<button
|
|
204
|
-
key={star}
|
|
205
|
-
type="button"
|
|
206
|
-
onClick={() => !disabled && handleChange(star)}
|
|
207
|
-
className="flex size-11 cursor-pointer items-center justify-center rounded-md p-2 transition-[color,transform] duration-150 ease-out active:scale-[0.96] motion-reduce:active:scale-100 disabled:cursor-not-allowed sm:size-10 sm:p-1.5"
|
|
215
|
+
{(value as number) >= star ? (
|
|
216
|
+
<IconStarFilled className="h-7 w-7 text-amber-400 sm:h-6 sm:w-6" />
|
|
217
|
+
) : (
|
|
218
|
+
<IconStar className="h-7 w-7 text-muted-foreground/30 sm:h-6 sm:w-6" />
|
|
219
|
+
)}
|
|
220
|
+
</button>
|
|
221
|
+
))}
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{field.type === "scale" && (
|
|
226
|
+
<div className="pt-2">
|
|
227
|
+
<Slider
|
|
228
|
+
value={[(value as number) || field.validation?.min || 1]}
|
|
229
|
+
onValueChange={([v]) => handleChange(v)}
|
|
230
|
+
min={field.validation?.min || 1}
|
|
231
|
+
max={field.validation?.max || 10}
|
|
232
|
+
step={1}
|
|
208
233
|
disabled={disabled}
|
|
209
|
-
|
|
210
|
-
>
|
|
211
|
-
{
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)}
|
|
216
|
-
</button>
|
|
217
|
-
))}
|
|
218
|
-
</div>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{field.type === "scale" && (
|
|
222
|
-
<div className="pt-2">
|
|
223
|
-
<Slider
|
|
224
|
-
value={[(value as number) || field.validation?.min || 1]}
|
|
225
|
-
onValueChange={([v]) => handleChange(v)}
|
|
226
|
-
min={field.validation?.min || 1}
|
|
227
|
-
max={field.validation?.max || 10}
|
|
228
|
-
step={1}
|
|
229
|
-
disabled={disabled}
|
|
230
|
-
/>
|
|
231
|
-
<div className="flex justify-between text-xs text-muted-foreground mt-1">
|
|
232
|
-
<span>{field.validation?.min || 1}</span>
|
|
233
|
-
<span>{String(value ?? "-")}</span>
|
|
234
|
-
<span>{field.validation?.max || 10}</span>
|
|
234
|
+
/>
|
|
235
|
+
<div className="flex justify-between text-xs text-muted-foreground mt-1">
|
|
236
|
+
<span>{field.validation?.min || 1}</span>
|
|
237
|
+
<span>{String(value ?? "-")}</span>
|
|
238
|
+
<span>{field.validation?.max || 10}</span>
|
|
239
|
+
</div>
|
|
235
240
|
</div>
|
|
236
|
-
|
|
237
|
-
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
238
243
|
</div>
|
|
239
244
|
);
|
|
240
245
|
}
|
|
@@ -150,7 +150,6 @@ function AppContent() {
|
|
|
150
150
|
<ThemeToggleItem />
|
|
151
151
|
</CommandMenu.Group>
|
|
152
152
|
</CommandMenu>
|
|
153
|
-
<Toaster position="bottom-left" />
|
|
154
153
|
<AppLayout>
|
|
155
154
|
<Outlet />
|
|
156
155
|
</AppLayout>
|
|
@@ -161,7 +160,11 @@ function AppContent() {
|
|
|
161
160
|
export default function Root() {
|
|
162
161
|
const [queryClient] = useState(() => createAgentNativeQueryClient());
|
|
163
162
|
return (
|
|
164
|
-
<AppProviders
|
|
163
|
+
<AppProviders
|
|
164
|
+
queryClient={queryClient}
|
|
165
|
+
toaster={<Toaster position="bottom-left" />}
|
|
166
|
+
i18n={{ catalog: i18nCatalog }}
|
|
167
|
+
>
|
|
165
168
|
<AppToolkitProvider>
|
|
166
169
|
<DbSyncSetup />
|
|
167
170
|
<AppContent />
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { FileUploadProvider } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Built-in Builder.io file upload provider.
|
|
4
|
-
* Uses the same
|
|
5
|
-
*
|
|
4
|
+
* Uses the same Builder connection as the browser/background-agent flows, so
|
|
5
|
+
* connecting Builder once (via the sidebar "Connect Builder" action)
|
|
6
6
|
* automatically enables file uploads.
|
|
7
7
|
*
|
|
8
8
|
* Upload API: https://www.builder.io/c/docs/upload-api
|