Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/mobile/src/features/threads/PairStatusNotice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ export function PairStatusNotice(props: {

return (
<Pressable
accessible
testID="pair-status-notice"
accessibilityRole="button"
accessibilityLabel={`${line}. Open executor`}
onPress={props.onOpenExecutor}
className="mb-2 gap-1 rounded-2xl border border-border bg-card px-3.5 py-3"
className="mx-4 mb-2 gap-1 rounded-2xl border border-border bg-card px-3.5 py-3"
>
<View className="gap-1">
<Text className="text-sm font-t3-bold text-foreground">{line}</Text>
Expand Down
52 changes: 52 additions & 0 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,13 @@ import {
} from "../voice-input/ComposerDictationControl";
import { useVoiceInputController } from "../voice-input/useVoiceInputController";
import { resolveVoiceComposerPresentation } from "../voice-input/voiceInputPresentation";
import { pairSettingsRow, type PairLead } from "@t3tools/client-runtime/state/pair-control";
import { delegatedParentThreadId } from "@t3tools/shared/delegatedThreads";
import {
type ExistingThreadSettingsRouteSession,
useExistingThreadSettingsRoutePresentation,
} from "./ThreadSettingsSheet";
import { usePairToggle } from "./usePairToggle";
import {
useThreadSettingsSheetPresentation,
type NavigationWithFinishTransitioning,
Expand Down Expand Up @@ -1562,6 +1565,53 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
}),
[currentModelOption?.capabilities, currentModelSelection.options],
);
const isDelegatedThread = delegatedParentThreadId(props.selectedThread.id) !== null;
const lead = useMemo<PairLead>(
() => ({
id: props.selectedThread.id,
projectId: props.selectedThread.projectId,
title: props.selectedThread.title,
runtimeMode: props.selectedThread.runtimeMode,
branch: props.selectedThread.branch,
worktreePath: props.selectedThread.worktreePath,
session: props.selectedThread.session,
}),
[props.selectedThread],
);
const leadDriverKind = composerAuthority.provider?.driver ?? null;
const {
state: pairState,
executorSelection,
lockedReason: pairLockedReasonValue,
onToggle: onTogglePair,
} = usePairToggle({
environmentId: props.environmentId,
projectId: props.selectedThread.projectId,
lead,
leadDriverKind,
});
const executorLabel = executorSelection?.model ?? "";
const pairRow = useMemo(
() =>
pairSettingsRow({
state: pairState,
lockedReason: pairLockedReasonValue,
executorSelection,
executorLabel,
}),
[executorLabel, executorSelection, pairLockedReasonValue, pairState],
);
const pairSession = useMemo(
() =>
isDelegatedThread
? undefined
: {
...pairRow,
onToggle: onTogglePair,
},
[isDelegatedThread, onTogglePair, pairRow],
);

const settingsOwnerId = composerOwnerKey;
const settingsRouteSession = useMemo<ExistingThreadSettingsRouteSession>(
() => ({
Expand All @@ -1583,12 +1633,14 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
if (!props.sessionInputBlocked) props.onUpdateRuntimeMode(mode);
},
getModelDisabledReason: getModelChangeDisabledReason,
...(pairSession ? { pair: pairSession } : {}),
}),
[
confirmSessionHarnessRefinement,
currentModelSelection,
currentRuntimeMode,
getModelChangeDisabledReason,
pairSession,
props.onUpdateModelSelection,
props.onUpdateRuntimeMode,
props.sessionInputBlocked,
Expand Down
18 changes: 9 additions & 9 deletions apps/mobile/src/features/threads/ThreadDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,15 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
onScrollToEnd={handleScrollToEnd}
/>
<View className="w-full self-center" style={{ maxWidth: contentMaxWidth }}>
{props.pairState !== undefined &&
props.pairExecutorLabel !== undefined &&
props.onOpenPairExecutor !== undefined ? (
<PairStatusNotice
state={props.pairState}
executorLabel={props.pairExecutorLabel}
onOpenExecutor={props.onOpenPairExecutor}
/>
) : null}
{props.feedbackSubmissions.map((submission) => (
<ComposerFeedback
key={submission.id}
Expand Down Expand Up @@ -1193,15 +1202,6 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
: undefined
}
>
{props.pairState !== undefined &&
props.pairExecutorLabel !== undefined &&
props.onOpenPairExecutor !== undefined ? (
<PairStatusNotice
state={props.pairState}
executorLabel={props.pairExecutorLabel}
onOpenExecutor={props.onOpenPairExecutor}
/>
) : null}
<GlassBlurTargetContext value={feedBlurTarget}>
<ThreadComposer
editorRef={composerEditorRef}
Expand Down
36 changes: 36 additions & 0 deletions apps/mobile/src/features/threads/ThreadSettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ function SwitchRow(props: {
readonly value: boolean;
readonly onValueChange: (value: boolean) => void;
readonly isLast?: boolean;
readonly disabled?: boolean;
}) {
return (
<View
Expand All @@ -306,6 +307,7 @@ function SwitchRow(props: {
<Text className="text-sm font-t3-medium text-foreground">{props.label}</Text>
<ThemedSwitch
accessibilityLabel={props.label}
disabled={props.disabled}
onValueChange={props.onValueChange}
value={props.value}
/>
Expand All @@ -327,6 +329,12 @@ type ThreadSettingsSessionProps = {
readonly onUpdateOptionSelections: (selections: ReadonlyArray<ProviderOptionSelection>) => void;
readonly runtimeMode: RuntimeMode;
readonly onUpdateRuntimeMode: (mode: RuntimeMode) => void;
readonly pair?: {
readonly value: boolean;
readonly disabled: boolean;
readonly detail: string | null;
readonly onToggle: (on: boolean) => void;
};
};

export type ExistingThreadSettingsRouteSession = ThreadSettingsSessionProps & {
Expand Down Expand Up @@ -393,6 +401,12 @@ type ThreadSettingsSessionValue = {
readonly setSearchQuery: (query: string) => void;
readonly setShowLegacy: (showLegacy: boolean) => void;
readonly toggleProvider: (providerKey: string) => void;
readonly pair?: {
readonly value: boolean;
readonly disabled: boolean;
readonly detail: string | null;
readonly onToggle: (on: boolean) => void;
};
};

const ThreadSettingsSessionContext = createContext<ThreadSettingsSessionValue | null>(null);
Expand Down Expand Up @@ -526,6 +540,7 @@ function ThreadSettingsSessionProvider(
setSearchQuery,
setShowLegacy: setShowLegacyToggle,
toggleProvider,
pair: props.pair,
}),
[
applyOptionChange,
Expand All @@ -541,6 +556,7 @@ function ThreadSettingsSessionProvider(
providerFilter,
props.getModelDisabledReason,
props.onUpdateRuntimeMode,
props.pair,
props.providerGroups,
displayedRuntimeMode,
runtimeChoices,
Expand Down Expand Up @@ -781,6 +797,26 @@ function ThreadSettingsOptionsItem(props: {
</Animated.View>
</Animated.View>

{session.pair !== undefined ? (
<>
<Text className="px-5 pb-2 pt-7 text-sm font-t3-medium text-foreground-muted">Pair</Text>
<View className="mx-4 overflow-hidden rounded-2xl bg-card">
<SwitchRow
isLast
label="Pair with an executor"
value={session.pair.value}
disabled={session.pair.disabled}
onValueChange={session.pair.onToggle}
/>
</View>
{session.pair.detail !== null ? (
<Text className="px-5 pt-2 text-xs leading-snug text-foreground-muted">
{session.pair.detail}
</Text>
) : null}
</>
) : null}

{Platform.OS !== "ios" && session.hasLegacyModels ? (
<>
<Text className="px-5 pb-2 pt-7 text-sm font-t3-medium text-foreground-muted">
Expand Down
Loading
Loading