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