mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 01:57:57 +00:00
* fix(mobile): draw answered approvals and questions as receipts, and a pending question as its ask row
The phone projected a structured chat's journal with the host's plain
projection: an answered approval read "Allow Bash? {json} resolved", an
answered question listed its choices without the answer, and every
AskUserQuestion call stayed a raw tool line, also while its card was open
above the composer.
Desktop's question projection and receipt rules move to src/shared
(structured-agent-question-projection, native-chat-resolution-receipt,
with the receipt subject and answer heading and their words). The phone's
structured session now projects through them and hands each row its
receipt; new MobileNativeChatResolutionReceipt and
MobileNativeChatAwaitingInputRow draw them as desktop does. In a terminal
chat, a question call draws as "Awaiting user input:" until answered and
"Asked:" after.
* fix(mobile): say a question was asked, not awaited, once its agent has ended
A structured chat's question stays pending in the journal after the agent
that asked it ends (a restart, a crash, a new generation). The host stops
listing it as actionable, so the answer card goes away, but the phone's
row still read "Awaiting user input" and announced itself as live.
The session hook now returns the host's actionable prompt ids next to the
receipts, and each row asks the same shared
isActionableStructuredAgentSessionPrompt desktop's transcript row calls.
A question nobody waits on reads "Asked: …", as on desktop. The row
callback moves to its own hook so the chat view stays within its size.
* fix(mobile): decide a native chat's open question with desktop's selector
The phone decided whether a question call was still awaited with its own
rule (no answer yet and the turn working). Desktop asks the shared
selectActiveToolCall, which also reads the call's state, so a call marked
completed with no output reads "Asked" there and "Awaiting" on the phone.
Native chats now call the same selector. Terminal chats keep their rule.
* fix(mobile): zoom a receipt and its question row with the chat
Before receipts, an answered question or approval was a faint system line
that grew with the chat's pinch zoom. The receipt and the question row drew
at fixed sizes, so zooming no longer enlarged them. Both now take the
chat's zoom, through the same scaledType the notice row uses, which moves
next to the other row styles.
* fix(mobile): list a group's questions once nobody can answer them, and zoom the row's icon
A grouped prompt's row names only a count ("Asked: 2 questions"). Once it
is cancelled, or the agent that asked has ended, the answer card is gone
and the receipt lists no answers, so the phone showed the questions
nowhere. The row now lists them under its line, following desktop's
listsQuestions rule: the receipt asks for the list only when it shows no
answers, and the transcript's own question rows keep it off, as desktop's
tool run does. While the host waits, the card shows them.
The row's icon also grows with the chat's zoom, as the notice row's icon
does, so it stays level with its line.
* refactor(mobile): trim comments and simplify the question row and receipt
Comments that restated the code go. The answer heading takes the subject
its caller already has, dropping two conditions that never changed the
result, on desktop and the phone. The question row lists a group's
questions whenever its caller asks, as desktop's does, and the receipt
asks only while nobody waits. fontScale is required on both new rows.
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { StyleSheet, View } from 'react-native'
|
|
import {
|
|
NATIVE_CHAT_RECEIPT_COPY,
|
|
nativeChatReceiptAnswerHeading,
|
|
nativeChatReceiptAnswers,
|
|
nativeChatReceiptSubject,
|
|
type NativeChatResolvedPrompt
|
|
} from '../../../src/shared/native-chat-resolution-receipt'
|
|
import { MobileSelectableText as Text } from '../components/MobileSelectableText'
|
|
import { INLINE_TEXT_SELECTION } from '../components/inline-text-selection'
|
|
import { colors, spacing, typography } from '../theme/mobile-theme'
|
|
import { MobileNativeChatAwaitingInputRow } from './MobileNativeChatAwaitingInputRow'
|
|
import { scaledType } from './mobile-native-chat-message-styles'
|
|
|
|
/** Desktop parity: `NativeChatResolutionReceipt`. */
|
|
export function MobileNativeChatResolutionReceipt({
|
|
body,
|
|
awaiting = true,
|
|
fontScale
|
|
}: {
|
|
body: NativeChatResolvedPrompt
|
|
/** False once the agent that asked has ended. */
|
|
awaiting?: boolean
|
|
fontScale: number
|
|
}): React.JSX.Element | null {
|
|
const subject = nativeChatReceiptSubject(body)
|
|
if (body.resolution.state === 'pending') {
|
|
return body.kind === 'question' ? (
|
|
<MobileNativeChatAwaitingInputRow
|
|
subject={subject}
|
|
pending={awaiting}
|
|
fontScale={fontScale}
|
|
// While the host waits, its answer card lists them.
|
|
listsQuestions={!awaiting}
|
|
/>
|
|
) : null
|
|
}
|
|
const { resolution } = body
|
|
const cancelled = resolution.state === 'cancelled'
|
|
const answers = nativeChatReceiptAnswers(body)
|
|
const outcome = [
|
|
cancelled ? NATIVE_CHAT_RECEIPT_COPY.cancelled : NATIVE_CHAT_RECEIPT_COPY.resolved,
|
|
resolution.resolvedBy
|
|
? (cancelled
|
|
? NATIVE_CHAT_RECEIPT_COPY.cancelledBy
|
|
: NATIVE_CHAT_RECEIPT_COPY.resolver
|
|
).replace('{{device}}', resolution.resolvedBy)
|
|
: null
|
|
]
|
|
const text = [styles.text, scaledType(typography.metaSize, fontScale, 5)]
|
|
return (
|
|
<View style={styles.receipt}>
|
|
{body.kind === 'question' ? (
|
|
<MobileNativeChatAwaitingInputRow
|
|
subject={subject}
|
|
pending={false}
|
|
fontScale={fontScale}
|
|
// An answered group already shows each question above its answer.
|
|
listsQuestions={answers.length === 0}
|
|
/>
|
|
) : (
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={[text, styles.title]}>
|
|
{body.displayName ?? body.title}
|
|
</Text>
|
|
)}
|
|
{body.kind === 'approval' && body.detail ? (
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={text} numberOfLines={3}>
|
|
{body.detail}
|
|
</Text>
|
|
) : null}
|
|
{answers.map((answer, index) => {
|
|
const heading = nativeChatReceiptAnswerHeading(subject, answer)
|
|
return (
|
|
<View
|
|
key={body.kind === 'question' ? (body.questions?.[index]?.id ?? 'answer') : 'answer'}
|
|
>
|
|
{heading ? <Text style={text}>{heading}</Text> : null}
|
|
<Text selectable={INLINE_TEXT_SELECTION} style={text} numberOfLines={3}>
|
|
{answer.answer === ''
|
|
? NATIVE_CHAT_RECEIPT_COPY.emptyAnswer
|
|
: (answer.answer ?? NATIVE_CHAT_RECEIPT_COPY.unavailable)}
|
|
</Text>
|
|
</View>
|
|
)
|
|
})}
|
|
<Text style={text}>{outcome.filter(Boolean).join(' · ')}</Text>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
receipt: {
|
|
gap: spacing.xs,
|
|
paddingLeft: spacing.md,
|
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
borderLeftColor: colors.borderSubtle
|
|
},
|
|
text: {
|
|
color: colors.textMuted
|
|
},
|
|
title: {
|
|
fontWeight: '600'
|
|
}
|
|
})
|