@comergehq/studio 0.1.13 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +820 -192
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +814 -186
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/chat/ChatPage.tsx +19 -2
- package/src/components/chat/ChatQueue.tsx +163 -0
- package/src/data/agent/types.ts +2 -1
- package/src/data/apps/bundles/remote.ts +17 -0
- package/src/data/apps/bundles/repository.ts +14 -0
- package/src/data/apps/bundles/types.ts +15 -0
- package/src/data/apps/edit-queue/remote.ts +45 -0
- package/src/data/apps/edit-queue/repository.ts +136 -0
- package/src/data/apps/edit-queue/types.ts +31 -0
- package/src/studio/ComergeStudio.tsx +70 -2
- package/src/studio/hooks/useBundleManager.ts +273 -22
- package/src/studio/hooks/useEditQueue.ts +71 -0
- package/src/studio/hooks/useEditQueueActions.ts +29 -0
- package/src/studio/hooks/useOptimisticChatMessages.ts +4 -2
- package/src/studio/hooks/useStudioActions.ts +14 -2
- package/src/studio/hooks/useThreadMessages.ts +39 -5
- package/src/studio/ui/ChatPanel.tsx +11 -0
- package/src/studio/ui/StudioOverlay.tsx +8 -0
package/dist/index.js
CHANGED
|
@@ -36,8 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/studio/ComergeStudio.tsx
|
|
39
|
-
var
|
|
40
|
-
var
|
|
39
|
+
var React46 = __toESM(require("react"));
|
|
40
|
+
var import_react_native56 = require("react-native");
|
|
41
41
|
var import_bottom_sheet6 = require("@gorhom/bottom-sheet");
|
|
42
42
|
|
|
43
43
|
// src/studio/bootstrap/StudioBootstrap.tsx
|
|
@@ -846,6 +846,35 @@ function extractMeta(payload) {
|
|
|
846
846
|
threadId: typeof obj.threadId === "string" ? obj.threadId : void 0
|
|
847
847
|
};
|
|
848
848
|
}
|
|
849
|
+
function getPayloadMeta(payload) {
|
|
850
|
+
const meta = payload == null ? void 0 : payload.meta;
|
|
851
|
+
if (!meta || typeof meta !== "object") return null;
|
|
852
|
+
return meta;
|
|
853
|
+
}
|
|
854
|
+
function isQueuedHiddenMessage(m) {
|
|
855
|
+
if (m.authorType !== "human") return false;
|
|
856
|
+
const meta = getPayloadMeta(m.payload);
|
|
857
|
+
return (meta == null ? void 0 : meta.visibility) === "queued";
|
|
858
|
+
}
|
|
859
|
+
function toEpochMs(value) {
|
|
860
|
+
if (value == null) return 0;
|
|
861
|
+
if (typeof value === "number") return value;
|
|
862
|
+
if (value instanceof Date) return value.getTime();
|
|
863
|
+
const parsed = Date.parse(String(value));
|
|
864
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
865
|
+
}
|
|
866
|
+
function getEffectiveSortMs(m) {
|
|
867
|
+
const meta = getPayloadMeta(m.payload);
|
|
868
|
+
const runStartedAt = meta == null ? void 0 : meta.runStartedAt;
|
|
869
|
+
const runMs = toEpochMs(runStartedAt);
|
|
870
|
+
return runMs > 0 ? runMs : toEpochMs(m.createdAt);
|
|
871
|
+
}
|
|
872
|
+
function compareMessages(a, b) {
|
|
873
|
+
const aMs = getEffectiveSortMs(a);
|
|
874
|
+
const bMs = getEffectiveSortMs(b);
|
|
875
|
+
if (aMs !== bMs) return aMs - bMs;
|
|
876
|
+
return String(a.createdAt).localeCompare(String(b.createdAt));
|
|
877
|
+
}
|
|
849
878
|
function mapMessageToChatMessage(m) {
|
|
850
879
|
var _a, _b;
|
|
851
880
|
const kind = typeof ((_a = m.payload) == null ? void 0 : _a.type) === "string" ? String(m.payload.type) : null;
|
|
@@ -865,8 +894,10 @@ function useThreadMessages(threadId) {
|
|
|
865
894
|
const activeRequestIdRef = React4.useRef(0);
|
|
866
895
|
const foregroundSignal = useForegroundSignal(Boolean(threadId));
|
|
867
896
|
const upsertSorted = React4.useCallback((prev, m) => {
|
|
868
|
-
const
|
|
869
|
-
next.
|
|
897
|
+
const include = !isQueuedHiddenMessage(m);
|
|
898
|
+
const next = prev.filter((x) => x.id !== m.id);
|
|
899
|
+
if (include) next.push(m);
|
|
900
|
+
next.sort(compareMessages);
|
|
870
901
|
return next;
|
|
871
902
|
}, []);
|
|
872
903
|
const refetch = React4.useCallback(async () => {
|
|
@@ -880,7 +911,7 @@ function useThreadMessages(threadId) {
|
|
|
880
911
|
try {
|
|
881
912
|
const list = await messagesRepository.list(threadId);
|
|
882
913
|
if (activeRequestIdRef.current !== requestId) return;
|
|
883
|
-
setRaw([...list].
|
|
914
|
+
setRaw([...list].filter((m) => !isQueuedHiddenMessage(m)).sort(compareMessages));
|
|
884
915
|
} catch (e) {
|
|
885
916
|
if (activeRequestIdRef.current !== requestId) return;
|
|
886
917
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -914,6 +945,7 @@ function useThreadMessages(threadId) {
|
|
|
914
945
|
var React5 = __toESM(require("react"));
|
|
915
946
|
var FileSystem = __toESM(require("expo-file-system/legacy"));
|
|
916
947
|
var import_expo_asset = require("expo-asset");
|
|
948
|
+
var import_react_native_zip_archive = require("react-native-zip-archive");
|
|
917
949
|
|
|
918
950
|
// src/data/apps/bundles/remote.ts
|
|
919
951
|
var BundlesRemoteDataSourceImpl = class extends BaseRemote {
|
|
@@ -937,6 +969,13 @@ var BundlesRemoteDataSourceImpl = class extends BaseRemote {
|
|
|
937
969
|
);
|
|
938
970
|
return data;
|
|
939
971
|
}
|
|
972
|
+
async getSignedAssetsDownloadUrl(appId, bundleId, options) {
|
|
973
|
+
const { data } = await api.get(
|
|
974
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
|
|
975
|
+
{ params: { redirect: (options == null ? void 0 : options.redirect) ?? false, kind: options == null ? void 0 : options.kind } }
|
|
976
|
+
);
|
|
977
|
+
return data;
|
|
978
|
+
}
|
|
940
979
|
};
|
|
941
980
|
var bundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
|
|
942
981
|
|
|
@@ -958,6 +997,10 @@ var BundlesRepositoryImpl = class extends BaseRepository {
|
|
|
958
997
|
const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
|
|
959
998
|
return this.unwrapOrThrow(res);
|
|
960
999
|
}
|
|
1000
|
+
async getSignedAssetsDownloadUrl(appId, bundleId, options) {
|
|
1001
|
+
const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
|
|
1002
|
+
return this.unwrapOrThrow(res);
|
|
1003
|
+
}
|
|
961
1004
|
};
|
|
962
1005
|
var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
|
|
963
1006
|
|
|
@@ -1009,20 +1052,40 @@ async function ensureDir(path) {
|
|
|
1009
1052
|
if (info.exists) return;
|
|
1010
1053
|
await FileSystem.makeDirectoryAsync(path, { intermediates: true });
|
|
1011
1054
|
}
|
|
1055
|
+
async function ensureBundleDir(key) {
|
|
1056
|
+
await ensureDir(bundlesCacheDir());
|
|
1057
|
+
await ensureDir(bundleDir(key));
|
|
1058
|
+
}
|
|
1012
1059
|
function baseBundleKey(appId, platform) {
|
|
1013
1060
|
return `base:${appId}:${platform}`;
|
|
1014
1061
|
}
|
|
1015
1062
|
function testBundleKey(appId, commitId, platform, bundleId) {
|
|
1016
1063
|
return `test:${appId}:${commitId ?? "head"}:${platform}:${bundleId}`;
|
|
1017
1064
|
}
|
|
1018
|
-
function
|
|
1065
|
+
function legacyBundleFileUri(key) {
|
|
1019
1066
|
const dir = bundlesCacheDir();
|
|
1020
1067
|
return `${dir}${safeName(key)}.jsbundle`;
|
|
1021
1068
|
}
|
|
1022
|
-
function
|
|
1069
|
+
function legacyBundleMetaFileUri(key) {
|
|
1023
1070
|
const dir = bundlesCacheDir();
|
|
1024
1071
|
return `${dir}${safeName(key)}.meta.json`;
|
|
1025
1072
|
}
|
|
1073
|
+
function bundleDir(key) {
|
|
1074
|
+
const dir = bundlesCacheDir();
|
|
1075
|
+
return `${dir}${safeName(key)}/`;
|
|
1076
|
+
}
|
|
1077
|
+
function toBundleFileUri(key, platform) {
|
|
1078
|
+
return `${bundleDir(key)}index.${platform}.jsbundle`;
|
|
1079
|
+
}
|
|
1080
|
+
function toBundleMetaFileUri(key) {
|
|
1081
|
+
return `${bundleDir(key)}bundle.meta.json`;
|
|
1082
|
+
}
|
|
1083
|
+
function toAssetsMetaFileUri(key) {
|
|
1084
|
+
return `${bundleDir(key)}assets.meta.json`;
|
|
1085
|
+
}
|
|
1086
|
+
function toAssetsDir(key) {
|
|
1087
|
+
return `${bundleDir(key)}assets/`;
|
|
1088
|
+
}
|
|
1026
1089
|
async function readJsonFile(fileUri) {
|
|
1027
1090
|
try {
|
|
1028
1091
|
const info = await FileSystem.getInfoAsync(fileUri);
|
|
@@ -1049,6 +1112,14 @@ async function getExistingNonEmptyFileUri(fileUri) {
|
|
|
1049
1112
|
return null;
|
|
1050
1113
|
}
|
|
1051
1114
|
}
|
|
1115
|
+
async function getExistingBundleFileUri(key, platform) {
|
|
1116
|
+
const nextPath = toBundleFileUri(key, platform);
|
|
1117
|
+
const next = await getExistingNonEmptyFileUri(nextPath);
|
|
1118
|
+
if (next) return next;
|
|
1119
|
+
const legacyPath = legacyBundleFileUri(key);
|
|
1120
|
+
const legacy = await getExistingNonEmptyFileUri(legacyPath);
|
|
1121
|
+
return legacy;
|
|
1122
|
+
}
|
|
1052
1123
|
async function downloadIfMissing(url, fileUri) {
|
|
1053
1124
|
const existing = await getExistingNonEmptyFileUri(fileUri);
|
|
1054
1125
|
if (existing) return existing;
|
|
@@ -1075,9 +1146,12 @@ async function deleteFileIfExists(fileUri) {
|
|
|
1075
1146
|
async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
|
|
1076
1147
|
if (!(embedded == null ? void 0 : embedded.module)) return null;
|
|
1077
1148
|
const key = baseBundleKey(appId, platform);
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1149
|
+
const existing = await getExistingBundleFileUri(key, platform);
|
|
1150
|
+
if (existing) {
|
|
1151
|
+
return { bundlePath: existing, meta: embedded.meta ?? null };
|
|
1152
|
+
}
|
|
1153
|
+
await ensureBundleDir(key);
|
|
1154
|
+
const targetUri = toBundleFileUri(key, platform);
|
|
1081
1155
|
const asset = import_expo_asset.Asset.fromModule(embedded.module);
|
|
1082
1156
|
await asset.downloadAsync();
|
|
1083
1157
|
const sourceUri = asset.localUri ?? asset.uri;
|
|
@@ -1090,8 +1164,50 @@ async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
|
|
|
1090
1164
|
if (!finalUri) return null;
|
|
1091
1165
|
return { bundlePath: finalUri, meta: embedded.meta ?? null };
|
|
1092
1166
|
}
|
|
1093
|
-
async function
|
|
1094
|
-
|
|
1167
|
+
async function hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded) {
|
|
1168
|
+
var _a;
|
|
1169
|
+
const moduleId = embedded == null ? void 0 : embedded.assetsModule;
|
|
1170
|
+
if (!moduleId) return false;
|
|
1171
|
+
const assetsMeta = (embedded == null ? void 0 : embedded.assetsMeta) ?? null;
|
|
1172
|
+
const assetsDir = toAssetsDir(key);
|
|
1173
|
+
const metaUri = toAssetsMetaFileUri(key);
|
|
1174
|
+
const existingMeta = await readJsonFile(metaUri);
|
|
1175
|
+
const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
|
|
1176
|
+
const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
|
|
1177
|
+
const checksumMatches = Boolean(existingMeta == null ? void 0 : existingMeta.checksumSha256) && Boolean(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) && (existingMeta == null ? void 0 : existingMeta.checksumSha256) === (assetsMeta == null ? void 0 : assetsMeta.checksumSha256);
|
|
1178
|
+
const embeddedMetaMatches = (_a = existingMeta == null ? void 0 : existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:");
|
|
1179
|
+
if (assetsDirExists && checksumMatches && embeddedMetaMatches) {
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
await ensureBundleDir(key);
|
|
1183
|
+
await ensureDir(assetsDir);
|
|
1184
|
+
const asset = import_expo_asset.Asset.fromModule(moduleId);
|
|
1185
|
+
await asset.downloadAsync();
|
|
1186
|
+
const sourceUri = asset.localUri ?? asset.uri;
|
|
1187
|
+
if (!sourceUri) return false;
|
|
1188
|
+
const info = await FileSystem.getInfoAsync(sourceUri);
|
|
1189
|
+
if (!info.exists) return false;
|
|
1190
|
+
const zipUri = `${bundleDir(key)}assets.zip`;
|
|
1191
|
+
await deleteFileIfExists(zipUri);
|
|
1192
|
+
await FileSystem.copyAsync({ from: sourceUri, to: zipUri });
|
|
1193
|
+
try {
|
|
1194
|
+
await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
|
|
1195
|
+
});
|
|
1196
|
+
} catch {
|
|
1197
|
+
}
|
|
1198
|
+
await ensureDir(assetsDir);
|
|
1199
|
+
await unzipArchive(zipUri, assetsDir);
|
|
1200
|
+
await writeJsonFile(metaUri, {
|
|
1201
|
+
checksumSha256: (assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? null,
|
|
1202
|
+
storageKey: `embedded:${(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? "unknown"}`,
|
|
1203
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1204
|
+
});
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
async function safeReplaceFileFromUrl(url, targetUri, tmpKey, platform) {
|
|
1208
|
+
const tmpKeySafe = `tmp:${tmpKey}:${Date.now()}`;
|
|
1209
|
+
const tmpUri = toBundleFileUri(tmpKeySafe, platform);
|
|
1210
|
+
await ensureDir(bundleDir(tmpKeySafe));
|
|
1095
1211
|
try {
|
|
1096
1212
|
await withRetry(
|
|
1097
1213
|
async () => {
|
|
@@ -1111,6 +1227,82 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
|
|
|
1111
1227
|
await deleteFileIfExists(tmpUri);
|
|
1112
1228
|
}
|
|
1113
1229
|
}
|
|
1230
|
+
async function safeReplaceFileFromUrlToPath(url, targetUri, tmpKey) {
|
|
1231
|
+
const tmpDir = `${bundlesCacheDir()}tmp/`;
|
|
1232
|
+
await ensureDir(tmpDir);
|
|
1233
|
+
const tmpUri = `${tmpDir}${safeName(tmpKey)}.tmp`;
|
|
1234
|
+
try {
|
|
1235
|
+
await withRetry(
|
|
1236
|
+
async () => {
|
|
1237
|
+
await deleteFileIfExists(tmpUri);
|
|
1238
|
+
await FileSystem.downloadAsync(url, tmpUri);
|
|
1239
|
+
const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
|
|
1240
|
+
if (!tmpOk) throw new Error("Downloaded file is empty.");
|
|
1241
|
+
},
|
|
1242
|
+
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1243
|
+
);
|
|
1244
|
+
await deleteFileIfExists(targetUri);
|
|
1245
|
+
await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
|
|
1246
|
+
const finalOk = await getExistingNonEmptyFileUri(targetUri);
|
|
1247
|
+
if (!finalOk) throw new Error("File replacement failed.");
|
|
1248
|
+
return targetUri;
|
|
1249
|
+
} finally {
|
|
1250
|
+
await deleteFileIfExists(tmpUri);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function getMetroAssets(bundle) {
|
|
1254
|
+
const assets = bundle.assets ?? [];
|
|
1255
|
+
return assets.find((asset) => asset.kind === "metro-assets") ?? null;
|
|
1256
|
+
}
|
|
1257
|
+
async function ensureAssetsForBundle(appId, bundle, key, platform) {
|
|
1258
|
+
var _a;
|
|
1259
|
+
const asset = getMetroAssets(bundle);
|
|
1260
|
+
if (!(asset == null ? void 0 : asset.storageKey)) return;
|
|
1261
|
+
await ensureBundleDir(key);
|
|
1262
|
+
const assetsDir = toAssetsDir(key);
|
|
1263
|
+
await ensureDir(assetsDir);
|
|
1264
|
+
const metaUri = toAssetsMetaFileUri(key);
|
|
1265
|
+
const existingMeta = await readJsonFile(metaUri);
|
|
1266
|
+
const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
|
|
1267
|
+
const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
|
|
1268
|
+
if ((existingMeta == null ? void 0 : existingMeta.checksumSha256) && asset.checksumSha256 && existingMeta.checksumSha256 === asset.checksumSha256 && (existingMeta.storageKey === asset.storageKey || ((_a = existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:"))) && assetsDirExists) {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const signed = await withRetry(
|
|
1272
|
+
async () => {
|
|
1273
|
+
return await bundlesRepository.getSignedAssetsDownloadUrl(appId, bundle.id, {
|
|
1274
|
+
redirect: false,
|
|
1275
|
+
kind: asset.kind
|
|
1276
|
+
});
|
|
1277
|
+
},
|
|
1278
|
+
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1279
|
+
);
|
|
1280
|
+
const zipUri = `${bundleDir(key)}assets.zip`;
|
|
1281
|
+
await safeReplaceFileFromUrlToPath(signed.url, zipUri, `${appId}:${bundle.id}:${platform}:${asset.kind}`);
|
|
1282
|
+
try {
|
|
1283
|
+
await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
|
|
1284
|
+
});
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
await ensureDir(assetsDir);
|
|
1288
|
+
await unzipArchive(zipUri, assetsDir);
|
|
1289
|
+
await writeJsonFile(metaUri, {
|
|
1290
|
+
checksumSha256: asset.checksumSha256 ?? null,
|
|
1291
|
+
storageKey: asset.storageKey,
|
|
1292
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
async function unzipArchive(sourceUri, destDir) {
|
|
1296
|
+
try {
|
|
1297
|
+
await (0, import_react_native_zip_archive.unzip)(sourceUri, destDir);
|
|
1298
|
+
} catch (e) {
|
|
1299
|
+
throw new Error(
|
|
1300
|
+
`Failed to extract assets archive. Ensure 'react-native-zip-archive' is installed in the host app. ${String(
|
|
1301
|
+
(e == null ? void 0 : e.message) ?? e
|
|
1302
|
+
)}`
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1114
1306
|
async function pollBundle(appId, bundleId, opts) {
|
|
1115
1307
|
const start = Date.now();
|
|
1116
1308
|
while (true) {
|
|
@@ -1146,18 +1338,33 @@ async function resolveBundlePath(src, platform, mode) {
|
|
|
1146
1338
|
if (finalBundle.status === "failed") {
|
|
1147
1339
|
throw new Error("Bundle build failed.");
|
|
1148
1340
|
}
|
|
1341
|
+
let bundleWithAssets = finalBundle;
|
|
1342
|
+
if (finalBundle.status === "succeeded" && (!finalBundle.assets || finalBundle.assets.length === 0)) {
|
|
1343
|
+
try {
|
|
1344
|
+
bundleWithAssets = await bundlesRepository.getById(appId, finalBundle.id);
|
|
1345
|
+
} catch {
|
|
1346
|
+
bundleWithAssets = finalBundle;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1149
1349
|
const signed = await withRetry(
|
|
1150
1350
|
async () => {
|
|
1151
1351
|
return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
|
|
1152
1352
|
},
|
|
1153
1353
|
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1154
1354
|
);
|
|
1355
|
+
const key = mode === "base" ? baseBundleKey(appId, platform) : testBundleKey(appId, commitId, platform, finalBundle.id);
|
|
1356
|
+
await ensureBundleDir(key);
|
|
1155
1357
|
const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
|
|
1156
1358
|
signed.url,
|
|
1157
|
-
toBundleFileUri(
|
|
1158
|
-
`${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}
|
|
1159
|
-
|
|
1160
|
-
|
|
1359
|
+
toBundleFileUri(key, platform),
|
|
1360
|
+
`${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`,
|
|
1361
|
+
platform
|
|
1362
|
+
) : await downloadIfMissing(signed.url, toBundleFileUri(key, platform));
|
|
1363
|
+
try {
|
|
1364
|
+
await ensureAssetsForBundle(appId, bundleWithAssets, key, platform);
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
return { bundlePath, label: "Ready", bundle: bundleWithAssets };
|
|
1161
1368
|
}
|
|
1162
1369
|
function useBundleManager({
|
|
1163
1370
|
base,
|
|
@@ -1198,13 +1405,12 @@ function useBundleManager({
|
|
|
1198
1405
|
const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
|
|
1199
1406
|
const hydrateBaseFromDisk = React5.useCallback(
|
|
1200
1407
|
async (appId, reason) => {
|
|
1201
|
-
var _a;
|
|
1408
|
+
var _a, _b, _c;
|
|
1202
1409
|
try {
|
|
1203
1410
|
const dir = bundlesCacheDir();
|
|
1204
1411
|
await ensureDir(dir);
|
|
1205
1412
|
const key = baseBundleKey(appId, platform);
|
|
1206
|
-
|
|
1207
|
-
let existing = await getExistingNonEmptyFileUri(uri);
|
|
1413
|
+
let existing = await getExistingBundleFileUri(key, platform);
|
|
1208
1414
|
let embeddedMeta = null;
|
|
1209
1415
|
if (!existing) {
|
|
1210
1416
|
const embedded = (_a = embeddedBaseBundlesRef.current) == null ? void 0 : _a[platform];
|
|
@@ -1213,14 +1419,25 @@ function useBundleManager({
|
|
|
1213
1419
|
existing = hydrated.bundlePath;
|
|
1214
1420
|
embeddedMeta = hydrated.meta ?? null;
|
|
1215
1421
|
if (embeddedMeta) {
|
|
1422
|
+
await ensureBundleDir(key);
|
|
1216
1423
|
await writeJsonFile(toBundleMetaFileUri(key), embeddedMeta);
|
|
1424
|
+
await writeJsonFile(legacyBundleMetaFileUri(key), embeddedMeta);
|
|
1217
1425
|
}
|
|
1218
1426
|
}
|
|
1219
1427
|
}
|
|
1220
1428
|
if (existing) {
|
|
1221
1429
|
lastBaseBundlePathRef.current = existing;
|
|
1222
1430
|
setBundlePath(existing);
|
|
1223
|
-
const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key));
|
|
1431
|
+
const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key)) ?? await readJsonFile(legacyBundleMetaFileUri(key));
|
|
1432
|
+
const embedded = (_b = embeddedBaseBundlesRef.current) == null ? void 0 : _b[platform];
|
|
1433
|
+
const embeddedFingerprint = ((_c = embedded == null ? void 0 : embedded.meta) == null ? void 0 : _c.fingerprint) ?? null;
|
|
1434
|
+
const actualFingerprint = (meta == null ? void 0 : meta.fingerprint) ?? (embeddedMeta == null ? void 0 : embeddedMeta.fingerprint) ?? null;
|
|
1435
|
+
if ((embedded == null ? void 0 : embedded.assetsModule) && embeddedFingerprint && actualFingerprint === embeddedFingerprint) {
|
|
1436
|
+
try {
|
|
1437
|
+
await hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded);
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1224
1441
|
if (meta == null ? void 0 : meta.fingerprint) {
|
|
1225
1442
|
lastBaseFingerprintRef.current = meta.fingerprint;
|
|
1226
1443
|
}
|
|
@@ -1285,7 +1502,16 @@ function useBundleManager({
|
|
|
1285
1502
|
lastBaseFingerprintRef.current = fingerprint;
|
|
1286
1503
|
hasCompletedFirstNetworkBaseLoadRef.current = true;
|
|
1287
1504
|
initialHydratedBaseFromDiskRef.current = false;
|
|
1288
|
-
|
|
1505
|
+
const metaKey = baseBundleKey(src.appId, platform);
|
|
1506
|
+
await ensureBundleDir(metaKey);
|
|
1507
|
+
void writeJsonFile(toBundleMetaFileUri(metaKey), {
|
|
1508
|
+
fingerprint,
|
|
1509
|
+
bundleId: bundle.id,
|
|
1510
|
+
checksumSha256: bundle.checksumSha256 ?? null,
|
|
1511
|
+
size: bundle.size ?? null,
|
|
1512
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1513
|
+
});
|
|
1514
|
+
void writeJsonFile(legacyBundleMetaFileUri(metaKey), {
|
|
1289
1515
|
fingerprint,
|
|
1290
1516
|
bundleId: bundle.id,
|
|
1291
1517
|
checksumSha256: bundle.checksumSha256 ?? null,
|
|
@@ -1737,6 +1963,9 @@ function useStudioActions({
|
|
|
1737
1963
|
userId,
|
|
1738
1964
|
app,
|
|
1739
1965
|
onForkedApp,
|
|
1966
|
+
onEditStart,
|
|
1967
|
+
onEditQueued,
|
|
1968
|
+
onEditFinished,
|
|
1740
1969
|
uploadAttachments
|
|
1741
1970
|
}) {
|
|
1742
1971
|
const [forking, setForking] = React8.useState(false);
|
|
@@ -1752,6 +1981,7 @@ function useStudioActions({
|
|
|
1752
1981
|
setSending(true);
|
|
1753
1982
|
setError(null);
|
|
1754
1983
|
try {
|
|
1984
|
+
onEditStart == null ? void 0 : onEditStart();
|
|
1755
1985
|
let targetApp = app;
|
|
1756
1986
|
if (shouldForkOnEdit) {
|
|
1757
1987
|
setForking(true);
|
|
@@ -1767,12 +1997,16 @@ function useStudioActions({
|
|
|
1767
1997
|
if (attachments && attachments.length > 0 && uploadAttachments) {
|
|
1768
1998
|
attachmentMetas = await uploadAttachments({ threadId, appId: targetApp.id, dataUrls: attachments });
|
|
1769
1999
|
}
|
|
1770
|
-
await agentRepository.editApp({
|
|
2000
|
+
const editResult = await agentRepository.editApp({
|
|
1771
2001
|
prompt,
|
|
1772
2002
|
thread_id: threadId,
|
|
1773
2003
|
app_id: targetApp.id,
|
|
1774
2004
|
attachments: attachmentMetas && attachmentMetas.length > 0 ? attachmentMetas : void 0
|
|
1775
2005
|
});
|
|
2006
|
+
onEditQueued == null ? void 0 : onEditQueued({
|
|
2007
|
+
queueItemId: editResult.queueItemId ?? null,
|
|
2008
|
+
queuePosition: editResult.queuePosition ?? null
|
|
2009
|
+
});
|
|
1776
2010
|
} catch (e) {
|
|
1777
2011
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
1778
2012
|
setError(err);
|
|
@@ -1780,32 +2014,14 @@ function useStudioActions({
|
|
|
1780
2014
|
} finally {
|
|
1781
2015
|
setForking(false);
|
|
1782
2016
|
setSending(false);
|
|
2017
|
+
onEditFinished == null ? void 0 : onEditFinished();
|
|
1783
2018
|
}
|
|
1784
2019
|
},
|
|
1785
|
-
[app, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
|
|
2020
|
+
[app, onEditFinished, onEditQueued, onEditStart, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
|
|
1786
2021
|
);
|
|
1787
2022
|
return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
|
|
1788
2023
|
}
|
|
1789
2024
|
|
|
1790
|
-
// src/studio/lib/chat.ts
|
|
1791
|
-
function hasNoOutcomeAfterLastHuman(messages) {
|
|
1792
|
-
if (!messages || messages.length === 0) return false;
|
|
1793
|
-
let lastHumanIndex = -1;
|
|
1794
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
1795
|
-
if (messages[i].authorType === "human") {
|
|
1796
|
-
lastHumanIndex = i;
|
|
1797
|
-
break;
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
if (lastHumanIndex === -1) return false;
|
|
1801
|
-
for (let i = lastHumanIndex + 1; i < messages.length; i += 1) {
|
|
1802
|
-
const m = messages[i];
|
|
1803
|
-
const payload = m.payload;
|
|
1804
|
-
if (m.authorType === "ai" && (payload == null ? void 0 : payload.type) === "outcome") return false;
|
|
1805
|
-
}
|
|
1806
|
-
return true;
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
2025
|
// src/studio/ui/RuntimeRenderer.tsx
|
|
1810
2026
|
var React9 = __toESM(require("react"));
|
|
1811
2027
|
var import_react_native6 = require("react-native");
|
|
@@ -1843,8 +2059,8 @@ function RuntimeRenderer({
|
|
|
1843
2059
|
}
|
|
1844
2060
|
|
|
1845
2061
|
// src/studio/ui/StudioOverlay.tsx
|
|
1846
|
-
var
|
|
1847
|
-
var
|
|
2062
|
+
var React43 = __toESM(require("react"));
|
|
2063
|
+
var import_react_native55 = require("react-native");
|
|
1848
2064
|
|
|
1849
2065
|
// src/components/studio-sheet/StudioBottomSheet.tsx
|
|
1850
2066
|
var React12 = __toESM(require("react"));
|
|
@@ -5648,8 +5864,8 @@ function PreviewPanel({
|
|
|
5648
5864
|
}
|
|
5649
5865
|
|
|
5650
5866
|
// src/studio/ui/ChatPanel.tsx
|
|
5651
|
-
var
|
|
5652
|
-
var
|
|
5867
|
+
var React40 = __toESM(require("react"));
|
|
5868
|
+
var import_react_native52 = require("react-native");
|
|
5653
5869
|
|
|
5654
5870
|
// src/components/chat/ChatPage.tsx
|
|
5655
5871
|
var React37 = __toESM(require("react"));
|
|
@@ -5846,6 +6062,7 @@ function ChatPage({
|
|
|
5846
6062
|
showTypingIndicator,
|
|
5847
6063
|
renderMessageContent,
|
|
5848
6064
|
topBanner,
|
|
6065
|
+
composerTop,
|
|
5849
6066
|
composer,
|
|
5850
6067
|
overlay,
|
|
5851
6068
|
style,
|
|
@@ -5856,6 +6073,7 @@ function ChatPage({
|
|
|
5856
6073
|
const theme = useTheme();
|
|
5857
6074
|
const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
|
|
5858
6075
|
const [composerHeight, setComposerHeight] = React37.useState(0);
|
|
6076
|
+
const [composerTopHeight, setComposerTopHeight] = React37.useState(0);
|
|
5859
6077
|
const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
|
|
5860
6078
|
React37.useEffect(() => {
|
|
5861
6079
|
if (import_react_native47.Platform.OS !== "ios") return;
|
|
@@ -5867,8 +6085,9 @@ function ChatPage({
|
|
|
5867
6085
|
};
|
|
5868
6086
|
}, []);
|
|
5869
6087
|
const footerBottomPadding = import_react_native47.Platform.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
|
|
5870
|
-
const
|
|
5871
|
-
const
|
|
6088
|
+
const totalComposerHeight = composerHeight + composerTopHeight;
|
|
6089
|
+
const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
|
|
6090
|
+
const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
|
|
5872
6091
|
const resolvedOverlay = React37.useMemo(() => {
|
|
5873
6092
|
var _a;
|
|
5874
6093
|
if (!overlay) return null;
|
|
@@ -5878,6 +6097,10 @@ function ChatPage({
|
|
|
5878
6097
|
style: [prevStyle, { bottom: overlayBottom }]
|
|
5879
6098
|
});
|
|
5880
6099
|
}, [overlay, overlayBottom]);
|
|
6100
|
+
React37.useEffect(() => {
|
|
6101
|
+
if (composerTop) return;
|
|
6102
|
+
setComposerTopHeight(0);
|
|
6103
|
+
}, [composerTop]);
|
|
5881
6104
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_react_native47.View, { style: [{ flex: 1 }, style], children: [
|
|
5882
6105
|
header ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { children: header }) : null,
|
|
5883
6106
|
topBanner ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_react_native47.View, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
|
|
@@ -5902,7 +6125,7 @@ function ChatPage({
|
|
|
5902
6125
|
]
|
|
5903
6126
|
}
|
|
5904
6127
|
),
|
|
5905
|
-
/* @__PURE__ */ (0, import_jsx_runtime49.
|
|
6128
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
5906
6129
|
import_react_native47.View,
|
|
5907
6130
|
{
|
|
5908
6131
|
style: {
|
|
@@ -5914,14 +6137,24 @@ function ChatPage({
|
|
|
5914
6137
|
paddingTop: theme.spacing.sm,
|
|
5915
6138
|
paddingBottom: footerBottomPadding
|
|
5916
6139
|
},
|
|
5917
|
-
children:
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6140
|
+
children: [
|
|
6141
|
+
composerTop ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
6142
|
+
import_react_native47.View,
|
|
6143
|
+
{
|
|
6144
|
+
style: { marginBottom: theme.spacing.sm },
|
|
6145
|
+
onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
|
|
6146
|
+
children: composerTop
|
|
6147
|
+
}
|
|
6148
|
+
) : null,
|
|
6149
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
6150
|
+
ChatComposer,
|
|
6151
|
+
{
|
|
6152
|
+
...composer,
|
|
6153
|
+
attachments: composer.attachments ?? [],
|
|
6154
|
+
onLayout: ({ height }) => setComposerHeight(height)
|
|
6155
|
+
}
|
|
6156
|
+
)
|
|
6157
|
+
]
|
|
5925
6158
|
}
|
|
5926
6159
|
)
|
|
5927
6160
|
] })
|
|
@@ -6071,8 +6304,154 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
|
|
|
6071
6304
|
);
|
|
6072
6305
|
}
|
|
6073
6306
|
|
|
6074
|
-
// src/
|
|
6307
|
+
// src/components/chat/ChatQueue.tsx
|
|
6308
|
+
var React39 = __toESM(require("react"));
|
|
6309
|
+
var import_react_native51 = require("react-native");
|
|
6075
6310
|
var import_jsx_runtime53 = require("react/jsx-runtime");
|
|
6311
|
+
function ChatQueue({ items, onRemove }) {
|
|
6312
|
+
const theme = useTheme();
|
|
6313
|
+
const [expanded, setExpanded] = React39.useState({});
|
|
6314
|
+
const [canExpand, setCanExpand] = React39.useState({});
|
|
6315
|
+
const [collapsedText, setCollapsedText] = React39.useState({});
|
|
6316
|
+
const [removing, setRemoving] = React39.useState({});
|
|
6317
|
+
const buildCollapsedText = React39.useCallback((lines) => {
|
|
6318
|
+
var _a, _b;
|
|
6319
|
+
const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
|
|
6320
|
+
const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
|
|
6321
|
+
const moreLabel = "more";
|
|
6322
|
+
const reserve = `\u2026 ${moreLabel}`.length;
|
|
6323
|
+
let trimmedLine2 = line2;
|
|
6324
|
+
if (trimmedLine2.length > reserve) {
|
|
6325
|
+
trimmedLine2 = trimmedLine2.slice(0, Math.max(0, trimmedLine2.length - reserve));
|
|
6326
|
+
} else {
|
|
6327
|
+
trimmedLine2 = "";
|
|
6328
|
+
}
|
|
6329
|
+
trimmedLine2 = trimmedLine2.replace(/\s+$/, "");
|
|
6330
|
+
return `${line1}
|
|
6331
|
+
${trimmedLine2}\u2026 `;
|
|
6332
|
+
}, []);
|
|
6333
|
+
React39.useEffect(() => {
|
|
6334
|
+
if (items.length === 0) return;
|
|
6335
|
+
const ids = new Set(items.map((item) => item.id));
|
|
6336
|
+
setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6337
|
+
setCanExpand((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6338
|
+
setCollapsedText((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6339
|
+
setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6340
|
+
}, [items]);
|
|
6341
|
+
if (items.length === 0) return null;
|
|
6342
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
|
|
6343
|
+
import_react_native51.View,
|
|
6344
|
+
{
|
|
6345
|
+
style: {
|
|
6346
|
+
borderWidth: 1,
|
|
6347
|
+
borderColor: theme.colors.border,
|
|
6348
|
+
borderRadius: theme.radii.lg,
|
|
6349
|
+
marginHorizontal: theme.spacing.md,
|
|
6350
|
+
padding: theme.spacing.md,
|
|
6351
|
+
backgroundColor: "transparent"
|
|
6352
|
+
},
|
|
6353
|
+
children: [
|
|
6354
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
|
|
6355
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.View, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
|
|
6356
|
+
const isExpanded = Boolean(expanded[item.id]);
|
|
6357
|
+
const showToggle = Boolean(canExpand[item.id]);
|
|
6358
|
+
const prompt = item.prompt ?? "";
|
|
6359
|
+
const moreLabel = "more";
|
|
6360
|
+
const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
|
|
6361
|
+
const isRemoving = Boolean(removing[item.id]);
|
|
6362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
|
|
6363
|
+
import_react_native51.View,
|
|
6364
|
+
{
|
|
6365
|
+
style: {
|
|
6366
|
+
flexDirection: "row",
|
|
6367
|
+
alignItems: "flex-start",
|
|
6368
|
+
gap: theme.spacing.sm,
|
|
6369
|
+
paddingHorizontal: theme.spacing.md,
|
|
6370
|
+
paddingVertical: theme.spacing.sm,
|
|
6371
|
+
borderRadius: theme.radii.md,
|
|
6372
|
+
backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
|
|
6373
|
+
},
|
|
6374
|
+
children: [
|
|
6375
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(import_react_native51.View, { style: { flex: 1 }, children: [
|
|
6376
|
+
!canExpand[item.id] ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
6377
|
+
Text,
|
|
6378
|
+
{
|
|
6379
|
+
style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
|
|
6380
|
+
onTextLayout: (e) => {
|
|
6381
|
+
var _a;
|
|
6382
|
+
const lines = (_a = e.nativeEvent) == null ? void 0 : _a.lines;
|
|
6383
|
+
if (!lines) return;
|
|
6384
|
+
if (lines.length > 2) {
|
|
6385
|
+
setCanExpand((prev) => ({ ...prev, [item.id]: true }));
|
|
6386
|
+
setCollapsedText((prev) => ({
|
|
6387
|
+
...prev,
|
|
6388
|
+
[item.id]: buildCollapsedText(lines)
|
|
6389
|
+
}));
|
|
6390
|
+
}
|
|
6391
|
+
},
|
|
6392
|
+
children: prompt
|
|
6393
|
+
}
|
|
6394
|
+
) : null,
|
|
6395
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsxs)(
|
|
6396
|
+
Text,
|
|
6397
|
+
{
|
|
6398
|
+
variant: "bodyMuted",
|
|
6399
|
+
numberOfLines: isExpanded ? void 0 : 2,
|
|
6400
|
+
children: [
|
|
6401
|
+
displayPrompt,
|
|
6402
|
+
!isExpanded && showToggle ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
6403
|
+
Text,
|
|
6404
|
+
{
|
|
6405
|
+
color: theme.colors.text,
|
|
6406
|
+
onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: true })),
|
|
6407
|
+
suppressHighlighting: true,
|
|
6408
|
+
children: moreLabel
|
|
6409
|
+
}
|
|
6410
|
+
) : null
|
|
6411
|
+
]
|
|
6412
|
+
}
|
|
6413
|
+
),
|
|
6414
|
+
showToggle && isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
6415
|
+
import_react_native51.Pressable,
|
|
6416
|
+
{
|
|
6417
|
+
onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
|
|
6418
|
+
hitSlop: 6,
|
|
6419
|
+
style: { alignSelf: "flex-start", marginTop: 4 },
|
|
6420
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
|
|
6421
|
+
}
|
|
6422
|
+
) : null
|
|
6423
|
+
] }),
|
|
6424
|
+
/* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
6425
|
+
import_react_native51.Pressable,
|
|
6426
|
+
{
|
|
6427
|
+
onPress: () => {
|
|
6428
|
+
if (!onRemove || isRemoving) return;
|
|
6429
|
+
setRemoving((prev) => ({ ...prev, [item.id]: true }));
|
|
6430
|
+
Promise.resolve(onRemove(item.id)).finally(() => {
|
|
6431
|
+
setRemoving((prev) => {
|
|
6432
|
+
if (!prev[item.id]) return prev;
|
|
6433
|
+
const { [item.id]: _removed, ...rest } = prev;
|
|
6434
|
+
return rest;
|
|
6435
|
+
});
|
|
6436
|
+
});
|
|
6437
|
+
},
|
|
6438
|
+
hitSlop: 8,
|
|
6439
|
+
style: { alignSelf: "center" },
|
|
6440
|
+
children: isRemoving ? /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_react_native51.ActivityIndicator, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(IconClose, { size: 14, colorToken: "text" })
|
|
6441
|
+
}
|
|
6442
|
+
)
|
|
6443
|
+
]
|
|
6444
|
+
},
|
|
6445
|
+
item.id
|
|
6446
|
+
);
|
|
6447
|
+
}) })
|
|
6448
|
+
]
|
|
6449
|
+
}
|
|
6450
|
+
);
|
|
6451
|
+
}
|
|
6452
|
+
|
|
6453
|
+
// src/studio/ui/ChatPanel.tsx
|
|
6454
|
+
var import_jsx_runtime54 = require("react/jsx-runtime");
|
|
6076
6455
|
function ChatPanel({
|
|
6077
6456
|
title = "Chat",
|
|
6078
6457
|
autoFocusComposer = false,
|
|
@@ -6090,11 +6469,13 @@ function ChatPanel({
|
|
|
6090
6469
|
onClose,
|
|
6091
6470
|
onNavigateHome,
|
|
6092
6471
|
onStartDraw,
|
|
6093
|
-
onSend
|
|
6472
|
+
onSend,
|
|
6473
|
+
queueItems = [],
|
|
6474
|
+
onRemoveQueueItem
|
|
6094
6475
|
}) {
|
|
6095
|
-
const listRef =
|
|
6096
|
-
const [nearBottom, setNearBottom] =
|
|
6097
|
-
const handleSend =
|
|
6476
|
+
const listRef = React40.useRef(null);
|
|
6477
|
+
const [nearBottom, setNearBottom] = React40.useState(true);
|
|
6478
|
+
const handleSend = React40.useCallback(
|
|
6098
6479
|
async (text, composerAttachments) => {
|
|
6099
6480
|
const all = composerAttachments ?? attachments;
|
|
6100
6481
|
await onSend(text, all.length > 0 ? all : void 0);
|
|
@@ -6108,25 +6489,25 @@ function ChatPanel({
|
|
|
6108
6489
|
},
|
|
6109
6490
|
[attachments, nearBottom, onClearAttachments, onSend]
|
|
6110
6491
|
);
|
|
6111
|
-
const handleScrollToBottom =
|
|
6492
|
+
const handleScrollToBottom = React40.useCallback(() => {
|
|
6112
6493
|
var _a;
|
|
6113
6494
|
(_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
|
|
6114
6495
|
}, []);
|
|
6115
|
-
const header = /* @__PURE__ */ (0,
|
|
6496
|
+
const header = /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
6116
6497
|
ChatHeader,
|
|
6117
6498
|
{
|
|
6118
|
-
left: /* @__PURE__ */ (0,
|
|
6119
|
-
/* @__PURE__ */ (0,
|
|
6120
|
-
onNavigateHome ? /* @__PURE__ */ (0,
|
|
6499
|
+
left: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
6500
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconBack, { size: 20, colorToken: "floatingContent" }) }),
|
|
6501
|
+
onNavigateHome ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
|
|
6121
6502
|
] }),
|
|
6122
|
-
right: /* @__PURE__ */ (0,
|
|
6123
|
-
onStartDraw ? /* @__PURE__ */ (0,
|
|
6124
|
-
/* @__PURE__ */ (0,
|
|
6503
|
+
right: /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
6504
|
+
onStartDraw ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
|
|
6505
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconClose, { size: 20, colorToken: "floatingContent" }) })
|
|
6125
6506
|
] }),
|
|
6126
6507
|
center: null
|
|
6127
6508
|
}
|
|
6128
6509
|
);
|
|
6129
|
-
const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0,
|
|
6510
|
+
const topBanner = shouldForkOnEdit ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
6130
6511
|
ForkNoticeBanner,
|
|
6131
6512
|
{
|
|
6132
6513
|
isOwner: !shouldForkOnEdit,
|
|
@@ -6135,33 +6516,35 @@ function ChatPanel({
|
|
|
6135
6516
|
) : null;
|
|
6136
6517
|
const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
|
|
6137
6518
|
if (showMessagesLoading) {
|
|
6138
|
-
return /* @__PURE__ */ (0,
|
|
6139
|
-
/* @__PURE__ */ (0,
|
|
6140
|
-
topBanner ? /* @__PURE__ */ (0,
|
|
6141
|
-
/* @__PURE__ */ (0,
|
|
6142
|
-
/* @__PURE__ */ (0,
|
|
6143
|
-
/* @__PURE__ */ (0,
|
|
6144
|
-
/* @__PURE__ */ (0,
|
|
6519
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1 }, children: [
|
|
6520
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { children: header }),
|
|
6521
|
+
topBanner ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
|
|
6522
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_react_native52.View, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
|
|
6523
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.ActivityIndicator, {}),
|
|
6524
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_react_native52.View, { style: { height: 12 } }),
|
|
6525
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
|
|
6145
6526
|
] })
|
|
6146
6527
|
] });
|
|
6147
6528
|
}
|
|
6148
|
-
|
|
6529
|
+
const queueTop = queueItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null;
|
|
6530
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
6149
6531
|
ChatPage,
|
|
6150
6532
|
{
|
|
6151
6533
|
header,
|
|
6152
6534
|
messages,
|
|
6153
6535
|
showTypingIndicator,
|
|
6154
6536
|
topBanner,
|
|
6537
|
+
composerTop: queueTop,
|
|
6155
6538
|
composerHorizontalPadding: 0,
|
|
6156
6539
|
listRef,
|
|
6157
6540
|
onNearBottomChange: setNearBottom,
|
|
6158
|
-
overlay: /* @__PURE__ */ (0,
|
|
6541
|
+
overlay: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
6159
6542
|
ScrollToBottomButton,
|
|
6160
6543
|
{
|
|
6161
6544
|
visible: !nearBottom,
|
|
6162
6545
|
onPress: handleScrollToBottom,
|
|
6163
6546
|
style: { bottom: 80 },
|
|
6164
|
-
children: /* @__PURE__ */ (0,
|
|
6547
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(IconArrowDown, { size: 20, colorToken: "floatingContent" })
|
|
6165
6548
|
}
|
|
6166
6549
|
),
|
|
6167
6550
|
composer: {
|
|
@@ -6182,12 +6565,12 @@ function ChatPanel({
|
|
|
6182
6565
|
}
|
|
6183
6566
|
|
|
6184
6567
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
6185
|
-
var
|
|
6186
|
-
var
|
|
6568
|
+
var React41 = __toESM(require("react"));
|
|
6569
|
+
var import_react_native54 = require("react-native");
|
|
6187
6570
|
|
|
6188
6571
|
// src/components/primitives/Modal.tsx
|
|
6189
|
-
var
|
|
6190
|
-
var
|
|
6572
|
+
var import_react_native53 = require("react-native");
|
|
6573
|
+
var import_jsx_runtime55 = require("react/jsx-runtime");
|
|
6191
6574
|
function Modal({
|
|
6192
6575
|
visible,
|
|
6193
6576
|
onRequestClose,
|
|
@@ -6196,30 +6579,30 @@ function Modal({
|
|
|
6196
6579
|
contentStyle
|
|
6197
6580
|
}) {
|
|
6198
6581
|
const theme = useTheme();
|
|
6199
|
-
return /* @__PURE__ */ (0,
|
|
6200
|
-
|
|
6582
|
+
return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
6583
|
+
import_react_native53.Modal,
|
|
6201
6584
|
{
|
|
6202
6585
|
visible,
|
|
6203
6586
|
transparent: true,
|
|
6204
6587
|
animationType: "fade",
|
|
6205
6588
|
onRequestClose,
|
|
6206
|
-
children: /* @__PURE__ */ (0,
|
|
6207
|
-
/* @__PURE__ */ (0,
|
|
6208
|
-
|
|
6589
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_react_native53.View, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
|
|
6590
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
6591
|
+
import_react_native53.Pressable,
|
|
6209
6592
|
{
|
|
6210
6593
|
accessibilityRole: "button",
|
|
6211
6594
|
onPress: dismissOnBackdropPress ? onRequestClose : void 0,
|
|
6212
6595
|
style: { position: "absolute", inset: 0 }
|
|
6213
6596
|
}
|
|
6214
6597
|
),
|
|
6215
|
-
/* @__PURE__ */ (0,
|
|
6598
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
|
|
6216
6599
|
] })
|
|
6217
6600
|
}
|
|
6218
6601
|
);
|
|
6219
6602
|
}
|
|
6220
6603
|
|
|
6221
6604
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
6222
|
-
var
|
|
6605
|
+
var import_jsx_runtime56 = require("react/jsx-runtime");
|
|
6223
6606
|
function ConfirmMergeRequestDialog({
|
|
6224
6607
|
visible,
|
|
6225
6608
|
onOpenChange,
|
|
@@ -6230,14 +6613,14 @@ function ConfirmMergeRequestDialog({
|
|
|
6230
6613
|
onTestFirst
|
|
6231
6614
|
}) {
|
|
6232
6615
|
const theme = useTheme();
|
|
6233
|
-
const close =
|
|
6616
|
+
const close = React41.useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
6234
6617
|
const canConfirm = Boolean(mergeRequest) && !approveDisabled;
|
|
6235
|
-
const handleConfirm =
|
|
6618
|
+
const handleConfirm = React41.useCallback(() => {
|
|
6236
6619
|
if (!mergeRequest) return;
|
|
6237
6620
|
onOpenChange(false);
|
|
6238
6621
|
void onConfirm();
|
|
6239
6622
|
}, [mergeRequest, onConfirm, onOpenChange]);
|
|
6240
|
-
const handleTestFirst =
|
|
6623
|
+
const handleTestFirst = React41.useCallback(() => {
|
|
6241
6624
|
if (!mergeRequest) return;
|
|
6242
6625
|
onOpenChange(false);
|
|
6243
6626
|
void onTestFirst(mergeRequest);
|
|
@@ -6249,7 +6632,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6249
6632
|
justifyContent: "center",
|
|
6250
6633
|
alignSelf: "stretch"
|
|
6251
6634
|
};
|
|
6252
|
-
return /* @__PURE__ */ (0,
|
|
6635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
|
|
6253
6636
|
Modal,
|
|
6254
6637
|
{
|
|
6255
6638
|
visible,
|
|
@@ -6260,7 +6643,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6260
6643
|
backgroundColor: theme.colors.background
|
|
6261
6644
|
},
|
|
6262
6645
|
children: [
|
|
6263
|
-
/* @__PURE__ */ (0,
|
|
6646
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6264
6647
|
Text,
|
|
6265
6648
|
{
|
|
6266
6649
|
style: {
|
|
@@ -6272,9 +6655,9 @@ function ConfirmMergeRequestDialog({
|
|
|
6272
6655
|
children: "Are you sure you want to approve this merge request?"
|
|
6273
6656
|
}
|
|
6274
6657
|
) }),
|
|
6275
|
-
/* @__PURE__ */ (0,
|
|
6276
|
-
/* @__PURE__ */ (0,
|
|
6277
|
-
|
|
6658
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_react_native54.View, { style: { marginTop: 16 }, children: [
|
|
6659
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6660
|
+
import_react_native54.View,
|
|
6278
6661
|
{
|
|
6279
6662
|
style: [
|
|
6280
6663
|
fullWidthButtonBase,
|
|
@@ -6283,22 +6666,22 @@ function ConfirmMergeRequestDialog({
|
|
|
6283
6666
|
opacity: canConfirm ? 1 : 0.5
|
|
6284
6667
|
}
|
|
6285
6668
|
],
|
|
6286
|
-
children: /* @__PURE__ */ (0,
|
|
6287
|
-
|
|
6669
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6670
|
+
import_react_native54.Pressable,
|
|
6288
6671
|
{
|
|
6289
6672
|
accessibilityRole: "button",
|
|
6290
6673
|
accessibilityLabel: "Approve Merge",
|
|
6291
6674
|
disabled: !canConfirm,
|
|
6292
6675
|
onPress: handleConfirm,
|
|
6293
6676
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6294
|
-
children: /* @__PURE__ */ (0,
|
|
6677
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
|
|
6295
6678
|
}
|
|
6296
6679
|
)
|
|
6297
6680
|
}
|
|
6298
6681
|
),
|
|
6299
|
-
/* @__PURE__ */ (0,
|
|
6300
|
-
/* @__PURE__ */ (0,
|
|
6301
|
-
|
|
6682
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
|
|
6683
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6684
|
+
import_react_native54.View,
|
|
6302
6685
|
{
|
|
6303
6686
|
style: [
|
|
6304
6687
|
fullWidthButtonBase,
|
|
@@ -6309,22 +6692,22 @@ function ConfirmMergeRequestDialog({
|
|
|
6309
6692
|
opacity: isBuilding || !mergeRequest ? 0.5 : 1
|
|
6310
6693
|
}
|
|
6311
6694
|
],
|
|
6312
|
-
children: /* @__PURE__ */ (0,
|
|
6313
|
-
|
|
6695
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6696
|
+
import_react_native54.Pressable,
|
|
6314
6697
|
{
|
|
6315
6698
|
accessibilityRole: "button",
|
|
6316
6699
|
accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
|
|
6317
6700
|
disabled: isBuilding || !mergeRequest,
|
|
6318
6701
|
onPress: handleTestFirst,
|
|
6319
6702
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6320
|
-
children: /* @__PURE__ */ (0,
|
|
6703
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
|
|
6321
6704
|
}
|
|
6322
6705
|
)
|
|
6323
6706
|
}
|
|
6324
6707
|
),
|
|
6325
|
-
/* @__PURE__ */ (0,
|
|
6326
|
-
/* @__PURE__ */ (0,
|
|
6327
|
-
|
|
6708
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_react_native54.View, { style: { height: 8 } }),
|
|
6709
|
+
/* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6710
|
+
import_react_native54.View,
|
|
6328
6711
|
{
|
|
6329
6712
|
style: [
|
|
6330
6713
|
fullWidthButtonBase,
|
|
@@ -6334,14 +6717,14 @@ function ConfirmMergeRequestDialog({
|
|
|
6334
6717
|
borderColor: theme.colors.border
|
|
6335
6718
|
}
|
|
6336
6719
|
],
|
|
6337
|
-
children: /* @__PURE__ */ (0,
|
|
6338
|
-
|
|
6720
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
|
|
6721
|
+
import_react_native54.Pressable,
|
|
6339
6722
|
{
|
|
6340
6723
|
accessibilityRole: "button",
|
|
6341
6724
|
accessibilityLabel: "Cancel",
|
|
6342
6725
|
onPress: close,
|
|
6343
6726
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6344
|
-
children: /* @__PURE__ */ (0,
|
|
6727
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
|
|
6345
6728
|
}
|
|
6346
6729
|
)
|
|
6347
6730
|
}
|
|
@@ -6353,7 +6736,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6353
6736
|
}
|
|
6354
6737
|
|
|
6355
6738
|
// src/studio/ui/ConfirmMergeFlow.tsx
|
|
6356
|
-
var
|
|
6739
|
+
var import_jsx_runtime57 = require("react/jsx-runtime");
|
|
6357
6740
|
function ConfirmMergeFlow({
|
|
6358
6741
|
visible,
|
|
6359
6742
|
onOpenChange,
|
|
@@ -6364,7 +6747,7 @@ function ConfirmMergeFlow({
|
|
|
6364
6747
|
onConfirm,
|
|
6365
6748
|
onTestFirst
|
|
6366
6749
|
}) {
|
|
6367
|
-
return /* @__PURE__ */ (0,
|
|
6750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
|
|
6368
6751
|
ConfirmMergeRequestDialog,
|
|
6369
6752
|
{
|
|
6370
6753
|
visible,
|
|
@@ -6386,11 +6769,11 @@ function ConfirmMergeFlow({
|
|
|
6386
6769
|
}
|
|
6387
6770
|
|
|
6388
6771
|
// src/studio/hooks/useOptimisticChatMessages.ts
|
|
6389
|
-
var
|
|
6772
|
+
var React42 = __toESM(require("react"));
|
|
6390
6773
|
function makeOptimisticId() {
|
|
6391
6774
|
return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
|
|
6392
6775
|
}
|
|
6393
|
-
function
|
|
6776
|
+
function toEpochMs2(createdAt) {
|
|
6394
6777
|
if (createdAt == null) return 0;
|
|
6395
6778
|
if (typeof createdAt === "number") return createdAt;
|
|
6396
6779
|
if (createdAt instanceof Date) return createdAt.getTime();
|
|
@@ -6409,7 +6792,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
|
|
|
6409
6792
|
for (const m of candidates) {
|
|
6410
6793
|
if (m.author !== "human") continue;
|
|
6411
6794
|
if (normalize(m.content) !== target) continue;
|
|
6412
|
-
const serverMs =
|
|
6795
|
+
const serverMs = toEpochMs2(m.createdAt);
|
|
6413
6796
|
const optimisticMs = Date.parse(o.createdAtIso);
|
|
6414
6797
|
if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
|
|
6415
6798
|
if (serverMs + 12e4 < optimisticMs) continue;
|
|
@@ -6421,14 +6804,15 @@ function isOptimisticResolvedByServer(chatMessages, o) {
|
|
|
6421
6804
|
function useOptimisticChatMessages({
|
|
6422
6805
|
threadId,
|
|
6423
6806
|
shouldForkOnEdit,
|
|
6807
|
+
disableOptimistic = false,
|
|
6424
6808
|
chatMessages,
|
|
6425
6809
|
onSendChat
|
|
6426
6810
|
}) {
|
|
6427
|
-
const [optimisticChat, setOptimisticChat] =
|
|
6428
|
-
|
|
6811
|
+
const [optimisticChat, setOptimisticChat] = React42.useState([]);
|
|
6812
|
+
React42.useEffect(() => {
|
|
6429
6813
|
setOptimisticChat([]);
|
|
6430
6814
|
}, [threadId]);
|
|
6431
|
-
const messages =
|
|
6815
|
+
const messages = React42.useMemo(() => {
|
|
6432
6816
|
if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
|
|
6433
6817
|
const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
|
|
6434
6818
|
if (unresolved.length === 0) return chatMessages;
|
|
@@ -6444,7 +6828,7 @@ function useOptimisticChatMessages({
|
|
|
6444
6828
|
merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
6445
6829
|
return merged;
|
|
6446
6830
|
}, [chatMessages, optimisticChat]);
|
|
6447
|
-
|
|
6831
|
+
React42.useEffect(() => {
|
|
6448
6832
|
if (optimisticChat.length === 0) return;
|
|
6449
6833
|
setOptimisticChat((prev) => {
|
|
6450
6834
|
if (prev.length === 0) return prev;
|
|
@@ -6452,9 +6836,9 @@ function useOptimisticChatMessages({
|
|
|
6452
6836
|
return next.length === prev.length ? prev : next;
|
|
6453
6837
|
});
|
|
6454
6838
|
}, [chatMessages, optimisticChat.length]);
|
|
6455
|
-
const onSend =
|
|
6839
|
+
const onSend = React42.useCallback(
|
|
6456
6840
|
async (text, attachments) => {
|
|
6457
|
-
if (shouldForkOnEdit) {
|
|
6841
|
+
if (shouldForkOnEdit || disableOptimistic) {
|
|
6458
6842
|
await onSendChat(text, attachments);
|
|
6459
6843
|
return;
|
|
6460
6844
|
}
|
|
@@ -6466,14 +6850,14 @@ function useOptimisticChatMessages({
|
|
|
6466
6850
|
setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
|
|
6467
6851
|
});
|
|
6468
6852
|
},
|
|
6469
|
-
[chatMessages, onSendChat, shouldForkOnEdit]
|
|
6853
|
+
[chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
|
|
6470
6854
|
);
|
|
6471
6855
|
return { messages, onSend };
|
|
6472
6856
|
}
|
|
6473
6857
|
|
|
6474
6858
|
// src/studio/ui/StudioOverlay.tsx
|
|
6475
6859
|
var import_studio_control = require("@comergehq/studio-control");
|
|
6476
|
-
var
|
|
6860
|
+
var import_jsx_runtime58 = require("react/jsx-runtime");
|
|
6477
6861
|
function StudioOverlay({
|
|
6478
6862
|
captureTargetRef,
|
|
6479
6863
|
app,
|
|
@@ -6500,46 +6884,50 @@ function StudioOverlay({
|
|
|
6500
6884
|
chatSending,
|
|
6501
6885
|
chatShowTypingIndicator,
|
|
6502
6886
|
onSendChat,
|
|
6887
|
+
chatQueueItems,
|
|
6888
|
+
onRemoveQueueItem,
|
|
6503
6889
|
onNavigateHome,
|
|
6504
6890
|
showBubble,
|
|
6505
6891
|
studioControlOptions
|
|
6506
6892
|
}) {
|
|
6507
6893
|
const theme = useTheme();
|
|
6508
|
-
const { width } = (0,
|
|
6509
|
-
const [sheetOpen, setSheetOpen] =
|
|
6510
|
-
const sheetOpenRef =
|
|
6511
|
-
const [activePage, setActivePage] =
|
|
6512
|
-
const [drawing, setDrawing] =
|
|
6513
|
-
const [chatAttachments, setChatAttachments] =
|
|
6514
|
-
const [commentsAppId, setCommentsAppId] =
|
|
6515
|
-
const [commentsCount, setCommentsCount] =
|
|
6894
|
+
const { width } = (0, import_react_native55.useWindowDimensions)();
|
|
6895
|
+
const [sheetOpen, setSheetOpen] = React43.useState(false);
|
|
6896
|
+
const sheetOpenRef = React43.useRef(sheetOpen);
|
|
6897
|
+
const [activePage, setActivePage] = React43.useState("preview");
|
|
6898
|
+
const [drawing, setDrawing] = React43.useState(false);
|
|
6899
|
+
const [chatAttachments, setChatAttachments] = React43.useState([]);
|
|
6900
|
+
const [commentsAppId, setCommentsAppId] = React43.useState(null);
|
|
6901
|
+
const [commentsCount, setCommentsCount] = React43.useState(null);
|
|
6516
6902
|
const threadId = (app == null ? void 0 : app.threadId) ?? null;
|
|
6903
|
+
const disableOptimistic = Boolean(chatQueueItems && chatQueueItems.length > 0) || (app == null ? void 0 : app.status) === "editing";
|
|
6517
6904
|
const optimistic = useOptimisticChatMessages({
|
|
6518
6905
|
threadId,
|
|
6519
6906
|
shouldForkOnEdit,
|
|
6907
|
+
disableOptimistic,
|
|
6520
6908
|
chatMessages,
|
|
6521
6909
|
onSendChat
|
|
6522
6910
|
});
|
|
6523
|
-
const [confirmMrId, setConfirmMrId] =
|
|
6524
|
-
const confirmMr =
|
|
6911
|
+
const [confirmMrId, setConfirmMrId] = React43.useState(null);
|
|
6912
|
+
const confirmMr = React43.useMemo(
|
|
6525
6913
|
() => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
|
|
6526
6914
|
[confirmMrId, incomingMergeRequests]
|
|
6527
6915
|
);
|
|
6528
|
-
const handleSheetOpenChange =
|
|
6916
|
+
const handleSheetOpenChange = React43.useCallback((open) => {
|
|
6529
6917
|
setSheetOpen(open);
|
|
6530
|
-
if (!open)
|
|
6918
|
+
if (!open) import_react_native55.Keyboard.dismiss();
|
|
6531
6919
|
}, []);
|
|
6532
|
-
const closeSheet =
|
|
6920
|
+
const closeSheet = React43.useCallback(() => {
|
|
6533
6921
|
handleSheetOpenChange(false);
|
|
6534
6922
|
}, [handleSheetOpenChange]);
|
|
6535
|
-
const openSheet =
|
|
6536
|
-
const goToChat =
|
|
6923
|
+
const openSheet = React43.useCallback(() => setSheetOpen(true), []);
|
|
6924
|
+
const goToChat = React43.useCallback(() => {
|
|
6537
6925
|
setActivePage("chat");
|
|
6538
6926
|
openSheet();
|
|
6539
6927
|
}, [openSheet]);
|
|
6540
|
-
const backToPreview =
|
|
6541
|
-
if (
|
|
6542
|
-
|
|
6928
|
+
const backToPreview = React43.useCallback(() => {
|
|
6929
|
+
if (import_react_native55.Platform.OS !== "ios") {
|
|
6930
|
+
import_react_native55.Keyboard.dismiss();
|
|
6543
6931
|
setActivePage("preview");
|
|
6544
6932
|
return;
|
|
6545
6933
|
}
|
|
@@ -6551,15 +6939,15 @@ function StudioOverlay({
|
|
|
6551
6939
|
clearTimeout(t);
|
|
6552
6940
|
setActivePage("preview");
|
|
6553
6941
|
};
|
|
6554
|
-
const sub =
|
|
6942
|
+
const sub = import_react_native55.Keyboard.addListener("keyboardDidHide", finalize);
|
|
6555
6943
|
const t = setTimeout(finalize, 350);
|
|
6556
|
-
|
|
6944
|
+
import_react_native55.Keyboard.dismiss();
|
|
6557
6945
|
}, []);
|
|
6558
|
-
const startDraw =
|
|
6946
|
+
const startDraw = React43.useCallback(() => {
|
|
6559
6947
|
setDrawing(true);
|
|
6560
6948
|
closeSheet();
|
|
6561
6949
|
}, [closeSheet]);
|
|
6562
|
-
const handleDrawCapture =
|
|
6950
|
+
const handleDrawCapture = React43.useCallback(
|
|
6563
6951
|
(dataUrl) => {
|
|
6564
6952
|
setChatAttachments((prev) => [...prev, dataUrl]);
|
|
6565
6953
|
setDrawing(false);
|
|
@@ -6568,7 +6956,7 @@ function StudioOverlay({
|
|
|
6568
6956
|
},
|
|
6569
6957
|
[openSheet]
|
|
6570
6958
|
);
|
|
6571
|
-
const toggleSheet =
|
|
6959
|
+
const toggleSheet = React43.useCallback(async () => {
|
|
6572
6960
|
if (!sheetOpen) {
|
|
6573
6961
|
const shouldExitTest = Boolean(testingMrId) || isTesting;
|
|
6574
6962
|
if (shouldExitTest) {
|
|
@@ -6580,7 +6968,7 @@ function StudioOverlay({
|
|
|
6580
6968
|
closeSheet();
|
|
6581
6969
|
}
|
|
6582
6970
|
}, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
|
|
6583
|
-
const handleTestMr =
|
|
6971
|
+
const handleTestMr = React43.useCallback(
|
|
6584
6972
|
async (mr) => {
|
|
6585
6973
|
if (!onTestMr) return;
|
|
6586
6974
|
await onTestMr(mr);
|
|
@@ -6588,10 +6976,10 @@ function StudioOverlay({
|
|
|
6588
6976
|
},
|
|
6589
6977
|
[closeSheet, onTestMr]
|
|
6590
6978
|
);
|
|
6591
|
-
|
|
6979
|
+
React43.useEffect(() => {
|
|
6592
6980
|
sheetOpenRef.current = sheetOpen;
|
|
6593
6981
|
}, [sheetOpen]);
|
|
6594
|
-
|
|
6982
|
+
React43.useEffect(() => {
|
|
6595
6983
|
const poller = (0, import_studio_control.startStudioControlPolling)((action) => {
|
|
6596
6984
|
if (action === "show" && !sheetOpenRef.current) openSheet();
|
|
6597
6985
|
if (action === "hide" && sheetOpenRef.current) closeSheet();
|
|
@@ -6599,17 +6987,17 @@ function StudioOverlay({
|
|
|
6599
6987
|
}, studioControlOptions);
|
|
6600
6988
|
return () => poller.stop();
|
|
6601
6989
|
}, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
|
|
6602
|
-
|
|
6990
|
+
React43.useEffect(() => {
|
|
6603
6991
|
void (0, import_studio_control.publishComergeStudioUIState)(sheetOpen, studioControlOptions);
|
|
6604
6992
|
}, [sheetOpen, studioControlOptions]);
|
|
6605
|
-
return /* @__PURE__ */ (0,
|
|
6606
|
-
/* @__PURE__ */ (0,
|
|
6607
|
-
/* @__PURE__ */ (0,
|
|
6993
|
+
return /* @__PURE__ */ (0, import_jsx_runtime58.jsxs)(import_jsx_runtime58.Fragment, { children: [
|
|
6994
|
+
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
|
|
6995
|
+
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6608
6996
|
StudioSheetPager,
|
|
6609
6997
|
{
|
|
6610
6998
|
activePage,
|
|
6611
6999
|
width,
|
|
6612
|
-
preview: /* @__PURE__ */ (0,
|
|
7000
|
+
preview: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6613
7001
|
PreviewPanel,
|
|
6614
7002
|
{
|
|
6615
7003
|
app,
|
|
@@ -6635,7 +7023,7 @@ function StudioOverlay({
|
|
|
6635
7023
|
commentCountOverride: commentsCount ?? void 0
|
|
6636
7024
|
}
|
|
6637
7025
|
),
|
|
6638
|
-
chat: /* @__PURE__ */ (0,
|
|
7026
|
+
chat: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6639
7027
|
ChatPanel,
|
|
6640
7028
|
{
|
|
6641
7029
|
messages: optimistic.messages,
|
|
@@ -6653,12 +7041,14 @@ function StudioOverlay({
|
|
|
6653
7041
|
onClose: closeSheet,
|
|
6654
7042
|
onNavigateHome,
|
|
6655
7043
|
onStartDraw: startDraw,
|
|
6656
|
-
onSend: optimistic.onSend
|
|
7044
|
+
onSend: optimistic.onSend,
|
|
7045
|
+
queueItems: chatQueueItems,
|
|
7046
|
+
onRemoveQueueItem
|
|
6657
7047
|
}
|
|
6658
7048
|
)
|
|
6659
7049
|
}
|
|
6660
7050
|
) }),
|
|
6661
|
-
showBubble && /* @__PURE__ */ (0,
|
|
7051
|
+
showBubble && /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6662
7052
|
Bubble,
|
|
6663
7053
|
{
|
|
6664
7054
|
visible: !sheetOpen && !drawing,
|
|
@@ -6666,10 +7056,10 @@ function StudioOverlay({
|
|
|
6666
7056
|
badgeCount: incomingMergeRequests.length,
|
|
6667
7057
|
onPress: toggleSheet,
|
|
6668
7058
|
isLoading: (app == null ? void 0 : app.status) === "editing",
|
|
6669
|
-
children: /* @__PURE__ */ (0,
|
|
7059
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(import_react_native55.View, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
|
|
6670
7060
|
}
|
|
6671
7061
|
),
|
|
6672
|
-
/* @__PURE__ */ (0,
|
|
7062
|
+
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6673
7063
|
DrawModeOverlay,
|
|
6674
7064
|
{
|
|
6675
7065
|
visible: drawing,
|
|
@@ -6678,7 +7068,7 @@ function StudioOverlay({
|
|
|
6678
7068
|
onCapture: handleDrawCapture
|
|
6679
7069
|
}
|
|
6680
7070
|
),
|
|
6681
|
-
/* @__PURE__ */ (0,
|
|
7071
|
+
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6682
7072
|
ConfirmMergeFlow,
|
|
6683
7073
|
{
|
|
6684
7074
|
visible: Boolean(confirmMr),
|
|
@@ -6691,7 +7081,7 @@ function StudioOverlay({
|
|
|
6691
7081
|
onTestFirst: handleTestMr
|
|
6692
7082
|
}
|
|
6693
7083
|
),
|
|
6694
|
-
/* @__PURE__ */ (0,
|
|
7084
|
+
/* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
|
|
6695
7085
|
AppCommentsSheet,
|
|
6696
7086
|
{
|
|
6697
7087
|
appId: commentsAppId,
|
|
@@ -6703,8 +7093,190 @@ function StudioOverlay({
|
|
|
6703
7093
|
] });
|
|
6704
7094
|
}
|
|
6705
7095
|
|
|
7096
|
+
// src/studio/hooks/useEditQueue.ts
|
|
7097
|
+
var React44 = __toESM(require("react"));
|
|
7098
|
+
|
|
7099
|
+
// src/data/apps/edit-queue/remote.ts
|
|
7100
|
+
var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
|
|
7101
|
+
async list(appId) {
|
|
7102
|
+
const { data } = await api.get(
|
|
7103
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue`
|
|
7104
|
+
);
|
|
7105
|
+
return data;
|
|
7106
|
+
}
|
|
7107
|
+
async update(appId, queueItemId, payload) {
|
|
7108
|
+
const { data } = await api.patch(
|
|
7109
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`,
|
|
7110
|
+
payload
|
|
7111
|
+
);
|
|
7112
|
+
return data;
|
|
7113
|
+
}
|
|
7114
|
+
async cancel(appId, queueItemId) {
|
|
7115
|
+
const { data } = await api.delete(
|
|
7116
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`
|
|
7117
|
+
);
|
|
7118
|
+
return data;
|
|
7119
|
+
}
|
|
7120
|
+
};
|
|
7121
|
+
var editQueueRemoteDataSource = new EditQueueRemoteDataSourceImpl();
|
|
7122
|
+
|
|
7123
|
+
// src/data/apps/edit-queue/repository.ts
|
|
7124
|
+
var ACTIVE_STATUSES = ["pending"];
|
|
7125
|
+
function toString(value) {
|
|
7126
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
7127
|
+
}
|
|
7128
|
+
function toAttachments(value) {
|
|
7129
|
+
return Array.isArray(value) ? value : [];
|
|
7130
|
+
}
|
|
7131
|
+
function mapQueueItem(row) {
|
|
7132
|
+
const payload = row.payload ?? {};
|
|
7133
|
+
return {
|
|
7134
|
+
id: row.id,
|
|
7135
|
+
status: row.status,
|
|
7136
|
+
prompt: toString(payload.trimmedPrompt),
|
|
7137
|
+
messageId: toString(payload.messageId),
|
|
7138
|
+
attachments: toAttachments(payload.attachments),
|
|
7139
|
+
createdAt: row.created_at,
|
|
7140
|
+
updatedAt: row.updated_at,
|
|
7141
|
+
runAfter: row.run_after,
|
|
7142
|
+
priority: row.priority
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
var EditQueueRepositoryImpl = class extends BaseRepository {
|
|
7146
|
+
constructor(remote) {
|
|
7147
|
+
super();
|
|
7148
|
+
this.remote = remote;
|
|
7149
|
+
}
|
|
7150
|
+
async list(appId) {
|
|
7151
|
+
const res = await this.remote.list(appId);
|
|
7152
|
+
const data = this.unwrapOrThrow(res);
|
|
7153
|
+
return data.items ?? [];
|
|
7154
|
+
}
|
|
7155
|
+
async update(appId, queueItemId, payload) {
|
|
7156
|
+
const res = await this.remote.update(appId, queueItemId, payload);
|
|
7157
|
+
return this.unwrapOrThrow(res);
|
|
7158
|
+
}
|
|
7159
|
+
async cancel(appId, queueItemId) {
|
|
7160
|
+
const res = await this.remote.cancel(appId, queueItemId);
|
|
7161
|
+
return this.unwrapOrThrow(res);
|
|
7162
|
+
}
|
|
7163
|
+
subscribeEditQueue(appId, handlers) {
|
|
7164
|
+
const supabase = getSupabaseClient();
|
|
7165
|
+
const channel = supabase.channel(`edit-queue:app:${appId}`).on(
|
|
7166
|
+
"postgres_changes",
|
|
7167
|
+
{ event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7168
|
+
(payload) => {
|
|
7169
|
+
var _a;
|
|
7170
|
+
const row = payload.new;
|
|
7171
|
+
if (row.kind !== "edit") return;
|
|
7172
|
+
const item = mapQueueItem(row);
|
|
7173
|
+
if (!ACTIVE_STATUSES.includes(item.status)) return;
|
|
7174
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
|
|
7175
|
+
}
|
|
7176
|
+
).on(
|
|
7177
|
+
"postgres_changes",
|
|
7178
|
+
{ event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7179
|
+
(payload) => {
|
|
7180
|
+
var _a, _b;
|
|
7181
|
+
const row = payload.new;
|
|
7182
|
+
if (row.kind !== "edit") return;
|
|
7183
|
+
const item = mapQueueItem(row);
|
|
7184
|
+
if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
|
|
7185
|
+
else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
|
|
7186
|
+
}
|
|
7187
|
+
).on(
|
|
7188
|
+
"postgres_changes",
|
|
7189
|
+
{ event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7190
|
+
(payload) => {
|
|
7191
|
+
var _a;
|
|
7192
|
+
const row = payload.old;
|
|
7193
|
+
if (row.kind !== "edit") return;
|
|
7194
|
+
(_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
|
|
7195
|
+
}
|
|
7196
|
+
).subscribe();
|
|
7197
|
+
return () => {
|
|
7198
|
+
supabase.removeChannel(channel);
|
|
7199
|
+
};
|
|
7200
|
+
}
|
|
7201
|
+
};
|
|
7202
|
+
var editQueueRepository = new EditQueueRepositoryImpl(
|
|
7203
|
+
editQueueRemoteDataSource
|
|
7204
|
+
);
|
|
7205
|
+
|
|
7206
|
+
// src/studio/hooks/useEditQueue.ts
|
|
7207
|
+
function useEditQueue(appId) {
|
|
7208
|
+
const [items, setItems] = React44.useState([]);
|
|
7209
|
+
const [loading, setLoading] = React44.useState(false);
|
|
7210
|
+
const [error, setError] = React44.useState(null);
|
|
7211
|
+
const activeRequestIdRef = React44.useRef(0);
|
|
7212
|
+
const foregroundSignal = useForegroundSignal(Boolean(appId));
|
|
7213
|
+
const upsertSorted = React44.useCallback((prev, nextItem) => {
|
|
7214
|
+
const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
|
|
7215
|
+
next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
7216
|
+
return next;
|
|
7217
|
+
}, []);
|
|
7218
|
+
const refetch = React44.useCallback(async () => {
|
|
7219
|
+
if (!appId) {
|
|
7220
|
+
setItems([]);
|
|
7221
|
+
return;
|
|
7222
|
+
}
|
|
7223
|
+
const requestId = ++activeRequestIdRef.current;
|
|
7224
|
+
setLoading(true);
|
|
7225
|
+
setError(null);
|
|
7226
|
+
try {
|
|
7227
|
+
const list = await editQueueRepository.list(appId);
|
|
7228
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
7229
|
+
setItems([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
|
|
7230
|
+
} catch (e) {
|
|
7231
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
7232
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
7233
|
+
setItems([]);
|
|
7234
|
+
} finally {
|
|
7235
|
+
if (activeRequestIdRef.current === requestId) setLoading(false);
|
|
7236
|
+
}
|
|
7237
|
+
}, [appId]);
|
|
7238
|
+
React44.useEffect(() => {
|
|
7239
|
+
void refetch();
|
|
7240
|
+
}, [refetch]);
|
|
7241
|
+
React44.useEffect(() => {
|
|
7242
|
+
if (!appId) return;
|
|
7243
|
+
const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
|
|
7244
|
+
onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
|
|
7245
|
+
onUpdate: (item) => setItems((prev) => upsertSorted(prev, item)),
|
|
7246
|
+
onDelete: (item) => setItems((prev) => prev.filter((x) => x.id !== item.id))
|
|
7247
|
+
});
|
|
7248
|
+
return unsubscribe;
|
|
7249
|
+
}, [appId, upsertSorted, foregroundSignal]);
|
|
7250
|
+
React44.useEffect(() => {
|
|
7251
|
+
if (!appId) return;
|
|
7252
|
+
if (foregroundSignal <= 0) return;
|
|
7253
|
+
void refetch();
|
|
7254
|
+
}, [appId, foregroundSignal, refetch]);
|
|
7255
|
+
return { items, loading, error, refetch };
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
// src/studio/hooks/useEditQueueActions.ts
|
|
7259
|
+
var React45 = __toESM(require("react"));
|
|
7260
|
+
function useEditQueueActions(appId) {
|
|
7261
|
+
const update = React45.useCallback(
|
|
7262
|
+
async (queueItemId, payload) => {
|
|
7263
|
+
if (!appId) return;
|
|
7264
|
+
await editQueueRepository.update(appId, queueItemId, payload);
|
|
7265
|
+
},
|
|
7266
|
+
[appId]
|
|
7267
|
+
);
|
|
7268
|
+
const cancel = React45.useCallback(
|
|
7269
|
+
async (queueItemId) => {
|
|
7270
|
+
if (!appId) return;
|
|
7271
|
+
await editQueueRepository.cancel(appId, queueItemId);
|
|
7272
|
+
},
|
|
7273
|
+
[appId]
|
|
7274
|
+
);
|
|
7275
|
+
return { update, cancel };
|
|
7276
|
+
}
|
|
7277
|
+
|
|
6706
7278
|
// src/studio/ComergeStudio.tsx
|
|
6707
|
-
var
|
|
7279
|
+
var import_jsx_runtime59 = require("react/jsx-runtime");
|
|
6708
7280
|
function ComergeStudio({
|
|
6709
7281
|
appId,
|
|
6710
7282
|
clientKey: clientKey2,
|
|
@@ -6715,17 +7287,17 @@ function ComergeStudio({
|
|
|
6715
7287
|
studioControlOptions,
|
|
6716
7288
|
embeddedBaseBundles
|
|
6717
7289
|
}) {
|
|
6718
|
-
const [activeAppId, setActiveAppId] =
|
|
6719
|
-
const [runtimeAppId, setRuntimeAppId] =
|
|
6720
|
-
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] =
|
|
6721
|
-
const platform =
|
|
6722
|
-
|
|
7290
|
+
const [activeAppId, setActiveAppId] = React46.useState(appId);
|
|
7291
|
+
const [runtimeAppId, setRuntimeAppId] = React46.useState(appId);
|
|
7292
|
+
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React46.useState(null);
|
|
7293
|
+
const platform = React46.useMemo(() => import_react_native56.Platform.OS === "ios" ? "ios" : "android", []);
|
|
7294
|
+
React46.useEffect(() => {
|
|
6723
7295
|
setActiveAppId(appId);
|
|
6724
7296
|
setRuntimeAppId(appId);
|
|
6725
7297
|
setPendingRuntimeTargetAppId(null);
|
|
6726
7298
|
}, [appId]);
|
|
6727
|
-
const captureTargetRef =
|
|
6728
|
-
return /* @__PURE__ */ (0,
|
|
7299
|
+
const captureTargetRef = React46.useRef(null);
|
|
7300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native56.View, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_bottom_sheet6.BottomSheetModalProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
|
|
6729
7301
|
ComergeStudioInner,
|
|
6730
7302
|
{
|
|
6731
7303
|
userId,
|
|
@@ -6766,11 +7338,11 @@ function ComergeStudioInner({
|
|
|
6766
7338
|
const { app, loading: appLoading } = useApp(activeAppId);
|
|
6767
7339
|
const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
|
|
6768
7340
|
const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
|
|
6769
|
-
const sawEditingOnPendingTargetRef =
|
|
6770
|
-
|
|
7341
|
+
const sawEditingOnPendingTargetRef = React46.useRef(false);
|
|
7342
|
+
React46.useEffect(() => {
|
|
6771
7343
|
sawEditingOnPendingTargetRef.current = false;
|
|
6772
7344
|
}, [pendingRuntimeTargetAppId]);
|
|
6773
|
-
|
|
7345
|
+
React46.useEffect(() => {
|
|
6774
7346
|
if (!pendingRuntimeTargetAppId) return;
|
|
6775
7347
|
if (activeAppId !== pendingRuntimeTargetAppId) return;
|
|
6776
7348
|
if ((app == null ? void 0 : app.status) === "editing") {
|
|
@@ -6788,13 +7360,13 @@ function ComergeStudioInner({
|
|
|
6788
7360
|
canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
|
|
6789
7361
|
embeddedBaseBundles
|
|
6790
7362
|
});
|
|
6791
|
-
const sawEditingOnActiveAppRef =
|
|
6792
|
-
const [showPostEditPreparing, setShowPostEditPreparing] =
|
|
6793
|
-
|
|
7363
|
+
const sawEditingOnActiveAppRef = React46.useRef(false);
|
|
7364
|
+
const [showPostEditPreparing, setShowPostEditPreparing] = React46.useState(false);
|
|
7365
|
+
React46.useEffect(() => {
|
|
6794
7366
|
sawEditingOnActiveAppRef.current = false;
|
|
6795
7367
|
setShowPostEditPreparing(false);
|
|
6796
7368
|
}, [activeAppId]);
|
|
6797
|
-
|
|
7369
|
+
React46.useEffect(() => {
|
|
6798
7370
|
if (!(app == null ? void 0 : app.id)) return;
|
|
6799
7371
|
if (app.status === "editing") {
|
|
6800
7372
|
sawEditingOnActiveAppRef.current = true;
|
|
@@ -6806,7 +7378,7 @@ function ComergeStudioInner({
|
|
|
6806
7378
|
sawEditingOnActiveAppRef.current = false;
|
|
6807
7379
|
}
|
|
6808
7380
|
}, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
|
|
6809
|
-
|
|
7381
|
+
React46.useEffect(() => {
|
|
6810
7382
|
if (!showPostEditPreparing) return;
|
|
6811
7383
|
const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
|
|
6812
7384
|
if (!stillProcessingBaseBundle) {
|
|
@@ -6815,15 +7387,27 @@ function ComergeStudioInner({
|
|
|
6815
7387
|
}, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
|
|
6816
7388
|
const threadId = (app == null ? void 0 : app.threadId) ?? "";
|
|
6817
7389
|
const thread = useThreadMessages(threadId);
|
|
7390
|
+
const editQueue = useEditQueue(activeAppId);
|
|
7391
|
+
const editQueueActions = useEditQueueActions(activeAppId);
|
|
7392
|
+
const [lastEditQueueInfo, setLastEditQueueInfo] = React46.useState(null);
|
|
7393
|
+
const lastEditQueueInfoRef = React46.useRef(null);
|
|
7394
|
+
const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React46.useState(false);
|
|
6818
7395
|
const mergeRequests = useMergeRequests({ appId: activeAppId });
|
|
6819
|
-
const hasOpenOutgoingMr =
|
|
7396
|
+
const hasOpenOutgoingMr = React46.useMemo(() => {
|
|
6820
7397
|
return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
|
|
6821
7398
|
}, [mergeRequests.lists.outgoing]);
|
|
6822
|
-
const incomingReviewMrs =
|
|
7399
|
+
const incomingReviewMrs = React46.useMemo(() => {
|
|
6823
7400
|
if (!userId) return mergeRequests.lists.incoming;
|
|
6824
7401
|
return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
|
|
6825
7402
|
}, [mergeRequests.lists.incoming, userId]);
|
|
6826
7403
|
const uploader = useAttachmentUpload();
|
|
7404
|
+
const updateLastEditQueueInfo = React46.useCallback(
|
|
7405
|
+
(info) => {
|
|
7406
|
+
lastEditQueueInfoRef.current = info;
|
|
7407
|
+
setLastEditQueueInfo(info);
|
|
7408
|
+
},
|
|
7409
|
+
[]
|
|
7410
|
+
);
|
|
6827
7411
|
const actions = useStudioActions({
|
|
6828
7412
|
userId,
|
|
6829
7413
|
app,
|
|
@@ -6838,20 +7422,62 @@ function ComergeStudioInner({
|
|
|
6838
7422
|
setPendingRuntimeTargetAppId(null);
|
|
6839
7423
|
}
|
|
6840
7424
|
},
|
|
6841
|
-
uploadAttachments: uploader.uploadBase64Images
|
|
7425
|
+
uploadAttachments: uploader.uploadBase64Images,
|
|
7426
|
+
onEditStart: () => {
|
|
7427
|
+
if (editQueue.items.length === 0) {
|
|
7428
|
+
setSuppressQueueUntilResponse(true);
|
|
7429
|
+
}
|
|
7430
|
+
},
|
|
7431
|
+
onEditQueued: (info) => {
|
|
7432
|
+
updateLastEditQueueInfo(info);
|
|
7433
|
+
if (info.queuePosition !== 1) {
|
|
7434
|
+
setSuppressQueueUntilResponse(false);
|
|
7435
|
+
}
|
|
7436
|
+
},
|
|
7437
|
+
onEditFinished: () => {
|
|
7438
|
+
var _a;
|
|
7439
|
+
if (((_a = lastEditQueueInfoRef.current) == null ? void 0 : _a.queuePosition) !== 1) {
|
|
7440
|
+
setSuppressQueueUntilResponse(false);
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
6842
7443
|
});
|
|
6843
|
-
const chatSendDisabled =
|
|
6844
|
-
const [processingMrId, setProcessingMrId] =
|
|
6845
|
-
const [testingMrId, setTestingMrId] =
|
|
6846
|
-
const chatShowTypingIndicator =
|
|
7444
|
+
const chatSendDisabled = false;
|
|
7445
|
+
const [processingMrId, setProcessingMrId] = React46.useState(null);
|
|
7446
|
+
const [testingMrId, setTestingMrId] = React46.useState(null);
|
|
7447
|
+
const chatShowTypingIndicator = React46.useMemo(() => {
|
|
6847
7448
|
var _a;
|
|
6848
7449
|
if (!thread.raw || thread.raw.length === 0) return false;
|
|
6849
7450
|
const last = thread.raw[thread.raw.length - 1];
|
|
6850
7451
|
const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
|
|
6851
7452
|
return payloadType !== "outcome";
|
|
6852
7453
|
}, [thread.raw]);
|
|
6853
|
-
|
|
6854
|
-
|
|
7454
|
+
React46.useEffect(() => {
|
|
7455
|
+
updateLastEditQueueInfo(null);
|
|
7456
|
+
setSuppressQueueUntilResponse(false);
|
|
7457
|
+
}, [activeAppId, updateLastEditQueueInfo]);
|
|
7458
|
+
React46.useEffect(() => {
|
|
7459
|
+
if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
|
|
7460
|
+
const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
|
|
7461
|
+
if (!stillPresent) {
|
|
7462
|
+
updateLastEditQueueInfo(null);
|
|
7463
|
+
setSuppressQueueUntilResponse(false);
|
|
7464
|
+
}
|
|
7465
|
+
}, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
|
|
7466
|
+
const chatQueueItems = React46.useMemo(() => {
|
|
7467
|
+
var _a;
|
|
7468
|
+
if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
|
|
7469
|
+
return [];
|
|
7470
|
+
}
|
|
7471
|
+
if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
|
|
7472
|
+
return editQueue.items;
|
|
7473
|
+
}
|
|
7474
|
+
if (editQueue.items.length === 1 && ((_a = editQueue.items[0]) == null ? void 0 : _a.id) === lastEditQueueInfo.queueItemId) {
|
|
7475
|
+
return [];
|
|
7476
|
+
}
|
|
7477
|
+
return editQueue.items;
|
|
7478
|
+
}, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
|
|
7479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(import_react_native56.View, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_react_native56.View, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
|
|
7480
|
+
/* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
|
|
6855
7481
|
RuntimeRenderer,
|
|
6856
7482
|
{
|
|
6857
7483
|
appKey,
|
|
@@ -6861,7 +7487,7 @@ function ComergeStudioInner({
|
|
|
6861
7487
|
allowInitialPreparing: !embeddedBaseBundles
|
|
6862
7488
|
}
|
|
6863
7489
|
),
|
|
6864
|
-
/* @__PURE__ */ (0,
|
|
7490
|
+
/* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
|
|
6865
7491
|
StudioOverlay,
|
|
6866
7492
|
{
|
|
6867
7493
|
captureTargetRef,
|
|
@@ -6913,6 +7539,8 @@ function ComergeStudioInner({
|
|
|
6913
7539
|
chatSending: actions.sending,
|
|
6914
7540
|
chatShowTypingIndicator,
|
|
6915
7541
|
onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
|
|
7542
|
+
chatQueueItems,
|
|
7543
|
+
onRemoveQueueItem: (id) => editQueueActions.cancel(id),
|
|
6916
7544
|
onNavigateHome,
|
|
6917
7545
|
showBubble,
|
|
6918
7546
|
studioControlOptions
|