| ... | @@ -137,6 +137,7 @@ import { | ... | @@ -137,6 +137,7 @@ import { |
| 137 | COMPOSER_TRANSITION_DURATION_MS, | 137 | COMPOSER_TRANSITION_DURATION_MS, |
| 138 | ThreadComposer, | 138 | ThreadComposer, |
| 139 | } from "./ThreadComposer"; | 139 | } from "./ThreadComposer"; |
| | 140 | import { ComposerPopoverHost } from "./ComposerPopoverHost"; |
| 140 | import { ThreadFeed, type ThreadFeedHistoryControls } from "./ThreadFeed"; | 141 | import { ThreadFeed, type ThreadFeedHistoryControls } from "./ThreadFeed"; |
| 141 | import { useThreadTurnSubagents } from "./ThreadAgentsSheet"; | 142 | import { useThreadTurnSubagents } from "./ThreadAgentsSheet"; |
| 142 | import { ComposerQueuedEditBanner } from "./ComposerQueuedEdit"; | 143 | import { ComposerQueuedEditBanner } from "./ComposerQueuedEdit"; |
| ... | @@ -559,6 +560,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -559,6 +560,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 559 | const [collapsedUserInputRequestId, setCollapsedUserInputRequestId] = | 560 | const [collapsedUserInputRequestId, setCollapsedUserInputRequestId] = |
| 560 | useState<RuntimeRequestId | null>(null); | 561 | useState<RuntimeRequestId | null>(null); |
| 561 | const activeUserInputRequestId = props.activePendingUserInput?.requestId ?? null; | 562 | const activeUserInputRequestId = props.activePendingUserInput?.requestId ?? null; |
| | 563 | // A pending user-input request or a failed creation owns the composer slot. |
| | 564 | const composerSlotHidden = |
| | 565 | activeUserInputRequestId !== null || props.creationState?.kind === "failed"; |
| 562 | // The open /usage-limits panel for this thread, model and turn. Only the open | 566 | // The open /usage-limits panel for this thread, model and turn. Only the open |
| 563 | // moment is stored: the rows read live provider data, so a redeemed reset | 567 | // moment is stored: the rows read live provider data, so a redeemed reset |
| 564 | // credit or refreshed probe shows through. Anything that spends quota closes | 568 | // credit or refreshed probe shows through. Anything that spends quota closes |
| ... | @@ -1172,265 +1176,263 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread | ... | @@ -1172,265 +1176,263 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread |
| 1172 | style={{ position: "absolute", bottom: 0, left: 0, right: 0, top: 0 }} | 1176 | style={{ position: "absolute", bottom: 0, left: 0, right: 0, top: 0 }} |
| 1173 | offset={{ closed: 0, opened: 0 }} | 1177 | offset={{ closed: 0, opened: 0 }} |
| 1174 | > | 1178 | > |
| 1175 | {/* The fixed sticky host gives this bottom-anchored child a stable | 1179 | <ComposerPopoverHost hidden={composerSlotHidden}> |
| | 1180 | {/* The fixed sticky host gives this bottom-anchored child a stable |
| 1176 | coordinate space. Its top and height can then animate together | 1181 | coordinate space. Its top and height can then animate together |
| 1177 | instead of the auto-sized host jumping to Yoga's destination. */} | 1182 | instead of the auto-sized host jumping to Yoga's destination. */} |
| 1178 | <Animated.View | 1183 | <Animated.View |
| 1179 | layout={COMPOSER_LAYOUT_TRANSITION} | 1184 | layout={COMPOSER_LAYOUT_TRANSITION} |
| 1180 | pointerEvents="box-none" | 1185 | pointerEvents="box-none" |
| 1181 | style={[{ position: "absolute", bottom: 0, left: 0 }, composerWidthStyle]} | 1186 | style={[{ position: "absolute", bottom: 0, left: 0 }, composerWidthStyle]} |
| 1182 | > | 1187 | > |
| 1183 | {/* No paddingTop here: the overlay's measured height becomes the | 1188 | {/* No paddingTop here: the overlay's measured height becomes the |
| 1184 | list's bottom inset, so any padding above the pill/composer | 1189 | list's bottom inset, so any padding above the pill/composer |
| 1185 | pushes the resting content floor up by the same amount. */} | 1190 | pushes the resting content floor up by the same amount. */} |
| 1186 | <View ref={composerOverlayRef} onLayout={onComposerLayout} className="w-full"> | 1191 | <View ref={composerOverlayRef} onLayout={onComposerLayout} className="w-full"> |
| 1187 | <FloatingWorkingControl | 1192 | <FloatingWorkingControl |
| 1188 | colorScheme={isDarkMode ? "dark" : "light"} | 1193 | colorScheme={isDarkMode ? "dark" : "light"} |
| 1189 | status={floatingStatus} | 1194 | status={floatingStatus} |
| 1190 | lift={floatingControlLift} | 1195 | lift={floatingControlLift} |
| 1191 | devicePreview={ | 1196 | devicePreview={ |
| 1192 | devicePreviews.length > 0 | 1197 | devicePreviews.length > 0 |
| 1193 | ? { count: devicePreviews.length, onPress: openDevicePreview } | 1198 | ? { count: devicePreviews.length, onPress: openDevicePreview } |
| 1194 | : null | 1199 | : null |
| 1195 | } | 1200 | } |
| 1196 | browserPreview={ | 1201 | browserPreview={ |
| 1197 | browserTabs.tabs.length > 0 | 1202 | browserTabs.tabs.length > 0 |
| 1198 | ? { count: browserTabs.tabs.length, onPress: () => openBrowserPreview() } | 1203 | ? { count: browserTabs.tabs.length, onPress: () => openBrowserPreview() } |
| 1199 | : null | 1204 | : null |
| 1200 | } | 1205 | } |
| 1201 | showScrollToEnd={showScrollToEndButton} | 1206 | showScrollToEnd={showScrollToEndButton} |
| 1202 | onScrollToEnd={handleScrollToEnd} | 1207 | onScrollToEnd={handleScrollToEnd} |
| 1203 | agents={agentsSegment} | 1208 | agents={agentsSegment} |
| 1204 | onOpenAgents={() => { | 1209 | onOpenAgents={() => { |
| 1205 | Keyboard.dismiss(); | 1210 | Keyboard.dismiss(); |
| 1206 | navigation.navigate("ThreadAgents", { | 1211 | navigation.navigate("ThreadAgents", { |
| 1207 | environmentId: props.environmentId, | 1212 | environmentId: props.environmentId, |
| 1208 | threadId: props.selectedThread.id, | 1213 | threadId: props.selectedThread.id, |
| 1209 | }); | 1214 | }); |
| 1210 | }} | 1215 | }} |
| 1211 | queuedCount={queuedCount} | 1216 | queuedCount={queuedCount} |
| 1212 | onOpenQueue={() => { | 1217 | onOpenQueue={() => { |
| 1213 | Keyboard.dismiss(); | 1218 | Keyboard.dismiss(); |
| 1214 | navigation.navigate("ThreadQueue", { | 1219 | navigation.navigate("ThreadQueue", { |
| 1215 | environmentId: props.environmentId, | 1220 | environmentId: props.environmentId, |
| 1216 | threadId: props.selectedThread.id, | 1221 | threadId: props.selectedThread.id, |
| 1217 | }); | 1222 | }); |
| 1218 | }} | 1223 | }} |
| 1219 | /> | | |
| 1220 | <View className="w-full self-center" style={{ maxWidth: contentMaxWidth }}> | | |
| 1221 | {props.queuedRunEdit !== null ? ( | | |
| 1222 | <Animated.View | | |
| 1223 | className="shrink-0" | | |
| 1224 | entering={FadeInDown.duration(180)} | | |
| 1225 | exiting={FadeOut.duration(120)} | | |
| 1226 | > | | |
| 1227 | <ComposerQueuedEditBanner | | |
| 1228 | saving={props.isSavingQueuedEdit} | | |
| 1229 | onCancel={() => { | | |
| 1230 | voiceInputSession.cancel(props.composerDraftKey); | | |
| 1231 | props.onCancelQueuedRunEdit(); | | |
| 1232 | }} | | |
| 1233 | /> | | |
| 1234 | </Animated.View> | | |
| 1235 | ) : null} | | |
| 1236 | <UsageLimitRecoveryCard | | |
| 1237 | key={props.selectedThread.latestRun?.runId} | | |
| 1238 | thread={props.selectedThread} | | |
| 1239 | environmentId={props.environmentId} | | |
| 1240 | /> | 1224 | /> |
| 1241 | {props.feedbackSubmissions.map((submission) => ( | 1225 | <View className="w-full self-center" style={{ maxWidth: contentMaxWidth }}> |
| 1242 | <ComposerFeedback | 1226 | {props.queuedRunEdit !== null ? ( |
| 1243 | key={submission.id} | 1227 | <Animated.View |
| 1244 | submission={submission} | 1228 | className="shrink-0" |
| 1245 | onDismiss={() => props.onDismissFeedback(submission.id)} | 1229 | entering={FadeInDown.duration(180)} |
| | 1230 | exiting={FadeOut.duration(120)} |
| | 1231 | > |
| | 1232 | <ComposerQueuedEditBanner |
| | 1233 | saving={props.isSavingQueuedEdit} |
| | 1234 | onCancel={() => { |
| | 1235 | voiceInputSession.cancel(props.composerDraftKey); |
| | 1236 | props.onCancelQueuedRunEdit(); |
| | 1237 | }} |
| | 1238 | /> |
| | 1239 | </Animated.View> |
| | 1240 | ) : null} |
| | 1241 | <UsageLimitRecoveryCard |
| | 1242 | key={props.selectedThread.latestRun?.runId} |
| | 1243 | thread={props.selectedThread} |
| | 1244 | environmentId={props.environmentId} |
| 1246 | /> | 1245 | /> |
| 1247 | ))} | 1246 | {props.feedbackSubmissions.map((submission) => ( |
| 1248 | {composerError !== null ? ( | 1247 | <ComposerFeedback |
| 1249 | <Animated.View | 1248 | key={submission.id} |
| 1250 | className="shrink-0" | 1249 | submission={submission} |
| 1251 | entering={FadeInDown.duration(180)} | 1250 | onDismiss={() => props.onDismissFeedback(submission.id)} |
| 1252 | exiting={FadeOut.duration(120)} | | |
| 1253 | > | | |
| 1254 | <ComposerErrorNotice | | |
| 1255 | message={composerError} | | |
| 1256 | onDismiss={() => clearThreadComposerError(selectedThreadKey)} | | |
| 1257 | /> | | |
| 1258 | </Animated.View> | | |
| 1259 | ) : null} | | |
| 1260 | {usageLimitsReport && activeUserInputRequestId === null ? ( | | |
| 1261 | <Animated.View | | |
| 1262 | className="shrink-0 px-4 pb-3" | | |
| 1263 | entering={FadeInDown.duration(220)} | | |
| 1264 | exiting={FadeOut.duration(140)} | | |
| 1265 | > | | |
| 1266 | <ComposerUsageLimits | | |
| 1267 | report={usageLimitsReport} | | |
| 1268 | environmentId={props.environmentId} | | |
| 1269 | onClose={dismissUsageLimits} | | |
| 1270 | /> | 1251 | /> |
| 1271 | </Animated.View> | 1252 | ))} |
| 1272 | ) : null} | 1253 | {composerError !== null ? ( |
| 1273 | {props.creationState?.kind === "failed" ? ( | 1254 | <Animated.View |
| 1274 | <Animated.View | 1255 | className="shrink-0" |
| 1275 | className="shrink-0 px-4" | 1256 | entering={FadeInDown.duration(180)} |
| 1276 | style={{ paddingBottom: composerBottomInset }} | 1257 | exiting={FadeOut.duration(120)} |
| 1277 | entering={FadeInDown.duration(220)} | 1258 | > |
| 1278 | exiting={FadeOut.duration(140)} | 1259 | <ComposerErrorNotice |
| 1279 | > | 1260 | message={composerError} |
| 1280 | <ThreadCreationFailedCard | 1261 | onDismiss={() => clearThreadComposerError(selectedThreadKey)} |
| 1281 | reason={props.creationState.reason} | | |
| 1282 | onEditTask={props.creationState.onEditTask} | | |
| 1283 | /> | | |
| 1284 | </Animated.View> | | |
| 1285 | ) : null} | | |
| 1286 | {props.activePendingApproval || props.activePendingUserInput ? ( | | |
| 1287 | <Animated.View | | |
| 1288 | className="shrink-0 gap-3 px-4 pb-3" | | |
| 1289 | // The questionnaire replaces the composer, so it must pad | | |
| 1290 | // the home indicator the composer normally covers. | | |
| 1291 | style={ | | |
| 1292 | activeUserInputRequestId !== null | | |
| 1293 | ? { paddingBottom: composerBottomInset } | | |
| 1294 | : undefined | | |
| 1295 | } | | |
| 1296 | entering={FadeInDown.duration(220)} | | |
| 1297 | exiting={FadeOut.duration(140)} | | |
| 1298 | > | | |
| 1299 | {props.activePendingApproval ? ( | | |
| 1300 | <PendingApprovalCard | | |
| 1301 | canOperateThread={props.canOperateThread} | | |
| 1302 | approval={props.activePendingApproval} | | |
| 1303 | respondingApprovalId={props.respondingApprovalId} | | |
| 1304 | onRespond={props.onRespondToApproval} | | |
| 1305 | /> | 1262 | /> |
| 1306 | ) : null} | 1263 | </Animated.View> |
| 1307 | {props.activePendingUserInput ? ( | 1264 | ) : null} |
| 1308 | <PendingUserInputCard | 1265 | {usageLimitsReport && activeUserInputRequestId === null ? ( |
| 1309 | canOperateThread={props.canOperateThread} | 1266 | <Animated.View |
| 1310 | pendingUserInput={props.activePendingUserInput} | 1267 | className="shrink-0 px-4 pb-3" |
| 1311 | maxHeight={pendingUserInputMaxHeight} | 1268 | entering={FadeInDown.duration(220)} |
| 1312 | collapsed={userInputCollapsed} | 1269 | exiting={FadeOut.duration(140)} |
| 1313 | onToggleCollapsed={handleToggleUserInputCollapsed} | 1270 | > |
| 1314 | onStopThread={props.onStopThread} | 1271 | <ComposerUsageLimits |
| 1315 | cardProgress={userInputCardProgress} | 1272 | report={usageLimitsReport} |
| 1316 | cardCoverage={userInputCardCoverage} | 1273 | environmentId={props.environmentId} |
| 1317 | onInputFocusChange={handleOwnedInputFocusChange} | 1274 | onClose={dismissUsageLimits} |
| 1318 | drafts={props.activePendingUserInputDrafts} | | |
| 1319 | answers={props.activePendingUserInputAnswers} | | |
| 1320 | respondingUserInputId={props.respondingUserInputId} | | |
| 1321 | onSelectOption={props.onSelectUserInputOption} | | |
| 1322 | onChangeCustomAnswer={props.onChangeUserInputCustomAnswer} | | |
| 1323 | onSubmit={props.onSubmitUserInput} | | |
| 1324 | onDismiss={props.onDismissUserInput} | | |
| 1325 | /> | 1275 | /> |
| 1326 | ) : null} | 1276 | </Animated.View> |
| 1327 | </Animated.View> | 1277 | ) : null} |
| 1328 | ) : null} | 1278 | {props.creationState?.kind === "failed" ? ( |
| 1329 | </View> | 1279 | <Animated.View |
| | 1280 | className="shrink-0 px-4" |
| | 1281 | style={{ paddingBottom: composerBottomInset }} |
| | 1282 | entering={FadeInDown.duration(220)} |
| | 1283 | exiting={FadeOut.duration(140)} |
| | 1284 | > |
| | 1285 | <ThreadCreationFailedCard |
| | 1286 | reason={props.creationState.reason} |
| | 1287 | onEditTask={props.creationState.onEditTask} |
| | 1288 | /> |
| | 1289 | </Animated.View> |
| | 1290 | ) : null} |
| | 1291 | {props.activePendingApproval || props.activePendingUserInput ? ( |
| | 1292 | <Animated.View |
| | 1293 | className="shrink-0 gap-3 px-4 pb-3" |
| | 1294 | // The questionnaire replaces the composer, so it must pad |
| | 1295 | // the home indicator the composer normally covers. |
| | 1296 | style={ |
| | 1297 | activeUserInputRequestId !== null |
| | 1298 | ? { paddingBottom: composerBottomInset } |
| | 1299 | : undefined |
| | 1300 | } |
| | 1301 | entering={FadeInDown.duration(220)} |
| | 1302 | exiting={FadeOut.duration(140)} |
| | 1303 | > |
| | 1304 | {props.activePendingApproval ? ( |
| | 1305 | <PendingApprovalCard |
| | 1306 | canOperateThread={props.canOperateThread} |
| | 1307 | approval={props.activePendingApproval} |
| | 1308 | respondingApprovalId={props.respondingApprovalId} |
| | 1309 | onRespond={props.onRespondToApproval} |
| | 1310 | /> |
| | 1311 | ) : null} |
| | 1312 | {props.activePendingUserInput ? ( |
| | 1313 | <PendingUserInputCard |
| | 1314 | canOperateThread={props.canOperateThread} |
| | 1315 | pendingUserInput={props.activePendingUserInput} |
| | 1316 | maxHeight={pendingUserInputMaxHeight} |
| | 1317 | collapsed={userInputCollapsed} |
| | 1318 | onToggleCollapsed={handleToggleUserInputCollapsed} |
| | 1319 | onStopThread={props.onStopThread} |
| | 1320 | cardProgress={userInputCardProgress} |
| | 1321 | cardCoverage={userInputCardCoverage} |
| | 1322 | onInputFocusChange={handleOwnedInputFocusChange} |
| | 1323 | drafts={props.activePendingUserInputDrafts} |
| | 1324 | answers={props.activePendingUserInputAnswers} |
| | 1325 | respondingUserInputId={props.respondingUserInputId} |
| | 1326 | onSelectOption={props.onSelectUserInputOption} |
| | 1327 | onChangeCustomAnswer={props.onChangeUserInputCustomAnswer} |
| | 1328 | onSubmit={props.onSubmitUserInput} |
| | 1329 | onDismiss={props.onDismissUserInput} |
| | 1330 | /> |
| | 1331 | ) : null} |
| | 1332 | </Animated.View> |
| | 1333 | ) : null} |
| | 1334 | </View> |
| 1330 | | 1335 | |
| 1331 | {/* Hidden (not unmounted) while a user-input request owns the | 1336 | {/* Hidden (not unmounted) while a user-input request owns the |
| 1332 | composer slot, so composer drafts and editor state survive. | 1337 | composer slot, so composer drafts and editor state survive. |
| 1333 | A rejected creation has no thread to send to; the failure card | 1338 | A rejected creation has no thread to send to; the failure card |
| 1334 | owns the slot instead. */} | 1339 | owns the slot instead. */} |
| 1335 | <View | 1340 | <View style={composerSlotHidden ? { display: "none" } : undefined}> |
| 1336 | style={ | 1341 | {isProviderSubagent ? ( |
| 1337 | activeUserInputRequestId !== null || props.creationState?.kind === "failed" | 1342 | <View |
| 1338 | ? { display: "none" } | 1343 | className="self-center px-3 pt-1.5" |
| 1339 | : undefined | 1344 | style={{ |
| 1340 | } | 1345 | width: "100%", |
| 1341 | > | 1346 | maxWidth: contentMaxWidth, |
| 1342 | {isProviderSubagent ? ( | 1347 | paddingBottom: composerBottomInset + 6, |
| 1343 | <View | 1348 | }} |
| 1344 | className="self-center px-3 pt-1.5" | 1349 | > |
| 1345 | style={{ | 1350 | <ProviderSubagentBar |
| 1346 | width: "100%", | 1351 | provider={providerSubagentProvider ?? null} |
| 1347 | maxWidth: contentMaxWidth, | 1352 | modelLabel={ |
| 1348 | paddingBottom: composerBottomInset + 6, | 1353 | providerSubagentCatalogModel?.name ?? |
| 1349 | }} | 1354 | formatModelSlugName(props.selectedThread.modelSelection.model) |
| 1350 | > | 1355 | } |
| 1351 | <ProviderSubagentBar | 1356 | effortLabel={formatModelSelectionEffort( |
| 1352 | provider={providerSubagentProvider ?? null} | 1357 | props.selectedThread.modelSelection, |
| 1353 | modelLabel={ | 1358 | providerSubagentProvider?.models, |
| 1354 | providerSubagentCatalogModel?.name ?? | 1359 | reportedModelSelection, |
| 1355 | formatModelSlugName(props.selectedThread.modelSelection.model) | 1360 | )} |
| 1356 | } | 1361 | status={props.providerSubagentStatus ?? null} |
| 1357 | effortLabel={formatModelSelectionEffort( | 1362 | onOpenParent={ |
| 1358 | props.selectedThread.modelSelection, | 1363 | props.selectedThread.lineage.parentThreadId === null |
| 1359 | providerSubagentProvider?.models, | 1364 | ? null |
| 1360 | reportedModelSelection, | 1365 | : () => |
| 1361 | )} | 1366 | navigation.navigate("Thread", { |
| 1362 | status={props.providerSubagentStatus ?? null} | 1367 | environmentId: String(props.environmentId), |
| 1363 | onOpenParent={ | 1368 | threadId: String(props.selectedThread.lineage.parentThreadId), |
| 1364 | props.selectedThread.lineage.parentThreadId === null | 1369 | }) |
| 1365 | ? null | 1370 | } |
| 1366 | : () => | 1371 | /> |
| 1367 | navigation.navigate("Thread", { | 1372 | </View> |
| 1368 | environmentId: String(props.environmentId), | 1373 | ) : ( |
| 1369 | threadId: String(props.selectedThread.lineage.parentThreadId), | 1374 | <> |
| 1370 | }) | 1375 | <ThreadComposer |
| 1371 | } | 1376 | canOperateThread={props.canOperateThread} |
| 1372 | /> | 1377 | reportedModelSelection={reportedModelSelection} |
| 1373 | </View> | 1378 | editorRef={composerEditorRef} |
| 1374 | ) : ( | 1379 | draftMessage={props.draftMessage} |
| 1375 | <> | 1380 | draftAttachments={props.draftAttachments} |
| 1376 | <ThreadComposer | 1381 | placeholder="Ask the repo agent, or run a command…" |
| 1377 | canOperateThread={props.canOperateThread} | 1382 | contentMaxWidth={contentMaxWidth} |
| 1378 | reportedModelSelection={reportedModelSelection} | 1383 | connectionState={props.connectionStateLabel} |
| 1379 | editorRef={composerEditorRef} | 1384 | environmentLabel={props.environmentLabel} |
| 1380 | draftMessage={props.draftMessage} | 1385 | selectedThread={props.selectedThread} |
| 1381 | draftAttachments={props.draftAttachments} | 1386 | hasCompactableConversation={ |
| 1382 | placeholder="Ask the repo agent, or run a command…" | 1387 | hasCompactableConversation && !props.isCompacting |
| 1383 | contentMaxWidth={contentMaxWidth} | 1388 | } |
| 1384 | connectionState={props.connectionStateLabel} | 1389 | serverConfig={props.serverConfig} |
| 1385 | environmentLabel={props.environmentLabel} | 1390 | queueCount={props.selectedThreadQueueCount} |
| 1386 | selectedThread={props.selectedThread} | 1391 | activeThreadBusy={props.activeThreadBusy} |
| 1387 | hasCompactableConversation={hasCompactableConversation && !props.isCompacting} | 1392 | canStopThread={props.canStopThread} |
| 1388 | serverConfig={props.serverConfig} | 1393 | environmentId={props.environmentId} |
| 1389 | queueCount={props.selectedThreadQueueCount} | 1394 | projectCwd={props.threadCwd ?? props.projectWorkspaceRoot} |
| 1390 | activeThreadBusy={props.activeThreadBusy} | 1395 | // Follow-ups typed during setup wait in the draft: queueing |
| 1391 | canStopThread={props.canStopThread} | 1396 | // them against a thread id the server may still reject |
| 1392 | environmentId={props.environmentId} | 1397 | // would strand them in the outbox. |
| 1393 | projectCwd={props.threadCwd ?? props.projectWorkspaceRoot} | 1398 | sendBlockedReason={ |
| 1394 | // Follow-ups typed during setup wait in the draft: queueing | 1399 | props.creationState?.kind === "preparing" ? "Starting the task…" : null |
| 1395 | // them against a thread id the server may still reject | 1400 | } |
| 1396 | // would strand them in the outbox. | 1401 | draftKey={props.composerDraftKey ?? undefined} |
| 1397 | sendBlockedReason={ | 1402 | followUpBehavior={props.followUpBehavior} |
| 1398 | props.creationState?.kind === "preparing" ? "Starting the task…" : null | 1403 | canSteerActiveTurn={props.canSteerActiveTurn} |
| 1399 | } | 1404 | queuedEdit={ |
| 1400 | draftKey={props.composerDraftKey ?? undefined} | 1405 | props.queuedRunEdit === null |
| 1401 | followUpBehavior={props.followUpBehavior} | 1406 | ? null |
| 1402 | canSteerActiveTurn={props.canSteerActiveTurn} | 1407 | : { |
| 1403 | queuedEdit={ | 1408 | existingAttachments: props.queuedRunEdit.existingAttachments, |
| 1404 | props.queuedRunEdit === null | 1409 | saving: props.isSavingQueuedEdit, |
| 1405 | ? null | 1410 | onRemoveExistingAttachment: props.onRemoveQueuedEditAttachment, |
| 1406 | : { | 1411 | } |
| 1407 | existingAttachments: props.queuedRunEdit.existingAttachments, | 1412 | } |
| 1408 | saving: props.isSavingQueuedEdit, | 1413 | bottomInset={composerBottomInset} |
| 1409 | onRemoveExistingAttachment: props.onRemoveQueuedEditAttachment, | 1414 | onChangeDraftMessage={props.onChangeDraftMessage} |
| 1410 | } | 1415 | onPickDraftMedia={props.onPickDraftMedia} |
| 1411 | } | 1416 | onPickDraftFiles={props.onPickDraftFiles} |
| 1412 | bottomInset={composerBottomInset} | 1417 | onNativePasteImages={props.onNativePasteImages} |
| 1413 | onChangeDraftMessage={props.onChangeDraftMessage} | 1418 | onNativePasteText={props.onNativePasteText} |
| 1414 | onPickDraftMedia={props.onPickDraftMedia} | 1419 | onRemoveDraftImage={props.onRemoveDraftImage} |
| 1415 | onPickDraftFiles={props.onPickDraftFiles} | 1420 | onStopThread={props.onStopThread} |
| 1416 | onNativePasteImages={props.onNativePasteImages} | 1421 | onSendMessage={handleSendMessage} |
| 1417 | onNativePasteText={props.onNativePasteText} | 1422 | onShowUsageLimits={showUsageLimits} |
| 1418 | onRemoveDraftImage={props.onRemoveDraftImage} | 1423 | canSwitchProvider={props.canSwitchThreadProvider} |
| 1419 | onStopThread={props.onStopThread} | 1424 | onUpdateModelSelection={props.onUpdateThreadModelSelection} |
| 1420 | onSendMessage={handleSendMessage} | 1425 | onUpdateRuntimeMode={props.onUpdateThreadRuntimeMode} |
| 1421 | onShowUsageLimits={showUsageLimits} | 1426 | onUpdateInteractionMode={props.onUpdateThreadInteractionMode} |
| 1422 | canSwitchProvider={props.canSwitchThreadProvider} | 1427 | onExpandedChange={setComposerExpanded} |
| 1423 | onUpdateModelSelection={props.onUpdateThreadModelSelection} | 1428 | onEditorFocusChange={handleComposerFocusChange} |
| 1424 | onUpdateRuntimeMode={props.onUpdateThreadRuntimeMode} | 1429 | /> |
| 1425 | onUpdateInteractionMode={props.onUpdateThreadInteractionMode} | 1430 | </> |
| 1426 | onExpandedChange={setComposerExpanded} | 1431 | )} |
| 1427 | onEditorFocusChange={handleComposerFocusChange} | 1432 | </View> |
| 1428 | /> | | |
| 1429 | </> | | |
| 1430 | )} | | |
| 1431 | </View> | 1433 | </View> |
| 1432 | </View> | 1434 | </Animated.View> |
| 1433 | </Animated.View> | 1435 | </ComposerPopoverHost> |
| 1434 | </KeyboardStickyView> | 1436 | </KeyboardStickyView> |
| 1435 | ) : null} | 1437 | ) : null} |
| 1436 | </View> | 1438 | </View> |