@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.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 React46 from "react";
|
|
10
|
+
import { Platform as RNPlatform, View as View46 } from "react-native";
|
|
11
11
|
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
12
12
|
|
|
13
13
|
// src/studio/bootstrap/StudioBootstrap.tsx
|
|
@@ -816,6 +816,35 @@ function extractMeta(payload) {
|
|
|
816
816
|
threadId: typeof obj.threadId === "string" ? obj.threadId : void 0
|
|
817
817
|
};
|
|
818
818
|
}
|
|
819
|
+
function getPayloadMeta(payload) {
|
|
820
|
+
const meta = payload == null ? void 0 : payload.meta;
|
|
821
|
+
if (!meta || typeof meta !== "object") return null;
|
|
822
|
+
return meta;
|
|
823
|
+
}
|
|
824
|
+
function isQueuedHiddenMessage(m) {
|
|
825
|
+
if (m.authorType !== "human") return false;
|
|
826
|
+
const meta = getPayloadMeta(m.payload);
|
|
827
|
+
return (meta == null ? void 0 : meta.visibility) === "queued";
|
|
828
|
+
}
|
|
829
|
+
function toEpochMs(value) {
|
|
830
|
+
if (value == null) return 0;
|
|
831
|
+
if (typeof value === "number") return value;
|
|
832
|
+
if (value instanceof Date) return value.getTime();
|
|
833
|
+
const parsed = Date.parse(String(value));
|
|
834
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
835
|
+
}
|
|
836
|
+
function getEffectiveSortMs(m) {
|
|
837
|
+
const meta = getPayloadMeta(m.payload);
|
|
838
|
+
const runStartedAt = meta == null ? void 0 : meta.runStartedAt;
|
|
839
|
+
const runMs = toEpochMs(runStartedAt);
|
|
840
|
+
return runMs > 0 ? runMs : toEpochMs(m.createdAt);
|
|
841
|
+
}
|
|
842
|
+
function compareMessages(a, b) {
|
|
843
|
+
const aMs = getEffectiveSortMs(a);
|
|
844
|
+
const bMs = getEffectiveSortMs(b);
|
|
845
|
+
if (aMs !== bMs) return aMs - bMs;
|
|
846
|
+
return String(a.createdAt).localeCompare(String(b.createdAt));
|
|
847
|
+
}
|
|
819
848
|
function mapMessageToChatMessage(m) {
|
|
820
849
|
var _a, _b;
|
|
821
850
|
const kind = typeof ((_a = m.payload) == null ? void 0 : _a.type) === "string" ? String(m.payload.type) : null;
|
|
@@ -835,8 +864,10 @@ function useThreadMessages(threadId) {
|
|
|
835
864
|
const activeRequestIdRef = React4.useRef(0);
|
|
836
865
|
const foregroundSignal = useForegroundSignal(Boolean(threadId));
|
|
837
866
|
const upsertSorted = React4.useCallback((prev, m) => {
|
|
838
|
-
const
|
|
839
|
-
next.
|
|
867
|
+
const include = !isQueuedHiddenMessage(m);
|
|
868
|
+
const next = prev.filter((x) => x.id !== m.id);
|
|
869
|
+
if (include) next.push(m);
|
|
870
|
+
next.sort(compareMessages);
|
|
840
871
|
return next;
|
|
841
872
|
}, []);
|
|
842
873
|
const refetch = React4.useCallback(async () => {
|
|
@@ -850,7 +881,7 @@ function useThreadMessages(threadId) {
|
|
|
850
881
|
try {
|
|
851
882
|
const list = await messagesRepository.list(threadId);
|
|
852
883
|
if (activeRequestIdRef.current !== requestId) return;
|
|
853
|
-
setRaw([...list].
|
|
884
|
+
setRaw([...list].filter((m) => !isQueuedHiddenMessage(m)).sort(compareMessages));
|
|
854
885
|
} catch (e) {
|
|
855
886
|
if (activeRequestIdRef.current !== requestId) return;
|
|
856
887
|
setError(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -884,6 +915,7 @@ function useThreadMessages(threadId) {
|
|
|
884
915
|
import * as React5 from "react";
|
|
885
916
|
import * as FileSystem from "expo-file-system/legacy";
|
|
886
917
|
import { Asset } from "expo-asset";
|
|
918
|
+
import { unzip } from "react-native-zip-archive";
|
|
887
919
|
|
|
888
920
|
// src/data/apps/bundles/remote.ts
|
|
889
921
|
var BundlesRemoteDataSourceImpl = class extends BaseRemote {
|
|
@@ -907,6 +939,13 @@ var BundlesRemoteDataSourceImpl = class extends BaseRemote {
|
|
|
907
939
|
);
|
|
908
940
|
return data;
|
|
909
941
|
}
|
|
942
|
+
async getSignedAssetsDownloadUrl(appId, bundleId, options) {
|
|
943
|
+
const { data } = await api.get(
|
|
944
|
+
`/v1/apps/${encodeURIComponent(appId)}/bundles/${encodeURIComponent(bundleId)}/assets/download`,
|
|
945
|
+
{ params: { redirect: (options == null ? void 0 : options.redirect) ?? false, kind: options == null ? void 0 : options.kind } }
|
|
946
|
+
);
|
|
947
|
+
return data;
|
|
948
|
+
}
|
|
910
949
|
};
|
|
911
950
|
var bundlesRemoteDataSource = new BundlesRemoteDataSourceImpl();
|
|
912
951
|
|
|
@@ -928,6 +967,10 @@ var BundlesRepositoryImpl = class extends BaseRepository {
|
|
|
928
967
|
const res = await this.remote.getSignedDownloadUrl(appId, bundleId, options);
|
|
929
968
|
return this.unwrapOrThrow(res);
|
|
930
969
|
}
|
|
970
|
+
async getSignedAssetsDownloadUrl(appId, bundleId, options) {
|
|
971
|
+
const res = await this.remote.getSignedAssetsDownloadUrl(appId, bundleId, options);
|
|
972
|
+
return this.unwrapOrThrow(res);
|
|
973
|
+
}
|
|
931
974
|
};
|
|
932
975
|
var bundlesRepository = new BundlesRepositoryImpl(bundlesRemoteDataSource);
|
|
933
976
|
|
|
@@ -979,20 +1022,40 @@ async function ensureDir(path) {
|
|
|
979
1022
|
if (info.exists) return;
|
|
980
1023
|
await FileSystem.makeDirectoryAsync(path, { intermediates: true });
|
|
981
1024
|
}
|
|
1025
|
+
async function ensureBundleDir(key) {
|
|
1026
|
+
await ensureDir(bundlesCacheDir());
|
|
1027
|
+
await ensureDir(bundleDir(key));
|
|
1028
|
+
}
|
|
982
1029
|
function baseBundleKey(appId, platform) {
|
|
983
1030
|
return `base:${appId}:${platform}`;
|
|
984
1031
|
}
|
|
985
1032
|
function testBundleKey(appId, commitId, platform, bundleId) {
|
|
986
1033
|
return `test:${appId}:${commitId ?? "head"}:${platform}:${bundleId}`;
|
|
987
1034
|
}
|
|
988
|
-
function
|
|
1035
|
+
function legacyBundleFileUri(key) {
|
|
989
1036
|
const dir = bundlesCacheDir();
|
|
990
1037
|
return `${dir}${safeName(key)}.jsbundle`;
|
|
991
1038
|
}
|
|
992
|
-
function
|
|
1039
|
+
function legacyBundleMetaFileUri(key) {
|
|
993
1040
|
const dir = bundlesCacheDir();
|
|
994
1041
|
return `${dir}${safeName(key)}.meta.json`;
|
|
995
1042
|
}
|
|
1043
|
+
function bundleDir(key) {
|
|
1044
|
+
const dir = bundlesCacheDir();
|
|
1045
|
+
return `${dir}${safeName(key)}/`;
|
|
1046
|
+
}
|
|
1047
|
+
function toBundleFileUri(key, platform) {
|
|
1048
|
+
return `${bundleDir(key)}index.${platform}.jsbundle`;
|
|
1049
|
+
}
|
|
1050
|
+
function toBundleMetaFileUri(key) {
|
|
1051
|
+
return `${bundleDir(key)}bundle.meta.json`;
|
|
1052
|
+
}
|
|
1053
|
+
function toAssetsMetaFileUri(key) {
|
|
1054
|
+
return `${bundleDir(key)}assets.meta.json`;
|
|
1055
|
+
}
|
|
1056
|
+
function toAssetsDir(key) {
|
|
1057
|
+
return `${bundleDir(key)}assets/`;
|
|
1058
|
+
}
|
|
996
1059
|
async function readJsonFile(fileUri) {
|
|
997
1060
|
try {
|
|
998
1061
|
const info = await FileSystem.getInfoAsync(fileUri);
|
|
@@ -1019,6 +1082,14 @@ async function getExistingNonEmptyFileUri(fileUri) {
|
|
|
1019
1082
|
return null;
|
|
1020
1083
|
}
|
|
1021
1084
|
}
|
|
1085
|
+
async function getExistingBundleFileUri(key, platform) {
|
|
1086
|
+
const nextPath = toBundleFileUri(key, platform);
|
|
1087
|
+
const next = await getExistingNonEmptyFileUri(nextPath);
|
|
1088
|
+
if (next) return next;
|
|
1089
|
+
const legacyPath = legacyBundleFileUri(key);
|
|
1090
|
+
const legacy = await getExistingNonEmptyFileUri(legacyPath);
|
|
1091
|
+
return legacy;
|
|
1092
|
+
}
|
|
1022
1093
|
async function downloadIfMissing(url, fileUri) {
|
|
1023
1094
|
const existing = await getExistingNonEmptyFileUri(fileUri);
|
|
1024
1095
|
if (existing) return existing;
|
|
@@ -1045,9 +1116,12 @@ async function deleteFileIfExists(fileUri) {
|
|
|
1045
1116
|
async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
|
|
1046
1117
|
if (!(embedded == null ? void 0 : embedded.module)) return null;
|
|
1047
1118
|
const key = baseBundleKey(appId, platform);
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1119
|
+
const existing = await getExistingBundleFileUri(key, platform);
|
|
1120
|
+
if (existing) {
|
|
1121
|
+
return { bundlePath: existing, meta: embedded.meta ?? null };
|
|
1122
|
+
}
|
|
1123
|
+
await ensureBundleDir(key);
|
|
1124
|
+
const targetUri = toBundleFileUri(key, platform);
|
|
1051
1125
|
const asset = Asset.fromModule(embedded.module);
|
|
1052
1126
|
await asset.downloadAsync();
|
|
1053
1127
|
const sourceUri = asset.localUri ?? asset.uri;
|
|
@@ -1060,8 +1134,50 @@ async function hydrateBaseFromEmbeddedAsset(appId, platform, embedded) {
|
|
|
1060
1134
|
if (!finalUri) return null;
|
|
1061
1135
|
return { bundlePath: finalUri, meta: embedded.meta ?? null };
|
|
1062
1136
|
}
|
|
1063
|
-
async function
|
|
1064
|
-
|
|
1137
|
+
async function hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded) {
|
|
1138
|
+
var _a;
|
|
1139
|
+
const moduleId = embedded == null ? void 0 : embedded.assetsModule;
|
|
1140
|
+
if (!moduleId) return false;
|
|
1141
|
+
const assetsMeta = (embedded == null ? void 0 : embedded.assetsMeta) ?? null;
|
|
1142
|
+
const assetsDir = toAssetsDir(key);
|
|
1143
|
+
const metaUri = toAssetsMetaFileUri(key);
|
|
1144
|
+
const existingMeta = await readJsonFile(metaUri);
|
|
1145
|
+
const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
|
|
1146
|
+
const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
|
|
1147
|
+
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);
|
|
1148
|
+
const embeddedMetaMatches = (_a = existingMeta == null ? void 0 : existingMeta.storageKey) == null ? void 0 : _a.startsWith("embedded:");
|
|
1149
|
+
if (assetsDirExists && checksumMatches && embeddedMetaMatches) {
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
await ensureBundleDir(key);
|
|
1153
|
+
await ensureDir(assetsDir);
|
|
1154
|
+
const asset = Asset.fromModule(moduleId);
|
|
1155
|
+
await asset.downloadAsync();
|
|
1156
|
+
const sourceUri = asset.localUri ?? asset.uri;
|
|
1157
|
+
if (!sourceUri) return false;
|
|
1158
|
+
const info = await FileSystem.getInfoAsync(sourceUri);
|
|
1159
|
+
if (!info.exists) return false;
|
|
1160
|
+
const zipUri = `${bundleDir(key)}assets.zip`;
|
|
1161
|
+
await deleteFileIfExists(zipUri);
|
|
1162
|
+
await FileSystem.copyAsync({ from: sourceUri, to: zipUri });
|
|
1163
|
+
try {
|
|
1164
|
+
await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
|
|
1165
|
+
});
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
await ensureDir(assetsDir);
|
|
1169
|
+
await unzipArchive(zipUri, assetsDir);
|
|
1170
|
+
await writeJsonFile(metaUri, {
|
|
1171
|
+
checksumSha256: (assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? null,
|
|
1172
|
+
storageKey: `embedded:${(assetsMeta == null ? void 0 : assetsMeta.checksumSha256) ?? "unknown"}`,
|
|
1173
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1174
|
+
});
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
async function safeReplaceFileFromUrl(url, targetUri, tmpKey, platform) {
|
|
1178
|
+
const tmpKeySafe = `tmp:${tmpKey}:${Date.now()}`;
|
|
1179
|
+
const tmpUri = toBundleFileUri(tmpKeySafe, platform);
|
|
1180
|
+
await ensureDir(bundleDir(tmpKeySafe));
|
|
1065
1181
|
try {
|
|
1066
1182
|
await withRetry(
|
|
1067
1183
|
async () => {
|
|
@@ -1081,6 +1197,82 @@ async function safeReplaceFileFromUrl(url, targetUri, tmpKey) {
|
|
|
1081
1197
|
await deleteFileIfExists(tmpUri);
|
|
1082
1198
|
}
|
|
1083
1199
|
}
|
|
1200
|
+
async function safeReplaceFileFromUrlToPath(url, targetUri, tmpKey) {
|
|
1201
|
+
const tmpDir = `${bundlesCacheDir()}tmp/`;
|
|
1202
|
+
await ensureDir(tmpDir);
|
|
1203
|
+
const tmpUri = `${tmpDir}${safeName(tmpKey)}.tmp`;
|
|
1204
|
+
try {
|
|
1205
|
+
await withRetry(
|
|
1206
|
+
async () => {
|
|
1207
|
+
await deleteFileIfExists(tmpUri);
|
|
1208
|
+
await FileSystem.downloadAsync(url, tmpUri);
|
|
1209
|
+
const tmpOk = await getExistingNonEmptyFileUri(tmpUri);
|
|
1210
|
+
if (!tmpOk) throw new Error("Downloaded file is empty.");
|
|
1211
|
+
},
|
|
1212
|
+
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1213
|
+
);
|
|
1214
|
+
await deleteFileIfExists(targetUri);
|
|
1215
|
+
await FileSystem.moveAsync({ from: tmpUri, to: targetUri });
|
|
1216
|
+
const finalOk = await getExistingNonEmptyFileUri(targetUri);
|
|
1217
|
+
if (!finalOk) throw new Error("File replacement failed.");
|
|
1218
|
+
return targetUri;
|
|
1219
|
+
} finally {
|
|
1220
|
+
await deleteFileIfExists(tmpUri);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function getMetroAssets(bundle) {
|
|
1224
|
+
const assets = bundle.assets ?? [];
|
|
1225
|
+
return assets.find((asset) => asset.kind === "metro-assets") ?? null;
|
|
1226
|
+
}
|
|
1227
|
+
async function ensureAssetsForBundle(appId, bundle, key, platform) {
|
|
1228
|
+
var _a;
|
|
1229
|
+
const asset = getMetroAssets(bundle);
|
|
1230
|
+
if (!(asset == null ? void 0 : asset.storageKey)) return;
|
|
1231
|
+
await ensureBundleDir(key);
|
|
1232
|
+
const assetsDir = toAssetsDir(key);
|
|
1233
|
+
await ensureDir(assetsDir);
|
|
1234
|
+
const metaUri = toAssetsMetaFileUri(key);
|
|
1235
|
+
const existingMeta = await readJsonFile(metaUri);
|
|
1236
|
+
const assetsDirInfo = await FileSystem.getInfoAsync(assetsDir);
|
|
1237
|
+
const assetsDirExists = assetsDirInfo.exists && assetsDirInfo.isDirectory;
|
|
1238
|
+
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) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const signed = await withRetry(
|
|
1242
|
+
async () => {
|
|
1243
|
+
return await bundlesRepository.getSignedAssetsDownloadUrl(appId, bundle.id, {
|
|
1244
|
+
redirect: false,
|
|
1245
|
+
kind: asset.kind
|
|
1246
|
+
});
|
|
1247
|
+
},
|
|
1248
|
+
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1249
|
+
);
|
|
1250
|
+
const zipUri = `${bundleDir(key)}assets.zip`;
|
|
1251
|
+
await safeReplaceFileFromUrlToPath(signed.url, zipUri, `${appId}:${bundle.id}:${platform}:${asset.kind}`);
|
|
1252
|
+
try {
|
|
1253
|
+
await FileSystem.deleteAsync(assetsDir, { idempotent: true }).catch(() => {
|
|
1254
|
+
});
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
await ensureDir(assetsDir);
|
|
1258
|
+
await unzipArchive(zipUri, assetsDir);
|
|
1259
|
+
await writeJsonFile(metaUri, {
|
|
1260
|
+
checksumSha256: asset.checksumSha256 ?? null,
|
|
1261
|
+
storageKey: asset.storageKey,
|
|
1262
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
async function unzipArchive(sourceUri, destDir) {
|
|
1266
|
+
try {
|
|
1267
|
+
await unzip(sourceUri, destDir);
|
|
1268
|
+
} catch (e) {
|
|
1269
|
+
throw new Error(
|
|
1270
|
+
`Failed to extract assets archive. Ensure 'react-native-zip-archive' is installed in the host app. ${String(
|
|
1271
|
+
(e == null ? void 0 : e.message) ?? e
|
|
1272
|
+
)}`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1084
1276
|
async function pollBundle(appId, bundleId, opts) {
|
|
1085
1277
|
const start = Date.now();
|
|
1086
1278
|
while (true) {
|
|
@@ -1116,18 +1308,33 @@ async function resolveBundlePath(src, platform, mode) {
|
|
|
1116
1308
|
if (finalBundle.status === "failed") {
|
|
1117
1309
|
throw new Error("Bundle build failed.");
|
|
1118
1310
|
}
|
|
1311
|
+
let bundleWithAssets = finalBundle;
|
|
1312
|
+
if (finalBundle.status === "succeeded" && (!finalBundle.assets || finalBundle.assets.length === 0)) {
|
|
1313
|
+
try {
|
|
1314
|
+
bundleWithAssets = await bundlesRepository.getById(appId, finalBundle.id);
|
|
1315
|
+
} catch {
|
|
1316
|
+
bundleWithAssets = finalBundle;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1119
1319
|
const signed = await withRetry(
|
|
1120
1320
|
async () => {
|
|
1121
1321
|
return await bundlesRepository.getSignedDownloadUrl(appId, finalBundle.id, { redirect: false });
|
|
1122
1322
|
},
|
|
1123
1323
|
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
1124
1324
|
);
|
|
1325
|
+
const key = mode === "base" ? baseBundleKey(appId, platform) : testBundleKey(appId, commitId, platform, finalBundle.id);
|
|
1326
|
+
await ensureBundleDir(key);
|
|
1125
1327
|
const bundlePath = mode === "base" ? await safeReplaceFileFromUrl(
|
|
1126
1328
|
signed.url,
|
|
1127
|
-
toBundleFileUri(
|
|
1128
|
-
`${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}
|
|
1129
|
-
|
|
1130
|
-
|
|
1329
|
+
toBundleFileUri(key, platform),
|
|
1330
|
+
`${appId}:${commitId ?? "head"}:${platform}:${finalBundle.id}`,
|
|
1331
|
+
platform
|
|
1332
|
+
) : await downloadIfMissing(signed.url, toBundleFileUri(key, platform));
|
|
1333
|
+
try {
|
|
1334
|
+
await ensureAssetsForBundle(appId, bundleWithAssets, key, platform);
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
return { bundlePath, label: "Ready", bundle: bundleWithAssets };
|
|
1131
1338
|
}
|
|
1132
1339
|
function useBundleManager({
|
|
1133
1340
|
base,
|
|
@@ -1168,13 +1375,12 @@ function useBundleManager({
|
|
|
1168
1375
|
const hasCompletedFirstNetworkBaseLoadRef = React5.useRef(false);
|
|
1169
1376
|
const hydrateBaseFromDisk = React5.useCallback(
|
|
1170
1377
|
async (appId, reason) => {
|
|
1171
|
-
var _a;
|
|
1378
|
+
var _a, _b, _c;
|
|
1172
1379
|
try {
|
|
1173
1380
|
const dir = bundlesCacheDir();
|
|
1174
1381
|
await ensureDir(dir);
|
|
1175
1382
|
const key = baseBundleKey(appId, platform);
|
|
1176
|
-
|
|
1177
|
-
let existing = await getExistingNonEmptyFileUri(uri);
|
|
1383
|
+
let existing = await getExistingBundleFileUri(key, platform);
|
|
1178
1384
|
let embeddedMeta = null;
|
|
1179
1385
|
if (!existing) {
|
|
1180
1386
|
const embedded = (_a = embeddedBaseBundlesRef.current) == null ? void 0 : _a[platform];
|
|
@@ -1183,14 +1389,25 @@ function useBundleManager({
|
|
|
1183
1389
|
existing = hydrated.bundlePath;
|
|
1184
1390
|
embeddedMeta = hydrated.meta ?? null;
|
|
1185
1391
|
if (embeddedMeta) {
|
|
1392
|
+
await ensureBundleDir(key);
|
|
1186
1393
|
await writeJsonFile(toBundleMetaFileUri(key), embeddedMeta);
|
|
1394
|
+
await writeJsonFile(legacyBundleMetaFileUri(key), embeddedMeta);
|
|
1187
1395
|
}
|
|
1188
1396
|
}
|
|
1189
1397
|
}
|
|
1190
1398
|
if (existing) {
|
|
1191
1399
|
lastBaseBundlePathRef.current = existing;
|
|
1192
1400
|
setBundlePath(existing);
|
|
1193
|
-
const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key));
|
|
1401
|
+
const meta = embeddedMeta ?? await readJsonFile(toBundleMetaFileUri(key)) ?? await readJsonFile(legacyBundleMetaFileUri(key));
|
|
1402
|
+
const embedded = (_b = embeddedBaseBundlesRef.current) == null ? void 0 : _b[platform];
|
|
1403
|
+
const embeddedFingerprint = ((_c = embedded == null ? void 0 : embedded.meta) == null ? void 0 : _c.fingerprint) ?? null;
|
|
1404
|
+
const actualFingerprint = (meta == null ? void 0 : meta.fingerprint) ?? (embeddedMeta == null ? void 0 : embeddedMeta.fingerprint) ?? null;
|
|
1405
|
+
if ((embedded == null ? void 0 : embedded.assetsModule) && embeddedFingerprint && actualFingerprint === embeddedFingerprint) {
|
|
1406
|
+
try {
|
|
1407
|
+
await hydrateAssetsFromEmbeddedAsset(appId, platform, key, embedded);
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1194
1411
|
if (meta == null ? void 0 : meta.fingerprint) {
|
|
1195
1412
|
lastBaseFingerprintRef.current = meta.fingerprint;
|
|
1196
1413
|
}
|
|
@@ -1255,7 +1472,16 @@ function useBundleManager({
|
|
|
1255
1472
|
lastBaseFingerprintRef.current = fingerprint;
|
|
1256
1473
|
hasCompletedFirstNetworkBaseLoadRef.current = true;
|
|
1257
1474
|
initialHydratedBaseFromDiskRef.current = false;
|
|
1258
|
-
|
|
1475
|
+
const metaKey = baseBundleKey(src.appId, platform);
|
|
1476
|
+
await ensureBundleDir(metaKey);
|
|
1477
|
+
void writeJsonFile(toBundleMetaFileUri(metaKey), {
|
|
1478
|
+
fingerprint,
|
|
1479
|
+
bundleId: bundle.id,
|
|
1480
|
+
checksumSha256: bundle.checksumSha256 ?? null,
|
|
1481
|
+
size: bundle.size ?? null,
|
|
1482
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1483
|
+
});
|
|
1484
|
+
void writeJsonFile(legacyBundleMetaFileUri(metaKey), {
|
|
1259
1485
|
fingerprint,
|
|
1260
1486
|
bundleId: bundle.id,
|
|
1261
1487
|
checksumSha256: bundle.checksumSha256 ?? null,
|
|
@@ -1707,6 +1933,9 @@ function useStudioActions({
|
|
|
1707
1933
|
userId,
|
|
1708
1934
|
app,
|
|
1709
1935
|
onForkedApp,
|
|
1936
|
+
onEditStart,
|
|
1937
|
+
onEditQueued,
|
|
1938
|
+
onEditFinished,
|
|
1710
1939
|
uploadAttachments
|
|
1711
1940
|
}) {
|
|
1712
1941
|
const [forking, setForking] = React8.useState(false);
|
|
@@ -1722,6 +1951,7 @@ function useStudioActions({
|
|
|
1722
1951
|
setSending(true);
|
|
1723
1952
|
setError(null);
|
|
1724
1953
|
try {
|
|
1954
|
+
onEditStart == null ? void 0 : onEditStart();
|
|
1725
1955
|
let targetApp = app;
|
|
1726
1956
|
if (shouldForkOnEdit) {
|
|
1727
1957
|
setForking(true);
|
|
@@ -1737,12 +1967,16 @@ function useStudioActions({
|
|
|
1737
1967
|
if (attachments && attachments.length > 0 && uploadAttachments) {
|
|
1738
1968
|
attachmentMetas = await uploadAttachments({ threadId, appId: targetApp.id, dataUrls: attachments });
|
|
1739
1969
|
}
|
|
1740
|
-
await agentRepository.editApp({
|
|
1970
|
+
const editResult = await agentRepository.editApp({
|
|
1741
1971
|
prompt,
|
|
1742
1972
|
thread_id: threadId,
|
|
1743
1973
|
app_id: targetApp.id,
|
|
1744
1974
|
attachments: attachmentMetas && attachmentMetas.length > 0 ? attachmentMetas : void 0
|
|
1745
1975
|
});
|
|
1976
|
+
onEditQueued == null ? void 0 : onEditQueued({
|
|
1977
|
+
queueItemId: editResult.queueItemId ?? null,
|
|
1978
|
+
queuePosition: editResult.queuePosition ?? null
|
|
1979
|
+
});
|
|
1746
1980
|
} catch (e) {
|
|
1747
1981
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
1748
1982
|
setError(err);
|
|
@@ -1750,32 +1984,14 @@ function useStudioActions({
|
|
|
1750
1984
|
} finally {
|
|
1751
1985
|
setForking(false);
|
|
1752
1986
|
setSending(false);
|
|
1987
|
+
onEditFinished == null ? void 0 : onEditFinished();
|
|
1753
1988
|
}
|
|
1754
1989
|
},
|
|
1755
|
-
[app, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
|
|
1990
|
+
[app, onEditFinished, onEditQueued, onEditStart, onForkedApp, sending, shouldForkOnEdit, uploadAttachments, userId]
|
|
1756
1991
|
);
|
|
1757
1992
|
return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
|
|
1758
1993
|
}
|
|
1759
1994
|
|
|
1760
|
-
// src/studio/lib/chat.ts
|
|
1761
|
-
function hasNoOutcomeAfterLastHuman(messages) {
|
|
1762
|
-
if (!messages || messages.length === 0) return false;
|
|
1763
|
-
let lastHumanIndex = -1;
|
|
1764
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
1765
|
-
if (messages[i].authorType === "human") {
|
|
1766
|
-
lastHumanIndex = i;
|
|
1767
|
-
break;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
if (lastHumanIndex === -1) return false;
|
|
1771
|
-
for (let i = lastHumanIndex + 1; i < messages.length; i += 1) {
|
|
1772
|
-
const m = messages[i];
|
|
1773
|
-
const payload = m.payload;
|
|
1774
|
-
if (m.authorType === "ai" && (payload == null ? void 0 : payload.type) === "outcome") return false;
|
|
1775
|
-
}
|
|
1776
|
-
return true;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
1995
|
// src/studio/ui/RuntimeRenderer.tsx
|
|
1780
1996
|
import * as React9 from "react";
|
|
1781
1997
|
import { View as View2 } from "react-native";
|
|
@@ -1813,8 +2029,8 @@ function RuntimeRenderer({
|
|
|
1813
2029
|
}
|
|
1814
2030
|
|
|
1815
2031
|
// src/studio/ui/StudioOverlay.tsx
|
|
1816
|
-
import * as
|
|
1817
|
-
import { Keyboard as Keyboard5, Platform as Platform10, View as
|
|
2032
|
+
import * as React43 from "react";
|
|
2033
|
+
import { Keyboard as Keyboard5, Platform as Platform10, View as View45, useWindowDimensions as useWindowDimensions4 } from "react-native";
|
|
1818
2034
|
|
|
1819
2035
|
// src/components/studio-sheet/StudioBottomSheet.tsx
|
|
1820
2036
|
import * as React12 from "react";
|
|
@@ -5674,8 +5890,8 @@ function PreviewPanel({
|
|
|
5674
5890
|
}
|
|
5675
5891
|
|
|
5676
5892
|
// src/studio/ui/ChatPanel.tsx
|
|
5677
|
-
import * as
|
|
5678
|
-
import { ActivityIndicator as
|
|
5893
|
+
import * as React40 from "react";
|
|
5894
|
+
import { ActivityIndicator as ActivityIndicator9, View as View42 } from "react-native";
|
|
5679
5895
|
|
|
5680
5896
|
// src/components/chat/ChatPage.tsx
|
|
5681
5897
|
import * as React37 from "react";
|
|
@@ -5872,6 +6088,7 @@ function ChatPage({
|
|
|
5872
6088
|
showTypingIndicator,
|
|
5873
6089
|
renderMessageContent,
|
|
5874
6090
|
topBanner,
|
|
6091
|
+
composerTop,
|
|
5875
6092
|
composer,
|
|
5876
6093
|
overlay,
|
|
5877
6094
|
style,
|
|
@@ -5882,6 +6099,7 @@ function ChatPage({
|
|
|
5882
6099
|
const theme = useTheme();
|
|
5883
6100
|
const insets = useSafeAreaInsets4();
|
|
5884
6101
|
const [composerHeight, setComposerHeight] = React37.useState(0);
|
|
6102
|
+
const [composerTopHeight, setComposerTopHeight] = React37.useState(0);
|
|
5885
6103
|
const [keyboardVisible, setKeyboardVisible] = React37.useState(false);
|
|
5886
6104
|
React37.useEffect(() => {
|
|
5887
6105
|
if (Platform9.OS !== "ios") return;
|
|
@@ -5893,8 +6111,9 @@ function ChatPage({
|
|
|
5893
6111
|
};
|
|
5894
6112
|
}, []);
|
|
5895
6113
|
const footerBottomPadding = Platform9.OS === "ios" ? keyboardVisible ? 0 : insets.bottom : insets.bottom + 10;
|
|
5896
|
-
const
|
|
5897
|
-
const
|
|
6114
|
+
const totalComposerHeight = composerHeight + composerTopHeight;
|
|
6115
|
+
const overlayBottom = totalComposerHeight + footerBottomPadding + theme.spacing.lg;
|
|
6116
|
+
const bottomInset = totalComposerHeight + footerBottomPadding + theme.spacing.xl;
|
|
5898
6117
|
const resolvedOverlay = React37.useMemo(() => {
|
|
5899
6118
|
var _a;
|
|
5900
6119
|
if (!overlay) return null;
|
|
@@ -5904,6 +6123,10 @@ function ChatPage({
|
|
|
5904
6123
|
style: [prevStyle, { bottom: overlayBottom }]
|
|
5905
6124
|
});
|
|
5906
6125
|
}, [overlay, overlayBottom]);
|
|
6126
|
+
React37.useEffect(() => {
|
|
6127
|
+
if (composerTop) return;
|
|
6128
|
+
setComposerTopHeight(0);
|
|
6129
|
+
}, [composerTop]);
|
|
5907
6130
|
return /* @__PURE__ */ jsxs29(View37, { style: [{ flex: 1 }, style], children: [
|
|
5908
6131
|
header ? /* @__PURE__ */ jsx49(View37, { children: header }) : null,
|
|
5909
6132
|
topBanner ? /* @__PURE__ */ jsx49(View37, { style: { paddingHorizontal: theme.spacing.lg, paddingTop: theme.spacing.sm }, children: topBanner }) : null,
|
|
@@ -5928,7 +6151,7 @@ function ChatPage({
|
|
|
5928
6151
|
]
|
|
5929
6152
|
}
|
|
5930
6153
|
),
|
|
5931
|
-
/* @__PURE__ */
|
|
6154
|
+
/* @__PURE__ */ jsxs29(
|
|
5932
6155
|
View37,
|
|
5933
6156
|
{
|
|
5934
6157
|
style: {
|
|
@@ -5940,14 +6163,24 @@ function ChatPage({
|
|
|
5940
6163
|
paddingTop: theme.spacing.sm,
|
|
5941
6164
|
paddingBottom: footerBottomPadding
|
|
5942
6165
|
},
|
|
5943
|
-
children:
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
6166
|
+
children: [
|
|
6167
|
+
composerTop ? /* @__PURE__ */ jsx49(
|
|
6168
|
+
View37,
|
|
6169
|
+
{
|
|
6170
|
+
style: { marginBottom: theme.spacing.sm },
|
|
6171
|
+
onLayout: (e) => setComposerTopHeight(e.nativeEvent.layout.height),
|
|
6172
|
+
children: composerTop
|
|
6173
|
+
}
|
|
6174
|
+
) : null,
|
|
6175
|
+
/* @__PURE__ */ jsx49(
|
|
6176
|
+
ChatComposer,
|
|
6177
|
+
{
|
|
6178
|
+
...composer,
|
|
6179
|
+
attachments: composer.attachments ?? [],
|
|
6180
|
+
onLayout: ({ height }) => setComposerHeight(height)
|
|
6181
|
+
}
|
|
6182
|
+
)
|
|
6183
|
+
]
|
|
5951
6184
|
}
|
|
5952
6185
|
)
|
|
5953
6186
|
] })
|
|
@@ -6097,8 +6330,154 @@ function ForkNoticeBanner({ isOwner = true, title, description, style }) {
|
|
|
6097
6330
|
);
|
|
6098
6331
|
}
|
|
6099
6332
|
|
|
6100
|
-
// src/
|
|
6333
|
+
// src/components/chat/ChatQueue.tsx
|
|
6334
|
+
import * as React39 from "react";
|
|
6335
|
+
import { ActivityIndicator as ActivityIndicator8, Pressable as Pressable13, View as View41 } from "react-native";
|
|
6101
6336
|
import { jsx as jsx53, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
6337
|
+
function ChatQueue({ items, onRemove }) {
|
|
6338
|
+
const theme = useTheme();
|
|
6339
|
+
const [expanded, setExpanded] = React39.useState({});
|
|
6340
|
+
const [canExpand, setCanExpand] = React39.useState({});
|
|
6341
|
+
const [collapsedText, setCollapsedText] = React39.useState({});
|
|
6342
|
+
const [removing, setRemoving] = React39.useState({});
|
|
6343
|
+
const buildCollapsedText = React39.useCallback((lines) => {
|
|
6344
|
+
var _a, _b;
|
|
6345
|
+
const line1 = ((_a = lines[0]) == null ? void 0 : _a.text) ?? "";
|
|
6346
|
+
const line2 = ((_b = lines[1]) == null ? void 0 : _b.text) ?? "";
|
|
6347
|
+
const moreLabel = "more";
|
|
6348
|
+
const reserve = `\u2026 ${moreLabel}`.length;
|
|
6349
|
+
let trimmedLine2 = line2;
|
|
6350
|
+
if (trimmedLine2.length > reserve) {
|
|
6351
|
+
trimmedLine2 = trimmedLine2.slice(0, Math.max(0, trimmedLine2.length - reserve));
|
|
6352
|
+
} else {
|
|
6353
|
+
trimmedLine2 = "";
|
|
6354
|
+
}
|
|
6355
|
+
trimmedLine2 = trimmedLine2.replace(/\s+$/, "");
|
|
6356
|
+
return `${line1}
|
|
6357
|
+
${trimmedLine2}\u2026 `;
|
|
6358
|
+
}, []);
|
|
6359
|
+
React39.useEffect(() => {
|
|
6360
|
+
if (items.length === 0) return;
|
|
6361
|
+
const ids = new Set(items.map((item) => item.id));
|
|
6362
|
+
setExpanded((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6363
|
+
setCanExpand((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6364
|
+
setCollapsedText((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6365
|
+
setRemoving((prev) => Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))));
|
|
6366
|
+
}, [items]);
|
|
6367
|
+
if (items.length === 0) return null;
|
|
6368
|
+
return /* @__PURE__ */ jsxs31(
|
|
6369
|
+
View41,
|
|
6370
|
+
{
|
|
6371
|
+
style: {
|
|
6372
|
+
borderWidth: 1,
|
|
6373
|
+
borderColor: theme.colors.border,
|
|
6374
|
+
borderRadius: theme.radii.lg,
|
|
6375
|
+
marginHorizontal: theme.spacing.md,
|
|
6376
|
+
padding: theme.spacing.md,
|
|
6377
|
+
backgroundColor: "transparent"
|
|
6378
|
+
},
|
|
6379
|
+
children: [
|
|
6380
|
+
/* @__PURE__ */ jsx53(Text, { variant: "caption", style: { marginBottom: theme.spacing.sm }, children: "Queue" }),
|
|
6381
|
+
/* @__PURE__ */ jsx53(View41, { style: { gap: theme.spacing.sm }, children: items.map((item) => {
|
|
6382
|
+
const isExpanded = Boolean(expanded[item.id]);
|
|
6383
|
+
const showToggle = Boolean(canExpand[item.id]);
|
|
6384
|
+
const prompt = item.prompt ?? "";
|
|
6385
|
+
const moreLabel = "more";
|
|
6386
|
+
const displayPrompt = !isExpanded && showToggle && collapsedText[item.id] ? collapsedText[item.id] : prompt;
|
|
6387
|
+
const isRemoving = Boolean(removing[item.id]);
|
|
6388
|
+
return /* @__PURE__ */ jsxs31(
|
|
6389
|
+
View41,
|
|
6390
|
+
{
|
|
6391
|
+
style: {
|
|
6392
|
+
flexDirection: "row",
|
|
6393
|
+
alignItems: "flex-start",
|
|
6394
|
+
gap: theme.spacing.sm,
|
|
6395
|
+
paddingHorizontal: theme.spacing.md,
|
|
6396
|
+
paddingVertical: theme.spacing.sm,
|
|
6397
|
+
borderRadius: theme.radii.md,
|
|
6398
|
+
backgroundColor: withAlpha(theme.colors.surface, theme.scheme === "dark" ? 0.8 : 0.9)
|
|
6399
|
+
},
|
|
6400
|
+
children: [
|
|
6401
|
+
/* @__PURE__ */ jsxs31(View41, { style: { flex: 1 }, children: [
|
|
6402
|
+
!canExpand[item.id] ? /* @__PURE__ */ jsx53(
|
|
6403
|
+
Text,
|
|
6404
|
+
{
|
|
6405
|
+
style: { position: "absolute", opacity: 0, zIndex: -1, width: "100%" },
|
|
6406
|
+
onTextLayout: (e) => {
|
|
6407
|
+
var _a;
|
|
6408
|
+
const lines = (_a = e.nativeEvent) == null ? void 0 : _a.lines;
|
|
6409
|
+
if (!lines) return;
|
|
6410
|
+
if (lines.length > 2) {
|
|
6411
|
+
setCanExpand((prev) => ({ ...prev, [item.id]: true }));
|
|
6412
|
+
setCollapsedText((prev) => ({
|
|
6413
|
+
...prev,
|
|
6414
|
+
[item.id]: buildCollapsedText(lines)
|
|
6415
|
+
}));
|
|
6416
|
+
}
|
|
6417
|
+
},
|
|
6418
|
+
children: prompt
|
|
6419
|
+
}
|
|
6420
|
+
) : null,
|
|
6421
|
+
/* @__PURE__ */ jsxs31(
|
|
6422
|
+
Text,
|
|
6423
|
+
{
|
|
6424
|
+
variant: "bodyMuted",
|
|
6425
|
+
numberOfLines: isExpanded ? void 0 : 2,
|
|
6426
|
+
children: [
|
|
6427
|
+
displayPrompt,
|
|
6428
|
+
!isExpanded && showToggle ? /* @__PURE__ */ jsx53(
|
|
6429
|
+
Text,
|
|
6430
|
+
{
|
|
6431
|
+
color: theme.colors.text,
|
|
6432
|
+
onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: true })),
|
|
6433
|
+
suppressHighlighting: true,
|
|
6434
|
+
children: moreLabel
|
|
6435
|
+
}
|
|
6436
|
+
) : null
|
|
6437
|
+
]
|
|
6438
|
+
}
|
|
6439
|
+
),
|
|
6440
|
+
showToggle && isExpanded ? /* @__PURE__ */ jsx53(
|
|
6441
|
+
Pressable13,
|
|
6442
|
+
{
|
|
6443
|
+
onPress: () => setExpanded((prev) => ({ ...prev, [item.id]: false })),
|
|
6444
|
+
hitSlop: 6,
|
|
6445
|
+
style: { alignSelf: "flex-start", marginTop: 4 },
|
|
6446
|
+
children: /* @__PURE__ */ jsx53(Text, { variant: "captionMuted", color: theme.colors.text, children: "less" })
|
|
6447
|
+
}
|
|
6448
|
+
) : null
|
|
6449
|
+
] }),
|
|
6450
|
+
/* @__PURE__ */ jsx53(
|
|
6451
|
+
Pressable13,
|
|
6452
|
+
{
|
|
6453
|
+
onPress: () => {
|
|
6454
|
+
if (!onRemove || isRemoving) return;
|
|
6455
|
+
setRemoving((prev) => ({ ...prev, [item.id]: true }));
|
|
6456
|
+
Promise.resolve(onRemove(item.id)).finally(() => {
|
|
6457
|
+
setRemoving((prev) => {
|
|
6458
|
+
if (!prev[item.id]) return prev;
|
|
6459
|
+
const { [item.id]: _removed, ...rest } = prev;
|
|
6460
|
+
return rest;
|
|
6461
|
+
});
|
|
6462
|
+
});
|
|
6463
|
+
},
|
|
6464
|
+
hitSlop: 8,
|
|
6465
|
+
style: { alignSelf: "center" },
|
|
6466
|
+
children: isRemoving ? /* @__PURE__ */ jsx53(ActivityIndicator8, { size: "small", color: theme.colors.text }) : /* @__PURE__ */ jsx53(IconClose, { size: 14, colorToken: "text" })
|
|
6467
|
+
}
|
|
6468
|
+
)
|
|
6469
|
+
]
|
|
6470
|
+
},
|
|
6471
|
+
item.id
|
|
6472
|
+
);
|
|
6473
|
+
}) })
|
|
6474
|
+
]
|
|
6475
|
+
}
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
|
|
6479
|
+
// src/studio/ui/ChatPanel.tsx
|
|
6480
|
+
import { jsx as jsx54, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
6102
6481
|
function ChatPanel({
|
|
6103
6482
|
title = "Chat",
|
|
6104
6483
|
autoFocusComposer = false,
|
|
@@ -6116,11 +6495,13 @@ function ChatPanel({
|
|
|
6116
6495
|
onClose,
|
|
6117
6496
|
onNavigateHome,
|
|
6118
6497
|
onStartDraw,
|
|
6119
|
-
onSend
|
|
6498
|
+
onSend,
|
|
6499
|
+
queueItems = [],
|
|
6500
|
+
onRemoveQueueItem
|
|
6120
6501
|
}) {
|
|
6121
|
-
const listRef =
|
|
6122
|
-
const [nearBottom, setNearBottom] =
|
|
6123
|
-
const handleSend =
|
|
6502
|
+
const listRef = React40.useRef(null);
|
|
6503
|
+
const [nearBottom, setNearBottom] = React40.useState(true);
|
|
6504
|
+
const handleSend = React40.useCallback(
|
|
6124
6505
|
async (text, composerAttachments) => {
|
|
6125
6506
|
const all = composerAttachments ?? attachments;
|
|
6126
6507
|
await onSend(text, all.length > 0 ? all : void 0);
|
|
@@ -6134,25 +6515,25 @@ function ChatPanel({
|
|
|
6134
6515
|
},
|
|
6135
6516
|
[attachments, nearBottom, onClearAttachments, onSend]
|
|
6136
6517
|
);
|
|
6137
|
-
const handleScrollToBottom =
|
|
6518
|
+
const handleScrollToBottom = React40.useCallback(() => {
|
|
6138
6519
|
var _a;
|
|
6139
6520
|
(_a = listRef.current) == null ? void 0 : _a.scrollToBottom({ animated: true });
|
|
6140
6521
|
}, []);
|
|
6141
|
-
const header = /* @__PURE__ */
|
|
6522
|
+
const header = /* @__PURE__ */ jsx54(
|
|
6142
6523
|
ChatHeader,
|
|
6143
6524
|
{
|
|
6144
|
-
left: /* @__PURE__ */
|
|
6145
|
-
/* @__PURE__ */
|
|
6146
|
-
onNavigateHome ? /* @__PURE__ */
|
|
6525
|
+
left: /* @__PURE__ */ jsxs32(View42, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
6526
|
+
/* @__PURE__ */ jsx54(StudioSheetHeaderIconButton, { onPress: onBack, accessibilityLabel: "Back", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx54(IconBack, { size: 20, colorToken: "floatingContent" }) }),
|
|
6527
|
+
onNavigateHome ? /* @__PURE__ */ jsx54(StudioSheetHeaderIconButton, { onPress: onNavigateHome, accessibilityLabel: "Home", children: /* @__PURE__ */ jsx54(IconHome, { size: 20, colorToken: "floatingContent" }) }) : null
|
|
6147
6528
|
] }),
|
|
6148
|
-
right: /* @__PURE__ */
|
|
6149
|
-
onStartDraw ? /* @__PURE__ */
|
|
6150
|
-
/* @__PURE__ */
|
|
6529
|
+
right: /* @__PURE__ */ jsxs32(View42, { style: { flexDirection: "row", alignItems: "center" }, children: [
|
|
6530
|
+
onStartDraw ? /* @__PURE__ */ jsx54(StudioSheetHeaderIconButton, { onPress: onStartDraw, accessibilityLabel: "Draw", intent: "danger", style: { marginRight: 8 }, children: /* @__PURE__ */ jsx54(IconDraw, { size: 20, colorToken: "onDanger" }) }) : null,
|
|
6531
|
+
/* @__PURE__ */ jsx54(StudioSheetHeaderIconButton, { onPress: onClose, accessibilityLabel: "Close", children: /* @__PURE__ */ jsx54(IconClose, { size: 20, colorToken: "floatingContent" }) })
|
|
6151
6532
|
] }),
|
|
6152
6533
|
center: null
|
|
6153
6534
|
}
|
|
6154
6535
|
);
|
|
6155
|
-
const topBanner = shouldForkOnEdit ? /* @__PURE__ */
|
|
6536
|
+
const topBanner = shouldForkOnEdit ? /* @__PURE__ */ jsx54(
|
|
6156
6537
|
ForkNoticeBanner,
|
|
6157
6538
|
{
|
|
6158
6539
|
isOwner: !shouldForkOnEdit,
|
|
@@ -6161,33 +6542,35 @@ function ChatPanel({
|
|
|
6161
6542
|
) : null;
|
|
6162
6543
|
const showMessagesLoading = Boolean(loading) && messages.length === 0 || forking;
|
|
6163
6544
|
if (showMessagesLoading) {
|
|
6164
|
-
return /* @__PURE__ */
|
|
6165
|
-
/* @__PURE__ */
|
|
6166
|
-
topBanner ? /* @__PURE__ */
|
|
6167
|
-
/* @__PURE__ */
|
|
6168
|
-
/* @__PURE__ */
|
|
6169
|
-
/* @__PURE__ */
|
|
6170
|
-
/* @__PURE__ */
|
|
6545
|
+
return /* @__PURE__ */ jsxs32(View42, { style: { flex: 1 }, children: [
|
|
6546
|
+
/* @__PURE__ */ jsx54(View42, { children: header }),
|
|
6547
|
+
topBanner ? /* @__PURE__ */ jsx54(View42, { style: { paddingHorizontal: 16, paddingTop: 8 }, children: topBanner }) : null,
|
|
6548
|
+
/* @__PURE__ */ jsxs32(View42, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
|
|
6549
|
+
/* @__PURE__ */ jsx54(ActivityIndicator9, {}),
|
|
6550
|
+
/* @__PURE__ */ jsx54(View42, { style: { height: 12 } }),
|
|
6551
|
+
/* @__PURE__ */ jsx54(Text, { variant: "bodyMuted", children: forking ? "Creating your copy\u2026" : "Loading messages\u2026" })
|
|
6171
6552
|
] })
|
|
6172
6553
|
] });
|
|
6173
6554
|
}
|
|
6174
|
-
|
|
6555
|
+
const queueTop = queueItems.length > 0 ? /* @__PURE__ */ jsx54(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null;
|
|
6556
|
+
return /* @__PURE__ */ jsx54(
|
|
6175
6557
|
ChatPage,
|
|
6176
6558
|
{
|
|
6177
6559
|
header,
|
|
6178
6560
|
messages,
|
|
6179
6561
|
showTypingIndicator,
|
|
6180
6562
|
topBanner,
|
|
6563
|
+
composerTop: queueTop,
|
|
6181
6564
|
composerHorizontalPadding: 0,
|
|
6182
6565
|
listRef,
|
|
6183
6566
|
onNearBottomChange: setNearBottom,
|
|
6184
|
-
overlay: /* @__PURE__ */
|
|
6567
|
+
overlay: /* @__PURE__ */ jsx54(
|
|
6185
6568
|
ScrollToBottomButton,
|
|
6186
6569
|
{
|
|
6187
6570
|
visible: !nearBottom,
|
|
6188
6571
|
onPress: handleScrollToBottom,
|
|
6189
6572
|
style: { bottom: 80 },
|
|
6190
|
-
children: /* @__PURE__ */
|
|
6573
|
+
children: /* @__PURE__ */ jsx54(IconArrowDown, { size: 20, colorToken: "floatingContent" })
|
|
6191
6574
|
}
|
|
6192
6575
|
),
|
|
6193
6576
|
composer: {
|
|
@@ -6208,16 +6591,16 @@ function ChatPanel({
|
|
|
6208
6591
|
}
|
|
6209
6592
|
|
|
6210
6593
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
6211
|
-
import * as
|
|
6212
|
-
import { Pressable as
|
|
6594
|
+
import * as React41 from "react";
|
|
6595
|
+
import { Pressable as Pressable15, View as View44 } from "react-native";
|
|
6213
6596
|
|
|
6214
6597
|
// src/components/primitives/Modal.tsx
|
|
6215
6598
|
import {
|
|
6216
6599
|
Modal as RNModal,
|
|
6217
|
-
Pressable as
|
|
6218
|
-
View as
|
|
6600
|
+
Pressable as Pressable14,
|
|
6601
|
+
View as View43
|
|
6219
6602
|
} from "react-native";
|
|
6220
|
-
import { jsx as
|
|
6603
|
+
import { jsx as jsx55, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
6221
6604
|
function Modal({
|
|
6222
6605
|
visible,
|
|
6223
6606
|
onRequestClose,
|
|
@@ -6226,30 +6609,30 @@ function Modal({
|
|
|
6226
6609
|
contentStyle
|
|
6227
6610
|
}) {
|
|
6228
6611
|
const theme = useTheme();
|
|
6229
|
-
return /* @__PURE__ */
|
|
6612
|
+
return /* @__PURE__ */ jsx55(
|
|
6230
6613
|
RNModal,
|
|
6231
6614
|
{
|
|
6232
6615
|
visible,
|
|
6233
6616
|
transparent: true,
|
|
6234
6617
|
animationType: "fade",
|
|
6235
6618
|
onRequestClose,
|
|
6236
|
-
children: /* @__PURE__ */
|
|
6237
|
-
/* @__PURE__ */
|
|
6238
|
-
|
|
6619
|
+
children: /* @__PURE__ */ jsxs33(View43, { style: { flex: 1, backgroundColor: theme.colors.backdrop, justifyContent: "center", padding: theme.spacing.lg }, children: [
|
|
6620
|
+
/* @__PURE__ */ jsx55(
|
|
6621
|
+
Pressable14,
|
|
6239
6622
|
{
|
|
6240
6623
|
accessibilityRole: "button",
|
|
6241
6624
|
onPress: dismissOnBackdropPress ? onRequestClose : void 0,
|
|
6242
6625
|
style: { position: "absolute", inset: 0 }
|
|
6243
6626
|
}
|
|
6244
6627
|
),
|
|
6245
|
-
/* @__PURE__ */
|
|
6628
|
+
/* @__PURE__ */ jsx55(Card, { variant: "surfaceRaised", padded: true, style: [{ borderRadius: theme.radii.xl }, contentStyle], children })
|
|
6246
6629
|
] })
|
|
6247
6630
|
}
|
|
6248
6631
|
);
|
|
6249
6632
|
}
|
|
6250
6633
|
|
|
6251
6634
|
// src/components/dialogs/ConfirmMergeRequestDialog.tsx
|
|
6252
|
-
import { jsx as
|
|
6635
|
+
import { jsx as jsx56, jsxs as jsxs34 } from "react/jsx-runtime";
|
|
6253
6636
|
function ConfirmMergeRequestDialog({
|
|
6254
6637
|
visible,
|
|
6255
6638
|
onOpenChange,
|
|
@@ -6260,14 +6643,14 @@ function ConfirmMergeRequestDialog({
|
|
|
6260
6643
|
onTestFirst
|
|
6261
6644
|
}) {
|
|
6262
6645
|
const theme = useTheme();
|
|
6263
|
-
const close =
|
|
6646
|
+
const close = React41.useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
6264
6647
|
const canConfirm = Boolean(mergeRequest) && !approveDisabled;
|
|
6265
|
-
const handleConfirm =
|
|
6648
|
+
const handleConfirm = React41.useCallback(() => {
|
|
6266
6649
|
if (!mergeRequest) return;
|
|
6267
6650
|
onOpenChange(false);
|
|
6268
6651
|
void onConfirm();
|
|
6269
6652
|
}, [mergeRequest, onConfirm, onOpenChange]);
|
|
6270
|
-
const handleTestFirst =
|
|
6653
|
+
const handleTestFirst = React41.useCallback(() => {
|
|
6271
6654
|
if (!mergeRequest) return;
|
|
6272
6655
|
onOpenChange(false);
|
|
6273
6656
|
void onTestFirst(mergeRequest);
|
|
@@ -6279,7 +6662,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6279
6662
|
justifyContent: "center",
|
|
6280
6663
|
alignSelf: "stretch"
|
|
6281
6664
|
};
|
|
6282
|
-
return /* @__PURE__ */
|
|
6665
|
+
return /* @__PURE__ */ jsxs34(
|
|
6283
6666
|
Modal,
|
|
6284
6667
|
{
|
|
6285
6668
|
visible,
|
|
@@ -6290,7 +6673,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6290
6673
|
backgroundColor: theme.colors.background
|
|
6291
6674
|
},
|
|
6292
6675
|
children: [
|
|
6293
|
-
/* @__PURE__ */
|
|
6676
|
+
/* @__PURE__ */ jsx56(View44, { children: /* @__PURE__ */ jsx56(
|
|
6294
6677
|
Text,
|
|
6295
6678
|
{
|
|
6296
6679
|
style: {
|
|
@@ -6302,9 +6685,9 @@ function ConfirmMergeRequestDialog({
|
|
|
6302
6685
|
children: "Are you sure you want to approve this merge request?"
|
|
6303
6686
|
}
|
|
6304
6687
|
) }),
|
|
6305
|
-
/* @__PURE__ */
|
|
6306
|
-
/* @__PURE__ */
|
|
6307
|
-
|
|
6688
|
+
/* @__PURE__ */ jsxs34(View44, { style: { marginTop: 16 }, children: [
|
|
6689
|
+
/* @__PURE__ */ jsx56(
|
|
6690
|
+
View44,
|
|
6308
6691
|
{
|
|
6309
6692
|
style: [
|
|
6310
6693
|
fullWidthButtonBase,
|
|
@@ -6313,22 +6696,22 @@ function ConfirmMergeRequestDialog({
|
|
|
6313
6696
|
opacity: canConfirm ? 1 : 0.5
|
|
6314
6697
|
}
|
|
6315
6698
|
],
|
|
6316
|
-
children: /* @__PURE__ */
|
|
6317
|
-
|
|
6699
|
+
children: /* @__PURE__ */ jsx56(
|
|
6700
|
+
Pressable15,
|
|
6318
6701
|
{
|
|
6319
6702
|
accessibilityRole: "button",
|
|
6320
6703
|
accessibilityLabel: "Approve Merge",
|
|
6321
6704
|
disabled: !canConfirm,
|
|
6322
6705
|
onPress: handleConfirm,
|
|
6323
6706
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6324
|
-
children: /* @__PURE__ */
|
|
6707
|
+
children: /* @__PURE__ */ jsx56(Text, { style: { textAlign: "center", color: theme.colors.onPrimary }, children: "Approve Merge" })
|
|
6325
6708
|
}
|
|
6326
6709
|
)
|
|
6327
6710
|
}
|
|
6328
6711
|
),
|
|
6329
|
-
/* @__PURE__ */
|
|
6330
|
-
/* @__PURE__ */
|
|
6331
|
-
|
|
6712
|
+
/* @__PURE__ */ jsx56(View44, { style: { height: 8 } }),
|
|
6713
|
+
/* @__PURE__ */ jsx56(
|
|
6714
|
+
View44,
|
|
6332
6715
|
{
|
|
6333
6716
|
style: [
|
|
6334
6717
|
fullWidthButtonBase,
|
|
@@ -6339,22 +6722,22 @@ function ConfirmMergeRequestDialog({
|
|
|
6339
6722
|
opacity: isBuilding || !mergeRequest ? 0.5 : 1
|
|
6340
6723
|
}
|
|
6341
6724
|
],
|
|
6342
|
-
children: /* @__PURE__ */
|
|
6343
|
-
|
|
6725
|
+
children: /* @__PURE__ */ jsx56(
|
|
6726
|
+
Pressable15,
|
|
6344
6727
|
{
|
|
6345
6728
|
accessibilityRole: "button",
|
|
6346
6729
|
accessibilityLabel: isBuilding ? "Preparing\u2026" : "Test edits first",
|
|
6347
6730
|
disabled: isBuilding || !mergeRequest,
|
|
6348
6731
|
onPress: handleTestFirst,
|
|
6349
6732
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6350
|
-
children: /* @__PURE__ */
|
|
6733
|
+
children: /* @__PURE__ */ jsx56(Text, { style: { textAlign: "center", color: theme.colors.text }, children: isBuilding ? "Preparing\u2026" : "Test edits first" })
|
|
6351
6734
|
}
|
|
6352
6735
|
)
|
|
6353
6736
|
}
|
|
6354
6737
|
),
|
|
6355
|
-
/* @__PURE__ */
|
|
6356
|
-
/* @__PURE__ */
|
|
6357
|
-
|
|
6738
|
+
/* @__PURE__ */ jsx56(View44, { style: { height: 8 } }),
|
|
6739
|
+
/* @__PURE__ */ jsx56(
|
|
6740
|
+
View44,
|
|
6358
6741
|
{
|
|
6359
6742
|
style: [
|
|
6360
6743
|
fullWidthButtonBase,
|
|
@@ -6364,14 +6747,14 @@ function ConfirmMergeRequestDialog({
|
|
|
6364
6747
|
borderColor: theme.colors.border
|
|
6365
6748
|
}
|
|
6366
6749
|
],
|
|
6367
|
-
children: /* @__PURE__ */
|
|
6368
|
-
|
|
6750
|
+
children: /* @__PURE__ */ jsx56(
|
|
6751
|
+
Pressable15,
|
|
6369
6752
|
{
|
|
6370
6753
|
accessibilityRole: "button",
|
|
6371
6754
|
accessibilityLabel: "Cancel",
|
|
6372
6755
|
onPress: close,
|
|
6373
6756
|
style: [fullWidthButtonBase, { flex: 1 }],
|
|
6374
|
-
children: /* @__PURE__ */
|
|
6757
|
+
children: /* @__PURE__ */ jsx56(Text, { style: { textAlign: "center", color: theme.colors.text }, children: "Cancel" })
|
|
6375
6758
|
}
|
|
6376
6759
|
)
|
|
6377
6760
|
}
|
|
@@ -6383,7 +6766,7 @@ function ConfirmMergeRequestDialog({
|
|
|
6383
6766
|
}
|
|
6384
6767
|
|
|
6385
6768
|
// src/studio/ui/ConfirmMergeFlow.tsx
|
|
6386
|
-
import { jsx as
|
|
6769
|
+
import { jsx as jsx57 } from "react/jsx-runtime";
|
|
6387
6770
|
function ConfirmMergeFlow({
|
|
6388
6771
|
visible,
|
|
6389
6772
|
onOpenChange,
|
|
@@ -6394,7 +6777,7 @@ function ConfirmMergeFlow({
|
|
|
6394
6777
|
onConfirm,
|
|
6395
6778
|
onTestFirst
|
|
6396
6779
|
}) {
|
|
6397
|
-
return /* @__PURE__ */
|
|
6780
|
+
return /* @__PURE__ */ jsx57(
|
|
6398
6781
|
ConfirmMergeRequestDialog,
|
|
6399
6782
|
{
|
|
6400
6783
|
visible,
|
|
@@ -6416,11 +6799,11 @@ function ConfirmMergeFlow({
|
|
|
6416
6799
|
}
|
|
6417
6800
|
|
|
6418
6801
|
// src/studio/hooks/useOptimisticChatMessages.ts
|
|
6419
|
-
import * as
|
|
6802
|
+
import * as React42 from "react";
|
|
6420
6803
|
function makeOptimisticId() {
|
|
6421
6804
|
return `optimistic:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 10)}`;
|
|
6422
6805
|
}
|
|
6423
|
-
function
|
|
6806
|
+
function toEpochMs2(createdAt) {
|
|
6424
6807
|
if (createdAt == null) return 0;
|
|
6425
6808
|
if (typeof createdAt === "number") return createdAt;
|
|
6426
6809
|
if (createdAt instanceof Date) return createdAt.getTime();
|
|
@@ -6439,7 +6822,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
|
|
|
6439
6822
|
for (const m of candidates) {
|
|
6440
6823
|
if (m.author !== "human") continue;
|
|
6441
6824
|
if (normalize(m.content) !== target) continue;
|
|
6442
|
-
const serverMs =
|
|
6825
|
+
const serverMs = toEpochMs2(m.createdAt);
|
|
6443
6826
|
const optimisticMs = Date.parse(o.createdAtIso);
|
|
6444
6827
|
if (Number.isFinite(optimisticMs) && optimisticMs > 0 && serverMs > 0) {
|
|
6445
6828
|
if (serverMs + 12e4 < optimisticMs) continue;
|
|
@@ -6451,14 +6834,15 @@ function isOptimisticResolvedByServer(chatMessages, o) {
|
|
|
6451
6834
|
function useOptimisticChatMessages({
|
|
6452
6835
|
threadId,
|
|
6453
6836
|
shouldForkOnEdit,
|
|
6837
|
+
disableOptimistic = false,
|
|
6454
6838
|
chatMessages,
|
|
6455
6839
|
onSendChat
|
|
6456
6840
|
}) {
|
|
6457
|
-
const [optimisticChat, setOptimisticChat] =
|
|
6458
|
-
|
|
6841
|
+
const [optimisticChat, setOptimisticChat] = React42.useState([]);
|
|
6842
|
+
React42.useEffect(() => {
|
|
6459
6843
|
setOptimisticChat([]);
|
|
6460
6844
|
}, [threadId]);
|
|
6461
|
-
const messages =
|
|
6845
|
+
const messages = React42.useMemo(() => {
|
|
6462
6846
|
if (!optimisticChat || optimisticChat.length === 0) return chatMessages;
|
|
6463
6847
|
const unresolved = optimisticChat.filter((o) => !isOptimisticResolvedByServer(chatMessages, o));
|
|
6464
6848
|
if (unresolved.length === 0) return chatMessages;
|
|
@@ -6474,7 +6858,7 @@ function useOptimisticChatMessages({
|
|
|
6474
6858
|
merged.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
6475
6859
|
return merged;
|
|
6476
6860
|
}, [chatMessages, optimisticChat]);
|
|
6477
|
-
|
|
6861
|
+
React42.useEffect(() => {
|
|
6478
6862
|
if (optimisticChat.length === 0) return;
|
|
6479
6863
|
setOptimisticChat((prev) => {
|
|
6480
6864
|
if (prev.length === 0) return prev;
|
|
@@ -6482,9 +6866,9 @@ function useOptimisticChatMessages({
|
|
|
6482
6866
|
return next.length === prev.length ? prev : next;
|
|
6483
6867
|
});
|
|
6484
6868
|
}, [chatMessages, optimisticChat.length]);
|
|
6485
|
-
const onSend =
|
|
6869
|
+
const onSend = React42.useCallback(
|
|
6486
6870
|
async (text, attachments) => {
|
|
6487
|
-
if (shouldForkOnEdit) {
|
|
6871
|
+
if (shouldForkOnEdit || disableOptimistic) {
|
|
6488
6872
|
await onSendChat(text, attachments);
|
|
6489
6873
|
return;
|
|
6490
6874
|
}
|
|
@@ -6496,7 +6880,7 @@ function useOptimisticChatMessages({
|
|
|
6496
6880
|
setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
|
|
6497
6881
|
});
|
|
6498
6882
|
},
|
|
6499
|
-
[chatMessages, onSendChat, shouldForkOnEdit]
|
|
6883
|
+
[chatMessages, disableOptimistic, onSendChat, shouldForkOnEdit]
|
|
6500
6884
|
);
|
|
6501
6885
|
return { messages, onSend };
|
|
6502
6886
|
}
|
|
@@ -6506,7 +6890,7 @@ import {
|
|
|
6506
6890
|
publishComergeStudioUIState,
|
|
6507
6891
|
startStudioControlPolling
|
|
6508
6892
|
} from "@comergehq/studio-control";
|
|
6509
|
-
import { Fragment as Fragment6, jsx as
|
|
6893
|
+
import { Fragment as Fragment6, jsx as jsx58, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
6510
6894
|
function StudioOverlay({
|
|
6511
6895
|
captureTargetRef,
|
|
6512
6896
|
app,
|
|
@@ -6533,44 +6917,48 @@ function StudioOverlay({
|
|
|
6533
6917
|
chatSending,
|
|
6534
6918
|
chatShowTypingIndicator,
|
|
6535
6919
|
onSendChat,
|
|
6920
|
+
chatQueueItems,
|
|
6921
|
+
onRemoveQueueItem,
|
|
6536
6922
|
onNavigateHome,
|
|
6537
6923
|
showBubble,
|
|
6538
6924
|
studioControlOptions
|
|
6539
6925
|
}) {
|
|
6540
6926
|
const theme = useTheme();
|
|
6541
6927
|
const { width } = useWindowDimensions4();
|
|
6542
|
-
const [sheetOpen, setSheetOpen] =
|
|
6543
|
-
const sheetOpenRef =
|
|
6544
|
-
const [activePage, setActivePage] =
|
|
6545
|
-
const [drawing, setDrawing] =
|
|
6546
|
-
const [chatAttachments, setChatAttachments] =
|
|
6547
|
-
const [commentsAppId, setCommentsAppId] =
|
|
6548
|
-
const [commentsCount, setCommentsCount] =
|
|
6928
|
+
const [sheetOpen, setSheetOpen] = React43.useState(false);
|
|
6929
|
+
const sheetOpenRef = React43.useRef(sheetOpen);
|
|
6930
|
+
const [activePage, setActivePage] = React43.useState("preview");
|
|
6931
|
+
const [drawing, setDrawing] = React43.useState(false);
|
|
6932
|
+
const [chatAttachments, setChatAttachments] = React43.useState([]);
|
|
6933
|
+
const [commentsAppId, setCommentsAppId] = React43.useState(null);
|
|
6934
|
+
const [commentsCount, setCommentsCount] = React43.useState(null);
|
|
6549
6935
|
const threadId = (app == null ? void 0 : app.threadId) ?? null;
|
|
6936
|
+
const disableOptimistic = Boolean(chatQueueItems && chatQueueItems.length > 0) || (app == null ? void 0 : app.status) === "editing";
|
|
6550
6937
|
const optimistic = useOptimisticChatMessages({
|
|
6551
6938
|
threadId,
|
|
6552
6939
|
shouldForkOnEdit,
|
|
6940
|
+
disableOptimistic,
|
|
6553
6941
|
chatMessages,
|
|
6554
6942
|
onSendChat
|
|
6555
6943
|
});
|
|
6556
|
-
const [confirmMrId, setConfirmMrId] =
|
|
6557
|
-
const confirmMr =
|
|
6944
|
+
const [confirmMrId, setConfirmMrId] = React43.useState(null);
|
|
6945
|
+
const confirmMr = React43.useMemo(
|
|
6558
6946
|
() => confirmMrId ? incomingMergeRequests.find((m) => m.id === confirmMrId) ?? null : null,
|
|
6559
6947
|
[confirmMrId, incomingMergeRequests]
|
|
6560
6948
|
);
|
|
6561
|
-
const handleSheetOpenChange =
|
|
6949
|
+
const handleSheetOpenChange = React43.useCallback((open) => {
|
|
6562
6950
|
setSheetOpen(open);
|
|
6563
6951
|
if (!open) Keyboard5.dismiss();
|
|
6564
6952
|
}, []);
|
|
6565
|
-
const closeSheet =
|
|
6953
|
+
const closeSheet = React43.useCallback(() => {
|
|
6566
6954
|
handleSheetOpenChange(false);
|
|
6567
6955
|
}, [handleSheetOpenChange]);
|
|
6568
|
-
const openSheet =
|
|
6569
|
-
const goToChat =
|
|
6956
|
+
const openSheet = React43.useCallback(() => setSheetOpen(true), []);
|
|
6957
|
+
const goToChat = React43.useCallback(() => {
|
|
6570
6958
|
setActivePage("chat");
|
|
6571
6959
|
openSheet();
|
|
6572
6960
|
}, [openSheet]);
|
|
6573
|
-
const backToPreview =
|
|
6961
|
+
const backToPreview = React43.useCallback(() => {
|
|
6574
6962
|
if (Platform10.OS !== "ios") {
|
|
6575
6963
|
Keyboard5.dismiss();
|
|
6576
6964
|
setActivePage("preview");
|
|
@@ -6588,11 +6976,11 @@ function StudioOverlay({
|
|
|
6588
6976
|
const t = setTimeout(finalize, 350);
|
|
6589
6977
|
Keyboard5.dismiss();
|
|
6590
6978
|
}, []);
|
|
6591
|
-
const startDraw =
|
|
6979
|
+
const startDraw = React43.useCallback(() => {
|
|
6592
6980
|
setDrawing(true);
|
|
6593
6981
|
closeSheet();
|
|
6594
6982
|
}, [closeSheet]);
|
|
6595
|
-
const handleDrawCapture =
|
|
6983
|
+
const handleDrawCapture = React43.useCallback(
|
|
6596
6984
|
(dataUrl) => {
|
|
6597
6985
|
setChatAttachments((prev) => [...prev, dataUrl]);
|
|
6598
6986
|
setDrawing(false);
|
|
@@ -6601,7 +6989,7 @@ function StudioOverlay({
|
|
|
6601
6989
|
},
|
|
6602
6990
|
[openSheet]
|
|
6603
6991
|
);
|
|
6604
|
-
const toggleSheet =
|
|
6992
|
+
const toggleSheet = React43.useCallback(async () => {
|
|
6605
6993
|
if (!sheetOpen) {
|
|
6606
6994
|
const shouldExitTest = Boolean(testingMrId) || isTesting;
|
|
6607
6995
|
if (shouldExitTest) {
|
|
@@ -6613,7 +7001,7 @@ function StudioOverlay({
|
|
|
6613
7001
|
closeSheet();
|
|
6614
7002
|
}
|
|
6615
7003
|
}, [closeSheet, isTesting, onRestoreBase, sheetOpen, testingMrId]);
|
|
6616
|
-
const handleTestMr =
|
|
7004
|
+
const handleTestMr = React43.useCallback(
|
|
6617
7005
|
async (mr) => {
|
|
6618
7006
|
if (!onTestMr) return;
|
|
6619
7007
|
await onTestMr(mr);
|
|
@@ -6621,10 +7009,10 @@ function StudioOverlay({
|
|
|
6621
7009
|
},
|
|
6622
7010
|
[closeSheet, onTestMr]
|
|
6623
7011
|
);
|
|
6624
|
-
|
|
7012
|
+
React43.useEffect(() => {
|
|
6625
7013
|
sheetOpenRef.current = sheetOpen;
|
|
6626
7014
|
}, [sheetOpen]);
|
|
6627
|
-
|
|
7015
|
+
React43.useEffect(() => {
|
|
6628
7016
|
const poller = startStudioControlPolling((action) => {
|
|
6629
7017
|
if (action === "show" && !sheetOpenRef.current) openSheet();
|
|
6630
7018
|
if (action === "hide" && sheetOpenRef.current) closeSheet();
|
|
@@ -6632,17 +7020,17 @@ function StudioOverlay({
|
|
|
6632
7020
|
}, studioControlOptions);
|
|
6633
7021
|
return () => poller.stop();
|
|
6634
7022
|
}, [closeSheet, openSheet, studioControlOptions, toggleSheet]);
|
|
6635
|
-
|
|
7023
|
+
React43.useEffect(() => {
|
|
6636
7024
|
void publishComergeStudioUIState(sheetOpen, studioControlOptions);
|
|
6637
7025
|
}, [sheetOpen, studioControlOptions]);
|
|
6638
|
-
return /* @__PURE__ */
|
|
6639
|
-
/* @__PURE__ */
|
|
6640
|
-
/* @__PURE__ */
|
|
7026
|
+
return /* @__PURE__ */ jsxs35(Fragment6, { children: [
|
|
7027
|
+
/* @__PURE__ */ jsx58(EdgeGlowFrame, { visible: isTesting, role: "accent", thickness: 40, intensity: 1 }),
|
|
7028
|
+
/* @__PURE__ */ jsx58(StudioBottomSheet, { open: sheetOpen, onOpenChange: handleSheetOpenChange, children: /* @__PURE__ */ jsx58(
|
|
6641
7029
|
StudioSheetPager,
|
|
6642
7030
|
{
|
|
6643
7031
|
activePage,
|
|
6644
7032
|
width,
|
|
6645
|
-
preview: /* @__PURE__ */
|
|
7033
|
+
preview: /* @__PURE__ */ jsx58(
|
|
6646
7034
|
PreviewPanel,
|
|
6647
7035
|
{
|
|
6648
7036
|
app,
|
|
@@ -6668,7 +7056,7 @@ function StudioOverlay({
|
|
|
6668
7056
|
commentCountOverride: commentsCount ?? void 0
|
|
6669
7057
|
}
|
|
6670
7058
|
),
|
|
6671
|
-
chat: /* @__PURE__ */
|
|
7059
|
+
chat: /* @__PURE__ */ jsx58(
|
|
6672
7060
|
ChatPanel,
|
|
6673
7061
|
{
|
|
6674
7062
|
messages: optimistic.messages,
|
|
@@ -6686,12 +7074,14 @@ function StudioOverlay({
|
|
|
6686
7074
|
onClose: closeSheet,
|
|
6687
7075
|
onNavigateHome,
|
|
6688
7076
|
onStartDraw: startDraw,
|
|
6689
|
-
onSend: optimistic.onSend
|
|
7077
|
+
onSend: optimistic.onSend,
|
|
7078
|
+
queueItems: chatQueueItems,
|
|
7079
|
+
onRemoveQueueItem
|
|
6690
7080
|
}
|
|
6691
7081
|
)
|
|
6692
7082
|
}
|
|
6693
7083
|
) }),
|
|
6694
|
-
showBubble && /* @__PURE__ */
|
|
7084
|
+
showBubble && /* @__PURE__ */ jsx58(
|
|
6695
7085
|
Bubble,
|
|
6696
7086
|
{
|
|
6697
7087
|
visible: !sheetOpen && !drawing,
|
|
@@ -6699,10 +7089,10 @@ function StudioOverlay({
|
|
|
6699
7089
|
badgeCount: incomingMergeRequests.length,
|
|
6700
7090
|
onPress: toggleSheet,
|
|
6701
7091
|
isLoading: (app == null ? void 0 : app.status) === "editing",
|
|
6702
|
-
children: /* @__PURE__ */
|
|
7092
|
+
children: /* @__PURE__ */ jsx58(View45, { style: { width: 28, height: 28, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx58(MergeIcon, { width: 24, height: 24, color: theme.colors.floatingContent }) })
|
|
6703
7093
|
}
|
|
6704
7094
|
),
|
|
6705
|
-
/* @__PURE__ */
|
|
7095
|
+
/* @__PURE__ */ jsx58(
|
|
6706
7096
|
DrawModeOverlay,
|
|
6707
7097
|
{
|
|
6708
7098
|
visible: drawing,
|
|
@@ -6711,7 +7101,7 @@ function StudioOverlay({
|
|
|
6711
7101
|
onCapture: handleDrawCapture
|
|
6712
7102
|
}
|
|
6713
7103
|
),
|
|
6714
|
-
/* @__PURE__ */
|
|
7104
|
+
/* @__PURE__ */ jsx58(
|
|
6715
7105
|
ConfirmMergeFlow,
|
|
6716
7106
|
{
|
|
6717
7107
|
visible: Boolean(confirmMr),
|
|
@@ -6724,7 +7114,7 @@ function StudioOverlay({
|
|
|
6724
7114
|
onTestFirst: handleTestMr
|
|
6725
7115
|
}
|
|
6726
7116
|
),
|
|
6727
|
-
/* @__PURE__ */
|
|
7117
|
+
/* @__PURE__ */ jsx58(
|
|
6728
7118
|
AppCommentsSheet,
|
|
6729
7119
|
{
|
|
6730
7120
|
appId: commentsAppId,
|
|
@@ -6736,8 +7126,190 @@ function StudioOverlay({
|
|
|
6736
7126
|
] });
|
|
6737
7127
|
}
|
|
6738
7128
|
|
|
7129
|
+
// src/studio/hooks/useEditQueue.ts
|
|
7130
|
+
import * as React44 from "react";
|
|
7131
|
+
|
|
7132
|
+
// src/data/apps/edit-queue/remote.ts
|
|
7133
|
+
var EditQueueRemoteDataSourceImpl = class extends BaseRemote {
|
|
7134
|
+
async list(appId) {
|
|
7135
|
+
const { data } = await api.get(
|
|
7136
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue`
|
|
7137
|
+
);
|
|
7138
|
+
return data;
|
|
7139
|
+
}
|
|
7140
|
+
async update(appId, queueItemId, payload) {
|
|
7141
|
+
const { data } = await api.patch(
|
|
7142
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`,
|
|
7143
|
+
payload
|
|
7144
|
+
);
|
|
7145
|
+
return data;
|
|
7146
|
+
}
|
|
7147
|
+
async cancel(appId, queueItemId) {
|
|
7148
|
+
const { data } = await api.delete(
|
|
7149
|
+
`/v1/apps/${encodeURIComponent(appId)}/edit-queue/${encodeURIComponent(queueItemId)}`
|
|
7150
|
+
);
|
|
7151
|
+
return data;
|
|
7152
|
+
}
|
|
7153
|
+
};
|
|
7154
|
+
var editQueueRemoteDataSource = new EditQueueRemoteDataSourceImpl();
|
|
7155
|
+
|
|
7156
|
+
// src/data/apps/edit-queue/repository.ts
|
|
7157
|
+
var ACTIVE_STATUSES = ["pending"];
|
|
7158
|
+
function toString(value) {
|
|
7159
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
7160
|
+
}
|
|
7161
|
+
function toAttachments(value) {
|
|
7162
|
+
return Array.isArray(value) ? value : [];
|
|
7163
|
+
}
|
|
7164
|
+
function mapQueueItem(row) {
|
|
7165
|
+
const payload = row.payload ?? {};
|
|
7166
|
+
return {
|
|
7167
|
+
id: row.id,
|
|
7168
|
+
status: row.status,
|
|
7169
|
+
prompt: toString(payload.trimmedPrompt),
|
|
7170
|
+
messageId: toString(payload.messageId),
|
|
7171
|
+
attachments: toAttachments(payload.attachments),
|
|
7172
|
+
createdAt: row.created_at,
|
|
7173
|
+
updatedAt: row.updated_at,
|
|
7174
|
+
runAfter: row.run_after,
|
|
7175
|
+
priority: row.priority
|
|
7176
|
+
};
|
|
7177
|
+
}
|
|
7178
|
+
var EditQueueRepositoryImpl = class extends BaseRepository {
|
|
7179
|
+
constructor(remote) {
|
|
7180
|
+
super();
|
|
7181
|
+
this.remote = remote;
|
|
7182
|
+
}
|
|
7183
|
+
async list(appId) {
|
|
7184
|
+
const res = await this.remote.list(appId);
|
|
7185
|
+
const data = this.unwrapOrThrow(res);
|
|
7186
|
+
return data.items ?? [];
|
|
7187
|
+
}
|
|
7188
|
+
async update(appId, queueItemId, payload) {
|
|
7189
|
+
const res = await this.remote.update(appId, queueItemId, payload);
|
|
7190
|
+
return this.unwrapOrThrow(res);
|
|
7191
|
+
}
|
|
7192
|
+
async cancel(appId, queueItemId) {
|
|
7193
|
+
const res = await this.remote.cancel(appId, queueItemId);
|
|
7194
|
+
return this.unwrapOrThrow(res);
|
|
7195
|
+
}
|
|
7196
|
+
subscribeEditQueue(appId, handlers) {
|
|
7197
|
+
const supabase = getSupabaseClient();
|
|
7198
|
+
const channel = supabase.channel(`edit-queue:app:${appId}`).on(
|
|
7199
|
+
"postgres_changes",
|
|
7200
|
+
{ event: "INSERT", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7201
|
+
(payload) => {
|
|
7202
|
+
var _a;
|
|
7203
|
+
const row = payload.new;
|
|
7204
|
+
if (row.kind !== "edit") return;
|
|
7205
|
+
const item = mapQueueItem(row);
|
|
7206
|
+
if (!ACTIVE_STATUSES.includes(item.status)) return;
|
|
7207
|
+
(_a = handlers.onInsert) == null ? void 0 : _a.call(handlers, item);
|
|
7208
|
+
}
|
|
7209
|
+
).on(
|
|
7210
|
+
"postgres_changes",
|
|
7211
|
+
{ event: "UPDATE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7212
|
+
(payload) => {
|
|
7213
|
+
var _a, _b;
|
|
7214
|
+
const row = payload.new;
|
|
7215
|
+
if (row.kind !== "edit") return;
|
|
7216
|
+
const item = mapQueueItem(row);
|
|
7217
|
+
if (ACTIVE_STATUSES.includes(item.status)) (_a = handlers.onUpdate) == null ? void 0 : _a.call(handlers, item);
|
|
7218
|
+
else (_b = handlers.onDelete) == null ? void 0 : _b.call(handlers, item);
|
|
7219
|
+
}
|
|
7220
|
+
).on(
|
|
7221
|
+
"postgres_changes",
|
|
7222
|
+
{ event: "DELETE", schema: "public", table: "app_job_queue", filter: `app_id=eq.${appId}` },
|
|
7223
|
+
(payload) => {
|
|
7224
|
+
var _a;
|
|
7225
|
+
const row = payload.old;
|
|
7226
|
+
if (row.kind !== "edit") return;
|
|
7227
|
+
(_a = handlers.onDelete) == null ? void 0 : _a.call(handlers, mapQueueItem(row));
|
|
7228
|
+
}
|
|
7229
|
+
).subscribe();
|
|
7230
|
+
return () => {
|
|
7231
|
+
supabase.removeChannel(channel);
|
|
7232
|
+
};
|
|
7233
|
+
}
|
|
7234
|
+
};
|
|
7235
|
+
var editQueueRepository = new EditQueueRepositoryImpl(
|
|
7236
|
+
editQueueRemoteDataSource
|
|
7237
|
+
);
|
|
7238
|
+
|
|
7239
|
+
// src/studio/hooks/useEditQueue.ts
|
|
7240
|
+
function useEditQueue(appId) {
|
|
7241
|
+
const [items, setItems] = React44.useState([]);
|
|
7242
|
+
const [loading, setLoading] = React44.useState(false);
|
|
7243
|
+
const [error, setError] = React44.useState(null);
|
|
7244
|
+
const activeRequestIdRef = React44.useRef(0);
|
|
7245
|
+
const foregroundSignal = useForegroundSignal(Boolean(appId));
|
|
7246
|
+
const upsertSorted = React44.useCallback((prev, nextItem) => {
|
|
7247
|
+
const next = prev.some((x) => x.id === nextItem.id) ? prev.map((x) => x.id === nextItem.id ? nextItem : x) : [...prev, nextItem];
|
|
7248
|
+
next.sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt)));
|
|
7249
|
+
return next;
|
|
7250
|
+
}, []);
|
|
7251
|
+
const refetch = React44.useCallback(async () => {
|
|
7252
|
+
if (!appId) {
|
|
7253
|
+
setItems([]);
|
|
7254
|
+
return;
|
|
7255
|
+
}
|
|
7256
|
+
const requestId = ++activeRequestIdRef.current;
|
|
7257
|
+
setLoading(true);
|
|
7258
|
+
setError(null);
|
|
7259
|
+
try {
|
|
7260
|
+
const list = await editQueueRepository.list(appId);
|
|
7261
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
7262
|
+
setItems([...list].sort((a, b) => String(a.createdAt).localeCompare(String(b.createdAt))));
|
|
7263
|
+
} catch (e) {
|
|
7264
|
+
if (activeRequestIdRef.current !== requestId) return;
|
|
7265
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
7266
|
+
setItems([]);
|
|
7267
|
+
} finally {
|
|
7268
|
+
if (activeRequestIdRef.current === requestId) setLoading(false);
|
|
7269
|
+
}
|
|
7270
|
+
}, [appId]);
|
|
7271
|
+
React44.useEffect(() => {
|
|
7272
|
+
void refetch();
|
|
7273
|
+
}, [refetch]);
|
|
7274
|
+
React44.useEffect(() => {
|
|
7275
|
+
if (!appId) return;
|
|
7276
|
+
const unsubscribe = editQueueRepository.subscribeEditQueue(appId, {
|
|
7277
|
+
onInsert: (item) => setItems((prev) => upsertSorted(prev, item)),
|
|
7278
|
+
onUpdate: (item) => setItems((prev) => upsertSorted(prev, item)),
|
|
7279
|
+
onDelete: (item) => setItems((prev) => prev.filter((x) => x.id !== item.id))
|
|
7280
|
+
});
|
|
7281
|
+
return unsubscribe;
|
|
7282
|
+
}, [appId, upsertSorted, foregroundSignal]);
|
|
7283
|
+
React44.useEffect(() => {
|
|
7284
|
+
if (!appId) return;
|
|
7285
|
+
if (foregroundSignal <= 0) return;
|
|
7286
|
+
void refetch();
|
|
7287
|
+
}, [appId, foregroundSignal, refetch]);
|
|
7288
|
+
return { items, loading, error, refetch };
|
|
7289
|
+
}
|
|
7290
|
+
|
|
7291
|
+
// src/studio/hooks/useEditQueueActions.ts
|
|
7292
|
+
import * as React45 from "react";
|
|
7293
|
+
function useEditQueueActions(appId) {
|
|
7294
|
+
const update = React45.useCallback(
|
|
7295
|
+
async (queueItemId, payload) => {
|
|
7296
|
+
if (!appId) return;
|
|
7297
|
+
await editQueueRepository.update(appId, queueItemId, payload);
|
|
7298
|
+
},
|
|
7299
|
+
[appId]
|
|
7300
|
+
);
|
|
7301
|
+
const cancel = React45.useCallback(
|
|
7302
|
+
async (queueItemId) => {
|
|
7303
|
+
if (!appId) return;
|
|
7304
|
+
await editQueueRepository.cancel(appId, queueItemId);
|
|
7305
|
+
},
|
|
7306
|
+
[appId]
|
|
7307
|
+
);
|
|
7308
|
+
return { update, cancel };
|
|
7309
|
+
}
|
|
7310
|
+
|
|
6739
7311
|
// src/studio/ComergeStudio.tsx
|
|
6740
|
-
import { jsx as
|
|
7312
|
+
import { jsx as jsx59, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
6741
7313
|
function ComergeStudio({
|
|
6742
7314
|
appId,
|
|
6743
7315
|
clientKey: clientKey2,
|
|
@@ -6748,17 +7320,17 @@ function ComergeStudio({
|
|
|
6748
7320
|
studioControlOptions,
|
|
6749
7321
|
embeddedBaseBundles
|
|
6750
7322
|
}) {
|
|
6751
|
-
const [activeAppId, setActiveAppId] =
|
|
6752
|
-
const [runtimeAppId, setRuntimeAppId] =
|
|
6753
|
-
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] =
|
|
6754
|
-
const platform =
|
|
6755
|
-
|
|
7323
|
+
const [activeAppId, setActiveAppId] = React46.useState(appId);
|
|
7324
|
+
const [runtimeAppId, setRuntimeAppId] = React46.useState(appId);
|
|
7325
|
+
const [pendingRuntimeTargetAppId, setPendingRuntimeTargetAppId] = React46.useState(null);
|
|
7326
|
+
const platform = React46.useMemo(() => RNPlatform.OS === "ios" ? "ios" : "android", []);
|
|
7327
|
+
React46.useEffect(() => {
|
|
6756
7328
|
setActiveAppId(appId);
|
|
6757
7329
|
setRuntimeAppId(appId);
|
|
6758
7330
|
setPendingRuntimeTargetAppId(null);
|
|
6759
7331
|
}, [appId]);
|
|
6760
|
-
const captureTargetRef =
|
|
6761
|
-
return /* @__PURE__ */
|
|
7332
|
+
const captureTargetRef = React46.useRef(null);
|
|
7333
|
+
return /* @__PURE__ */ jsx59(StudioBootstrap, { clientKey: clientKey2, fallback: /* @__PURE__ */ jsx59(View46, { style: { flex: 1 } }), children: ({ userId }) => /* @__PURE__ */ jsx59(BottomSheetModalProvider, { children: /* @__PURE__ */ jsx59(LiquidGlassResetProvider, { resetTriggers: [appId, activeAppId, runtimeAppId], children: /* @__PURE__ */ jsx59(
|
|
6762
7334
|
ComergeStudioInner,
|
|
6763
7335
|
{
|
|
6764
7336
|
userId,
|
|
@@ -6799,11 +7371,11 @@ function ComergeStudioInner({
|
|
|
6799
7371
|
const { app, loading: appLoading } = useApp(activeAppId);
|
|
6800
7372
|
const { app: runtimeAppFromHook } = useApp(runtimeAppId, { enabled: runtimeAppId !== activeAppId });
|
|
6801
7373
|
const runtimeApp = runtimeAppId === activeAppId ? app : runtimeAppFromHook;
|
|
6802
|
-
const sawEditingOnPendingTargetRef =
|
|
6803
|
-
|
|
7374
|
+
const sawEditingOnPendingTargetRef = React46.useRef(false);
|
|
7375
|
+
React46.useEffect(() => {
|
|
6804
7376
|
sawEditingOnPendingTargetRef.current = false;
|
|
6805
7377
|
}, [pendingRuntimeTargetAppId]);
|
|
6806
|
-
|
|
7378
|
+
React46.useEffect(() => {
|
|
6807
7379
|
if (!pendingRuntimeTargetAppId) return;
|
|
6808
7380
|
if (activeAppId !== pendingRuntimeTargetAppId) return;
|
|
6809
7381
|
if ((app == null ? void 0 : app.status) === "editing") {
|
|
@@ -6821,13 +7393,13 @@ function ComergeStudioInner({
|
|
|
6821
7393
|
canRequestLatest: (runtimeApp == null ? void 0 : runtimeApp.status) === "ready",
|
|
6822
7394
|
embeddedBaseBundles
|
|
6823
7395
|
});
|
|
6824
|
-
const sawEditingOnActiveAppRef =
|
|
6825
|
-
const [showPostEditPreparing, setShowPostEditPreparing] =
|
|
6826
|
-
|
|
7396
|
+
const sawEditingOnActiveAppRef = React46.useRef(false);
|
|
7397
|
+
const [showPostEditPreparing, setShowPostEditPreparing] = React46.useState(false);
|
|
7398
|
+
React46.useEffect(() => {
|
|
6827
7399
|
sawEditingOnActiveAppRef.current = false;
|
|
6828
7400
|
setShowPostEditPreparing(false);
|
|
6829
7401
|
}, [activeAppId]);
|
|
6830
|
-
|
|
7402
|
+
React46.useEffect(() => {
|
|
6831
7403
|
if (!(app == null ? void 0 : app.id)) return;
|
|
6832
7404
|
if (app.status === "editing") {
|
|
6833
7405
|
sawEditingOnActiveAppRef.current = true;
|
|
@@ -6839,7 +7411,7 @@ function ComergeStudioInner({
|
|
|
6839
7411
|
sawEditingOnActiveAppRef.current = false;
|
|
6840
7412
|
}
|
|
6841
7413
|
}, [app == null ? void 0 : app.id, app == null ? void 0 : app.status]);
|
|
6842
|
-
|
|
7414
|
+
React46.useEffect(() => {
|
|
6843
7415
|
if (!showPostEditPreparing) return;
|
|
6844
7416
|
const stillProcessingBaseBundle = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
|
|
6845
7417
|
if (!stillProcessingBaseBundle) {
|
|
@@ -6848,15 +7420,27 @@ function ComergeStudioInner({
|
|
|
6848
7420
|
}, [showPostEditPreparing, bundle.loading, bundle.loadingMode, bundle.isTesting]);
|
|
6849
7421
|
const threadId = (app == null ? void 0 : app.threadId) ?? "";
|
|
6850
7422
|
const thread = useThreadMessages(threadId);
|
|
7423
|
+
const editQueue = useEditQueue(activeAppId);
|
|
7424
|
+
const editQueueActions = useEditQueueActions(activeAppId);
|
|
7425
|
+
const [lastEditQueueInfo, setLastEditQueueInfo] = React46.useState(null);
|
|
7426
|
+
const lastEditQueueInfoRef = React46.useRef(null);
|
|
7427
|
+
const [suppressQueueUntilResponse, setSuppressQueueUntilResponse] = React46.useState(false);
|
|
6851
7428
|
const mergeRequests = useMergeRequests({ appId: activeAppId });
|
|
6852
|
-
const hasOpenOutgoingMr =
|
|
7429
|
+
const hasOpenOutgoingMr = React46.useMemo(() => {
|
|
6853
7430
|
return mergeRequests.lists.outgoing.some((mr) => mr.status === "open");
|
|
6854
7431
|
}, [mergeRequests.lists.outgoing]);
|
|
6855
|
-
const incomingReviewMrs =
|
|
7432
|
+
const incomingReviewMrs = React46.useMemo(() => {
|
|
6856
7433
|
if (!userId) return mergeRequests.lists.incoming;
|
|
6857
7434
|
return mergeRequests.lists.incoming.filter((mr) => mr.createdBy !== userId);
|
|
6858
7435
|
}, [mergeRequests.lists.incoming, userId]);
|
|
6859
7436
|
const uploader = useAttachmentUpload();
|
|
7437
|
+
const updateLastEditQueueInfo = React46.useCallback(
|
|
7438
|
+
(info) => {
|
|
7439
|
+
lastEditQueueInfoRef.current = info;
|
|
7440
|
+
setLastEditQueueInfo(info);
|
|
7441
|
+
},
|
|
7442
|
+
[]
|
|
7443
|
+
);
|
|
6860
7444
|
const actions = useStudioActions({
|
|
6861
7445
|
userId,
|
|
6862
7446
|
app,
|
|
@@ -6871,20 +7455,62 @@ function ComergeStudioInner({
|
|
|
6871
7455
|
setPendingRuntimeTargetAppId(null);
|
|
6872
7456
|
}
|
|
6873
7457
|
},
|
|
6874
|
-
uploadAttachments: uploader.uploadBase64Images
|
|
7458
|
+
uploadAttachments: uploader.uploadBase64Images,
|
|
7459
|
+
onEditStart: () => {
|
|
7460
|
+
if (editQueue.items.length === 0) {
|
|
7461
|
+
setSuppressQueueUntilResponse(true);
|
|
7462
|
+
}
|
|
7463
|
+
},
|
|
7464
|
+
onEditQueued: (info) => {
|
|
7465
|
+
updateLastEditQueueInfo(info);
|
|
7466
|
+
if (info.queuePosition !== 1) {
|
|
7467
|
+
setSuppressQueueUntilResponse(false);
|
|
7468
|
+
}
|
|
7469
|
+
},
|
|
7470
|
+
onEditFinished: () => {
|
|
7471
|
+
var _a;
|
|
7472
|
+
if (((_a = lastEditQueueInfoRef.current) == null ? void 0 : _a.queuePosition) !== 1) {
|
|
7473
|
+
setSuppressQueueUntilResponse(false);
|
|
7474
|
+
}
|
|
7475
|
+
}
|
|
6875
7476
|
});
|
|
6876
|
-
const chatSendDisabled =
|
|
6877
|
-
const [processingMrId, setProcessingMrId] =
|
|
6878
|
-
const [testingMrId, setTestingMrId] =
|
|
6879
|
-
const chatShowTypingIndicator =
|
|
7477
|
+
const chatSendDisabled = false;
|
|
7478
|
+
const [processingMrId, setProcessingMrId] = React46.useState(null);
|
|
7479
|
+
const [testingMrId, setTestingMrId] = React46.useState(null);
|
|
7480
|
+
const chatShowTypingIndicator = React46.useMemo(() => {
|
|
6880
7481
|
var _a;
|
|
6881
7482
|
if (!thread.raw || thread.raw.length === 0) return false;
|
|
6882
7483
|
const last = thread.raw[thread.raw.length - 1];
|
|
6883
7484
|
const payloadType = typeof ((_a = last.payload) == null ? void 0 : _a.type) === "string" ? String(last.payload.type) : void 0;
|
|
6884
7485
|
return payloadType !== "outcome";
|
|
6885
7486
|
}, [thread.raw]);
|
|
6886
|
-
|
|
6887
|
-
|
|
7487
|
+
React46.useEffect(() => {
|
|
7488
|
+
updateLastEditQueueInfo(null);
|
|
7489
|
+
setSuppressQueueUntilResponse(false);
|
|
7490
|
+
}, [activeAppId, updateLastEditQueueInfo]);
|
|
7491
|
+
React46.useEffect(() => {
|
|
7492
|
+
if (!(lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId)) return;
|
|
7493
|
+
const stillPresent = editQueue.items.some((item) => item.id === lastEditQueueInfo.queueItemId);
|
|
7494
|
+
if (!stillPresent) {
|
|
7495
|
+
updateLastEditQueueInfo(null);
|
|
7496
|
+
setSuppressQueueUntilResponse(false);
|
|
7497
|
+
}
|
|
7498
|
+
}, [editQueue.items, lastEditQueueInfo == null ? void 0 : lastEditQueueInfo.queueItemId]);
|
|
7499
|
+
const chatQueueItems = React46.useMemo(() => {
|
|
7500
|
+
var _a;
|
|
7501
|
+
if (suppressQueueUntilResponse && editQueue.items.length <= 1) {
|
|
7502
|
+
return [];
|
|
7503
|
+
}
|
|
7504
|
+
if (!lastEditQueueInfo || lastEditQueueInfo.queuePosition !== 1 || !lastEditQueueInfo.queueItemId) {
|
|
7505
|
+
return editQueue.items;
|
|
7506
|
+
}
|
|
7507
|
+
if (editQueue.items.length === 1 && ((_a = editQueue.items[0]) == null ? void 0 : _a.id) === lastEditQueueInfo.queueItemId) {
|
|
7508
|
+
return [];
|
|
7509
|
+
}
|
|
7510
|
+
return editQueue.items;
|
|
7511
|
+
}, [editQueue.items, lastEditQueueInfo, suppressQueueUntilResponse]);
|
|
7512
|
+
return /* @__PURE__ */ jsx59(View46, { style: [{ flex: 1 }, style], children: /* @__PURE__ */ jsxs36(View46, { ref: captureTargetRef, style: { flex: 1 }, collapsable: false, children: [
|
|
7513
|
+
/* @__PURE__ */ jsx59(
|
|
6888
7514
|
RuntimeRenderer,
|
|
6889
7515
|
{
|
|
6890
7516
|
appKey,
|
|
@@ -6894,7 +7520,7 @@ function ComergeStudioInner({
|
|
|
6894
7520
|
allowInitialPreparing: !embeddedBaseBundles
|
|
6895
7521
|
}
|
|
6896
7522
|
),
|
|
6897
|
-
/* @__PURE__ */
|
|
7523
|
+
/* @__PURE__ */ jsx59(
|
|
6898
7524
|
StudioOverlay,
|
|
6899
7525
|
{
|
|
6900
7526
|
captureTargetRef,
|
|
@@ -6946,6 +7572,8 @@ function ComergeStudioInner({
|
|
|
6946
7572
|
chatSending: actions.sending,
|
|
6947
7573
|
chatShowTypingIndicator,
|
|
6948
7574
|
onSendChat: (text, attachments) => actions.sendEdit({ prompt: text, attachments }),
|
|
7575
|
+
chatQueueItems,
|
|
7576
|
+
onRemoveQueueItem: (id) => editQueueActions.cancel(id),
|
|
6949
7577
|
onNavigateHome,
|
|
6950
7578
|
showBubble,
|
|
6951
7579
|
studioControlOptions
|