mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
Attaching a picture in mobile native chat gave no visible feedback: the image uploaded to the host and its path was pasted into the hidden PTY, so nothing on screen ever showed it was attached. Defer the TUI paste to send time and hold picked images in composer state, rendered as a thumbnail tray above the input (spinner while uploading, X to remove, image-only sends allowed). On send, each ready image's path is pasted bracketed into the agent TUI right before the message text, matching the desktop composer-attachment model. The terminal live-input surface keeps the immediate paste, where the TUI's own input line is the feedback.
91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
import { StyleSheet, View } from 'react-native'
|
|
import { MobileNativeChatView, type MobileNativeChatInputLockReason } from './MobileNativeChatView'
|
|
import type { MobileNativeChatController } from './use-mobile-native-chat-controller'
|
|
import type { MobileNativeChatPendingImage } from './use-mobile-native-chat-pending-images'
|
|
|
|
type Props = {
|
|
controller: MobileNativeChatController
|
|
onAttachImage: () => void
|
|
isAttaching: boolean
|
|
/** Overrides the controller's plain-text send (e.g. to deliver pending image
|
|
* attachments first). Falls back to controller.handleNativeChatSend. */
|
|
onSend?: (text: string) => Promise<boolean>
|
|
pendingImages?: readonly MobileNativeChatPendingImage[]
|
|
onRemovePendingImage?: (id: string) => void
|
|
onMicPress: () => void
|
|
micActive: boolean
|
|
dictationMode: 'toggle' | 'hold'
|
|
onMicPressIn: () => void
|
|
onMicPressOut: () => void
|
|
inputLockReason: MobileNativeChatInputLockReason | null
|
|
keyboardInset: number
|
|
}
|
|
|
|
/** Keeps the terminal mounted underneath chat so its PTY subscription survives
|
|
* view toggles while the native surface owns the visible composer. */
|
|
export function MobileNativeChatOverlay({
|
|
controller,
|
|
onAttachImage,
|
|
isAttaching,
|
|
onSend,
|
|
pendingImages,
|
|
onRemovePendingImage,
|
|
onMicPress,
|
|
micActive,
|
|
dictationMode,
|
|
onMicPressIn,
|
|
onMicPressOut,
|
|
inputLockReason,
|
|
keyboardInset
|
|
}: Props): React.JSX.Element | null {
|
|
if (!controller.showNativeChat) {
|
|
return null
|
|
}
|
|
const session = controller.nativeChatSession
|
|
return (
|
|
<View style={styles.overlay}>
|
|
<MobileNativeChatView
|
|
messages={session.messages}
|
|
status={session.status}
|
|
error={session.error}
|
|
agent={controller.nativeChatAgent}
|
|
agentWorking={controller.nativeChatAgentWorking}
|
|
streamingText={controller.nativeChatStreamingText}
|
|
onStop={controller.handleNativeChatStop}
|
|
ask={controller.nativeChatAsk}
|
|
onAnswerAsk={controller.handleNativeChatAnswerAsk}
|
|
onCancelAsk={controller.handleNativeChatCancelAsk}
|
|
question={controller.nativeChatQuestion}
|
|
onAnswerQuestion={controller.handleNativeChatSend}
|
|
permission={controller.nativeChatPermission}
|
|
onRespondPermission={controller.handleNativeChatRespondPermission}
|
|
onOpenFile={controller.handleNativeChatOpenFile}
|
|
hasMore={session.hasMore}
|
|
loadingEarlier={session.loadingEarlier}
|
|
onLoadEarlier={session.loadEarlier}
|
|
onSend={onSend ?? controller.handleNativeChatSend}
|
|
pending={controller.chatPending}
|
|
composerText={controller.chatComposerText}
|
|
onComposerTextChange={controller.setChatComposerText}
|
|
onAttachImage={onAttachImage}
|
|
isAttaching={isAttaching}
|
|
pendingImages={pendingImages}
|
|
onRemovePendingImage={onRemovePendingImage}
|
|
onMicPress={onMicPress}
|
|
micActive={micActive}
|
|
dictationMode={dictationMode}
|
|
onMicPressIn={onMicPressIn}
|
|
onMicPressOut={onMicPressOut}
|
|
inputLockReason={inputLockReason}
|
|
filePaths={controller.nativeChatFilePaths}
|
|
onNeedFiles={controller.loadNativeChatFiles}
|
|
keyboardInset={keyboardInset}
|
|
/>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: StyleSheet.absoluteFillObject
|
|
})
|