@comergehq/studio 0.1.26 → 0.1.28
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/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1576 -601
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1292 -317
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/chat/AgentProgressCard.tsx +82 -0
- package/src/components/chat/BundleProgressCard.tsx +75 -0
- package/src/components/chat/ChatMessageBubble.tsx +48 -4
- package/src/components/chat/ChatMessageList.tsx +56 -36
- package/src/components/comments/useAppComments.ts +12 -0
- package/src/data/agent-progress/repository.ts +179 -0
- package/src/data/agent-progress/types.ts +67 -0
- package/src/studio/ComergeStudio.tsx +23 -2
- package/src/studio/analytics/client.ts +103 -0
- package/src/studio/analytics/events.ts +98 -0
- package/src/studio/analytics/track.ts +237 -0
- package/src/studio/bootstrap/StudioBootstrap.tsx +8 -2
- package/src/studio/bootstrap/useStudioBootstrap.ts +18 -2
- package/src/studio/hooks/useAgentRunProgress.ts +357 -0
- package/src/studio/hooks/useAppStats.ts +14 -2
- package/src/studio/hooks/useBundleManager.ts +43 -5
- package/src/studio/hooks/useMergeRequests.ts +63 -14
- package/src/studio/hooks/useStudioActions.ts +34 -1
- package/src/studio/ui/ChatPanel.tsx +13 -2
- package/src/studio/ui/PreviewPanel.tsx +10 -0
- package/src/studio/ui/RuntimeRenderer.tsx +6 -1
- package/src/studio/ui/StudioOverlay.tsx +3 -0
- package/src/studio/ui/preview-panel/usePreviewPanelData.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/studio/ComergeStudio.tsx
|
|
9
|
-
import * as
|
|
10
|
-
import { Platform as RNPlatform, View as
|
|
9
|
+
import * as React49 from "react";
|
|
10
|
+
import { Platform as RNPlatform, View as View48 } from "react-native";
|
|
11
11
|
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
12
12
|
|
|
13
13
|
// src/studio/bootstrap/StudioBootstrap.tsx
|
|
@@ -278,6 +278,109 @@ async function ensureAnonymousSession() {
|
|
|
278
278
|
return { user: data.user, isNew: true };
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
// src/studio/analytics/client.ts
|
|
282
|
+
import { Platform } from "react-native";
|
|
283
|
+
import { Mixpanel } from "mixpanel-react-native";
|
|
284
|
+
|
|
285
|
+
// src/core/logger.ts
|
|
286
|
+
import { logger, consoleTransport } from "react-native-logs";
|
|
287
|
+
var log = logger.createLogger(
|
|
288
|
+
{
|
|
289
|
+
levels: {
|
|
290
|
+
debug: 0,
|
|
291
|
+
info: 1,
|
|
292
|
+
warn: 2,
|
|
293
|
+
error: 3
|
|
294
|
+
},
|
|
295
|
+
severity: "debug",
|
|
296
|
+
transport: consoleTransport,
|
|
297
|
+
transportOptions: {
|
|
298
|
+
colors: {
|
|
299
|
+
info: "blueBright",
|
|
300
|
+
warn: "yellowBright",
|
|
301
|
+
error: "redBright"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
async: true,
|
|
305
|
+
dateFormat: "time",
|
|
306
|
+
printLevel: true,
|
|
307
|
+
printDate: true,
|
|
308
|
+
fixedExtLvlLength: false,
|
|
309
|
+
enabled: true
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// src/studio/analytics/client.ts
|
|
314
|
+
var studioMixpanel = null;
|
|
315
|
+
var studioAnalyticsEnabled = false;
|
|
316
|
+
var initPromise = null;
|
|
317
|
+
async function initStudioAnalytics(options) {
|
|
318
|
+
if (initPromise) return initPromise;
|
|
319
|
+
initPromise = (async () => {
|
|
320
|
+
if (!options.enabled) {
|
|
321
|
+
studioAnalyticsEnabled = false;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const token = (options.token ?? "").trim();
|
|
325
|
+
if (!token) {
|
|
326
|
+
studioAnalyticsEnabled = false;
|
|
327
|
+
log.warn("[studio-analytics] disabled: missing Mixpanel token");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const trackAutomaticEvents = false;
|
|
332
|
+
const useNative = false;
|
|
333
|
+
const serverUrl = (options.serverUrl ?? "").trim() || "https://api.mixpanel.com";
|
|
334
|
+
const superProperties = {
|
|
335
|
+
runtime: "comerge-studio",
|
|
336
|
+
platform: Platform.OS
|
|
337
|
+
};
|
|
338
|
+
studioMixpanel = new Mixpanel(token, trackAutomaticEvents, useNative);
|
|
339
|
+
await studioMixpanel.init(false, superProperties, serverUrl);
|
|
340
|
+
studioMixpanel.setLoggingEnabled(Boolean(options.debug));
|
|
341
|
+
studioMixpanel.setFlushBatchSize(50);
|
|
342
|
+
studioAnalyticsEnabled = true;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
studioMixpanel = null;
|
|
345
|
+
studioAnalyticsEnabled = false;
|
|
346
|
+
log.warn("[studio-analytics] init failed", error);
|
|
347
|
+
}
|
|
348
|
+
})();
|
|
349
|
+
return initPromise;
|
|
350
|
+
}
|
|
351
|
+
async function trackStudioEvent(eventName, properties) {
|
|
352
|
+
if (!studioAnalyticsEnabled || !studioMixpanel) return;
|
|
353
|
+
try {
|
|
354
|
+
await studioMixpanel.track(eventName, properties);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
log.warn("[studio-analytics] track failed", { eventName, error });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function flushStudioAnalytics() {
|
|
360
|
+
if (!studioAnalyticsEnabled || !studioMixpanel) return;
|
|
361
|
+
try {
|
|
362
|
+
await studioMixpanel.flush();
|
|
363
|
+
} catch (error) {
|
|
364
|
+
log.warn("[studio-analytics] flush failed", error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function identifyStudioUser(userId) {
|
|
368
|
+
if (!studioAnalyticsEnabled || !studioMixpanel || !userId) return;
|
|
369
|
+
try {
|
|
370
|
+
await studioMixpanel.identify(userId);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
log.warn("[studio-analytics] identify failed", error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async function resetStudioAnalytics() {
|
|
376
|
+
if (!studioAnalyticsEnabled || !studioMixpanel) return;
|
|
377
|
+
try {
|
|
378
|
+
await studioMixpanel.reset();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
log.warn("[studio-analytics] reset failed", error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
281
384
|
// src/studio/bootstrap/useStudioBootstrap.ts
|
|
282
385
|
var SUPABASE_URL = "https://xtfxwbckjpfmqubnsusu.supabase.co";
|
|
283
386
|
var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inh0Znh3YmNranBmbXF1Ym5zdXN1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjA2MDEyMzAsImV4cCI6MjA3NjE3NzIzMH0.dzWGAWrK4CvrmHVHzf8w7JlUZohdap0ZPnLZnABMV8s";
|
|
@@ -292,11 +395,23 @@ function useStudioBootstrap(options) {
|
|
|
292
395
|
(async () => {
|
|
293
396
|
try {
|
|
294
397
|
setClientKey(options.clientKey);
|
|
295
|
-
const
|
|
398
|
+
const hasInjectedSupabase = isSupabaseClientInjected();
|
|
399
|
+
const requireAuth = hasInjectedSupabase;
|
|
400
|
+
const analyticsEnabled = options.analyticsEnabled ?? hasInjectedSupabase;
|
|
401
|
+
await initStudioAnalytics({
|
|
402
|
+
enabled: analyticsEnabled,
|
|
403
|
+
token: process.env.EXPO_PUBLIC_MIXPANEL_TOKEN,
|
|
404
|
+
serverUrl: process.env.EXPO_PUBLIC_MIXPANEL_SERVER_URL,
|
|
405
|
+
debug: __DEV__
|
|
406
|
+
});
|
|
296
407
|
if (!requireAuth) {
|
|
297
408
|
setSupabaseConfig({ url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY });
|
|
409
|
+
await resetStudioAnalytics();
|
|
298
410
|
}
|
|
299
411
|
const { user } = requireAuth ? await ensureAuthenticatedSession() : await ensureAnonymousSession();
|
|
412
|
+
if (requireAuth) {
|
|
413
|
+
await identifyStudioUser(user.id);
|
|
414
|
+
}
|
|
300
415
|
if (cancelled) return;
|
|
301
416
|
setState({ ready: true, userId: user.id, error: null });
|
|
302
417
|
} catch (e) {
|
|
@@ -308,14 +423,20 @@ function useStudioBootstrap(options) {
|
|
|
308
423
|
return () => {
|
|
309
424
|
cancelled = true;
|
|
310
425
|
};
|
|
311
|
-
}, [options.clientKey]);
|
|
426
|
+
}, [options.analyticsEnabled, options.clientKey]);
|
|
312
427
|
return state;
|
|
313
428
|
}
|
|
314
429
|
|
|
315
430
|
// src/studio/bootstrap/StudioBootstrap.tsx
|
|
316
431
|
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
317
|
-
function StudioBootstrap({
|
|
318
|
-
|
|
432
|
+
function StudioBootstrap({
|
|
433
|
+
children,
|
|
434
|
+
fallback,
|
|
435
|
+
renderError,
|
|
436
|
+
clientKey: clientKey2,
|
|
437
|
+
analyticsEnabled
|
|
438
|
+
}) {
|
|
439
|
+
const { ready, error, userId } = useStudioBootstrap({ clientKey: clientKey2, analyticsEnabled });
|
|
319
440
|
if (error) {
|
|
320
441
|
return /* @__PURE__ */ jsx2(View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: renderError ? renderError(error) : /* @__PURE__ */ jsx2(Text, { variant: "bodyMuted", children: error.message }) });
|
|
321
442
|
}
|
|
@@ -333,36 +454,6 @@ import * as React3 from "react";
|
|
|
333
454
|
|
|
334
455
|
// src/core/services/http/index.ts
|
|
335
456
|
import axios2 from "axios";
|
|
336
|
-
|
|
337
|
-
// src/core/logger.ts
|
|
338
|
-
import { logger, consoleTransport } from "react-native-logs";
|
|
339
|
-
var log = logger.createLogger(
|
|
340
|
-
{
|
|
341
|
-
levels: {
|
|
342
|
-
debug: 0,
|
|
343
|
-
info: 1,
|
|
344
|
-
warn: 2,
|
|
345
|
-
error: 3
|
|
346
|
-
},
|
|
347
|
-
severity: "debug",
|
|
348
|
-
transport: consoleTransport,
|
|
349
|
-
transportOptions: {
|
|
350
|
-
colors: {
|
|
351
|
-
info: "blueBright",
|
|
352
|
-
warn: "yellowBright",
|
|
353
|
-
error: "redBright"
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
async: true,
|
|
357
|
-
dateFormat: "time",
|
|
358
|
-
printLevel: true,
|
|
359
|
-
printDate: true,
|
|
360
|
-
fixedExtLvlLength: false,
|
|
361
|
-
enabled: true
|
|
362
|
-
}
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
// src/core/services/http/index.ts
|
|
366
457
|
var RETRYABLE_MAX_ATTEMPTS = 3;
|
|
367
458
|
var RETRYABLE_BASE_DELAY_MS = 500;
|
|
368
459
|
var RETRYABLE_MAX_DELAY_MS = 4e3;
|
|
@@ -1177,6 +1268,154 @@ var BundlesRepositoryImpl = class extends BaseRepository {
|
|
|
1177
1268
|
};
|
|
1178
1269
|
var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
|
|
1179
1270
|
|
|
1271
|
+
// src/studio/analytics/events.ts
|
|
1272
|
+
var STUDIO_ANALYTICS_EVENT_VERSION = 1;
|
|
1273
|
+
|
|
1274
|
+
// src/studio/analytics/track.ts
|
|
1275
|
+
function baseProps() {
|
|
1276
|
+
return { event_version: STUDIO_ANALYTICS_EVENT_VERSION };
|
|
1277
|
+
}
|
|
1278
|
+
function normalizeError(error) {
|
|
1279
|
+
if (!error) return {};
|
|
1280
|
+
if (typeof error === "string") {
|
|
1281
|
+
return { error_code: error.slice(0, 120), error_domain: "string" };
|
|
1282
|
+
}
|
|
1283
|
+
if (error instanceof Error) {
|
|
1284
|
+
return {
|
|
1285
|
+
error_code: error.message.slice(0, 120),
|
|
1286
|
+
error_domain: error.name || "Error"
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
if (typeof error === "object") {
|
|
1290
|
+
const candidate = error;
|
|
1291
|
+
return {
|
|
1292
|
+
error_code: String(candidate.code ?? candidate.message ?? "unknown_error").slice(0, 120),
|
|
1293
|
+
error_domain: candidate.name ?? "object"
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
return { error_code: "unknown_error", error_domain: typeof error };
|
|
1297
|
+
}
|
|
1298
|
+
async function trackMutationEvent(name, payload) {
|
|
1299
|
+
await trackStudioEvent(name, payload);
|
|
1300
|
+
await flushStudioAnalytics();
|
|
1301
|
+
}
|
|
1302
|
+
var lastOpenCommentsKey = null;
|
|
1303
|
+
var lastOpenCommentsAt = 0;
|
|
1304
|
+
async function trackRemixApp(params) {
|
|
1305
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1306
|
+
await trackMutationEvent("remix_app", {
|
|
1307
|
+
app_id: params.appId,
|
|
1308
|
+
source_app_id: params.sourceAppId,
|
|
1309
|
+
thread_id: params.threadId,
|
|
1310
|
+
success: params.success,
|
|
1311
|
+
...errorProps,
|
|
1312
|
+
...baseProps()
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
async function trackEditApp(params) {
|
|
1316
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1317
|
+
await trackMutationEvent("edit_app", {
|
|
1318
|
+
app_id: params.appId,
|
|
1319
|
+
thread_id: params.threadId,
|
|
1320
|
+
prompt_length: params.promptLength,
|
|
1321
|
+
success: params.success,
|
|
1322
|
+
...errorProps,
|
|
1323
|
+
...baseProps()
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
async function trackShareApp(params) {
|
|
1327
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1328
|
+
await trackMutationEvent("share_app", {
|
|
1329
|
+
app_id: params.appId,
|
|
1330
|
+
success: params.success,
|
|
1331
|
+
...errorProps,
|
|
1332
|
+
...baseProps()
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
async function trackOpenMergeRequest(params) {
|
|
1336
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1337
|
+
await trackMutationEvent("open_merge_request", {
|
|
1338
|
+
app_id: params.appId,
|
|
1339
|
+
merge_request_id: params.mergeRequestId,
|
|
1340
|
+
success: params.success,
|
|
1341
|
+
...errorProps,
|
|
1342
|
+
...baseProps()
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
async function trackApproveMergeRequest(params) {
|
|
1346
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1347
|
+
await trackMutationEvent("approve_merge_request", {
|
|
1348
|
+
app_id: params.appId,
|
|
1349
|
+
merge_request_id: params.mergeRequestId,
|
|
1350
|
+
success: params.success,
|
|
1351
|
+
...errorProps,
|
|
1352
|
+
...baseProps()
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
async function trackRejectMergeRequest(params) {
|
|
1356
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1357
|
+
await trackMutationEvent("reject_merge_request", {
|
|
1358
|
+
app_id: params.appId,
|
|
1359
|
+
merge_request_id: params.mergeRequestId,
|
|
1360
|
+
success: params.success,
|
|
1361
|
+
...errorProps,
|
|
1362
|
+
...baseProps()
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
async function trackTestBundle(params) {
|
|
1366
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1367
|
+
await trackMutationEvent("test_bundle", {
|
|
1368
|
+
app_id: params.appId,
|
|
1369
|
+
commit_id: params.commitId,
|
|
1370
|
+
success: params.success,
|
|
1371
|
+
...errorProps,
|
|
1372
|
+
...baseProps()
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
async function trackLikeApp(params) {
|
|
1376
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1377
|
+
await trackMutationEvent("like_app", {
|
|
1378
|
+
app_id: params.appId,
|
|
1379
|
+
source: params.source ?? "unknown",
|
|
1380
|
+
success: params.success,
|
|
1381
|
+
...errorProps,
|
|
1382
|
+
...baseProps()
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
async function trackUnlikeApp(params) {
|
|
1386
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1387
|
+
await trackMutationEvent("unlike_app", {
|
|
1388
|
+
app_id: params.appId,
|
|
1389
|
+
source: params.source ?? "unknown",
|
|
1390
|
+
success: params.success,
|
|
1391
|
+
...errorProps,
|
|
1392
|
+
...baseProps()
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
async function trackOpenComments(params) {
|
|
1396
|
+
const key = `${params.appId}:${params.source ?? "unknown"}`;
|
|
1397
|
+
const now = Date.now();
|
|
1398
|
+
if (lastOpenCommentsKey === key && now - lastOpenCommentsAt < 1e3) return;
|
|
1399
|
+
lastOpenCommentsKey = key;
|
|
1400
|
+
lastOpenCommentsAt = now;
|
|
1401
|
+
await trackStudioEvent("open_comments", {
|
|
1402
|
+
app_id: params.appId,
|
|
1403
|
+
source: params.source ?? "unknown",
|
|
1404
|
+
...baseProps()
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
async function trackSubmitComment(params) {
|
|
1408
|
+
const errorProps = params.success ? {} : normalizeError(params.error);
|
|
1409
|
+
await trackMutationEvent("submit_comment", {
|
|
1410
|
+
app_id: params.appId,
|
|
1411
|
+
comment_type: "general",
|
|
1412
|
+
comment_length: params.commentLength,
|
|
1413
|
+
success: params.success,
|
|
1414
|
+
...errorProps,
|
|
1415
|
+
...baseProps()
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1180
1419
|
// src/studio/hooks/useBundleManager.ts
|
|
1181
1420
|
function sleep2(ms) {
|
|
1182
1421
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -1493,7 +1732,7 @@ async function pollBundle(appId, bundleId, opts) {
|
|
|
1493
1732
|
await sleep2(opts.intervalMs);
|
|
1494
1733
|
}
|
|
1495
1734
|
}
|
|
1496
|
-
async function resolveBundlePath(src, platform, mode) {
|
|
1735
|
+
async function resolveBundlePath(src, platform, mode, onStatus) {
|
|
1497
1736
|
const { appId, commitId } = src;
|
|
1498
1737
|
const dir = bundlesCacheDir();
|
|
1499
1738
|
await ensureDir(dir);
|
|
@@ -1507,7 +1746,9 @@ async function resolveBundlePath(src, platform, mode) {
|
|
|
1507
1746
|
},
|
|
1508
1747
|
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1509
1748
|
);
|
|
1749
|
+
onStatus == null ? void 0 : onStatus(initiate.status);
|
|
1510
1750
|
const finalBundle = initiate.status === "succeeded" || initiate.status === "failed" ? initiate : await pollBundle(appId, initiate.id, { timeoutMs: 3 * 60 * 1e3, intervalMs: 1200 });
|
|
1751
|
+
onStatus == null ? void 0 : onStatus(finalBundle.status);
|
|
1511
1752
|
if (finalBundle.status === "failed") {
|
|
1512
1753
|
throw new Error("Bundle build failed.");
|
|
1513
1754
|
}
|
|
@@ -1549,6 +1790,7 @@ function useBundleManager({
|
|
|
1549
1790
|
const [renderToken, setRenderToken] = React5.useState(0);
|
|
1550
1791
|
const [loading, setLoading] = React5.useState(false);
|
|
1551
1792
|
const [loadingMode, setLoadingMode] = React5.useState(null);
|
|
1793
|
+
const [bundleStatus, setBundleStatus] = React5.useState(null);
|
|
1552
1794
|
const [statusLabel, setStatusLabel] = React5.useState(null);
|
|
1553
1795
|
const [error, setError] = React5.useState(null);
|
|
1554
1796
|
const [isTesting, setIsTesting] = React5.useState(false);
|
|
@@ -1660,16 +1902,20 @@ function useBundleManager({
|
|
|
1660
1902
|
activeLoadModeRef.current = mode;
|
|
1661
1903
|
setLoading(true);
|
|
1662
1904
|
setLoadingMode(mode);
|
|
1905
|
+
setBundleStatus(null);
|
|
1663
1906
|
setError(null);
|
|
1664
1907
|
setStatusLabel(mode === "test" ? "Loading test bundle\u2026" : "Loading latest build\u2026");
|
|
1665
1908
|
if (mode === "base" && desiredModeRef.current === "base") {
|
|
1666
1909
|
void activateCachedBase(src.appId);
|
|
1667
1910
|
}
|
|
1668
1911
|
try {
|
|
1669
|
-
const { bundlePath: path, bundle } = await resolveBundlePath(src, platform, mode)
|
|
1912
|
+
const { bundlePath: path, bundle } = await resolveBundlePath(src, platform, mode, (status) => {
|
|
1913
|
+
setBundleStatus(status);
|
|
1914
|
+
});
|
|
1670
1915
|
if (mode === "base" && opId !== baseOpIdRef.current) return;
|
|
1671
1916
|
if (mode === "test" && opId !== testOpIdRef.current) return;
|
|
1672
1917
|
if (desiredModeRef.current !== mode) return;
|
|
1918
|
+
setBundleStatus(bundle.status);
|
|
1673
1919
|
setBundlePath(path);
|
|
1674
1920
|
const fingerprint = bundle.checksumSha256 ?? `id:${bundle.id}`;
|
|
1675
1921
|
const shouldSkipInitialBaseRemount = mode === "base" && initialHydratedBaseFromDiskRef.current && !hasCompletedFirstNetworkBaseLoadRef.current && Boolean(lastBaseFingerprintRef.current) && lastBaseFingerprintRef.current === fingerprint;
|
|
@@ -1705,6 +1951,7 @@ function useBundleManager({
|
|
|
1705
1951
|
} catch (e) {
|
|
1706
1952
|
if (mode === "base" && opId !== baseOpIdRef.current) return;
|
|
1707
1953
|
if (mode === "test" && opId !== testOpIdRef.current) return;
|
|
1954
|
+
setBundleStatus("failed");
|
|
1708
1955
|
const msg = e instanceof Error ? e.message : String(e);
|
|
1709
1956
|
setError(msg);
|
|
1710
1957
|
setStatusLabel(null);
|
|
@@ -1720,7 +1967,22 @@ function useBundleManager({
|
|
|
1720
1967
|
await load(baseRef.current, "base");
|
|
1721
1968
|
}, [load]);
|
|
1722
1969
|
const loadTest = React5.useCallback(async (src) => {
|
|
1723
|
-
|
|
1970
|
+
try {
|
|
1971
|
+
await load(src, "test");
|
|
1972
|
+
await trackTestBundle({
|
|
1973
|
+
appId: src.appId,
|
|
1974
|
+
commitId: src.commitId ?? void 0,
|
|
1975
|
+
success: true
|
|
1976
|
+
});
|
|
1977
|
+
} catch (error2) {
|
|
1978
|
+
await trackTestBundle({
|
|
1979
|
+
appId: src.appId,
|
|
1980
|
+
commitId: src.commitId ?? void 0,
|
|
1981
|
+
success: false,
|
|
1982
|
+
error: error2
|
|
1983
|
+
});
|
|
1984
|
+
throw error2;
|
|
1985
|
+
}
|
|
1724
1986
|
}, [load]);
|
|
1725
1987
|
const restoreBase = React5.useCallback(async () => {
|
|
1726
1988
|
const src = baseRef.current;
|
|
@@ -1736,7 +1998,19 @@ function useBundleManager({
|
|
|
1736
1998
|
if (!canRequestLatest) return;
|
|
1737
1999
|
void loadBase();
|
|
1738
2000
|
}, [base.appId, base.commitId, platform, canRequestLatest, loadBase]);
|
|
1739
|
-
return {
|
|
2001
|
+
return {
|
|
2002
|
+
bundlePath,
|
|
2003
|
+
renderToken,
|
|
2004
|
+
loading,
|
|
2005
|
+
loadingMode,
|
|
2006
|
+
bundleStatus,
|
|
2007
|
+
statusLabel,
|
|
2008
|
+
error,
|
|
2009
|
+
isTesting,
|
|
2010
|
+
loadBase,
|
|
2011
|
+
loadTest,
|
|
2012
|
+
restoreBase
|
|
2013
|
+
};
|
|
1740
2014
|
}
|
|
1741
2015
|
|
|
1742
2016
|
// src/studio/hooks/useMergeRequests.ts
|
|
@@ -1946,24 +2220,68 @@ function useMergeRequests(params) {
|
|
|
1946
2220
|
}, [appId]);
|
|
1947
2221
|
React6.useEffect(() => {
|
|
1948
2222
|
void refresh();
|
|
1949
|
-
}, [refresh]);
|
|
2223
|
+
}, [appId, refresh]);
|
|
1950
2224
|
const openMergeRequest = React6.useCallback(async (sourceAppId) => {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2225
|
+
try {
|
|
2226
|
+
const mr = await mergeRequestsRepository.open({ sourceAppId });
|
|
2227
|
+
await refresh();
|
|
2228
|
+
await trackOpenMergeRequest({
|
|
2229
|
+
appId,
|
|
2230
|
+
mergeRequestId: mr.id,
|
|
2231
|
+
success: true
|
|
2232
|
+
});
|
|
2233
|
+
return mr;
|
|
2234
|
+
} catch (error2) {
|
|
2235
|
+
await trackOpenMergeRequest({
|
|
2236
|
+
appId,
|
|
2237
|
+
success: false,
|
|
2238
|
+
error: error2
|
|
2239
|
+
});
|
|
2240
|
+
throw error2;
|
|
2241
|
+
}
|
|
1954
2242
|
}, [refresh]);
|
|
1955
2243
|
const approve = React6.useCallback(async (mrId) => {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
2244
|
+
try {
|
|
2245
|
+
const mr = await mergeRequestsRepository.update(mrId, { status: "approved" });
|
|
2246
|
+
await refresh();
|
|
2247
|
+
const merged = await pollUntilMerged(mrId);
|
|
2248
|
+
await refresh();
|
|
2249
|
+
await trackApproveMergeRequest({
|
|
2250
|
+
appId,
|
|
2251
|
+
mergeRequestId: mrId,
|
|
2252
|
+
success: true
|
|
2253
|
+
});
|
|
2254
|
+
return merged ?? mr;
|
|
2255
|
+
} catch (error2) {
|
|
2256
|
+
await trackApproveMergeRequest({
|
|
2257
|
+
appId,
|
|
2258
|
+
mergeRequestId: mrId,
|
|
2259
|
+
success: false,
|
|
2260
|
+
error: error2
|
|
2261
|
+
});
|
|
2262
|
+
throw error2;
|
|
2263
|
+
}
|
|
2264
|
+
}, [appId, pollUntilMerged, refresh]);
|
|
1962
2265
|
const reject = React6.useCallback(async (mrId) => {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2266
|
+
try {
|
|
2267
|
+
const mr = await mergeRequestsRepository.update(mrId, { status: "rejected" });
|
|
2268
|
+
await refresh();
|
|
2269
|
+
await trackRejectMergeRequest({
|
|
2270
|
+
appId,
|
|
2271
|
+
mergeRequestId: mrId,
|
|
2272
|
+
success: true
|
|
2273
|
+
});
|
|
2274
|
+
return mr;
|
|
2275
|
+
} catch (error2) {
|
|
2276
|
+
await trackRejectMergeRequest({
|
|
2277
|
+
appId,
|
|
2278
|
+
mergeRequestId: mrId,
|
|
2279
|
+
success: false,
|
|
2280
|
+
error: error2
|
|
2281
|
+
});
|
|
2282
|
+
throw error2;
|
|
2283
|
+
}
|
|
2284
|
+
}, [appId, refresh]);
|
|
1967
2285
|
const toSummary = React6.useCallback((mr) => {
|
|
1968
2286
|
const stats = creatorStatsById[mr.createdBy];
|
|
1969
2287
|
return {
|
|
@@ -1999,7 +2317,7 @@ function useMergeRequests(params) {
|
|
|
1999
2317
|
|
|
2000
2318
|
// src/studio/hooks/useAttachmentUpload.ts
|
|
2001
2319
|
import * as React7 from "react";
|
|
2002
|
-
import { Platform } from "react-native";
|
|
2320
|
+
import { Platform as Platform2 } from "react-native";
|
|
2003
2321
|
import * as FileSystem2 from "expo-file-system/legacy";
|
|
2004
2322
|
|
|
2005
2323
|
// src/data/attachment/remote.ts
|
|
@@ -2086,7 +2404,7 @@ function useAttachmentUpload() {
|
|
|
2086
2404
|
const blobs = await Promise.all(
|
|
2087
2405
|
dataUrls.map(async (dataUrl, idx) => {
|
|
2088
2406
|
const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
|
|
2089
|
-
const blob =
|
|
2407
|
+
const blob = Platform2.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
|
|
2090
2408
|
const mimeType = getMimeTypeFromDataUrl(normalized);
|
|
2091
2409
|
return { blob, idx, mimeType };
|
|
2092
2410
|
})
|
|
@@ -2204,13 +2522,21 @@ function useStudioActions({
|
|
|
2204
2522
|
if (sending) return;
|
|
2205
2523
|
setSending(true);
|
|
2206
2524
|
setError(null);
|
|
2525
|
+
let forkSucceeded = false;
|
|
2207
2526
|
try {
|
|
2208
2527
|
let targetApp = app;
|
|
2528
|
+
const sourceAppId = app.id;
|
|
2209
2529
|
if (shouldForkOnEdit) {
|
|
2210
2530
|
setForking(true);
|
|
2211
|
-
const sourceAppId = app.id;
|
|
2212
2531
|
const forked = await appsRepository.fork(app.id, {});
|
|
2213
2532
|
targetApp = forked;
|
|
2533
|
+
await trackRemixApp({
|
|
2534
|
+
appId: forked.id,
|
|
2535
|
+
sourceAppId,
|
|
2536
|
+
threadId: forked.threadId ?? void 0,
|
|
2537
|
+
success: true
|
|
2538
|
+
});
|
|
2539
|
+
forkSucceeded = true;
|
|
2214
2540
|
onForkedApp == null ? void 0 : onForkedApp(forked.id, { keepRenderingAppId: sourceAppId });
|
|
2215
2541
|
}
|
|
2216
2542
|
setForking(false);
|
|
@@ -2238,9 +2564,33 @@ function useStudioActions({
|
|
|
2238
2564
|
queueItemId: editResult.queueItemId ?? null,
|
|
2239
2565
|
queuePosition: editResult.queuePosition ?? null
|
|
2240
2566
|
});
|
|
2567
|
+
await trackEditApp({
|
|
2568
|
+
appId: targetApp.id,
|
|
2569
|
+
threadId,
|
|
2570
|
+
promptLength: prompt.trim().length,
|
|
2571
|
+
success: true
|
|
2572
|
+
});
|
|
2241
2573
|
} catch (e) {
|
|
2242
2574
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
2243
2575
|
setError(err);
|
|
2576
|
+
if (shouldForkOnEdit && !forkSucceeded && (app == null ? void 0 : app.id)) {
|
|
2577
|
+
await trackRemixApp({
|
|
2578
|
+
appId: app.id,
|
|
2579
|
+
sourceAppId: app.id,
|
|
2580
|
+
threadId: app.threadId ?? void 0,
|
|
2581
|
+
success: false,
|
|
2582
|
+
error: err
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
if ((app == null ? void 0 : app.id) && app.threadId) {
|
|
2586
|
+
await trackEditApp({
|
|
2587
|
+
appId: app.id,
|
|
2588
|
+
threadId: app.threadId,
|
|
2589
|
+
promptLength: prompt.trim().length,
|
|
2590
|
+
success: false,
|
|
2591
|
+
error: err
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2244
2594
|
throw err;
|
|
2245
2595
|
} finally {
|
|
2246
2596
|
setForking(false);
|
|
@@ -2261,6 +2611,7 @@ import { jsx as jsx3 } from "react/jsx-runtime";
|
|
|
2261
2611
|
function RuntimeRenderer({
|
|
2262
2612
|
appKey,
|
|
2263
2613
|
bundlePath,
|
|
2614
|
+
preparingText,
|
|
2264
2615
|
forcePreparing,
|
|
2265
2616
|
renderToken,
|
|
2266
2617
|
style,
|
|
@@ -2276,7 +2627,7 @@ function RuntimeRenderer({
|
|
|
2276
2627
|
if (!hasRenderedOnce && !forcePreparing && !allowInitialPreparing) {
|
|
2277
2628
|
return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1 }, style] });
|
|
2278
2629
|
}
|
|
2279
|
-
return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ jsx3(Text, { variant: "bodyMuted", children: "Preparing app\u2026" }) });
|
|
2630
|
+
return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, style], children: /* @__PURE__ */ jsx3(Text, { variant: "bodyMuted", children: preparingText ?? "Preparing app\u2026" }) });
|
|
2280
2631
|
}
|
|
2281
2632
|
return /* @__PURE__ */ jsx3(View2, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsx3(
|
|
2282
2633
|
ComergeRuntimeRenderer,
|
|
@@ -2290,8 +2641,8 @@ function RuntimeRenderer({
|
|
|
2290
2641
|
}
|
|
2291
2642
|
|
|
2292
2643
|
// src/studio/ui/StudioOverlay.tsx
|
|
2293
|
-
import * as
|
|
2294
|
-
import { Keyboard as Keyboard5, Platform as
|
|
2644
|
+
import * as React45 from "react";
|
|
2645
|
+
import { Keyboard as Keyboard5, Platform as Platform11, View as View47, useWindowDimensions as useWindowDimensions4 } from "react-native";
|
|
2295
2646
|
|
|
2296
2647
|
// src/components/studio-sheet/StudioBottomSheet.tsx
|
|
2297
2648
|
import * as React12 from "react";
|
|
@@ -2302,7 +2653,7 @@ import {
|
|
|
2302
2653
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
2303
2654
|
|
|
2304
2655
|
// src/components/studio-sheet/StudioSheetBackground.tsx
|
|
2305
|
-
import { Platform as
|
|
2656
|
+
import { Platform as Platform4, View as View3 } from "react-native";
|
|
2306
2657
|
import { isLiquidGlassSupported } from "@callstack/liquid-glass";
|
|
2307
2658
|
|
|
2308
2659
|
// src/components/utils/ResettableLiquidGlassView.tsx
|
|
@@ -2311,7 +2662,7 @@ import { LiquidGlassView } from "@callstack/liquid-glass";
|
|
|
2311
2662
|
|
|
2312
2663
|
// src/components/utils/liquidGlassReset.tsx
|
|
2313
2664
|
import * as React10 from "react";
|
|
2314
|
-
import { AppState as AppState2, Platform as
|
|
2665
|
+
import { AppState as AppState2, Platform as Platform3 } from "react-native";
|
|
2315
2666
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
2316
2667
|
var LiquidGlassResetContext = React10.createContext(0);
|
|
2317
2668
|
function LiquidGlassResetProvider({
|
|
@@ -2320,7 +2671,7 @@ function LiquidGlassResetProvider({
|
|
|
2320
2671
|
}) {
|
|
2321
2672
|
const [token, setToken] = React10.useState(0);
|
|
2322
2673
|
React10.useEffect(() => {
|
|
2323
|
-
if (
|
|
2674
|
+
if (Platform3.OS !== "ios") return;
|
|
2324
2675
|
const onChange = (state) => {
|
|
2325
2676
|
if (state === "active") setToken((t) => t + 1);
|
|
2326
2677
|
};
|
|
@@ -2361,7 +2712,7 @@ function StudioSheetBackground({
|
|
|
2361
2712
|
renderBackground
|
|
2362
2713
|
}) {
|
|
2363
2714
|
const theme = useTheme();
|
|
2364
|
-
const radius =
|
|
2715
|
+
const radius = Platform4.OS === "ios" ? 39 : 16;
|
|
2365
2716
|
const fallbackBgColor = theme.scheme === "dark" ? "rgba(11, 8, 15, 0.85)" : "rgba(255, 255, 255, 0.85)";
|
|
2366
2717
|
const secondaryBgBaseColor = theme.scheme === "dark" ? "rgb(24, 24, 27)" : "rgb(173, 173, 173)";
|
|
2367
2718
|
const containerStyle = {
|
|
@@ -3421,7 +3772,7 @@ var styles3 = StyleSheet3.create({
|
|
|
3421
3772
|
|
|
3422
3773
|
// src/components/comments/AppCommentsSheet.tsx
|
|
3423
3774
|
import * as React24 from "react";
|
|
3424
|
-
import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as
|
|
3775
|
+
import { ActivityIndicator as ActivityIndicator3, Keyboard as Keyboard3, Platform as Platform6, Pressable as Pressable5, View as View14 } from "react-native";
|
|
3425
3776
|
import {
|
|
3426
3777
|
BottomSheetBackdrop,
|
|
3427
3778
|
BottomSheetModal as BottomSheetModal2,
|
|
@@ -3971,7 +4322,18 @@ function useAppComments(appId) {
|
|
|
3971
4322
|
try {
|
|
3972
4323
|
const newComment = await appCommentsRepository.create(appId, { body: trimmed, commentType: "general" });
|
|
3973
4324
|
setComments((prev) => sortByCreatedAtAsc([...prev, newComment]));
|
|
4325
|
+
await trackSubmitComment({
|
|
4326
|
+
appId,
|
|
4327
|
+
commentLength: trimmed.length,
|
|
4328
|
+
success: true
|
|
4329
|
+
});
|
|
3974
4330
|
} catch (e) {
|
|
4331
|
+
await trackSubmitComment({
|
|
4332
|
+
appId,
|
|
4333
|
+
commentLength: trimmed.length,
|
|
4334
|
+
success: false,
|
|
4335
|
+
error: e
|
|
4336
|
+
});
|
|
3975
4337
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
3976
4338
|
throw e;
|
|
3977
4339
|
} finally {
|
|
@@ -4014,11 +4376,11 @@ function useAppDetails(appId) {
|
|
|
4014
4376
|
|
|
4015
4377
|
// src/components/comments/useIosKeyboardSnapFix.ts
|
|
4016
4378
|
import * as React23 from "react";
|
|
4017
|
-
import { Keyboard as Keyboard2, Platform as
|
|
4379
|
+
import { Keyboard as Keyboard2, Platform as Platform5 } from "react-native";
|
|
4018
4380
|
function useIosKeyboardSnapFix(sheetRef, options) {
|
|
4019
4381
|
const [keyboardVisible, setKeyboardVisible] = React23.useState(false);
|
|
4020
4382
|
React23.useEffect(() => {
|
|
4021
|
-
if (
|
|
4383
|
+
if (Platform5.OS !== "ios") return;
|
|
4022
4384
|
const show = Keyboard2.addListener("keyboardWillShow", () => setKeyboardVisible(true));
|
|
4023
4385
|
const hide = Keyboard2.addListener("keyboardWillHide", () => {
|
|
4024
4386
|
var _a;
|
|
@@ -4096,8 +4458,8 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
|
|
|
4096
4458
|
onChange: handleChange,
|
|
4097
4459
|
backgroundStyle: {
|
|
4098
4460
|
backgroundColor: theme.scheme === "dark" ? "#0B080F" : "#FFFFFF",
|
|
4099
|
-
borderTopLeftRadius:
|
|
4100
|
-
borderTopRightRadius:
|
|
4461
|
+
borderTopLeftRadius: Platform6.OS === "ios" ? 39 : 16,
|
|
4462
|
+
borderTopRightRadius: Platform6.OS === "ios" ? 39 : 16
|
|
4101
4463
|
},
|
|
4102
4464
|
handleIndicatorStyle: { backgroundColor: theme.colors.handleIndicator },
|
|
4103
4465
|
keyboardBehavior: "interactive",
|
|
@@ -4203,7 +4565,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
|
|
|
4203
4565
|
bottom: 0,
|
|
4204
4566
|
paddingHorizontal: theme.spacing.lg,
|
|
4205
4567
|
paddingTop: theme.spacing.sm,
|
|
4206
|
-
paddingBottom:
|
|
4568
|
+
paddingBottom: Platform6.OS === "ios" ? keyboardVisible ? theme.spacing.lg : insets.bottom : insets.bottom + 10,
|
|
4207
4569
|
borderTopWidth: 1,
|
|
4208
4570
|
borderTopColor: withAlpha(theme.colors.border, 0.1),
|
|
4209
4571
|
backgroundColor: withAlpha(theme.colors.background, 0.8)
|
|
@@ -4230,7 +4592,7 @@ function AppCommentsSheet({ appId, onClose, onCountChange, onPlayApp }) {
|
|
|
4230
4592
|
|
|
4231
4593
|
// src/studio/ui/PreviewPanel.tsx
|
|
4232
4594
|
import * as React35 from "react";
|
|
4233
|
-
import { ActivityIndicator as ActivityIndicator7, Platform as
|
|
4595
|
+
import { ActivityIndicator as ActivityIndicator7, Platform as Platform8, Share, View as View33 } from "react-native";
|
|
4234
4596
|
|
|
4235
4597
|
// src/components/preview/PreviewPage.tsx
|
|
4236
4598
|
import { View as View15 } from "react-native";
|
|
@@ -5121,7 +5483,7 @@ import { Animated as Animated7, Pressable as Pressable10, View as View28 } from
|
|
|
5121
5483
|
import { Ban, Check as Check3, CheckCheck, ChevronDown as ChevronDown2 } from "lucide-react-native";
|
|
5122
5484
|
|
|
5123
5485
|
// src/components/primitives/MarkdownText.tsx
|
|
5124
|
-
import { Dimensions as Dimensions2, Keyboard as Keyboard4, Modal, Platform as
|
|
5486
|
+
import { Dimensions as Dimensions2, Keyboard as Keyboard4, Modal, Platform as Platform7, Pressable as Pressable9, Text as Text3, View as View27 } from "react-native";
|
|
5125
5487
|
import Markdown from "react-native-markdown-display";
|
|
5126
5488
|
import { useEffect as useEffect22, useRef as useRef15, useState as useState21 } from "react";
|
|
5127
5489
|
import { jsx as jsx39, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
@@ -5209,7 +5571,7 @@ function MarkdownText({ markdown, variant = "chat", bodyColor, style }) {
|
|
|
5209
5571
|
paddingHorizontal: variant === "mergeRequest" ? 6 : 4,
|
|
5210
5572
|
paddingVertical: variant === "mergeRequest" ? 2 : 0,
|
|
5211
5573
|
borderRadius: variant === "mergeRequest" ? 6 : 4,
|
|
5212
|
-
fontFamily:
|
|
5574
|
+
fontFamily: Platform7.OS === "ios" ? "Menlo" : "monospace",
|
|
5213
5575
|
fontSize: 13
|
|
5214
5576
|
},
|
|
5215
5577
|
code_block: {
|
|
@@ -6051,7 +6413,8 @@ function useAppStats({
|
|
|
6051
6413
|
initialComments = 0,
|
|
6052
6414
|
initialForks = 0,
|
|
6053
6415
|
initialIsLiked = false,
|
|
6054
|
-
onOpenComments
|
|
6416
|
+
onOpenComments,
|
|
6417
|
+
interactionSource = "unknown"
|
|
6055
6418
|
}) {
|
|
6056
6419
|
const [likeCount, setLikeCount] = React33.useState(initialLikes);
|
|
6057
6420
|
const [commentCount, setCommentCount] = React33.useState(initialComments);
|
|
@@ -6095,23 +6458,31 @@ function useAppStats({
|
|
|
6095
6458
|
if (newIsLiked) {
|
|
6096
6459
|
const res = await appLikesRepository.create(appId, {});
|
|
6097
6460
|
if (typeof ((_a = res.stats) == null ? void 0 : _a.total) === "number") setLikeCount(Math.max(0, res.stats.total));
|
|
6461
|
+
await trackLikeApp({ appId, source: interactionSource, success: true });
|
|
6098
6462
|
} else {
|
|
6099
6463
|
const res = await appLikesRepository.removeMine(appId);
|
|
6100
6464
|
if (typeof ((_b = res.stats) == null ? void 0 : _b.total) === "number") setLikeCount(Math.max(0, res.stats.total));
|
|
6465
|
+
await trackUnlikeApp({ appId, source: interactionSource, success: true });
|
|
6101
6466
|
}
|
|
6102
6467
|
} catch (e) {
|
|
6103
6468
|
setIsLiked(!newIsLiked);
|
|
6104
6469
|
setLikeCount((prev) => Math.max(0, prev + (newIsLiked ? -1 : 1)));
|
|
6470
|
+
if (newIsLiked) {
|
|
6471
|
+
await trackLikeApp({ appId, source: interactionSource, success: false, error: e });
|
|
6472
|
+
} else {
|
|
6473
|
+
await trackUnlikeApp({ appId, source: interactionSource, success: false, error: e });
|
|
6474
|
+
}
|
|
6105
6475
|
}
|
|
6106
|
-
}, [appId, isLiked, likeCount]);
|
|
6476
|
+
}, [appId, interactionSource, isLiked, likeCount]);
|
|
6107
6477
|
const handleOpenComments = React33.useCallback(() => {
|
|
6108
6478
|
if (!appId) return;
|
|
6109
6479
|
try {
|
|
6110
6480
|
void Haptics2.impactAsync(Haptics2.ImpactFeedbackStyle.Light);
|
|
6111
6481
|
} catch {
|
|
6112
6482
|
}
|
|
6483
|
+
void trackOpenComments({ appId, source: interactionSource });
|
|
6113
6484
|
onOpenComments == null ? void 0 : onOpenComments();
|
|
6114
|
-
}, [appId, onOpenComments]);
|
|
6485
|
+
}, [appId, interactionSource, onOpenComments]);
|
|
6115
6486
|
return { likeCount, commentCount, forkCount, isLiked, setCommentCount, handleLike, handleOpenComments };
|
|
6116
6487
|
}
|
|
6117
6488
|
|
|
@@ -6194,7 +6565,8 @@ function usePreviewPanelData(params) {
|
|
|
6194
6565
|
initialForks: insights.forks,
|
|
6195
6566
|
initialComments: commentCountOverride ?? insights.comments,
|
|
6196
6567
|
initialIsLiked: Boolean(app == null ? void 0 : app.isLiked),
|
|
6197
|
-
onOpenComments
|
|
6568
|
+
onOpenComments,
|
|
6569
|
+
interactionSource: "preview_panel"
|
|
6198
6570
|
});
|
|
6199
6571
|
const canSubmitMergeRequest = React34.useMemo(() => {
|
|
6200
6572
|
if (!isOwner) return false;
|
|
@@ -6260,7 +6632,7 @@ ${shareUrl}` : `Check out this app on Remix
|
|
|
6260
6632
|
${shareUrl}`;
|
|
6261
6633
|
try {
|
|
6262
6634
|
const title = app.name ?? "Remix app";
|
|
6263
|
-
const payload =
|
|
6635
|
+
const payload = Platform8.OS === "ios" ? {
|
|
6264
6636
|
title,
|
|
6265
6637
|
message
|
|
6266
6638
|
} : {
|
|
@@ -6269,8 +6641,17 @@ ${shareUrl}`;
|
|
|
6269
6641
|
url: shareUrl
|
|
6270
6642
|
};
|
|
6271
6643
|
await Share.share(payload);
|
|
6644
|
+
await trackShareApp({
|
|
6645
|
+
appId: app.id,
|
|
6646
|
+
success: true
|
|
6647
|
+
});
|
|
6272
6648
|
} catch (error) {
|
|
6273
6649
|
log.warn("PreviewPanel share failed", error);
|
|
6650
|
+
await trackShareApp({
|
|
6651
|
+
appId: app.id,
|
|
6652
|
+
success: false,
|
|
6653
|
+
error
|
|
6654
|
+
});
|
|
6274
6655
|
}
|
|
6275
6656
|
}, [app]);
|
|
6276
6657
|
const {
|
|
@@ -6364,20 +6745,21 @@ ${shareUrl}`;
|
|
|
6364
6745
|
}
|
|
6365
6746
|
|
|
6366
6747
|
// src/studio/ui/ChatPanel.tsx
|
|
6367
|
-
import * as
|
|
6368
|
-
import { ActivityIndicator as ActivityIndicator9, View as
|
|
6748
|
+
import * as React42 from "react";
|
|
6749
|
+
import { ActivityIndicator as ActivityIndicator9, View as View44 } from "react-native";
|
|
6369
6750
|
|
|
6370
6751
|
// src/components/chat/ChatPage.tsx
|
|
6371
|
-
import * as
|
|
6372
|
-
import { Platform as
|
|
6752
|
+
import * as React39 from "react";
|
|
6753
|
+
import { Platform as Platform10, View as View37 } from "react-native";
|
|
6373
6754
|
import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
|
|
6374
6755
|
|
|
6375
6756
|
// src/components/chat/ChatMessageList.tsx
|
|
6376
|
-
import * as
|
|
6757
|
+
import * as React38 from "react";
|
|
6377
6758
|
import { View as View36 } from "react-native";
|
|
6378
6759
|
import { BottomSheetFlatList } from "@gorhom/bottom-sheet";
|
|
6379
6760
|
|
|
6380
6761
|
// src/components/chat/ChatMessageBubble.tsx
|
|
6762
|
+
import * as React36 from "react";
|
|
6381
6763
|
import { View as View34 } from "react-native";
|
|
6382
6764
|
import { CheckCheck as CheckCheck2, GitMerge as GitMerge2, RotateCcw } from "lucide-react-native";
|
|
6383
6765
|
|
|
@@ -6448,7 +6830,19 @@ function Button({
|
|
|
6448
6830
|
|
|
6449
6831
|
// src/components/chat/ChatMessageBubble.tsx
|
|
6450
6832
|
import { jsx as jsx47, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
6451
|
-
function
|
|
6833
|
+
function areMessageMetaEqual(a, b) {
|
|
6834
|
+
if (a === b) return true;
|
|
6835
|
+
if (!a || !b) return a === b;
|
|
6836
|
+
return a.kind === b.kind && a.event === b.event && a.status === b.status && a.mergeRequestId === b.mergeRequestId && a.sourceAppId === b.sourceAppId && a.targetAppId === b.targetAppId && a.appId === b.appId && a.threadId === b.threadId;
|
|
6837
|
+
}
|
|
6838
|
+
function ChatMessageBubbleInner({
|
|
6839
|
+
message,
|
|
6840
|
+
renderContent,
|
|
6841
|
+
isLast,
|
|
6842
|
+
retrying,
|
|
6843
|
+
onRetryMessage,
|
|
6844
|
+
style
|
|
6845
|
+
}) {
|
|
6452
6846
|
var _a, _b;
|
|
6453
6847
|
const theme = useTheme();
|
|
6454
6848
|
const metaEvent = ((_a = message.meta) == null ? void 0 : _a.event) ?? null;
|
|
@@ -6463,8 +6857,11 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
|
|
|
6463
6857
|
const bubbleVariant = isHuman ? "surface" : "surfaceRaised";
|
|
6464
6858
|
const cornerStyle = isHuman ? { borderTopRightRadius: 0 } : { borderTopLeftRadius: 0 };
|
|
6465
6859
|
const bodyColor = metaStatus === "success" ? theme.colors.success : metaStatus === "error" ? theme.colors.danger : void 0;
|
|
6466
|
-
const showRetry = Boolean(
|
|
6860
|
+
const showRetry = Boolean(onRetryMessage) && isLast && metaStatus === "error" && message.author === "human";
|
|
6467
6861
|
const retryLabel = retrying ? "Retrying..." : "Retry";
|
|
6862
|
+
const handleRetryPress = React36.useCallback(() => {
|
|
6863
|
+
onRetryMessage == null ? void 0 : onRetryMessage(message.id);
|
|
6864
|
+
}, [message.id, onRetryMessage]);
|
|
6468
6865
|
return /* @__PURE__ */ jsxs28(View34, { style: [align, style], children: [
|
|
6469
6866
|
/* @__PURE__ */ jsx47(
|
|
6470
6867
|
Surface,
|
|
@@ -6493,7 +6890,7 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
|
|
|
6493
6890
|
{
|
|
6494
6891
|
variant: "ghost",
|
|
6495
6892
|
size: "sm",
|
|
6496
|
-
onPress:
|
|
6893
|
+
onPress: handleRetryPress,
|
|
6497
6894
|
disabled: retrying,
|
|
6498
6895
|
style: { borderColor: theme.colors.danger },
|
|
6499
6896
|
accessibilityLabel: "Retry send",
|
|
@@ -6514,19 +6911,24 @@ function ChatMessageBubble({ message, renderContent, isLast, retrying, onRetry,
|
|
|
6514
6911
|
) }) : null
|
|
6515
6912
|
] });
|
|
6516
6913
|
}
|
|
6914
|
+
function areEqual(prev, next) {
|
|
6915
|
+
return prev.message.id === next.message.id && prev.message.author === next.message.author && prev.message.content === next.message.content && prev.message.kind === next.message.kind && String(prev.message.createdAt) === String(next.message.createdAt) && areMessageMetaEqual(prev.message.meta, next.message.meta) && prev.renderContent === next.renderContent && prev.isLast === next.isLast && prev.retrying === next.retrying && prev.onRetryMessage === next.onRetryMessage && prev.style === next.style;
|
|
6916
|
+
}
|
|
6917
|
+
var ChatMessageBubble = React36.memo(ChatMessageBubbleInner, areEqual);
|
|
6918
|
+
ChatMessageBubble.displayName = "ChatMessageBubble";
|
|
6517
6919
|
|
|
6518
6920
|
// src/components/chat/TypingIndicator.tsx
|
|
6519
|
-
import * as
|
|
6921
|
+
import * as React37 from "react";
|
|
6520
6922
|
import { Animated as Animated10, View as View35 } from "react-native";
|
|
6521
6923
|
import { jsx as jsx48 } from "react/jsx-runtime";
|
|
6522
6924
|
function TypingIndicator({ style }) {
|
|
6523
6925
|
const theme = useTheme();
|
|
6524
6926
|
const dotColor = theme.colors.textSubtle;
|
|
6525
|
-
const anims =
|
|
6927
|
+
const anims = React37.useMemo(
|
|
6526
6928
|
() => [new Animated10.Value(0.3), new Animated10.Value(0.3), new Animated10.Value(0.3)],
|
|
6527
6929
|
[]
|
|
6528
6930
|
);
|
|
6529
|
-
|
|
6931
|
+
React37.useEffect(() => {
|
|
6530
6932
|
const loops = [];
|
|
6531
6933
|
anims.forEach((a, idx) => {
|
|
6532
6934
|
const seq = Animated10.sequence([
|
|
@@ -6560,7 +6962,7 @@ function TypingIndicator({ style }) {
|
|
|
6560
6962
|
|
|
6561
6963
|
// src/components/chat/ChatMessageList.tsx
|
|
6562
6964
|
import { jsx as jsx49, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
6563
|
-
var ChatMessageList =
|
|
6965
|
+
var ChatMessageList = React38.forwardRef(
|
|
6564
6966
|
({
|
|
6565
6967
|
messages,
|
|
6566
6968
|
showTypingIndicator = false,
|
|
@@ -6573,21 +6975,22 @@ var ChatMessageList = React37.forwardRef(
|
|
|
6573
6975
|
nearBottomThreshold = 200
|
|
6574
6976
|
}, ref) => {
|
|
6575
6977
|
const theme = useTheme();
|
|
6576
|
-
const listRef =
|
|
6577
|
-
const nearBottomRef =
|
|
6578
|
-
const initialScrollDoneRef =
|
|
6579
|
-
const lastMessageIdRef =
|
|
6580
|
-
const data =
|
|
6978
|
+
const listRef = React38.useRef(null);
|
|
6979
|
+
const nearBottomRef = React38.useRef(true);
|
|
6980
|
+
const initialScrollDoneRef = React38.useRef(false);
|
|
6981
|
+
const lastMessageIdRef = React38.useRef(null);
|
|
6982
|
+
const data = React38.useMemo(() => {
|
|
6581
6983
|
return [...messages].reverse();
|
|
6582
6984
|
}, [messages]);
|
|
6583
6985
|
const lastMessageId = messages.length > 0 ? messages[messages.length - 1].id : null;
|
|
6584
|
-
const
|
|
6986
|
+
const keyExtractor = React38.useCallback((m) => m.id, []);
|
|
6987
|
+
const scrollToBottom = React38.useCallback((options) => {
|
|
6585
6988
|
var _a;
|
|
6586
6989
|
const animated = (options == null ? void 0 : options.animated) ?? true;
|
|
6587
6990
|
(_a = listRef.current) == null ? void 0 : _a.scrollToOffset({ offset: 0, animated });
|
|
6588
6991
|
}, []);
|
|
6589
|
-
|
|
6590
|
-
const handleScroll =
|
|
6992
|
+
React38.useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);
|
|
6993
|
+
const handleScroll = React38.useCallback(
|
|
6591
6994
|
(e) => {
|
|
6592
6995
|
const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
|
|
6593
6996
|
const distanceFromBottom = Math.max(contentOffset.y - Math.max(bottomInset, 0), 0);
|
|
@@ -6599,7 +7002,7 @@ var ChatMessageList = React37.forwardRef(
|
|
|
6599
7002
|
},
|
|
6600
7003
|
[bottomInset, nearBottomThreshold, onNearBottomChange]
|
|
6601
7004
|
);
|
|
6602
|
-
|
|
7005
|
+
React38.useEffect(() => {
|
|
6603
7006
|
if (!initialScrollDoneRef.current) return;
|
|
6604
7007
|
const lastId = messages.length > 0 ? messages[messages.length - 1].id : null;
|
|
6605
7008
|
const prevLastId = lastMessageIdRef.current;
|
|
@@ -6609,54 +7012,68 @@ var ChatMessageList = React37.forwardRef(
|
|
|
6609
7012
|
const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
|
|
6610
7013
|
return () => cancelAnimationFrame(id);
|
|
6611
7014
|
}, [messages, scrollToBottom]);
|
|
6612
|
-
|
|
7015
|
+
React38.useEffect(() => {
|
|
6613
7016
|
if (showTypingIndicator && nearBottomRef.current) {
|
|
6614
7017
|
const id = requestAnimationFrame(() => scrollToBottom({ animated: true }));
|
|
6615
7018
|
return () => cancelAnimationFrame(id);
|
|
6616
7019
|
}
|
|
6617
7020
|
return void 0;
|
|
6618
7021
|
}, [showTypingIndicator, scrollToBottom]);
|
|
7022
|
+
const handleContentSizeChange = React38.useCallback(() => {
|
|
7023
|
+
if (initialScrollDoneRef.current) return;
|
|
7024
|
+
initialScrollDoneRef.current = true;
|
|
7025
|
+
lastMessageIdRef.current = messages.length > 0 ? messages[messages.length - 1].id : null;
|
|
7026
|
+
nearBottomRef.current = true;
|
|
7027
|
+
onNearBottomChange == null ? void 0 : onNearBottomChange(true);
|
|
7028
|
+
requestAnimationFrame(() => scrollToBottom({ animated: false }));
|
|
7029
|
+
}, [messages, onNearBottomChange, scrollToBottom]);
|
|
7030
|
+
const contentContainerStyle = React38.useMemo(
|
|
7031
|
+
() => [
|
|
7032
|
+
{
|
|
7033
|
+
paddingHorizontal: theme.spacing.lg,
|
|
7034
|
+
paddingVertical: theme.spacing.sm
|
|
7035
|
+
},
|
|
7036
|
+
contentStyle
|
|
7037
|
+
],
|
|
7038
|
+
[contentStyle, theme.spacing.lg, theme.spacing.sm]
|
|
7039
|
+
);
|
|
7040
|
+
const renderSeparator = React38.useCallback(() => /* @__PURE__ */ jsx49(View36, { style: { height: theme.spacing.sm } }), [theme.spacing.sm]);
|
|
7041
|
+
const listHeader = React38.useMemo(
|
|
7042
|
+
() => /* @__PURE__ */ jsxs29(View36, { children: [
|
|
7043
|
+
showTypingIndicator ? /* @__PURE__ */ jsx49(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx49(TypingIndicator, {}) }) : null,
|
|
7044
|
+
bottomInset > 0 ? /* @__PURE__ */ jsx49(View36, { style: { height: bottomInset } }) : null
|
|
7045
|
+
] }),
|
|
7046
|
+
[bottomInset, showTypingIndicator, theme.spacing.lg, theme.spacing.sm]
|
|
7047
|
+
);
|
|
7048
|
+
const renderItem = React38.useCallback(
|
|
7049
|
+
({ item }) => /* @__PURE__ */ jsx49(
|
|
7050
|
+
ChatMessageBubble,
|
|
7051
|
+
{
|
|
7052
|
+
message: item,
|
|
7053
|
+
renderContent: renderMessageContent,
|
|
7054
|
+
isLast: Boolean(lastMessageId && item.id === lastMessageId),
|
|
7055
|
+
retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
|
|
7056
|
+
onRetryMessage
|
|
7057
|
+
}
|
|
7058
|
+
),
|
|
7059
|
+
[isRetryingMessage, lastMessageId, onRetryMessage, renderMessageContent]
|
|
7060
|
+
);
|
|
6619
7061
|
return /* @__PURE__ */ jsx49(
|
|
6620
7062
|
BottomSheetFlatList,
|
|
6621
7063
|
{
|
|
6622
7064
|
ref: listRef,
|
|
6623
7065
|
inverted: true,
|
|
6624
7066
|
data,
|
|
6625
|
-
keyExtractor
|
|
7067
|
+
keyExtractor,
|
|
6626
7068
|
keyboardShouldPersistTaps: "handled",
|
|
6627
7069
|
onScroll: handleScroll,
|
|
6628
7070
|
scrollEventThrottle: 16,
|
|
6629
7071
|
showsVerticalScrollIndicator: false,
|
|
6630
|
-
onContentSizeChange:
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
onNearBottomChange == null ? void 0 : onNearBottomChange(true);
|
|
6636
|
-
requestAnimationFrame(() => scrollToBottom({ animated: false }));
|
|
6637
|
-
},
|
|
6638
|
-
contentContainerStyle: [
|
|
6639
|
-
{
|
|
6640
|
-
paddingHorizontal: theme.spacing.lg,
|
|
6641
|
-
paddingVertical: theme.spacing.sm
|
|
6642
|
-
},
|
|
6643
|
-
contentStyle
|
|
6644
|
-
],
|
|
6645
|
-
ItemSeparatorComponent: () => /* @__PURE__ */ jsx49(View36, { style: { height: theme.spacing.sm } }),
|
|
6646
|
-
renderItem: ({ item }) => /* @__PURE__ */ jsx49(
|
|
6647
|
-
ChatMessageBubble,
|
|
6648
|
-
{
|
|
6649
|
-
message: item,
|
|
6650
|
-
renderContent: renderMessageContent,
|
|
6651
|
-
isLast: Boolean(lastMessageId && item.id === lastMessageId),
|
|
6652
|
-
retrying: (isRetryingMessage == null ? void 0 : isRetryingMessage(item.id)) ?? false,
|
|
6653
|
-
onRetry: onRetryMessage ? () => onRetryMessage(item.id) : void 0
|
|
6654
|
-
}
|
|
6655
|
-
),
|
|
6656
|
-
ListHeaderComponent: /* @__PURE__ */ jsxs29(View36, { children: [
|
|
6657
|
-
showTypingIndicator ? /* @__PURE__ */ jsx49(View36, { style: { marginTop: theme.spacing.sm, alignSelf: "flex-start", paddingHorizontal: theme.spacing.lg }, children: /* @__PURE__ */ jsx49(TypingIndicator, {}) }) : null,
|
|
6658
|
-
bottomInset > 0 ? /* @__PURE__ */ jsx49(View36, { style: { height: bottomInset } }) : null
|
|
6659
|
-
] })
|
|
7072
|
+
onContentSizeChange: handleContentSizeChange,
|
|
7073
|
+
contentContainerStyle,
|
|
7074
|
+
ItemSeparatorComponent: renderSeparator,
|
|
7075
|
+
renderItem,
|
|
7076
|
+
ListHeaderComponent: listHeader
|
|
6660
7077
|
}
|
|
6661
7078
|
);
|
|
6662
7079
|
}
|
|
@@ -6683,22 +7100,22 @@ function ChatPage({
|
|
|
6683
7100
|
}) {
|
|
6684
7101
|
const theme = useTheme();
|
|
6685
7102
|
const insets = useSafeAreaInsets4();
|
|
6686
|
-
const [composerHeight, setComposerHeight] =
|
|
6687
|
-
const [composerTopHeight, setComposerTopHeight] =
|
|
6688
|
-
const footerBottomPadding =
|
|
7103
|
+
const [composerHeight, setComposerHeight] = React39.useState(0);
|
|
7104
|
+
const [composerTopHeight, setComposerTopHeight] = React39.useState(0);
|
|
7105
|
+
const footerBottomPadding = Platform10.OS === "ios" ? insets.bottom - 24 : insets.bottom + 10;
|
|
6689
7106
|
const totalComposerHeight = composerHeight + composerTopHeight;
|
|
6690
7107
|
const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
|
|
6691
7108
|
const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
|
|
6692
|
-
const resolvedOverlay =
|
|
7109
|
+
const resolvedOverlay = React39.useMemo(() => {
|
|
6693
7110
|
var _a;
|
|
6694
7111
|
if (!overlay) return null;
|
|
6695
|
-
if (!
|
|
7112
|
+
if (!React39.isValidElement(overlay)) return overlay;
|
|
6696
7113
|
const prevStyle = (_a = overlay.props) == null ? void 0 : _a.style;
|
|
6697
|
-
return
|
|
7114
|
+
return React39.cloneElement(overlay, {
|
|
6698
7115
|
style: [prevStyle, { bottom: overlayBottom }]
|
|
6699
7116
|
});
|
|
6700
7117
|
}, [overlay, overlayBottom]);
|
|
6701
|
-
|
|
7118
|
+
React39.useEffect(() => {
|
|
6702
7119
|
if (composerTop) return;
|
|
6703
7120
|
setComposerTopHeight(0);
|
|
6704
7121
|
}, [composerTop]);
|
|
@@ -6765,15 +7182,15 @@ function ChatPage({
|
|
|
6765
7182
|
}
|
|
6766
7183
|
|
|
6767
7184
|
// src/components/chat/ScrollToBottomButton.tsx
|
|
6768
|
-
import * as
|
|
7185
|
+
import * as React40 from "react";
|
|
6769
7186
|
import { Pressable as Pressable14, View as View38 } from "react-native";
|
|
6770
7187
|
import Animated11, { Easing as Easing2, useAnimatedStyle as useAnimatedStyle2, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
|
|
6771
7188
|
import { jsx as jsx51 } from "react/jsx-runtime";
|
|
6772
7189
|
function ScrollToBottomButton({ visible, onPress, children, style }) {
|
|
6773
7190
|
const theme = useTheme();
|
|
6774
7191
|
const progress = useSharedValue2(visible ? 1 : 0);
|
|
6775
|
-
const [pressed, setPressed] =
|
|
6776
|
-
|
|
7192
|
+
const [pressed, setPressed] = React40.useState(false);
|
|
7193
|
+
React40.useEffect(() => {
|
|
6777
7194
|
progress.value = withTiming2(visible ? 1 : 0, { duration: 200, easing: Easing2.out(Easing2.ease) });
|
|
6778
7195
|
}, [progress, visible]);
|
|
6779
7196
|
const animStyle = useAnimatedStyle2(() => ({
|
|
@@ -6908,16 +7325,16 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
|
|
|
6908
7325
|
}
|
|
6909
7326
|
|
|
6910
7327
|
// src/components/chat/ChatQueue.tsx
|
|
6911
|
-
import * as
|
|
7328
|
+
import * as React41 from "react";
|
|
6912
7329
|
import { ActivityIndicator as ActivityIndicator8, Pressable as Pressable15, View as View41 } from "react-native";
|
|
6913
7330
|
import { jsx as jsx54, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
6914
7331
|
function ChatQueue({ items, onRemove }) {
|
|
6915
7332
|
const theme = useTheme();
|
|
6916
|
-
const [expanded, setExpanded] =
|
|
6917
|
-
const [canExpand, setCanExpand] =
|
|
6918
|
-
const [collapsedText, setCollapsedText] =
|
|
6919
|
-
const [removing, setRemoving] =
|
|
6920
|
-
const buildCollapsedText =
|
|
7333
|
+
const [expanded, setExpanded] = React41.useState({});
|
|
7334
|
+
const [canExpand, setCanExpand] = React41.useState({});
|
|
7335
|
+
const [collapsedText, setCollapsedText] = React41.useState({});
|
|
7336
|
+
const [removing, setRemoving] = React41.useState({});
|
|
7337
|
+
const buildCollapsedText = React41.useCallback((lines) => {
|
|
6921
7338
|
var _a, _b;
|
|
6922
7339
|
const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
|
|
6923
7340
|
const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
|
|
@@ -6933,7 +7350,7 @@ function ChatQueue({ items, onRemove }) {
|
|
|
6933
7350
|
return `${line1}
|
|
6934
7351
|
${trimmedLine2}\u2026 `;
|
|
6935
7352
|
}, []);
|
|
6936
|
-
|
|
7353
|
+
React41.useEffect(() => {
|
|
6937
7354
|
if (items.length === 0) return;
|
|
6938
7355
|
const ids = new Set(items.map((item) => item.id));
|
|
6939
7356
|
setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
@@ -7053,8 +7470,137 @@ ${trimmedLine2}\u2026 `;
|
|
|
7053
7470
|
);
|
|
7054
7471
|
}
|
|
7055
7472
|
|
|
7056
|
-
// src/
|
|
7473
|
+
// src/components/chat/AgentProgressCard.tsx
|
|
7474
|
+
import { View as View42 } from "react-native";
|
|
7057
7475
|
import { jsx as jsx55, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
7476
|
+
function titleForPhase(phase) {
|
|
7477
|
+
if (phase === "planning") return "Planning";
|
|
7478
|
+
if (phase === "reasoning") return "Reasoning";
|
|
7479
|
+
if (phase === "analyzing") return "Analyzing";
|
|
7480
|
+
if (phase === "editing") return "Editing";
|
|
7481
|
+
if (phase === "executing") return "Executing";
|
|
7482
|
+
if (phase === "validating") return "Validating";
|
|
7483
|
+
if (phase === "finalizing") return "Finalizing";
|
|
7484
|
+
if (phase === "working") return "Working";
|
|
7485
|
+
return "Working";
|
|
7486
|
+
}
|
|
7487
|
+
function titleForStatus(status) {
|
|
7488
|
+
if (status === "succeeded") return "Completed";
|
|
7489
|
+
if (status === "failed") return "Failed";
|
|
7490
|
+
if (status === "cancelled") return "Cancelled";
|
|
7491
|
+
return "In Progress";
|
|
7492
|
+
}
|
|
7493
|
+
function AgentProgressCard({ progress }) {
|
|
7494
|
+
const theme = useTheme();
|
|
7495
|
+
const statusLabel = titleForStatus(progress.status);
|
|
7496
|
+
const phaseLabel = titleForPhase(progress.phase);
|
|
7497
|
+
const subtitle = progress.latestMessage || `Agent is ${phaseLabel.toLowerCase()}...`;
|
|
7498
|
+
const todo = progress.todoSummary;
|
|
7499
|
+
return /* @__PURE__ */ jsxs33(
|
|
7500
|
+
View42,
|
|
7501
|
+
{
|
|
7502
|
+
style: {
|
|
7503
|
+
borderWidth: 1,
|
|
7504
|
+
borderColor: theme.colors.border,
|
|
7505
|
+
borderRadius: theme.radii.lg,
|
|
7506
|
+
marginHorizontal: theme.spacing.md,
|
|
7507
|
+
padding: theme.spacing.md,
|
|
7508
|
+
backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
|
|
7509
|
+
},
|
|
7510
|
+
children: [
|
|
7511
|
+
/* @__PURE__ */ jsxs33(View42, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }, children: [
|
|
7512
|
+
/* @__PURE__ */ jsx55(Text, { variant: "caption", children: statusLabel }),
|
|
7513
|
+
/* @__PURE__ */ jsx55(Text, { variant: "captionMuted", children: phaseLabel })
|
|
7514
|
+
] }),
|
|
7515
|
+
/* @__PURE__ */ jsx55(Text, { variant: "bodyMuted", children: subtitle }),
|
|
7516
|
+
progress.changedFilesCount > 0 ? /* @__PURE__ */ jsxs33(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
|
|
7517
|
+
"Updated files: ",
|
|
7518
|
+
progress.changedFilesCount
|
|
7519
|
+
] }) : null,
|
|
7520
|
+
progress.recentFiles.length > 0 ? /* @__PURE__ */ jsx55(View42, { style: { marginTop: 6 }, children: progress.recentFiles.map((path) => /* @__PURE__ */ jsxs33(Text, { variant: "captionMuted", numberOfLines: 1, children: [
|
|
7521
|
+
"\u2022 ",
|
|
7522
|
+
path
|
|
7523
|
+
] }, path)) }) : null,
|
|
7524
|
+
todo ? /* @__PURE__ */ jsxs33(Text, { variant: "captionMuted", style: { marginTop: 8 }, children: [
|
|
7525
|
+
"Todos: ",
|
|
7526
|
+
todo.completed,
|
|
7527
|
+
"/",
|
|
7528
|
+
todo.total,
|
|
7529
|
+
" complete",
|
|
7530
|
+
todo.currentTask ? ` \u2022 ${todo.currentTask}` : ""
|
|
7531
|
+
] }) : null
|
|
7532
|
+
]
|
|
7533
|
+
}
|
|
7534
|
+
);
|
|
7535
|
+
}
|
|
7536
|
+
|
|
7537
|
+
// src/components/chat/BundleProgressCard.tsx
|
|
7538
|
+
import { View as View43 } from "react-native";
|
|
7539
|
+
import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
7540
|
+
function titleForStatus2(status) {
|
|
7541
|
+
if (status === "succeeded") return "Completed";
|
|
7542
|
+
if (status === "failed") return "Failed";
|
|
7543
|
+
return "In Progress";
|
|
7544
|
+
}
|
|
7545
|
+
function BundleProgressCard({ progress }) {
|
|
7546
|
+
const theme = useTheme();
|
|
7547
|
+
const statusLabel = titleForStatus2(progress.status);
|
|
7548
|
+
const percent = Math.round(Math.max(0, Math.min(1, progress.progressValue)) * 100);
|
|
7549
|
+
const fillColor = progress.status === "failed" ? theme.colors.danger : progress.status === "succeeded" ? theme.colors.success : theme.colors.warning;
|
|
7550
|
+
const detail = progress.errorMessage || progress.phaseLabel;
|
|
7551
|
+
return /* @__PURE__ */ jsxs34(
|
|
7552
|
+
View43,
|
|
7553
|
+
{
|
|
7554
|
+
accessible: true,
|
|
7555
|
+
accessibilityRole: "progressbar",
|
|
7556
|
+
accessibilityLabel: `Bundle progress ${statusLabel}`,
|
|
7557
|
+
accessibilityValue: { min: 0, max: 100, now: percent, text: `${percent}%` },
|
|
7558
|
+
style: {
|
|
7559
|
+
borderWidth: 1,
|
|
7560
|
+
borderColor: theme.colors.border,
|
|
7561
|
+
borderRadius: theme.radii.lg,
|
|
7562
|
+
marginHorizontal: theme.spacing.md,
|
|
7563
|
+
padding: theme.spacing.md,
|
|
7564
|
+
backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.84 : 0.94)
|
|
7565
|
+
},
|
|
7566
|
+
children: [
|
|
7567
|
+
/* @__PURE__ */ jsxs34(View43, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }, children: [
|
|
7568
|
+
/* @__PURE__ */ jsx56(Text, { variant: "caption", children: statusLabel }),
|
|
7569
|
+
/* @__PURE__ */ jsxs34(Text, { variant: "captionMuted", children: [
|
|
7570
|
+
percent,
|
|
7571
|
+
"%"
|
|
7572
|
+
] })
|
|
7573
|
+
] }),
|
|
7574
|
+
/* @__PURE__ */ jsx56(
|
|
7575
|
+
View43,
|
|
7576
|
+
{
|
|
7577
|
+
style: {
|
|
7578
|
+
width: "100%",
|
|
7579
|
+
height: 8,
|
|
7580
|
+
borderRadius: 999,
|
|
7581
|
+
backgroundColor: withAlpha(theme.colors.border, theme.scheme === "dark" ? 0.5 : 0.6),
|
|
7582
|
+
overflow: "hidden"
|
|
7583
|
+
},
|
|
7584
|
+
children: /* @__PURE__ */ jsx56(
|
|
7585
|
+
View43,
|
|
7586
|
+
{
|
|
7587
|
+
style: {
|
|
7588
|
+
width: `${percent}%`,
|
|
7589
|
+
height: "100%",
|
|
7590
|
+
backgroundColor: fillColor
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
)
|
|
7594
|
+
}
|
|
7595
|
+
),
|
|
7596
|
+
/* @__PURE__ */ jsx56(Text, { variant: "captionMuted", numberOfLines: 1, style: { marginTop: 8, minHeight: 16 }, children: detail })
|
|
7597
|
+
]
|
|
7598
|
+
}
|
|
7599
|
+
);
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7602
|
+
// src/studio/ui/ChatPanel.tsx
|
|
7603
|
+
import { jsx as jsx57, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
7058
7604
|
function ChatPanel({
|
|
7059
7605
|
title = "Chat",
|
|
7060
7606
|
messages,
|
|
@@ -7075,11 +7621,13 @@ function ChatPanel({
|
|
|
7075
7621
|
onRetryMessage,
|
|
7076
7622
|
isRetryingMessage,
|
|
7077
7623
|
queueItems = [],
|
|
7078
|
-
onRemoveQueueItem
|
|
7624
|
+
onRemoveQueueItem,
|
|
7625
|
+
progress = null
|
|
7079
7626
|
}) {
|
|
7080
|
-
const
|
|
7081
|
-
const
|
|
7082
|
-
const
|
|
7627
|
+
const theme = useTheme();
|
|
7628
|
+
const listRef = React42.useRef(null);
|
|
7629
|
+
const [nearBottom, setNearBottom] = React42.useState(true);
|
|
7630
|
+
const handleSend = React42.useCallback(
|
|
7083
7631
|
async (text, composerAttachments) => {
|
|
7084
7632
|
const all = composerAttachments ?? attachments;
|
|
7085
7633
|
await onSend(text, all.length > 0 ? all : void 0);
|
|
@@ -7093,25 +7641,25 @@ function ChatPanel({
|
|
|
7093
7641
|
},
|
|
7094
7642
|
[attachments, nearBottom, onClearAttachments, onSend]
|
|
7095
7643
|
);
|
|
7096
|
-
const handleScrollToBottom =
|
|
7644
|
+
const handleScrollToBottom = React42.useCallback(() => {
|
|
7097
7645
|
var _a;
|
|
7098
7646
|
(_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
|
|
7099
7647
|
}, []);
|
|
7100
|
-
const header = /* @__PURE__ */
|
|
7648
|
+
const header = /* @__PURE__ */ jsx57(
|
|
7101
7649
|
ChatHeader,
|
|
7102
7650
|
{
|
|
7103
|
-
left: /* @__PURE__ */
|
|
7104
|
-
/* @__PURE__ */
|
|
7105
|
-
onNavigateHome ? /* @__PURE__ */
|
|
7651
|
+
left: /* @__PURE__ */ jsxs35(View44, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
7652
|
+
/* @__PURE__ */ jsx57(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx57(IconBack, { size: 20, colorToken: "floatingContent" }) }),
|
|
7653
|
+
onNavigateHome ? /* @__PURE__ */ jsx57(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx57(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
|
|
7106
7654
|
] }),
|
|
7107
|
-
right: /* @__PURE__ */
|
|
7108
|
-
onStartDraw ? /* @__PURE__ */
|
|
7109
|
-
/* @__PURE__ */
|
|
7655
|
+
right: /* @__PURE__ */ jsxs35(View44, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
7656
|
+
onStartDraw ? /* @__PURE__ */ jsx57(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx57(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
|
|
7657
|
+
/* @__PURE__ */ jsx57(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx57(IconClose, { size: 20, colorToken: "floatingContent" }) })
|
|
7110
7658
|
] }),
|
|
7111
7659
|
center: null
|
|
7112
7660
|
}
|
|
7113
7661
|
);
|
|
7114
|
-
const topBanner = shouldForkOnEdit ? /* @__PURE__ */
|
|
7662
|
+
const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx57(
|
|
7115
7663
|
ForkNoticeBanner,
|
|
7116
7664
|
{
|
|
7117
7665
|
isOwner: !shouldForkOnEdit,
|
|
@@ -7120,18 +7668,22 @@ function ChatPanel({
|
|
|
7120
7668
|
) : null;
|
|
7121
7669
|
const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
|
|
7122
7670
|
if (showMessagesLoading) {
|
|
7123
|
-
return /* @__PURE__ */
|
|
7124
|
-
/* @__PURE__ */
|
|
7125
|
-
topBanner ? /* @__PURE__ */
|
|
7126
|
-
/* @__PURE__ */
|
|
7127
|
-
/* @__PURE__ */
|
|
7128
|
-
/* @__PURE__ */
|
|
7129
|
-
/* @__PURE__ */
|
|
7671
|
+
return /* @__PURE__ */ jsxs35(View44, { style: { flex: 1 }, children: [
|
|
7672
|
+
/* @__PURE__ */ jsx57(View44, { children: header }),
|
|
7673
|
+
topBanner ? /* @__PURE__ */ jsx57(View44, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
|
|
7674
|
+
/* @__PURE__ */ jsxs35(View44, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
|
|
7675
|
+
/* @__PURE__ */ jsx57(ActivityIndicator9, {}),
|
|
7676
|
+
/* @__PURE__ */ jsx57(View44, { style: { height: 12 } }),
|
|
7677
|
+
/* @__PURE__ */ jsx57(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
|
|
7130
7678
|
] })
|
|
7131
7679
|
] });
|
|
7132
7680
|
}
|
|
7133
|
-
const
|
|
7134
|
-
|
|
7681
|
+
const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
|
|
7682
|
+
const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ jsxs35(View44, { style: { gap: theme.spacing.sm }, children: [
|
|
7683
|
+
progress ? bundleProgress ? /* @__PURE__ */ jsx57(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ jsx57(AgentProgressCard, { progress }) : null,
|
|
7684
|
+
queueItems.length > 0 ? /* @__PURE__ */ jsx57(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
|
|
7685
|
+
] }) : null;
|
|
7686
|
+
return /* @__PURE__ */ jsx57(
|
|
7135
7687
|
ChatPage,
|
|
7136
7688
|
{
|
|
7137
7689
|
header,
|
|
@@ -7144,13 +7696,13 @@ function ChatPanel({
|
|
|
7144
7696
|
composerHorizontalPadding: 0,
|
|
7145
7697
|
listRef,
|
|
7146
7698
|
onNearBottomChange: setNearBottom,
|
|
7147
|
-
overlay: /* @__PURE__ */
|
|
7699
|
+
overlay: /* @__PURE__ */ jsx57(
|
|
7148
7700
|
ScrollToBottomButton,
|
|
7149
7701
|
{
|
|
7150
7702
|
visible: !nearBottom,
|
|
7151
7703
|
onPress: handleScrollToBottom,
|
|
7152
7704
|
style: { bottom: 80 },
|
|
7153
|
-
children: /* @__PURE__ */
|
|
7705
|
+
children: /* @__PURE__ */ jsx57(IconArrowDown, { size: 20, colorToken: "floatingContent" })
|
|
7154
7706
|
}
|
|
7155
7707
|
),
|
|
7156
7708
|
composer: {
|
|
@@ -7170,16 +7722,16 @@ function ChatPanel({
|
|
|
7170
7722
|
}
|
|
7171
7723
|
|
|
7172
7724
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
7173
|
-
import * as
|
|
7174
|
-
import { Pressable as Pressable17, View as
|
|
7725
|
+
import * as React43 from "react";
|
|
7726
|
+
import { Pressable as Pressable17, View as View46 } from "react-native";
|
|
7175
7727
|
|
|
7176
7728
|
// src/components/primitives/Modal.tsx
|
|
7177
7729
|
import {
|
|
7178
7730
|
Modal as RNModal,
|
|
7179
7731
|
Pressable as Pressable16,
|
|
7180
|
-
View as
|
|
7732
|
+
View as View45
|
|
7181
7733
|
} from "react-native";
|
|
7182
|
-
import { jsx as
|
|
7734
|
+
import { jsx as jsx58, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
7183
7735
|
function Modal2({
|
|
7184
7736
|
visible,
|
|
7185
7737
|
onRequestClose,
|
|
@@ -7188,15 +7740,15 @@ function Modal2({
|
|
|
7188
7740
|
contentStyle
|
|
7189
7741
|
}) {
|
|
7190
7742
|
const theme = useTheme();
|
|
7191
|
-
return /* @__PURE__ */
|
|
7743
|
+
return /* @__PURE__ */ jsx58(
|
|
7192
7744
|
RNModal,
|
|
7193
7745
|
{
|
|
7194
7746
|
visible,
|
|
7195
7747
|
transparent: true,
|
|
7196
7748
|
animationType: "fade",
|
|
7197
7749
|
onRequestClose,
|
|
7198
|
-
children: /* @__PURE__ */
|
|
7199
|
-
/* @__PURE__ */
|
|
7750
|
+
children: /* @__PURE__ */ jsxs36(View45, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
|
|
7751
|
+
/* @__PURE__ */ jsx58(
|
|
7200
7752
|
Pressable16,
|
|
7201
7753
|
{
|
|
7202
7754
|
accessibilityRole: "button",
|
|
@@ -7204,14 +7756,14 @@ function Modal2({
|
|
|
7204
7756
|
style: { position: "absolute", inset: 0 }
|
|
7205
7757
|
}
|
|
7206
7758
|
),
|
|
7207
|
-
/* @__PURE__ */
|
|
7759
|
+
/* @__PURE__ */ jsx58(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
|
|
7208
7760
|
] })
|
|
7209
7761
|
}
|
|
7210
7762
|
);
|
|
7211
7763
|
}
|
|
7212
7764
|
|
|
7213
7765
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
7214
|
-
import { jsx as
|
|
7766
|
+
import { jsx as jsx59, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
7215
7767
|
function ConfirmMergeRequestDialog({
|
|
7216
7768
|
visible,
|
|
7217
7769
|
onOpenChange,
|
|
@@ -7222,14 +7774,14 @@ function ConfirmMergeRequestDialog({
|
|
|
7222
7774
|
onTestFirst
|
|
7223
7775
|
}) {
|
|
7224
7776
|
const theme = useTheme();
|
|
7225
|
-
const close =
|
|
7777
|
+
const close = React43.useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
7226
7778
|
const canConfirm = Boolean(mergeRequest) && !approveDisabled;
|
|
7227
|
-
const handleConfirm =
|
|
7779
|
+
const handleConfirm = React43.useCallback(() => {
|
|
7228
7780
|
if (!mergeRequest) return;
|
|
7229
7781
|
onOpenChange(false);
|
|
7230
7782
|
void onConfirm();
|
|
7231
7783
|
}, [mergeRequest, onConfirm, onOpenChange]);
|
|
7232
|
-
const handleTestFirst =
|
|
7784
|
+
const handleTestFirst = React43.useCallback(() => {
|
|
7233
7785
|
if (!mergeRequest) return;
|
|
7234
7786
|
onOpenChange(false);
|
|
7235
7787
|
void onTestFirst(mergeRequest);
|
|
@@ -7241,7 +7793,7 @@ function ConfirmMergeRequestDialog({
|
|
|
7241
7793
|
justifyContent: "center",
|
|
7242
7794
|
alignSelf: "stretch"
|
|
7243
7795
|
};
|
|
7244
|
-
return /* @__PURE__ */
|
|
7796
|
+
return /* @__PURE__ */ jsxs37(
|
|
7245
7797
|
Modal2,
|
|
7246
7798
|
{
|
|
7247
7799
|
visible,
|
|
@@ -7252,7 +7804,7 @@ function ConfirmMergeRequestDialog({
|
|
|
7252
7804
|
backgroundColor: theme.colors.background
|
|
7253
7805
|
},
|
|
7254
7806
|
children: [
|
|
7255
|
-
/* @__PURE__ */
|
|
7807
|
+
/* @__PURE__ */ jsx59(View46, { children: /* @__PURE__ */ jsx59(
|
|
7256
7808
|
Text,
|
|
7257
7809
|
{
|
|
7258
7810
|
style: {
|
|
@@ -7264,9 +7816,9 @@ function ConfirmMergeRequestDialog({
|
|
|
7264
7816
|
children: "Are you sure you want to approve this merge request?"
|
|
7265
7817
|
}
|
|
7266
7818
|
) }),
|
|
7267
|
-
/* @__PURE__ */
|
|
7268
|
-
/* @__PURE__ */
|
|
7269
|
-
|
|
7819
|
+
/* @__PURE__ */ jsxs37(View46, { style: { marginTop: 16 }, children: [
|
|
7820
|
+
/* @__PURE__ */ jsx59(
|
|
7821
|
+
View46,
|
|
7270
7822
|
{
|
|
7271
7823
|
style: [
|
|
7272
7824
|
fullWidthButtonBase,
|
|
@@ -7275,7 +7827,7 @@ function ConfirmMergeRequestDialog({
|
|
|
7275
7827
|
opacity: canConfirm ? 1 : 0.5
|
|
7276
7828
|
}
|
|
7277
7829
|
],
|
|
7278
|
-
children: /* @__PURE__ */
|
|
7830
|
+
children: /* @__PURE__ */ jsx59(
|
|
7279
7831
|
Pressable17,
|
|
7280
7832
|
{
|
|
7281
7833
|
accessibilityRole: "button",
|
|
@@ -7283,14 +7835,14 @@ function ConfirmMergeRequestDialog({
|
|
|
7283
7835
|
disabled: !canConfirm,
|
|
7284
7836
|
onPress: handleConfirm,
|
|
7285
7837
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
7286
|
-
children: /* @__PURE__ */
|
|
7838
|
+
children: /* @__PURE__ */ jsx59(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
|
|
7287
7839
|
}
|
|
7288
7840
|
)
|
|
7289
7841
|
}
|
|
7290
7842
|
),
|
|
7291
|
-
/* @__PURE__ */
|
|
7292
|
-
/* @__PURE__ */
|
|
7293
|
-
|
|
7843
|
+
/* @__PURE__ */ jsx59(View46, { style: { height: 8 } }),
|
|
7844
|
+
/* @__PURE__ */ jsx59(
|
|
7845
|
+
View46,
|
|
7294
7846
|
{
|
|
7295
7847
|
style: [
|
|
7296
7848
|
fullWidthButtonBase,
|
|
@@ -7301,7 +7853,7 @@ function ConfirmMergeRequestDialog({
|
|
|
7301
7853
|
opacity: isBuilding || !mergeRequest ? 0.5 : 1
|
|
7302
7854
|
}
|
|
7303
7855
|
],
|
|
7304
|
-
children: /* @__PURE__ */
|
|
7856
|
+
children: /* @__PURE__ */ jsx59(
|
|
7305
7857
|
Pressable17,
|
|
7306
7858
|
{
|
|
7307
7859
|
accessibilityRole: "button",
|
|
@@ -7309,14 +7861,14 @@ function ConfirmMergeRequestDialog({
|
|
|
7309
7861
|
disabled: isBuilding || !mergeRequest,
|
|
7310
7862
|
onPress: handleTestFirst,
|
|
7311
7863
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
7312
|
-
children: /* @__PURE__ */
|
|
7864
|
+
children: /* @__PURE__ */ jsx59(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
|
|
7313
7865
|
}
|
|
7314
7866
|
)
|
|
7315
7867
|
}
|
|
7316
7868
|
),
|
|
7317
|
-
/* @__PURE__ */
|
|
7318
|
-
/* @__PURE__ */
|
|
7319
|
-
|
|
7869
|
+
/* @__PURE__ */ jsx59(View46, { style: { height: 8 } }),
|
|
7870
|
+
/* @__PURE__ */ jsx59(
|
|
7871
|
+
View46,
|
|
7320
7872
|
{
|
|
7321
7873
|
style: [
|
|
7322
7874
|
fullWidthButtonBase,
|
|
@@ -7326,14 +7878,14 @@ function ConfirmMergeRequestDialog({
|
|
|
7326
7878
|
borderColor: theme.colors.border
|
|
7327
7879
|
}
|
|
7328
7880
|
],
|
|
7329
|
-
children: /* @__PURE__ */
|
|
7881
|
+
children: /* @__PURE__ */ jsx59(
|
|
7330
7882
|
Pressable17,
|
|
7331
7883
|
{
|
|
7332
7884
|
accessibilityRole: "button",
|
|
7333
7885
|
accessibilityLabel: "Cancel",
|
|
7334
7886
|
onPress: close,
|
|
7335
7887
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
7336
|
-
children: /* @__PURE__ */
|
|
7888
|
+
children: /* @__PURE__ */ jsx59(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
|
|
7337
7889
|
}
|
|
7338
7890
|
)
|
|
7339
7891
|
}
|
|
@@ -7345,7 +7897,7 @@ function ConfirmMergeRequestDialog({
|
|
|
7345
7897
|
}
|
|
7346
7898
|
|
|
7347
7899
|
// src/studio/ui/ConfirmMergeFlow.tsx
|
|
7348
|
-
import { jsx as
|
|
7900
|
+
import { jsx as jsx60 } from "react/jsx-runtime";
|
|
7349
7901
|
function ConfirmMergeFlow({
|
|
7350
7902
|
visible,
|
|
7351
7903
|
onOpenChange,
|
|
@@ -7356,7 +7908,7 @@ function ConfirmMergeFlow({
|
|
|
7356
7908
|
onConfirm,
|
|
7357
7909
|
onTestFirst
|
|
7358
7910
|
}) {
|
|
7359
|
-
return /* @__PURE__ */
|
|
7911
|
+
return /* @__PURE__ */ jsx60(
|
|
7360
7912
|
ConfirmMergeRequestDialog,
|
|
7361
7913
|
{
|
|
7362
7914
|
visible,
|
|
@@ -7378,7 +7930,7 @@ function ConfirmMergeFlow({
|
|
|
7378
7930
|
}
|
|
7379
7931
|
|
|
7380
7932
|
// src/studio/hooks/useOptimisticChatMessages.ts
|
|
7381
|
-
import * as
|
|
7933
|
+
import * as React44 from "react";
|
|
7382
7934
|
function makeOptimisticId() {
|
|
7383
7935
|
return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
|
|
7384
7936
|
}
|
|
@@ -7417,11 +7969,11 @@ function useOptimisticChatMessages({
|
|
|
7417
7969
|
chatMessages,
|
|
7418
7970
|
onSendChat
|
|
7419
7971
|
}) {
|
|
7420
|
-
const [optimisticChat, setOptimisticChat] =
|
|
7421
|
-
|
|
7972
|
+
const [optimisticChat, setOptimisticChat] = React44.useState([]);
|
|
7973
|
+
React44.useEffect(() => {
|
|
7422
7974
|
setOptimisticChat([]);
|
|
7423
7975
|
}, [threadId]);
|
|
7424
|
-
const messages =
|
|
7976
|
+
const messages = React44.useMemo(() => {
|
|
7425
7977
|
if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
|
|
7426
7978
|
const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
|
|
7427
7979
|
if (unresolved.length === 0) return chatMessages;
|
|
@@ -7437,7 +7989,7 @@ function useOptimisticChatMessages({
|
|
|
7437
7989
|
merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
7438
7990
|
return merged;
|
|
7439
7991
|
}, [chatMessages, optimisticChat]);
|
|
7440
|
-
|
|
7992
|
+
React44.useEffect(() => {
|
|
7441
7993
|
if (optimisticChat.length === 0) return;
|
|
7442
7994
|
setOptimisticChat((prev) => {
|
|
7443
7995
|
if (prev.length === 0) return prev;
|
|
@@ -7445,7 +7997,7 @@ function useOptimisticChatMessages({
|
|
|
7445
7997
|
return next.length === prev.length ? prev : next;
|
|
7446
7998
|
});
|
|
7447
7999
|
}, [chatMessages, optimisticChat.length]);
|
|
7448
|
-
const onSend =
|
|
8000
|
+
const onSend = React44.useCallback(
|
|
7449
8001
|
async (text, attachments) => {
|
|
7450
8002
|
if (shouldForkOnEdit || disableOptimistic) {
|
|
7451
8003
|
await onSendChat(text, attachments);
|
|
@@ -7473,7 +8025,7 @@ function useOptimisticChatMessages({
|
|
|
7473
8025
|
},
|
|
7474
8026
|
[chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
|
|
7475
8027
|
);
|
|
7476
|
-
const onRetry =
|
|
8028
|
+
const onRetry = React44.useCallback(
|
|
7477
8029
|
async (messageId) => {
|
|
7478
8030
|
if (shouldForkOnEdit || disableOptimistic) return;
|
|
7479
8031
|
const target = optimisticChat.find((m) => m.id === messageId);
|
|
@@ -7497,7 +8049,7 @@ function useOptimisticChatMessages({
|
|
|
7497
8049
|
},
|
|
7498
8050
|
[chatMessages, disableOptimistic, onSendChat, optimisticChat, shouldForkOnEdit]
|
|
7499
8051
|
);
|
|
7500
|
-
const isRetrying =
|
|
8052
|
+
const isRetrying = React44.useCallback(
|
|
7501
8053
|
(messageId) => {
|
|
7502
8054
|
return optimisticChat.some((m) => m.id === messageId && m.retrying);
|
|
7503
8055
|
},
|
|
@@ -7511,7 +8063,7 @@ import {
|
|
|
7511
8063
|
publishComergeStudioUIState,
|
|
7512
8064
|
startStudioControlPolling
|
|
7513
8065
|
} from "@comergehq/studio-control";
|
|
7514
|
-
import { Fragment as Fragment6, jsx as
|
|
8066
|
+
import { Fragment as Fragment6, jsx as jsx61, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
7515
8067
|
function StudioOverlay({
|
|
7516
8068
|
captureTargetRef,
|
|
7517
8069
|
app,
|
|
@@ -7544,19 +8096,20 @@ function StudioOverlay({
|
|
|
7544
8096
|
onSendChat,
|
|
7545
8097
|
chatQueueItems,
|
|
7546
8098
|
onRemoveQueueItem,
|
|
8099
|
+
chatProgress,
|
|
7547
8100
|
onNavigateHome,
|
|
7548
8101
|
showBubble,
|
|
7549
8102
|
studioControlOptions
|
|
7550
8103
|
}) {
|
|
7551
8104
|
const theme = useTheme();
|
|
7552
8105
|
const { width } = useWindowDimensions4();
|
|
7553
|
-
const [sheetOpen, setSheetOpen] =
|
|
7554
|
-
const sheetOpenRef =
|
|
7555
|
-
const [activePage, setActivePage] =
|
|
7556
|
-
const [drawing, setDrawing] =
|
|
7557
|
-
const [chatAttachments, setChatAttachments] =
|
|
7558
|
-
const [commentsAppId, setCommentsAppId] =
|
|
7559
|
-
const [commentsCount, setCommentsCount] =
|
|
8106
|
+
const [sheetOpen, setSheetOpen] = React45.useState(false);
|
|
8107
|
+
const sheetOpenRef = React45.useRef(sheetOpen);
|
|
8108
|
+
const [activePage, setActivePage] = React45.useState("preview");
|
|
8109
|
+
const [drawing, setDrawing] = React45.useState(false);
|
|
8110
|
+
const [chatAttachments, setChatAttachments] = React45.useState([]);
|
|
8111
|
+
const [commentsAppId, setCommentsAppId] = React45.useState(null);
|
|
8112
|
+
const [commentsCount, setCommentsCount] = React45.useState(null);
|
|
7560
8113
|
const threadId = (app == null ? void 0 : app.threadId) ?? null;
|
|
7561
8114
|
const isForking = chatForking || (app == null ? void 0 : app.status) === "forking";
|
|
7562
8115
|
const queueItemsForChat = isForking ? [] : chatQueueItems;
|
|
@@ -7568,25 +8121,25 @@ function StudioOverlay({
|
|
|
7568
8121
|
chatMessages,
|
|
7569
8122
|
onSendChat
|
|
7570
8123
|
});
|
|
7571
|
-
const [confirmMrId, setConfirmMrId] =
|
|
7572
|
-
const confirmMr =
|
|
8124
|
+
const [confirmMrId, setConfirmMrId] = React45.useState(null);
|
|
8125
|
+
const confirmMr = React45.useMemo(
|
|
7573
8126
|
() => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
|
|
7574
8127
|
[confirmMrId, incomingMergeRequests]
|
|
7575
8128
|
);
|
|
7576
|
-
const handleSheetOpenChange =
|
|
8129
|
+
const handleSheetOpenChange = React45.useCallback((open) => {
|
|
7577
8130
|
setSheetOpen(open);
|
|
7578
8131
|
if (!open) Keyboard5.dismiss();
|
|
7579
8132
|
}, []);
|
|
7580
|
-
const closeSheet =
|
|
8133
|
+
const closeSheet = React45.useCallback(() => {
|
|
7581
8134
|
handleSheetOpenChange(false);
|
|
7582
8135
|
}, [handleSheetOpenChange]);
|
|
7583
|
-
const openSheet =
|
|
7584
|
-
const goToChat =
|
|
8136
|
+
const openSheet = React45.useCallback(() => setSheetOpen(true), []);
|
|
8137
|
+
const goToChat = React45.useCallback(() => {
|
|
7585
8138
|
setActivePage("chat");
|
|
7586
8139
|
openSheet();
|
|
7587
8140
|
}, [openSheet]);
|
|
7588
|
-
const backToPreview =
|
|
7589
|
-
if (
|
|
8141
|
+
const backToPreview = React45.useCallback(() => {
|
|
8142
|
+
if (Platform11.OS !== "ios") {
|
|
7590
8143
|
Keyboard5.dismiss();
|
|
7591
8144
|
setActivePage("preview");
|
|
7592
8145
|
return;
|
|
@@ -7603,11 +8156,11 @@ function StudioOverlay({
|
|
|
7603
8156
|
const t = setTimeout(finalize, 350);
|
|
7604
8157
|
Keyboard5.dismiss();
|
|
7605
8158
|
}, []);
|
|
7606
|
-
const startDraw =
|
|
8159
|
+
const startDraw = React45.useCallback(() => {
|
|
7607
8160
|
setDrawing(true);
|
|
7608
8161
|
closeSheet();
|
|
7609
8162
|
}, [closeSheet]);
|
|
7610
|
-
const handleDrawCapture =
|
|
8163
|
+
const handleDrawCapture = React45.useCallback(
|
|
7611
8164
|
(dataUrl) => {
|
|
7612
8165
|
setChatAttachments((prev) => [...prev, dataUrl]);
|
|
7613
8166
|
setDrawing(false);
|
|
@@ -7616,7 +8169,7 @@ function StudioOverlay({
|
|
|
7616
8169
|
},
|
|
7617
8170
|
[openSheet]
|
|
7618
8171
|
);
|
|
7619
|
-
const toggleSheet =
|
|
8172
|
+
const toggleSheet = React45.useCallback(async () => {
|
|
7620
8173
|
if (!sheetOpen) {
|
|
7621
8174
|
const shouldExitTest = Boolean(testingMrId) || isTesting;
|
|
7622
8175
|
if (shouldExitTest) {
|
|
@@ -7628,7 +8181,7 @@ function StudioOverlay({
|
|
|
7628
8181
|
closeSheet();
|
|
7629
8182
|
}
|
|
7630
8183
|
}, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
|
|
7631
|
-
const handleTestMr =
|
|
8184
|
+
const handleTestMr = React45.useCallback(
|
|
7632
8185
|
async (mr) => {
|
|
7633
8186
|
if (!onTestMr) return;
|
|
7634
8187
|
await onTestMr(mr);
|
|
@@ -7636,10 +8189,10 @@ function StudioOverlay({
|
|
|
7636
8189
|
},
|
|
7637
8190
|
[closeSheet, onTestMr]
|
|
7638
8191
|
);
|
|
7639
|
-
|
|
8192
|
+
React45.useEffect(() => {
|
|
7640
8193
|
sheetOpenRef.current = sheetOpen;
|
|
7641
8194
|
}, [sheetOpen]);
|
|
7642
|
-
|
|
8195
|
+
React45.useEffect(() => {
|
|
7643
8196
|
const poller = startStudioControlPolling((action) => {
|
|
7644
8197
|
if (action === "show" && !sheetOpenRef.current) openSheet();
|
|
7645
8198
|
if (action === "hide" && sheetOpenRef.current) closeSheet();
|
|
@@ -7647,17 +8200,17 @@ function StudioOverlay({
|
|
|
7647
8200
|
}, studioControlOptions);
|
|
7648
8201
|
return () => poller.stop();
|
|
7649
8202
|
}, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
|
|
7650
|
-
|
|
8203
|
+
React45.useEffect(() => {
|
|
7651
8204
|
void publishComergeStudioUIState(sheetOpen, studioControlOptions);
|
|
7652
8205
|
}, [sheetOpen, studioControlOptions]);
|
|
7653
|
-
return /* @__PURE__ */
|
|
7654
|
-
/* @__PURE__ */
|
|
7655
|
-
/* @__PURE__ */
|
|
8206
|
+
return /* @__PURE__ */ jsxs38(Fragment6, { children: [
|
|
8207
|
+
/* @__PURE__ */ jsx61(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
|
|
8208
|
+
/* @__PURE__ */ jsx61(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ jsx61(
|
|
7656
8209
|
StudioSheetPager,
|
|
7657
8210
|
{
|
|
7658
8211
|
activePage,
|
|
7659
8212
|
width,
|
|
7660
|
-
preview: /* @__PURE__ */
|
|
8213
|
+
preview: /* @__PURE__ */ jsx61(
|
|
7661
8214
|
PreviewPanel,
|
|
7662
8215
|
{
|
|
7663
8216
|
app,
|
|
@@ -7686,7 +8239,7 @@ function StudioOverlay({
|
|
|
7686
8239
|
commentCountOverride: commentsCount ?? void 0
|
|
7687
8240
|
}
|
|
7688
8241
|
),
|
|
7689
|
-
chat: /* @__PURE__ */
|
|
8242
|
+
chat: /* @__PURE__ */ jsx61(
|
|
7690
8243
|
ChatPanel,
|
|
7691
8244
|
{
|
|
7692
8245
|
messages: optimistic.messages,
|
|
@@ -7707,12 +8260,13 @@ function StudioOverlay({
|
|
|
7707
8260
|
onRetryMessage: optimistic.onRetry,
|
|
7708
8261
|
isRetryingMessage: optimistic.isRetrying,
|
|
7709
8262
|
queueItems: queueItemsForChat,
|
|
7710
|
-
onRemoveQueueItem
|
|
8263
|
+
onRemoveQueueItem,
|
|
8264
|
+
progress: chatProgress
|
|
7711
8265
|
}
|
|
7712
8266
|
)
|
|
7713
8267
|
}
|
|
7714
8268
|
) }),
|
|
7715
|
-
showBubble && /* @__PURE__ */
|
|
8269
|
+
showBubble && /* @__PURE__ */ jsx61(
|
|
7716
8270
|
Bubble,
|
|
7717
8271
|
{
|
|
7718
8272
|
visible: !sheetOpen && !drawing,
|
|
@@ -7721,10 +8275,10 @@ function StudioOverlay({
|
|
|
7721
8275
|
onPress: toggleSheet,
|
|
7722
8276
|
isLoading: (app == null ? void 0 : app.status) === "editing" || isBaseBundleDownloading,
|
|
7723
8277
|
loadingBorderTone: isBaseBundleDownloading ? "warning" : "default",
|
|
7724
|
-
children: /* @__PURE__ */
|
|
8278
|
+
children: /* @__PURE__ */ jsx61(View47, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx61(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
|
|
7725
8279
|
}
|
|
7726
8280
|
),
|
|
7727
|
-
/* @__PURE__ */
|
|
8281
|
+
/* @__PURE__ */ jsx61(
|
|
7728
8282
|
DrawModeOverlay,
|
|
7729
8283
|
{
|
|
7730
8284
|
visible: drawing,
|
|
@@ -7733,7 +8287,7 @@ function StudioOverlay({
|
|
|
7733
8287
|
onCapture: handleDrawCapture
|
|
7734
8288
|
}
|
|
7735
8289
|
),
|
|
7736
|
-
/* @__PURE__ */
|
|
8290
|
+
/* @__PURE__ */ jsx61(
|
|
7737
8291
|
ConfirmMergeFlow,
|
|
7738
8292
|
{
|
|
7739
8293
|
visible: Boolean(confirmMr),
|
|
@@ -7747,7 +8301,7 @@ function StudioOverlay({
|
|
|
7747
8301
|
onTestFirst: handleTestMr
|
|
7748
8302
|
}
|
|
7749
8303
|
),
|
|
7750
|
-
/* @__PURE__ */
|
|
8304
|
+
/* @__PURE__ */ jsx61(
|
|
7751
8305
|
AppCommentsSheet,
|
|
7752
8306
|
{
|
|
7753
8307
|
appId: commentsAppId,
|
|
@@ -7760,7 +8314,7 @@ function StudioOverlay({
|
|
|
7760
8314
|
}
|
|
7761
8315
|
|
|
7762
8316
|
// src/studio/hooks/useEditQueue.ts
|
|
7763
|
-
import * as
|
|
8317
|
+
import * as React46 from "react";
|
|
7764
8318
|
|
|
7765
8319
|
// src/data/apps/edit-queue/remote.ts
|
|
7766
8320
|
var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
|
|
@@ -7869,17 +8423,17 @@ var editQueueRepository = new EditQueueRepositoryImpl(
|
|
|
7869
8423
|
|
|
7870
8424
|
// src/studio/hooks/useEditQueue.ts
|
|
7871
8425
|
function useEditQueue(appId) {
|
|
7872
|
-
const [items, setItems] =
|
|
7873
|
-
const [loading, setLoading] =
|
|
7874
|
-
const [error, setError] =
|
|
7875
|
-
const activeRequestIdRef =
|
|
8426
|
+
const [items, setItems] = React46.useState([]);
|
|
8427
|
+
const [loading, setLoading] = React46.useState(false);
|
|
8428
|
+
const [error, setError] = React46.useState(null);
|
|
8429
|
+
const activeRequestIdRef = React46.useRef(0);
|
|
7876
8430
|
const foregroundSignal = useForegroundSignal(Boolean(appId));
|
|
7877
|
-
const upsertSorted =
|
|
8431
|
+
const upsertSorted = React46.useCallback((prev, nextItem) => {
|
|
7878
8432
|
const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
|
|
7879
8433
|
next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
7880
8434
|
return next;
|
|
7881
8435
|
}, []);
|
|
7882
|
-
const refetch =
|
|
8436
|
+
const refetch = React46.useCallback(async () => {
|
|
7883
8437
|
if (!appId) {
|
|
7884
8438
|
setItems([]);
|
|
7885
8439
|
return;
|
|
@@ -7899,10 +8453,10 @@ function useEditQueue(appId) {
|
|
|
7899
8453
|
if (activeRequestIdRef.current === requestId) setLoading(false);
|
|
7900
8454
|
}
|
|
7901
8455
|
}, [appId]);
|
|
7902
|
-
|
|
8456
|
+
React46.useEffect(() => {
|
|
7903
8457
|
void refetch();
|
|
7904
8458
|
}, [refetch]);
|
|
7905
|
-
|
|
8459
|
+
React46.useEffect(() => {
|
|
7906
8460
|
if (!appId) return;
|
|
7907
8461
|
const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
|
|
7908
8462
|
onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
|
|
@@ -7911,7 +8465,7 @@ function useEditQueue(appId) {
|
|
|
7911
8465
|
});
|
|
7912
8466
|
return unsubscribe;
|
|
7913
8467
|
}, [appId, upsertSorted, foregroundSignal]);
|
|
7914
|
-
|
|
8468
|
+
React46.useEffect(() => {
|
|
7915
8469
|
if (!appId) return;
|
|
7916
8470
|
if (foregroundSignal <= 0) return;
|
|
7917
8471
|
void refetch();
|
|
@@ -7920,16 +8474,16 @@ function useEditQueue(appId) {
|
|
|
7920
8474
|
}
|
|
7921
8475
|
|
|
7922
8476
|
// src/studio/hooks/useEditQueueActions.ts
|
|
7923
|
-
import * as
|
|
8477
|
+
import * as React47 from "react";
|
|
7924
8478
|
function useEditQueueActions(appId) {
|
|
7925
|
-
const update =
|
|
8479
|
+
const update = React47.useCallback(
|
|
7926
8480
|
async (queueItemId, payload) => {
|
|
7927
8481
|
if (!appId) return;
|
|
7928
8482
|
await editQueueRepository.update(appId, queueItemId, payload);
|
|
7929
8483
|
},
|
|
7930
8484
|
[appId]
|
|
7931
8485
|
);
|
|
7932
|
-
const cancel =
|
|
8486
|
+
const cancel = React47.useCallback(
|
|
7933
8487
|
async (queueItemId) => {
|
|
7934
8488
|
if (!appId) return;
|
|
7935
8489
|
await editQueueRepository.cancel(appId, queueItemId);
|
|
@@ -7939,48 +8493,461 @@ function useEditQueueActions(appId) {
|
|
|
7939
8493
|
return { update, cancel };
|
|
7940
8494
|
}
|
|
7941
8495
|
|
|
8496
|
+
// src/studio/hooks/useAgentRunProgress.ts
|
|
8497
|
+
import * as React48 from "react";
|
|
8498
|
+
|
|
8499
|
+
// src/data/agent-progress/repository.ts
|
|
8500
|
+
function mapRun(row) {
|
|
8501
|
+
return {
|
|
8502
|
+
id: row.id,
|
|
8503
|
+
appId: row.app_id,
|
|
8504
|
+
threadId: row.thread_id,
|
|
8505
|
+
queueItemId: row.queue_item_id,
|
|
8506
|
+
status: row.status,
|
|
8507
|
+
currentPhase: row.current_phase,
|
|
8508
|
+
lastSeq: Number(row.last_seq || 0),
|
|
8509
|
+
summary: row.summary || {},
|
|
8510
|
+
errorCode: row.error_code,
|
|
8511
|
+
errorMessage: row.error_message,
|
|
8512
|
+
startedAt: row.started_at,
|
|
8513
|
+
finishedAt: row.finished_at,
|
|
8514
|
+
createdAt: row.created_at,
|
|
8515
|
+
updatedAt: row.updated_at
|
|
8516
|
+
};
|
|
8517
|
+
}
|
|
8518
|
+
function mapEvent(row) {
|
|
8519
|
+
return {
|
|
8520
|
+
id: row.id,
|
|
8521
|
+
runId: row.run_id,
|
|
8522
|
+
appId: row.app_id,
|
|
8523
|
+
threadId: row.thread_id,
|
|
8524
|
+
queueItemId: row.queue_item_id,
|
|
8525
|
+
seq: Number(row.seq || 0),
|
|
8526
|
+
eventType: row.event_type,
|
|
8527
|
+
phase: row.phase,
|
|
8528
|
+
toolName: row.tool_name,
|
|
8529
|
+
path: row.path,
|
|
8530
|
+
payload: row.payload || {},
|
|
8531
|
+
createdAt: row.created_at
|
|
8532
|
+
};
|
|
8533
|
+
}
|
|
8534
|
+
var AgentProgressRepositoryImpl = class {
|
|
8535
|
+
async getLatestRun(threadId) {
|
|
8536
|
+
if (!threadId) return null;
|
|
8537
|
+
const supabase = getSupabaseClient();
|
|
8538
|
+
const { data, error } = await supabase.from("agent_run").select("*").eq("thread_id", threadId).order("started_at", { ascending: false }).limit(1).maybeSingle();
|
|
8539
|
+
if (error) throw new Error(error.message || "Failed to fetch latest agent run");
|
|
8540
|
+
if (!data) return null;
|
|
8541
|
+
return mapRun(data);
|
|
8542
|
+
}
|
|
8543
|
+
async listEvents(runId, afterSeq) {
|
|
8544
|
+
if (!runId) return [];
|
|
8545
|
+
const supabase = getSupabaseClient();
|
|
8546
|
+
let query = supabase.from("agent_run_event").select("*").eq("run_id", runId).order("seq", { ascending: true });
|
|
8547
|
+
if (typeof afterSeq === "number" && Number.isFinite(afterSeq) && afterSeq > 0) {
|
|
8548
|
+
query = query.gt("seq", afterSeq);
|
|
8549
|
+
}
|
|
8550
|
+
const { data, error } = await query;
|
|
8551
|
+
if (error) throw new Error(error.message || "Failed to fetch agent run events");
|
|
8552
|
+
const rows = Array.isArray(data) ? data : [];
|
|
8553
|
+
return rows.map(mapEvent);
|
|
8554
|
+
}
|
|
8555
|
+
subscribeThreadRuns(threadId, handlers) {
|
|
8556
|
+
return subscribeManagedChannel(`agent-progress:runs:thread:${threadId}`, (channel) => {
|
|
8557
|
+
channel.on(
|
|
8558
|
+
"postgres_changes",
|
|
8559
|
+
{ event: "INSERT", schema: "public", table: "agent_run", filter: `thread_id=eq.${threadId}` },
|
|
8560
|
+
(payload) => {
|
|
8561
|
+
var _a;
|
|
8562
|
+
const row = payload.new;
|
|
8563
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapRun(row));
|
|
8564
|
+
}
|
|
8565
|
+
).on(
|
|
8566
|
+
"postgres_changes",
|
|
8567
|
+
{ event: "UPDATE", schema: "public", table: "agent_run", filter: `thread_id=eq.${threadId}` },
|
|
8568
|
+
(payload) => {
|
|
8569
|
+
var _a;
|
|
8570
|
+
const row = payload.new;
|
|
8571
|
+
(_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapRun(row));
|
|
8572
|
+
}
|
|
8573
|
+
);
|
|
8574
|
+
});
|
|
8575
|
+
}
|
|
8576
|
+
subscribeRunEvents(runId, handlers) {
|
|
8577
|
+
return subscribeManagedChannel(`agent-progress:events:run:${runId}`, (channel) => {
|
|
8578
|
+
channel.on(
|
|
8579
|
+
"postgres_changes",
|
|
8580
|
+
{ event: "INSERT", schema: "public", table: "agent_run_event", filter: `run_id=eq.${runId}` },
|
|
8581
|
+
(payload) => {
|
|
8582
|
+
var _a;
|
|
8583
|
+
const row = payload.new;
|
|
8584
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, mapEvent(row));
|
|
8585
|
+
}
|
|
8586
|
+
).on(
|
|
8587
|
+
"postgres_changes",
|
|
8588
|
+
{ event: "UPDATE", schema: "public", table: "agent_run_event", filter: `run_id=eq.${runId}` },
|
|
8589
|
+
(payload) => {
|
|
8590
|
+
var _a;
|
|
8591
|
+
const row = payload.new;
|
|
8592
|
+
(_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, mapEvent(row));
|
|
8593
|
+
}
|
|
8594
|
+
);
|
|
8595
|
+
});
|
|
8596
|
+
}
|
|
8597
|
+
};
|
|
8598
|
+
var agentProgressRepository = new AgentProgressRepositoryImpl();
|
|
8599
|
+
|
|
8600
|
+
// src/studio/hooks/useAgentRunProgress.ts
|
|
8601
|
+
function upsertBySeq(prev, next) {
|
|
8602
|
+
const map = /* @__PURE__ */ new Map();
|
|
8603
|
+
for (const item of prev) map.set(item.seq, item);
|
|
8604
|
+
map.set(next.seq, next);
|
|
8605
|
+
return Array.from(map.values()).sort((a, b) => a.seq - b.seq);
|
|
8606
|
+
}
|
|
8607
|
+
function mergeMany(prev, incoming) {
|
|
8608
|
+
if (incoming.length === 0) return prev;
|
|
8609
|
+
const map = /* @__PURE__ */ new Map();
|
|
8610
|
+
for (const item of prev) map.set(item.seq, item);
|
|
8611
|
+
for (const item of incoming) map.set(item.seq, item);
|
|
8612
|
+
return Array.from(map.values()).sort((a, b) => a.seq - b.seq);
|
|
8613
|
+
}
|
|
8614
|
+
function toMs(v) {
|
|
8615
|
+
if (!v) return 0;
|
|
8616
|
+
const n = Date.parse(v);
|
|
8617
|
+
return Number.isFinite(n) ? n : 0;
|
|
8618
|
+
}
|
|
8619
|
+
function shouldSwitchRun(current, candidate) {
|
|
8620
|
+
if (!current) return true;
|
|
8621
|
+
if (candidate.id === current.id) return true;
|
|
8622
|
+
return toMs(candidate.startedAt) >= toMs(current.startedAt);
|
|
8623
|
+
}
|
|
8624
|
+
function toInt(value) {
|
|
8625
|
+
const n = Number(value);
|
|
8626
|
+
return Number.isFinite(n) ? n : 0;
|
|
8627
|
+
}
|
|
8628
|
+
function toBundleStage(value) {
|
|
8629
|
+
if (value === "queued") return "queued";
|
|
8630
|
+
if (value === "building") return "building";
|
|
8631
|
+
if (value === "fixing") return "fixing";
|
|
8632
|
+
if (value === "retrying") return "retrying";
|
|
8633
|
+
if (value === "finalizing") return "finalizing";
|
|
8634
|
+
if (value === "ready") return "ready";
|
|
8635
|
+
if (value === "failed") return "failed";
|
|
8636
|
+
return null;
|
|
8637
|
+
}
|
|
8638
|
+
function toBundlePlatform(value) {
|
|
8639
|
+
if (value === "ios" || value === "android") return value;
|
|
8640
|
+
return "both";
|
|
8641
|
+
}
|
|
8642
|
+
function clamp01(value) {
|
|
8643
|
+
if (value <= 0) return 0;
|
|
8644
|
+
if (value >= 1) return 1;
|
|
8645
|
+
return value;
|
|
8646
|
+
}
|
|
8647
|
+
function defaultBundleLabel(stage) {
|
|
8648
|
+
if (stage === "queued") return "Queued for build";
|
|
8649
|
+
if (stage === "building") return "Building bundle";
|
|
8650
|
+
if (stage === "fixing") return "Applying auto-fix";
|
|
8651
|
+
if (stage === "retrying") return "Retrying bundle";
|
|
8652
|
+
if (stage === "finalizing") return "Finalizing artifacts";
|
|
8653
|
+
if (stage === "ready") return "Bundle ready";
|
|
8654
|
+
if (stage === "failed") return "Bundle failed";
|
|
8655
|
+
return "Building bundle";
|
|
8656
|
+
}
|
|
8657
|
+
function fallbackBundleProgress(stage, startedAtMs, nowMs) {
|
|
8658
|
+
if (stage === "ready") return 1;
|
|
8659
|
+
if (stage === "failed") return 0.96;
|
|
8660
|
+
const elapsed = Math.max(0, nowMs - startedAtMs);
|
|
8661
|
+
const expectedMs = 6e4;
|
|
8662
|
+
if (elapsed <= expectedMs) {
|
|
8663
|
+
const t = clamp01(elapsed / expectedMs);
|
|
8664
|
+
return 0.05 + 0.85 * (1 - Math.pow(1 - t, 2));
|
|
8665
|
+
}
|
|
8666
|
+
const over = elapsed - expectedMs;
|
|
8667
|
+
return Math.min(0.9 + 0.07 * (1 - Math.exp(-over / 25e3)), 0.97);
|
|
8668
|
+
}
|
|
8669
|
+
function deriveView(run, events, nowMs) {
|
|
8670
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
8671
|
+
const files = [];
|
|
8672
|
+
const fileSeen = /* @__PURE__ */ new Set();
|
|
8673
|
+
let todoSummary = null;
|
|
8674
|
+
let latestMessage = null;
|
|
8675
|
+
let phase = (run == null ? void 0 : run.currentPhase) ?? null;
|
|
8676
|
+
let bundleStage = null;
|
|
8677
|
+
let bundleLabel = null;
|
|
8678
|
+
let bundleError = null;
|
|
8679
|
+
let bundleProgressHint = null;
|
|
8680
|
+
let bundlePlatform = "both";
|
|
8681
|
+
let bundleStartedAtMs = null;
|
|
8682
|
+
let lastBundleSig = null;
|
|
8683
|
+
for (const ev of events) {
|
|
8684
|
+
if (ev.eventType === "phase_changed") {
|
|
8685
|
+
if (typeof ((_a = ev.payload) == null ? void 0 : _a.message) === "string") latestMessage = ev.payload.message;
|
|
8686
|
+
if (ev.phase) phase = ev.phase;
|
|
8687
|
+
}
|
|
8688
|
+
if (ev.eventType === "file_changed") {
|
|
8689
|
+
if (ev.path && !fileSeen.has(ev.path)) {
|
|
8690
|
+
fileSeen.add(ev.path);
|
|
8691
|
+
files.push(ev.path);
|
|
8692
|
+
}
|
|
8693
|
+
}
|
|
8694
|
+
if (ev.eventType === "todo_updated") {
|
|
8695
|
+
todoSummary = {
|
|
8696
|
+
total: toInt((_b = ev.payload) == null ? void 0 : _b.total),
|
|
8697
|
+
pending: toInt((_c = ev.payload) == null ? void 0 : _c.pending),
|
|
8698
|
+
inProgress: toInt((_d = ev.payload) == null ? void 0 : _d.inProgress),
|
|
8699
|
+
completed: toInt((_e = ev.payload) == null ? void 0 : _e.completed),
|
|
8700
|
+
currentTask: typeof ((_f = ev.payload) == null ? void 0 : _f.currentTask) === "string" ? ev.payload.currentTask : null
|
|
8701
|
+
};
|
|
8702
|
+
}
|
|
8703
|
+
const stageType = typeof ((_g = ev.payload) == null ? void 0 : _g.stage) === "string" ? ev.payload.stage : null;
|
|
8704
|
+
if (stageType !== "bundle") continue;
|
|
8705
|
+
const nextStage = toBundleStage((_h = ev.payload) == null ? void 0 : _h.bundlePhase);
|
|
8706
|
+
if (!nextStage) continue;
|
|
8707
|
+
const nextPlatform = toBundlePlatform((_i = ev.payload) == null ? void 0 : _i.platform);
|
|
8708
|
+
const message = typeof ((_j = ev.payload) == null ? void 0 : _j.message) === "string" ? ev.payload.message : null;
|
|
8709
|
+
const phaseLabel = message || (typeof ((_k = ev.payload) == null ? void 0 : _k.message) === "string" ? ev.payload.message : null);
|
|
8710
|
+
const hintRaw = Number((_l = ev.payload) == null ? void 0 : _l.progressHint);
|
|
8711
|
+
const progressHint = Number.isFinite(hintRaw) ? clamp01(hintRaw) : null;
|
|
8712
|
+
const errorText = typeof ((_m = ev.payload) == null ? void 0 : _m.error) === "string" ? ev.payload.error : null;
|
|
8713
|
+
const sig = `${ev.seq}:${nextStage}:${nextPlatform}:${progressHint ?? "none"}:${phaseLabel ?? "none"}:${errorText ?? "none"}`;
|
|
8714
|
+
if (sig === lastBundleSig) continue;
|
|
8715
|
+
lastBundleSig = sig;
|
|
8716
|
+
bundleStage = nextStage;
|
|
8717
|
+
bundlePlatform = nextPlatform;
|
|
8718
|
+
if (phaseLabel) bundleLabel = phaseLabel;
|
|
8719
|
+
if (progressHint != null) bundleProgressHint = progressHint;
|
|
8720
|
+
if (errorText) bundleError = errorText;
|
|
8721
|
+
const evMs = toMs(ev.createdAt);
|
|
8722
|
+
if (!bundleStartedAtMs && evMs > 0) bundleStartedAtMs = evMs;
|
|
8723
|
+
}
|
|
8724
|
+
if (!latestMessage) {
|
|
8725
|
+
if (phase === "planning") latestMessage = "Planning changes...";
|
|
8726
|
+
else if (phase === "analyzing") latestMessage = "Analyzing relevant files...";
|
|
8727
|
+
else if (phase === "editing") latestMessage = "Applying code updates...";
|
|
8728
|
+
else if (phase === "validating") latestMessage = "Validating updates...";
|
|
8729
|
+
else if (phase === "finalizing") latestMessage = "Finalizing response...";
|
|
8730
|
+
else if (phase) latestMessage = `Working (${phase})...`;
|
|
8731
|
+
}
|
|
8732
|
+
const runFinished = (run == null ? void 0 : run.status) === "succeeded" || (run == null ? void 0 : run.status) === "failed" || (run == null ? void 0 : run.status) === "cancelled";
|
|
8733
|
+
let bundle = null;
|
|
8734
|
+
if (bundleStage && !runFinished) {
|
|
8735
|
+
const baseTime = bundleStartedAtMs ?? toMs(run == null ? void 0 : run.startedAt) ?? nowMs;
|
|
8736
|
+
const fallback = fallbackBundleProgress(bundleStage, baseTime || nowMs, nowMs);
|
|
8737
|
+
const value = bundleProgressHint != null ? Math.max(fallback, bundleProgressHint) : fallback;
|
|
8738
|
+
const status = bundleStage === "failed" ? "failed" : bundleStage === "ready" ? "succeeded" : "loading";
|
|
8739
|
+
bundle = {
|
|
8740
|
+
active: status === "loading",
|
|
8741
|
+
status,
|
|
8742
|
+
phaseLabel: bundleLabel || defaultBundleLabel(bundleStage),
|
|
8743
|
+
progressValue: clamp01(value),
|
|
8744
|
+
errorMessage: bundleError,
|
|
8745
|
+
platform: bundlePlatform
|
|
8746
|
+
};
|
|
8747
|
+
}
|
|
8748
|
+
return {
|
|
8749
|
+
runId: (run == null ? void 0 : run.id) ?? null,
|
|
8750
|
+
status: (run == null ? void 0 : run.status) ?? null,
|
|
8751
|
+
phase,
|
|
8752
|
+
latestMessage,
|
|
8753
|
+
changedFilesCount: fileSeen.size,
|
|
8754
|
+
recentFiles: files.slice(-5),
|
|
8755
|
+
todoSummary,
|
|
8756
|
+
bundle,
|
|
8757
|
+
events
|
|
8758
|
+
};
|
|
8759
|
+
}
|
|
8760
|
+
function useAgentRunProgress(threadId, opts) {
|
|
8761
|
+
var _a;
|
|
8762
|
+
const enabled = Boolean((opts == null ? void 0 : opts.enabled) ?? true);
|
|
8763
|
+
const [run, setRun] = React48.useState(null);
|
|
8764
|
+
const [events, setEvents] = React48.useState([]);
|
|
8765
|
+
const [loading, setLoading] = React48.useState(false);
|
|
8766
|
+
const [error, setError] = React48.useState(null);
|
|
8767
|
+
const activeRequestIdRef = React48.useRef(0);
|
|
8768
|
+
const lastSeqRef = React48.useRef(0);
|
|
8769
|
+
const runRef = React48.useRef(null);
|
|
8770
|
+
const foregroundSignal = useForegroundSignal(Boolean(threadId) && enabled);
|
|
8771
|
+
const [bundleTick, setBundleTick] = React48.useState(0);
|
|
8772
|
+
React48.useEffect(() => {
|
|
8773
|
+
lastSeqRef.current = 0;
|
|
8774
|
+
runRef.current = null;
|
|
8775
|
+
}, [threadId]);
|
|
8776
|
+
React48.useEffect(() => {
|
|
8777
|
+
runRef.current = run;
|
|
8778
|
+
}, [run]);
|
|
8779
|
+
const refetch = React48.useCallback(async () => {
|
|
8780
|
+
if (!threadId || !enabled) {
|
|
8781
|
+
setRun(null);
|
|
8782
|
+
setEvents([]);
|
|
8783
|
+
setLoading(false);
|
|
8784
|
+
setError(null);
|
|
8785
|
+
return;
|
|
8786
|
+
}
|
|
8787
|
+
const requestId = ++activeRequestIdRef.current;
|
|
8788
|
+
setLoading(true);
|
|
8789
|
+
setError(null);
|
|
8790
|
+
try {
|
|
8791
|
+
const latestRun = await agentProgressRepository.getLatestRun(threadId);
|
|
8792
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
8793
|
+
if (!latestRun) {
|
|
8794
|
+
setRun(null);
|
|
8795
|
+
setEvents([]);
|
|
8796
|
+
lastSeqRef.current = 0;
|
|
8797
|
+
return;
|
|
8798
|
+
}
|
|
8799
|
+
const initialEvents = await agentProgressRepository.listEvents(latestRun.id);
|
|
8800
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
8801
|
+
const sorted = [...initialEvents].sort((a, b) => a.seq - b.seq);
|
|
8802
|
+
setRun(latestRun);
|
|
8803
|
+
setEvents(sorted);
|
|
8804
|
+
lastSeqRef.current = sorted.length > 0 ? sorted[sorted.length - 1].seq : 0;
|
|
8805
|
+
} catch (e) {
|
|
8806
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
8807
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
8808
|
+
setRun(null);
|
|
8809
|
+
setEvents([]);
|
|
8810
|
+
lastSeqRef.current = 0;
|
|
8811
|
+
} finally {
|
|
8812
|
+
if (activeRequestIdRef.current === requestId) setLoading(false);
|
|
8813
|
+
}
|
|
8814
|
+
}, [enabled, threadId]);
|
|
8815
|
+
React48.useEffect(() => {
|
|
8816
|
+
void refetch();
|
|
8817
|
+
}, [refetch]);
|
|
8818
|
+
React48.useEffect(() => {
|
|
8819
|
+
if (!threadId || !enabled) return;
|
|
8820
|
+
if (foregroundSignal <= 0) return;
|
|
8821
|
+
void refetch();
|
|
8822
|
+
}, [enabled, foregroundSignal, refetch, threadId]);
|
|
8823
|
+
React48.useEffect(() => {
|
|
8824
|
+
if (!threadId || !enabled) return;
|
|
8825
|
+
const unsubRuns = agentProgressRepository.subscribeThreadRuns(threadId, {
|
|
8826
|
+
onInsert: (nextRun) => {
|
|
8827
|
+
const currentRun = runRef.current;
|
|
8828
|
+
if (!shouldSwitchRun(currentRun, nextRun)) return;
|
|
8829
|
+
setRun(nextRun);
|
|
8830
|
+
runRef.current = nextRun;
|
|
8831
|
+
if (!currentRun || currentRun.id !== nextRun.id) {
|
|
8832
|
+
lastSeqRef.current = 0;
|
|
8833
|
+
setEvents([]);
|
|
8834
|
+
void agentProgressRepository.listEvents(nextRun.id).then((initial) => {
|
|
8835
|
+
var _a2;
|
|
8836
|
+
if (((_a2 = runRef.current) == null ? void 0 : _a2.id) !== nextRun.id) return;
|
|
8837
|
+
setEvents((prev) => mergeMany(prev, initial));
|
|
8838
|
+
const maxSeq = initial.length > 0 ? initial[initial.length - 1].seq : 0;
|
|
8839
|
+
if (maxSeq > lastSeqRef.current) lastSeqRef.current = maxSeq;
|
|
8840
|
+
}).catch(() => {
|
|
8841
|
+
});
|
|
8842
|
+
}
|
|
8843
|
+
},
|
|
8844
|
+
onUpdate: (nextRun) => {
|
|
8845
|
+
const currentRun = runRef.current;
|
|
8846
|
+
if (!shouldSwitchRun(currentRun, nextRun)) return;
|
|
8847
|
+
setRun(nextRun);
|
|
8848
|
+
runRef.current = nextRun;
|
|
8849
|
+
}
|
|
8850
|
+
});
|
|
8851
|
+
return unsubRuns;
|
|
8852
|
+
}, [enabled, threadId, foregroundSignal]);
|
|
8853
|
+
React48.useEffect(() => {
|
|
8854
|
+
if (!enabled || !(run == null ? void 0 : run.id)) return;
|
|
8855
|
+
const runId = run.id;
|
|
8856
|
+
const processIncoming = (incoming) => {
|
|
8857
|
+
var _a2;
|
|
8858
|
+
if (((_a2 = runRef.current) == null ? void 0 : _a2.id) !== runId) return;
|
|
8859
|
+
setEvents((prev) => upsertBySeq(prev, incoming));
|
|
8860
|
+
if (incoming.seq > lastSeqRef.current) {
|
|
8861
|
+
const expectedNext = lastSeqRef.current + 1;
|
|
8862
|
+
const seenSeq = incoming.seq;
|
|
8863
|
+
const currentLast = lastSeqRef.current;
|
|
8864
|
+
lastSeqRef.current = seenSeq;
|
|
8865
|
+
if (seenSeq > expectedNext) {
|
|
8866
|
+
void agentProgressRepository.listEvents(runId, currentLast).then((missing) => {
|
|
8867
|
+
var _a3;
|
|
8868
|
+
if (((_a3 = runRef.current) == null ? void 0 : _a3.id) !== runId) return;
|
|
8869
|
+
setEvents((prev) => mergeMany(prev, missing));
|
|
8870
|
+
if (missing.length > 0) {
|
|
8871
|
+
const maxSeq = missing[missing.length - 1].seq;
|
|
8872
|
+
if (maxSeq > lastSeqRef.current) lastSeqRef.current = maxSeq;
|
|
8873
|
+
}
|
|
8874
|
+
}).catch(() => {
|
|
8875
|
+
});
|
|
8876
|
+
}
|
|
8877
|
+
}
|
|
8878
|
+
};
|
|
8879
|
+
const unsubscribe = agentProgressRepository.subscribeRunEvents(runId, {
|
|
8880
|
+
onInsert: processIncoming,
|
|
8881
|
+
onUpdate: processIncoming
|
|
8882
|
+
});
|
|
8883
|
+
return unsubscribe;
|
|
8884
|
+
}, [enabled, run == null ? void 0 : run.id, foregroundSignal]);
|
|
8885
|
+
const view = React48.useMemo(() => deriveView(run, events, Date.now()), [bundleTick, events, run]);
|
|
8886
|
+
React48.useEffect(() => {
|
|
8887
|
+
var _a2;
|
|
8888
|
+
if (!((_a2 = view.bundle) == null ? void 0 : _a2.active)) return;
|
|
8889
|
+
const interval = setInterval(() => {
|
|
8890
|
+
setBundleTick((v) => v + 1);
|
|
8891
|
+
}, 300);
|
|
8892
|
+
return () => clearInterval(interval);
|
|
8893
|
+
}, [(_a = view.bundle) == null ? void 0 : _a.active]);
|
|
8894
|
+
const hasLiveProgress = Boolean(run) && (run == null ? void 0 : run.status) === "running";
|
|
8895
|
+
return { run, view, loading, error, hasLiveProgress, refetch };
|
|
8896
|
+
}
|
|
8897
|
+
|
|
7942
8898
|
// src/studio/ComergeStudio.tsx
|
|
7943
|
-
import { jsx as
|
|
8899
|
+
import { jsx as jsx62, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
7944
8900
|
function ComergeStudio({
|
|
7945
8901
|
appId,
|
|
7946
8902
|
clientKey: clientKey2,
|
|
7947
8903
|
appKey = "MicroMain",
|
|
8904
|
+
analyticsEnabled,
|
|
7948
8905
|
onNavigateHome,
|
|
7949
8906
|
style,
|
|
7950
8907
|
showBubble = true,
|
|
8908
|
+
enableAgentProgress = true,
|
|
7951
8909
|
studioControlOptions,
|
|
7952
8910
|
embeddedBaseBundles
|
|
7953
8911
|
}) {
|
|
7954
|
-
const [activeAppId, setActiveAppId] =
|
|
7955
|
-
const [runtimeAppId, setRuntimeAppId] =
|
|
7956
|
-
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] =
|
|
7957
|
-
const platform =
|
|
7958
|
-
|
|
8912
|
+
const [activeAppId, setActiveAppId] = React49.useState(appId);
|
|
8913
|
+
const [runtimeAppId, setRuntimeAppId] = React49.useState(appId);
|
|
8914
|
+
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React49.useState(null);
|
|
8915
|
+
const platform = React49.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
|
|
8916
|
+
React49.useEffect(() => {
|
|
7959
8917
|
setActiveAppId(appId);
|
|
7960
8918
|
setRuntimeAppId(appId);
|
|
7961
8919
|
setPendingRuntimeTargetAppId(null);
|
|
7962
8920
|
}, [appId]);
|
|
7963
|
-
const captureTargetRef =
|
|
7964
|
-
return /* @__PURE__ */
|
|
7965
|
-
|
|
8921
|
+
const captureTargetRef = React49.useRef(null);
|
|
8922
|
+
return /* @__PURE__ */ jsx62(
|
|
8923
|
+
StudioBootstrap,
|
|
7966
8924
|
{
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
runtimeAppId,
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
8925
|
+
clientKey: clientKey2,
|
|
8926
|
+
analyticsEnabled,
|
|
8927
|
+
fallback: /* @__PURE__ */ jsx62(View48, { style: { flex: 1 } }),
|
|
8928
|
+
children: ({ userId }) => /* @__PURE__ */ jsx62(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx62(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx62(
|
|
8929
|
+
ComergeStudioInner,
|
|
8930
|
+
{
|
|
8931
|
+
userId,
|
|
8932
|
+
activeAppId,
|
|
8933
|
+
setActiveAppId,
|
|
8934
|
+
runtimeAppId,
|
|
8935
|
+
setRuntimeAppId,
|
|
8936
|
+
pendingRuntimeTargetAppId,
|
|
8937
|
+
setPendingRuntimeTargetAppId,
|
|
8938
|
+
appKey,
|
|
8939
|
+
platform,
|
|
8940
|
+
onNavigateHome,
|
|
8941
|
+
captureTargetRef,
|
|
8942
|
+
style,
|
|
8943
|
+
showBubble,
|
|
8944
|
+
enableAgentProgress,
|
|
8945
|
+
studioControlOptions,
|
|
8946
|
+
embeddedBaseBundles
|
|
8947
|
+
}
|
|
8948
|
+
) }) })
|
|
7982
8949
|
}
|
|
7983
|
-
)
|
|
8950
|
+
);
|
|
7984
8951
|
}
|
|
7985
8952
|
function ComergeStudioInner({
|
|
7986
8953
|
userId,
|
|
@@ -7996,17 +8963,19 @@ function ComergeStudioInner({
|
|
|
7996
8963
|
captureTargetRef,
|
|
7997
8964
|
style,
|
|
7998
8965
|
showBubble,
|
|
8966
|
+
enableAgentProgress,
|
|
7999
8967
|
studioControlOptions,
|
|
8000
8968
|
embeddedBaseBundles
|
|
8001
8969
|
}) {
|
|
8970
|
+
var _a;
|
|
8002
8971
|
const { app, loading: appLoading } = useApp(activeAppId);
|
|
8003
8972
|
const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
|
|
8004
8973
|
const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
|
|
8005
|
-
const sawEditingOnPendingTargetRef =
|
|
8006
|
-
|
|
8974
|
+
const sawEditingOnPendingTargetRef = React49.useRef(false);
|
|
8975
|
+
React49.useEffect(() => {
|
|
8007
8976
|
sawEditingOnPendingTargetRef.current = false;
|
|
8008
8977
|
}, [pendingRuntimeTargetAppId]);
|
|
8009
|
-
|
|
8978
|
+
React49.useEffect(() => {
|
|
8010
8979
|
if (!pendingRuntimeTargetAppId) return;
|
|
8011
8980
|
if (activeAppId !== pendingRuntimeTargetAppId) return;
|
|
8012
8981
|
if ((app == null ? void 0 : app.status) === "editing") {
|
|
@@ -8024,13 +8993,13 @@ function ComergeStudioInner({
|
|
|
8024
8993
|
canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
|
|
8025
8994
|
embeddedBaseBundles
|
|
8026
8995
|
});
|
|
8027
|
-
const sawEditingOnActiveAppRef =
|
|
8028
|
-
const [showPostEditPreparing, setShowPostEditPreparing] =
|
|
8029
|
-
|
|
8996
|
+
const sawEditingOnActiveAppRef = React49.useRef(false);
|
|
8997
|
+
const [showPostEditPreparing, setShowPostEditPreparing] = React49.useState(false);
|
|
8998
|
+
React49.useEffect(() => {
|
|
8030
8999
|
sawEditingOnActiveAppRef.current = false;
|
|
8031
9000
|
setShowPostEditPreparing(false);
|
|
8032
9001
|
}, [activeAppId]);
|
|
8033
|
-
|
|
9002
|
+
React49.useEffect(() => {
|
|
8034
9003
|
if (!(app == null ? void 0 : app.id)) return;
|
|
8035
9004
|
if (app.status === "editing") {
|
|
8036
9005
|
sawEditingOnActiveAppRef.current = true;
|
|
@@ -8042,7 +9011,7 @@ function ComergeStudioInner({
|
|
|
8042
9011
|
sawEditingOnActiveAppRef.current = false;
|
|
8043
9012
|
}
|
|
8044
9013
|
}, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
|
|
8045
|
-
|
|
9014
|
+
React49.useEffect(() => {
|
|
8046
9015
|
if (!showPostEditPreparing) return;
|
|
8047
9016
|
const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
|
|
8048
9017
|
if (!stillProcessingBaseBundle) {
|
|
@@ -8052,20 +9021,21 @@ function ComergeStudioInner({
|
|
|
8052
9021
|
const threadId = (app == null ? void 0 : app.threadId) ?? "";
|
|
8053
9022
|
const thread = useThreadMessages(threadId);
|
|
8054
9023
|
const editQueue = useEditQueue(activeAppId);
|
|
9024
|
+
const agentProgress = useAgentRunProgress(threadId, { enabled: enableAgentProgress });
|
|
8055
9025
|
const editQueueActions = useEditQueueActions(activeAppId);
|
|
8056
|
-
const [lastEditQueueInfo, setLastEditQueueInfo] =
|
|
8057
|
-
const lastEditQueueInfoRef =
|
|
8058
|
-
const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] =
|
|
9026
|
+
const [lastEditQueueInfo, setLastEditQueueInfo] = React49.useState(null);
|
|
9027
|
+
const lastEditQueueInfoRef = React49.useRef(null);
|
|
9028
|
+
const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React49.useState(false);
|
|
8059
9029
|
const mergeRequests = useMergeRequests({ appId: activeAppId });
|
|
8060
|
-
const hasOpenOutgoingMr =
|
|
9030
|
+
const hasOpenOutgoingMr = React49.useMemo(() => {
|
|
8061
9031
|
return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
|
|
8062
9032
|
}, [mergeRequests.lists.outgoing]);
|
|
8063
|
-
const incomingReviewMrs =
|
|
9033
|
+
const incomingReviewMrs = React49.useMemo(() => {
|
|
8064
9034
|
if (!userId) return mergeRequests.lists.incoming;
|
|
8065
9035
|
return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
|
|
8066
9036
|
}, [mergeRequests.lists.incoming, userId]);
|
|
8067
9037
|
const uploader = useAttachmentUpload();
|
|
8068
|
-
const updateLastEditQueueInfo =
|
|
9038
|
+
const updateLastEditQueueInfo = React49.useCallback(
|
|
8069
9039
|
(info) => {
|
|
8070
9040
|
lastEditQueueInfoRef.current = info;
|
|
8071
9041
|
setLastEditQueueInfo(info);
|
|
@@ -8099,32 +9069,35 @@ function ComergeStudioInner({
|
|
|
8099
9069
|
}
|
|
8100
9070
|
},
|
|
8101
9071
|
onEditFinished: () => {
|
|
8102
|
-
var
|
|
8103
|
-
if (((
|
|
9072
|
+
var _a2;
|
|
9073
|
+
if (((_a2 = lastEditQueueInfoRef.current) == null ? void 0 : _a2.queuePosition) !== 1) {
|
|
8104
9074
|
setSuppressQueueUntilResponse(false);
|
|
8105
9075
|
}
|
|
8106
9076
|
}
|
|
8107
9077
|
});
|
|
8108
9078
|
const chatSendDisabled = false;
|
|
8109
|
-
const [processingMrId, setProcessingMrId] =
|
|
8110
|
-
const [testingMrId, setTestingMrId] =
|
|
8111
|
-
const [syncingUpstream, setSyncingUpstream] =
|
|
8112
|
-
const [upstreamSyncStatus, setUpstreamSyncStatus] =
|
|
9079
|
+
const [processingMrId, setProcessingMrId] = React49.useState(null);
|
|
9080
|
+
const [testingMrId, setTestingMrId] = React49.useState(null);
|
|
9081
|
+
const [syncingUpstream, setSyncingUpstream] = React49.useState(false);
|
|
9082
|
+
const [upstreamSyncStatus, setUpstreamSyncStatus] = React49.useState(null);
|
|
8113
9083
|
const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
|
|
8114
9084
|
const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
|
|
8115
|
-
const
|
|
8116
|
-
|
|
9085
|
+
const runtimePreparingText = bundle.bundleStatus === "pending" ? "Bundling app\u2026 this may take a few minutes" : "Preparing app\u2026";
|
|
9086
|
+
const chatShowTypingIndicator = React49.useMemo(() => {
|
|
9087
|
+
var _a2;
|
|
9088
|
+
if (agentProgress.hasLiveProgress) return false;
|
|
8117
9089
|
if (!thread.raw || thread.raw.length === 0) return false;
|
|
8118
9090
|
const last = thread.raw[thread.raw.length - 1];
|
|
8119
|
-
const payloadType = typeof ((
|
|
9091
|
+
const payloadType = typeof ((_a2 = last.payload) == null ? void 0 : _a2.type) === "string" ? String(last.payload.type) : void 0;
|
|
8120
9092
|
return payloadType !== "outcome";
|
|
8121
|
-
}, [thread.raw]);
|
|
8122
|
-
|
|
9093
|
+
}, [agentProgress.hasLiveProgress, thread.raw]);
|
|
9094
|
+
const showChatProgress = agentProgress.hasLiveProgress || Boolean((_a = agentProgress.view.bundle) == null ? void 0 : _a.active);
|
|
9095
|
+
React49.useEffect(() => {
|
|
8123
9096
|
updateLastEditQueueInfo(null);
|
|
8124
9097
|
setSuppressQueueUntilResponse(false);
|
|
8125
9098
|
setUpstreamSyncStatus(null);
|
|
8126
9099
|
}, [activeAppId, updateLastEditQueueInfo]);
|
|
8127
|
-
const handleSyncUpstream =
|
|
9100
|
+
const handleSyncUpstream = React49.useCallback(async () => {
|
|
8128
9101
|
if (!(app == null ? void 0 : app.id)) {
|
|
8129
9102
|
throw new Error("Missing app");
|
|
8130
9103
|
}
|
|
@@ -8137,7 +9110,7 @@ function ComergeStudioInner({
|
|
|
8137
9110
|
setSyncingUpstream(false);
|
|
8138
9111
|
}
|
|
8139
9112
|
}, [activeAppId, app == null ? void 0 : app.id]);
|
|
8140
|
-
|
|
9113
|
+
React49.useEffect(() => {
|
|
8141
9114
|
if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
|
|
8142
9115
|
const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
|
|
8143
9116
|
if (!stillPresent) {
|
|
@@ -8145,31 +9118,32 @@ function ComergeStudioInner({
|
|
|
8145
9118
|
setSuppressQueueUntilResponse(false);
|
|
8146
9119
|
}
|
|
8147
9120
|
}, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
|
|
8148
|
-
const chatQueueItems =
|
|
8149
|
-
var
|
|
9121
|
+
const chatQueueItems = React49.useMemo(() => {
|
|
9122
|
+
var _a2;
|
|
8150
9123
|
if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
|
|
8151
9124
|
return [];
|
|
8152
9125
|
}
|
|
8153
9126
|
if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
|
|
8154
9127
|
return editQueue.items;
|
|
8155
9128
|
}
|
|
8156
|
-
if (editQueue.items.length === 1 && ((
|
|
9129
|
+
if (editQueue.items.length === 1 && ((_a2 = editQueue.items[0]) == null ? void 0 : _a2.id) === lastEditQueueInfo.queueItemId) {
|
|
8157
9130
|
return [];
|
|
8158
9131
|
}
|
|
8159
9132
|
return editQueue.items;
|
|
8160
9133
|
}, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
|
|
8161
|
-
return /* @__PURE__ */
|
|
8162
|
-
/* @__PURE__ */
|
|
9134
|
+
return /* @__PURE__ */ jsx62(View48, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs39(View48, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
|
|
9135
|
+
/* @__PURE__ */ jsx62(
|
|
8163
9136
|
RuntimeRenderer,
|
|
8164
9137
|
{
|
|
8165
9138
|
appKey,
|
|
8166
9139
|
bundlePath: bundle.bundlePath,
|
|
9140
|
+
preparingText: runtimePreparingText,
|
|
8167
9141
|
forcePreparing: showPostEditPreparing,
|
|
8168
9142
|
renderToken: bundle.renderToken,
|
|
8169
9143
|
allowInitialPreparing: !embeddedBaseBundles
|
|
8170
9144
|
}
|
|
8171
9145
|
),
|
|
8172
|
-
/* @__PURE__ */
|
|
9146
|
+
/* @__PURE__ */ jsx62(
|
|
8173
9147
|
StudioOverlay,
|
|
8174
9148
|
{
|
|
8175
9149
|
captureTargetRef,
|
|
@@ -8228,6 +9202,7 @@ function ComergeStudioInner({
|
|
|
8228
9202
|
onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
|
|
8229
9203
|
chatQueueItems,
|
|
8230
9204
|
onRemoveQueueItem: (id) => editQueueActions.cancel(id),
|
|
9205
|
+
chatProgress: showChatProgress ? agentProgress.view : null,
|
|
8231
9206
|
onNavigateHome,
|
|
8232
9207
|
showBubble,
|
|
8233
9208
|
studioControlOptions
|