@comergehq/studio 0.1.31 → 0.1.33
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.js +146 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/data/agent/remote.ts +8 -0
- package/src/data/agent/repository.ts +8 -0
- package/src/data/agent/types.ts +16 -0
- package/src/data/attachment/remote.ts +15 -0
- package/src/data/attachment/repository.ts +21 -0
- package/src/data/attachment/types.ts +20 -0
- package/src/studio/ComergeStudio.tsx +25 -7
- package/src/studio/hooks/useAttachmentUpload.ts +38 -1
- package/src/studio/hooks/useOptimisticChatMessages.ts +5 -5
- package/src/studio/hooks/useStudioActions.ts +50 -7
- package/src/studio/ui/ChatPanel.tsx +6 -6
- package/src/studio/ui/preview-panel/PreviewRelatedAppsSection.tsx +3 -3
package/dist/index.mjs
CHANGED
|
@@ -2869,6 +2869,13 @@ var AttachmentRemoteDataSourceImpl = class extends BaseRemote {
|
|
|
2869
2869
|
);
|
|
2870
2870
|
return data;
|
|
2871
2871
|
}
|
|
2872
|
+
async stagePresign(payload) {
|
|
2873
|
+
const { data } = await api.post(
|
|
2874
|
+
"/v1/attachments/stage/presign",
|
|
2875
|
+
payload
|
|
2876
|
+
);
|
|
2877
|
+
return data;
|
|
2878
|
+
}
|
|
2872
2879
|
};
|
|
2873
2880
|
var attachmentRemoteDataSource = new AttachmentRemoteDataSourceImpl();
|
|
2874
2881
|
|
|
@@ -2882,6 +2889,10 @@ var AttachmentRepositoryImpl = class extends BaseRepository {
|
|
|
2882
2889
|
const res = await this.remote.presign(payload);
|
|
2883
2890
|
return this.unwrapOrThrow(res);
|
|
2884
2891
|
}
|
|
2892
|
+
async stagePresign(payload) {
|
|
2893
|
+
const res = await this.remote.stagePresign(payload);
|
|
2894
|
+
return this.unwrapOrThrow(res);
|
|
2895
|
+
}
|
|
2885
2896
|
async upload(upload, file) {
|
|
2886
2897
|
const resp = await fetch(upload.uploadUrl, {
|
|
2887
2898
|
method: "PUT",
|
|
@@ -2892,6 +2903,16 @@ var AttachmentRepositoryImpl = class extends BaseRepository {
|
|
|
2892
2903
|
throw new Error(`upload failed: ${resp.status}`);
|
|
2893
2904
|
}
|
|
2894
2905
|
}
|
|
2906
|
+
async uploadStaged(upload, file) {
|
|
2907
|
+
const resp = await fetch(upload.uploadUrl, {
|
|
2908
|
+
method: "PUT",
|
|
2909
|
+
headers: upload.headers,
|
|
2910
|
+
body: file
|
|
2911
|
+
});
|
|
2912
|
+
if (!resp.ok) {
|
|
2913
|
+
throw new Error(`staged upload failed: ${resp.status}`);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2895
2916
|
};
|
|
2896
2917
|
var attachmentRepository = new AttachmentRepositoryImpl(
|
|
2897
2918
|
attachmentRemoteDataSource
|
|
@@ -2965,7 +2986,36 @@ function useAttachmentUpload() {
|
|
|
2965
2986
|
setUploading(false);
|
|
2966
2987
|
}
|
|
2967
2988
|
}, []);
|
|
2968
|
-
|
|
2989
|
+
const stageBase64Images = React7.useCallback(async ({ dataUrls }) => {
|
|
2990
|
+
if (!dataUrls || dataUrls.length === 0) return [];
|
|
2991
|
+
setUploading(true);
|
|
2992
|
+
setError(null);
|
|
2993
|
+
try {
|
|
2994
|
+
const blobs = await Promise.all(
|
|
2995
|
+
dataUrls.map(async (dataUrl) => {
|
|
2996
|
+
const normalized = dataUrl.startsWith("data:") ? dataUrl : `data:image/png;base64,${dataUrl}`;
|
|
2997
|
+
const blob = Platform2.OS === "android" ? await dataUrlToBlobAndroid(normalized) : await (await fetch(normalized)).blob();
|
|
2998
|
+
const mimeType = getMimeTypeFromDataUrl(normalized);
|
|
2999
|
+
return { blob, mimeType };
|
|
3000
|
+
})
|
|
3001
|
+
);
|
|
3002
|
+
const files = blobs.map(({ blob, mimeType }, idx) => ({
|
|
3003
|
+
name: `attachment-${Date.now()}-${idx}.png`,
|
|
3004
|
+
size: blob.size,
|
|
3005
|
+
mimeType
|
|
3006
|
+
}));
|
|
3007
|
+
const presign = await attachmentRepository.stagePresign({ files });
|
|
3008
|
+
await Promise.all(presign.uploads.map((u, index) => attachmentRepository.uploadStaged(u, blobs[index].blob)));
|
|
3009
|
+
return presign.uploads.map((u) => u.attachmentToken);
|
|
3010
|
+
} catch (e) {
|
|
3011
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
3012
|
+
setError(err);
|
|
3013
|
+
throw err;
|
|
3014
|
+
} finally {
|
|
3015
|
+
setUploading(false);
|
|
3016
|
+
}
|
|
3017
|
+
}, []);
|
|
3018
|
+
return { uploadBase64Images, stageBase64Images, uploading, error };
|
|
2969
3019
|
}
|
|
2970
3020
|
|
|
2971
3021
|
// src/studio/hooks/useStudioActions.ts
|
|
@@ -2981,6 +3031,10 @@ var AgentRemoteDataSourceImpl = class extends BaseRemote {
|
|
|
2981
3031
|
const { data } = await api.post("/v1/agent/editApp", payload);
|
|
2982
3032
|
return data;
|
|
2983
3033
|
}
|
|
3034
|
+
async forkEditStart(payload) {
|
|
3035
|
+
const { data } = await api.post("/v1/agent/forkEditStart", payload);
|
|
3036
|
+
return data;
|
|
3037
|
+
}
|
|
2984
3038
|
};
|
|
2985
3039
|
var agentRemoteDataSource = new AgentRemoteDataSourceImpl();
|
|
2986
3040
|
|
|
@@ -2998,6 +3052,10 @@ var AgentRepositoryImpl = class extends BaseRepository {
|
|
|
2998
3052
|
const res = await this.remote.editApp(payload);
|
|
2999
3053
|
return this.unwrapOrThrow(res);
|
|
3000
3054
|
}
|
|
3055
|
+
async forkEditStart(payload) {
|
|
3056
|
+
const res = await this.remote.forkEditStart(payload);
|
|
3057
|
+
return this.unwrapOrThrow(res);
|
|
3058
|
+
}
|
|
3001
3059
|
};
|
|
3002
3060
|
var agentRepository = new AgentRepositoryImpl(agentRemoteDataSource);
|
|
3003
3061
|
|
|
@@ -3048,7 +3106,8 @@ function useStudioActions({
|
|
|
3048
3106
|
onEditStart,
|
|
3049
3107
|
onEditQueued,
|
|
3050
3108
|
onEditFinished,
|
|
3051
|
-
uploadAttachments
|
|
3109
|
+
uploadAttachments,
|
|
3110
|
+
stageAttachments
|
|
3052
3111
|
}) {
|
|
3053
3112
|
const [forking, setForking] = React8.useState(false);
|
|
3054
3113
|
const [sending, setSending] = React8.useState(false);
|
|
@@ -3068,16 +3127,46 @@ function useStudioActions({
|
|
|
3068
3127
|
const sourceAppId = app.id;
|
|
3069
3128
|
if (shouldForkOnEdit) {
|
|
3070
3129
|
setForking(true);
|
|
3071
|
-
|
|
3072
|
-
|
|
3130
|
+
let attachmentTokens;
|
|
3131
|
+
if (attachments && attachments.length > 0 && stageAttachments) {
|
|
3132
|
+
attachmentTokens = await stageAttachments({ dataUrls: attachments });
|
|
3133
|
+
}
|
|
3134
|
+
const idempotencyKey2 = generateIdempotencyKey();
|
|
3135
|
+
const startResult = await withRetry2(
|
|
3136
|
+
async () => await agentRepository.forkEditStart({
|
|
3137
|
+
source_app_id: sourceAppId,
|
|
3138
|
+
prompt,
|
|
3139
|
+
attachmentTokens,
|
|
3140
|
+
idempotencyKey: idempotencyKey2
|
|
3141
|
+
}),
|
|
3142
|
+
{ attempts: 3, baseDelayMs: 500, maxDelayMs: 4e3 }
|
|
3143
|
+
);
|
|
3144
|
+
targetApp = {
|
|
3145
|
+
...app,
|
|
3146
|
+
id: startResult.targetAppId,
|
|
3147
|
+
threadId: startResult.targetThreadId
|
|
3148
|
+
};
|
|
3073
3149
|
await trackRemixApp({
|
|
3074
|
-
appId:
|
|
3150
|
+
appId: startResult.targetAppId,
|
|
3075
3151
|
sourceAppId,
|
|
3076
|
-
threadId:
|
|
3152
|
+
threadId: startResult.targetThreadId ?? void 0,
|
|
3077
3153
|
success: true
|
|
3078
3154
|
});
|
|
3079
3155
|
forkSucceeded = true;
|
|
3080
|
-
onForkedApp == null ? void 0 : onForkedApp(
|
|
3156
|
+
onForkedApp == null ? void 0 : onForkedApp(startResult.targetAppId, { keepRenderingAppId: sourceAppId });
|
|
3157
|
+
onEditStart == null ? void 0 : onEditStart();
|
|
3158
|
+
onEditQueued == null ? void 0 : onEditQueued({
|
|
3159
|
+
queueItemId: startResult.queueItemId ?? null,
|
|
3160
|
+
queuePosition: startResult.queuePosition ?? null
|
|
3161
|
+
});
|
|
3162
|
+
await trackEditApp({
|
|
3163
|
+
appId: startResult.targetAppId,
|
|
3164
|
+
threadId: startResult.targetThreadId,
|
|
3165
|
+
promptLength: prompt.trim().length,
|
|
3166
|
+
success: true
|
|
3167
|
+
});
|
|
3168
|
+
setForking(false);
|
|
3169
|
+
return;
|
|
3081
3170
|
}
|
|
3082
3171
|
setForking(false);
|
|
3083
3172
|
const threadId = targetApp.threadId;
|
|
@@ -3138,7 +3227,18 @@ function useStudioActions({
|
|
|
3138
3227
|
onEditFinished == null ? void 0 : onEditFinished();
|
|
3139
3228
|
}
|
|
3140
3229
|
},
|
|
3141
|
-
[
|
|
3230
|
+
[
|
|
3231
|
+
app,
|
|
3232
|
+
onEditFinished,
|
|
3233
|
+
onEditQueued,
|
|
3234
|
+
onEditStart,
|
|
3235
|
+
onForkedApp,
|
|
3236
|
+
sending,
|
|
3237
|
+
shouldForkOnEdit,
|
|
3238
|
+
stageAttachments,
|
|
3239
|
+
uploadAttachments,
|
|
3240
|
+
userId
|
|
3241
|
+
]
|
|
3142
3242
|
);
|
|
3143
3243
|
return { isOwner, shouldForkOnEdit, forking, sending, error, sendEdit };
|
|
3144
3244
|
}
|
|
@@ -5899,9 +5999,9 @@ function PreviewRelatedAppsSection({
|
|
|
5899
5999
|
const renderRelatedCard = React27.useCallback(
|
|
5900
6000
|
(item, options) => {
|
|
5901
6001
|
const isCurrent = item.app.id === currentAppId;
|
|
5902
|
-
const
|
|
6002
|
+
const isArchived = item.app.status === "archived";
|
|
5903
6003
|
const isSwitching = switchingRelatedAppId === item.app.id;
|
|
5904
|
-
const disabled = isCurrent ||
|
|
6004
|
+
const disabled = isCurrent || isArchived || Boolean(switchingRelatedAppId);
|
|
5905
6005
|
return /* @__PURE__ */ jsx36(
|
|
5906
6006
|
Pressable9,
|
|
5907
6007
|
{
|
|
@@ -5941,7 +6041,7 @@ function PreviewRelatedAppsSection({
|
|
|
5941
6041
|
)
|
|
5942
6042
|
] }),
|
|
5943
6043
|
/* @__PURE__ */ jsxs19(View24, { style: { alignItems: "flex-end", gap: 6 }, children: [
|
|
5944
|
-
/* @__PURE__ */ jsx36(View24, { style: { minHeight: 20, justifyContent: "center" }, children:
|
|
6044
|
+
/* @__PURE__ */ jsx36(View24, { style: { minHeight: 20, justifyContent: "center" }, children: item.app.status !== "ready" ? /* @__PURE__ */ jsx36(PreviewStatusBadge, { status: item.app.status }) : null }),
|
|
5945
6045
|
isSwitching ? /* @__PURE__ */ jsx36(ActivityIndicator4, { size: "small", color: theme.colors.primary }) : null
|
|
5946
6046
|
] })
|
|
5947
6047
|
] })
|
|
@@ -8431,7 +8531,7 @@ function ChatPanel({
|
|
|
8431
8531
|
showTypingIndicator,
|
|
8432
8532
|
loading,
|
|
8433
8533
|
sendDisabled,
|
|
8434
|
-
forking = false,
|
|
8534
|
+
forking: _forking = false,
|
|
8435
8535
|
sending,
|
|
8436
8536
|
shouldForkOnEdit,
|
|
8437
8537
|
attachments = [],
|
|
@@ -8490,7 +8590,7 @@ function ChatPanel({
|
|
|
8490
8590
|
style: { marginBottom: 12 }
|
|
8491
8591
|
}
|
|
8492
8592
|
) : null;
|
|
8493
|
-
const showMessagesLoading = Boolean(loading) && messages.length === 0
|
|
8593
|
+
const showMessagesLoading = Boolean(loading) && messages.length === 0;
|
|
8494
8594
|
if (showMessagesLoading) {
|
|
8495
8595
|
return /* @__PURE__ */ jsxs37(View46, { style: { flex: 1 }, children: [
|
|
8496
8596
|
/* @__PURE__ */ jsx59(View46, { children: header }),
|
|
@@ -8498,14 +8598,14 @@ function ChatPanel({
|
|
|
8498
8598
|
/* @__PURE__ */ jsxs37(View46, { style: { flex: 1, alignItems: "center", justifyContent: "center", paddingHorizontal: 24, paddingVertical: 12 }, children: [
|
|
8499
8599
|
/* @__PURE__ */ jsx59(ActivityIndicator10, {}),
|
|
8500
8600
|
/* @__PURE__ */ jsx59(View46, { style: { height: 12 } }),
|
|
8501
|
-
/* @__PURE__ */ jsx59(Text, { variant: "bodyMuted", children:
|
|
8601
|
+
/* @__PURE__ */ jsx59(Text, { variant: "bodyMuted", children: "Loading messages\u2026" })
|
|
8502
8602
|
] })
|
|
8503
8603
|
] });
|
|
8504
8604
|
}
|
|
8505
8605
|
const bundleProgress = (progress == null ? void 0 : progress.bundle) ?? null;
|
|
8506
8606
|
const queueTop = progress || queueItems.length > 0 ? /* @__PURE__ */ jsxs37(View46, { style: { gap: theme.spacing.sm }, children: [
|
|
8507
8607
|
progress ? bundleProgress ? /* @__PURE__ */ jsx59(BundleProgressCard, { progress: bundleProgress }) : /* @__PURE__ */ jsx59(AgentProgressCard, { progress }) : null,
|
|
8508
|
-
queueItems.length > 0 ? /* @__PURE__ */ jsx59(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
|
|
8608
|
+
!progress && queueItems.length > 0 ? /* @__PURE__ */ jsx59(ChatQueue, { items: queueItems, onRemove: onRemoveQueueItem }) : null
|
|
8509
8609
|
] }) : null;
|
|
8510
8610
|
return /* @__PURE__ */ jsx59(
|
|
8511
8611
|
ChatPage,
|
|
@@ -8532,8 +8632,8 @@ function ChatPanel({
|
|
|
8532
8632
|
composer: {
|
|
8533
8633
|
// Keep the input editable even when sending is disallowed (e.g. agent still working),
|
|
8534
8634
|
// otherwise iOS will drop focus/keyboard and BottomSheet can get "stuck" with a keyboard-sized gap.
|
|
8535
|
-
disabled: Boolean(loading)
|
|
8536
|
-
sendDisabled: Boolean(sendDisabled) || Boolean(loading)
|
|
8635
|
+
disabled: Boolean(loading),
|
|
8636
|
+
sendDisabled: Boolean(sendDisabled) || Boolean(loading),
|
|
8537
8637
|
sending: Boolean(sending),
|
|
8538
8638
|
onSend: handleSend,
|
|
8539
8639
|
attachments,
|
|
@@ -8749,7 +8849,7 @@ function isOptimisticResolvedByServer(chatMessages, o) {
|
|
|
8749
8849
|
}
|
|
8750
8850
|
function useOptimisticChatMessages({
|
|
8751
8851
|
threadId,
|
|
8752
|
-
shouldForkOnEdit,
|
|
8852
|
+
shouldForkOnEdit: _shouldForkOnEdit,
|
|
8753
8853
|
disableOptimistic = false,
|
|
8754
8854
|
chatMessages,
|
|
8755
8855
|
onSendChat
|
|
@@ -8784,7 +8884,7 @@ function useOptimisticChatMessages({
|
|
|
8784
8884
|
}, [chatMessages, optimisticChat.length]);
|
|
8785
8885
|
const onSend = React45.useCallback(
|
|
8786
8886
|
async (text, attachments) => {
|
|
8787
|
-
if (
|
|
8887
|
+
if (disableOptimistic) {
|
|
8788
8888
|
await onSendChat(text, attachments);
|
|
8789
8889
|
return;
|
|
8790
8890
|
}
|
|
@@ -8808,11 +8908,11 @@ function useOptimisticChatMessages({
|
|
|
8808
8908
|
setOptimisticChat((prev) => prev.map((m) => m.id === id ? { ...m, failed: true } : m));
|
|
8809
8909
|
});
|
|
8810
8910
|
},
|
|
8811
|
-
[chatMessages, disableOptimistic, onSendChat
|
|
8911
|
+
[chatMessages, disableOptimistic, onSendChat]
|
|
8812
8912
|
);
|
|
8813
8913
|
const onRetry = React45.useCallback(
|
|
8814
8914
|
async (messageId) => {
|
|
8815
|
-
if (
|
|
8915
|
+
if (disableOptimistic) return;
|
|
8816
8916
|
const target = optimisticChat.find((m) => m.id === messageId);
|
|
8817
8917
|
if (!target || target.retrying) return;
|
|
8818
8918
|
const baseServerLastId = chatMessages.length > 0 ? chatMessages[chatMessages.length - 1].id : null;
|
|
@@ -8832,7 +8932,7 @@ function useOptimisticChatMessages({
|
|
|
8832
8932
|
);
|
|
8833
8933
|
}
|
|
8834
8934
|
},
|
|
8835
|
-
[chatMessages, disableOptimistic, onSendChat, optimisticChat
|
|
8935
|
+
[chatMessages, disableOptimistic, onSendChat, optimisticChat]
|
|
8836
8936
|
);
|
|
8837
8937
|
const isRetrying = React45.useCallback(
|
|
8838
8938
|
(messageId) => {
|
|
@@ -9988,6 +10088,7 @@ function ComergeStudioInner({
|
|
|
9988
10088
|
}
|
|
9989
10089
|
},
|
|
9990
10090
|
uploadAttachments: uploader.uploadBase64Images,
|
|
10091
|
+
stageAttachments: uploader.stageBase64Images,
|
|
9991
10092
|
onEditStart: () => {
|
|
9992
10093
|
if (editQueue.items.length === 0) {
|
|
9993
10094
|
setSuppressQueueUntilResponse(true);
|
|
@@ -10013,7 +10114,26 @@ function ComergeStudioInner({
|
|
|
10013
10114
|
const [upstreamSyncStatus, setUpstreamSyncStatus] = React51.useState(null);
|
|
10014
10115
|
const isMrTestBuildInProgress = bundle.loading && bundle.loadingMode === "test";
|
|
10015
10116
|
const isBaseBundleDownloading = bundle.loading && bundle.loadingMode === "base" && !bundle.isTesting;
|
|
10016
|
-
const runtimePreparingText =
|
|
10117
|
+
const runtimePreparingText = React51.useMemo(() => {
|
|
10118
|
+
const status = app == null ? void 0 : app.status;
|
|
10119
|
+
if (status === "ready" && bundle.bundleStatus === "pending") {
|
|
10120
|
+
return "Bundling app\u2026 this may take a few minutes";
|
|
10121
|
+
}
|
|
10122
|
+
switch (status) {
|
|
10123
|
+
case "creating":
|
|
10124
|
+
return "Creating your app\u2026 this may take a moment";
|
|
10125
|
+
case "forking":
|
|
10126
|
+
return "Forking your app\u2026";
|
|
10127
|
+
case "editing":
|
|
10128
|
+
return "Applying your latest changes\u2026";
|
|
10129
|
+
case "merging":
|
|
10130
|
+
return "Merging app updates\u2026";
|
|
10131
|
+
case "error":
|
|
10132
|
+
return "This app hit an error while preparing.";
|
|
10133
|
+
default:
|
|
10134
|
+
return "Preparing app\u2026";
|
|
10135
|
+
}
|
|
10136
|
+
}, [app == null ? void 0 : app.status, bundle.bundleStatus]);
|
|
10017
10137
|
const chatShowTypingIndicator = React51.useMemo(() => {
|
|
10018
10138
|
var _a2;
|
|
10019
10139
|
if (agentProgress.hasLiveProgress) return false;
|
|
@@ -10080,9 +10200,9 @@ function ComergeStudioInner({
|
|
|
10080
10200
|
setSwitchingRelatedAppId(targetAppId);
|
|
10081
10201
|
try {
|
|
10082
10202
|
const targetApp = await appsRepository.getById(targetAppId);
|
|
10083
|
-
if (targetApp.status
|
|
10084
|
-
const reason =
|
|
10085
|
-
log.warn("[related-apps] switch blocked: target app
|
|
10203
|
+
if (targetApp.status === "archived") {
|
|
10204
|
+
const reason = "target_archived";
|
|
10205
|
+
log.warn("[related-apps] switch blocked: target app archived", {
|
|
10086
10206
|
fromAppId: activeAppId,
|
|
10087
10207
|
toAppId: targetAppId,
|
|
10088
10208
|
status: targetApp.status
|