Skip to content
Open
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
49 changes: 0 additions & 49 deletions web/src/components/AssistantChat/ComposerButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import { useFue } from '@/lib/use-fue'
import { FueCallout, FueDot } from '@/components/Fue'
import { useRef, useState } from 'react'

function ChevronIcon() {
return <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M2.5 3.75L5 6.25L7.5 3.75" /></svg>
}

function VoiceAssistantIcon() {
return (
<svg
Expand Down Expand Up @@ -461,15 +457,6 @@ export function ComposerButtons(props: {
// The composer must surface that constraint at UI time so the user never
// builds a submission the hub will reject — see hub/web/routes/messages.ts.
hasAttachments?: boolean
// Pi-specific toolbar buttons
piModelLabel?: string
piModelDisabled?: boolean
piModelOpen?: boolean
onPiModelToggle?: () => void
piThinkingLabel?: string
piThinkingDisabled?: boolean
piThinkingOpen?: boolean
onPiThinkingToggle?: () => void
// Scratchlist drawer toggle. When `onScratchlistToggle` is provided, a
// notepad icon appears next to the schedule-send icon. Click toggles
// composer-send-routing between chat and scratchlist; SessionChat owns
Expand Down Expand Up @@ -511,42 +498,6 @@ export function ComposerButtons(props: {
</button>
) : null}

{props.piModelLabel ? (
<button
type="button"
aria-label={props.piModelLabel}
title={props.piModelLabel}
className={`flex h-8 items-center gap-1 rounded-full px-3 text-xs font-medium transition-colors ${
props.piModelOpen
? 'bg-[var(--app-secondary-bg)] text-[var(--app-link)]'
: 'text-[var(--app-fg)]/60 hover:bg-[var(--app-bg)] hover:text-[var(--app-fg)]'
}`}
onClick={props.onPiModelToggle}
disabled={props.piModelDisabled}
>
{props.piModelLabel}
<ChevronIcon />
</button>
) : null}

{props.piThinkingLabel ? (
<button
type="button"
aria-label={props.piThinkingLabel}
title={props.piThinkingLabel}
className={`flex h-8 items-center gap-1 rounded-full px-3 text-xs font-medium transition-colors ${
props.piThinkingOpen
? 'bg-[var(--app-secondary-bg)] text-[var(--app-link)]'
: 'text-[var(--app-fg)]/60 hover:bg-[var(--app-bg)] hover:text-[var(--app-fg)]'
}`}
onClick={props.onPiThinkingToggle}
disabled={props.piThinkingDisabled}
>
{props.piThinkingLabel}
<ChevronIcon />
</button>
) : null}

{props.showTerminalButton ? (
<button
type="button"
Expand Down
132 changes: 22 additions & 110 deletions web/src/components/AssistantChat/HappyComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import { useActiveSuggestions } from '@/hooks/useActiveSuggestions'
import { applySuggestion } from '@/utils/applySuggestion'
import { usePlatform } from '@/hooks/usePlatform'
import { usePWAInstall } from '@/hooks/usePWAInstall'
import { supportsEffort, supportsModelChange, PI_THINKING_LEVEL_LABELS } from '@hapi/protocol'
import type { PiThinkingLevel } from '@hapi/protocol'
import { supportsEffort, supportsModelChange } from '@hapi/protocol'
import { markSkillUsed } from '@/lib/recent-skills'
import { useComposerDraft } from '@/hooks/useComposerDraft'
import { useComposerEnterBehavior } from '@/hooks/useComposerEnterBehavior'
Expand All @@ -38,8 +37,6 @@ import { getCodexComposerReasoningEffortOptions } from './codexReasoningEffortOp
import { getDisplayedCodexServiceTier } from './codexFastMode'
import { getPiThinkingLevelOptions, getHighestThinkingLevel, isThinkingLevelSupported } from './piThinkingLevelOptions'
import { groupModelsByProvider } from './piModelGroups'
import { PiModelPanel } from './PiModelPanel'
import { PiThinkingLevelPanel } from './PiThinkingLevelPanel'

export interface TextInputState {
text: string
Expand Down Expand Up @@ -292,8 +289,6 @@ export function HappyComposer(props: {
selection: { start: 0, end: 0 }
})
const [showSettings, setShowSettings] = useState(false)
const [showPiModelPanel, setShowPiModelPanel] = useState(false)
const [showPiThinkingPanel, setShowPiThinkingPanel] = useState(false)
const [isAborting, setIsAborting] = useState(false)
const [isSwitching, setIsSwitching] = useState(false)
const [showContinueHint, setShowContinueHint] = useState(false)
Expand Down Expand Up @@ -626,7 +621,7 @@ export function HappyComposer(props: {
// Pi needs { provider, modelId } to disambiguate duplicate model IDs,
// but this generic cycler only emits a bare modelId (or null), which
// would lose the provider and can pick the wrong cached match or clear
// the model. Pi model changes go only through the dedicated PiModelPanel.
// the model.
if (agentFlavor === 'pi') return
if (e.key === 'm' && (e.metaKey || e.ctrlKey) && onModelChange && supportsModelChange(agentFlavor)) {
e.preventDefault()
Expand Down Expand Up @@ -760,16 +755,22 @@ export function HappyComposer(props: {

const showCollaborationSettings = Boolean(onCollaborationModeChange && collaborationModeOptions.length > 0)
const showPermissionSettings = Boolean(onPermissionModeChange && permissionModeOptions.length > 0)
const showModelSettings = Boolean(onModelChange && supportsModelChange(agentFlavor) && (piModels && piModels.length > 0 || modelOptions.length > 0))
const showModelSettings = Boolean(
onModelChange
&& supportsModelChange(agentFlavor)
&& (agentFlavor === 'pi' ? piModels : modelOptions)?.length
)
const showModelEffortSettings = Boolean(
(onModelEffortChange ?? onModelChange)
&& modelEffortOptions
&& modelEffortOptions.length > 0
)
const showModelReasoningEffortSettings = Boolean(onModelReasoningEffortChange && codexReasoningEffortOptions.length > 0)
// For Pi: hide effort when selected model explicitly has reasoning: false
const piEffortHidden = piModels && selectedPiModel && selectedPiModel.reasoning === false
const showEffortSettings = Boolean(onEffortChange && supportsEffort(agentFlavor) && !piEffortHidden)
const showEffortSettings = Boolean(
onEffortChange
&& supportsEffort(agentFlavor)
&& (agentFlavor !== 'pi' || selectedPiModel?.reasoning === true)
)
const showFastModeSettings = Boolean(onServiceTierChange)
const showSettingsButton = Boolean(
showCollaborationSettings
Expand Down Expand Up @@ -797,89 +798,8 @@ export function HappyComposer(props: {
// the error context while the new attempt is in flight.
}, [api])

// Pi: selected model info for UI labels and thinking level filtering
const piModelLabel = agentFlavor === 'pi'
? (selectedPiModel?.name ?? selectedPiModel?.modelId ?? 'Model')
: undefined
const piThinkingLabel = agentFlavor === 'pi'
? (() => {
if (!selectedPiModel) return 'Thinking'
const effectiveLevel = effort && isThinkingLevelSupported(effort, selectedPiModel.thinkingLevelMap)
? effort
: getHighestThinkingLevel(selectedPiModel.thinkingLevelMap)
return effectiveLevel
? (PI_THINKING_LEVEL_LABELS[effectiveLevel as PiThinkingLevel] ?? effectiveLevel)
: 'Thinking'
})()
: undefined
const piHasModels = piModels && piModels.length > 0

const closeAllPanels = useCallback(() => {
setShowSettings(false)
setShowPiModelPanel(false)
setShowPiThinkingPanel(false)
}, [])

const handlePiModelToggle = useCallback(() => {
if (controlsDisabled) return
setShowPiModelPanel((v) => !v)
setShowSettings(false)
setShowPiThinkingPanel(false)
haptic('light')
}, [controlsDisabled, haptic])

const handlePiThinkingToggle = useCallback(() => {
if (controlsDisabled) return
setShowPiThinkingPanel((v) => !v)
setShowSettings(false)
setShowPiModelPanel(false)
haptic('light')
}, [controlsDisabled, haptic])

const overlays = useMemo(() => {
// Pi flavor: separate floating panels for model and thinking level.
// (Pi RPC mode has no runtime permission switching → no permission panel.)
if (agentFlavor === 'pi') {
const panels: React.ReactNode[] = []

// Model selection panel
if (showPiModelPanel && piModels && piModels.length > 0) {
const currentPiModel = selectedPiModel ?? null
panels.push(
<div key="model" className="absolute bottom-[100%] mb-2 left-2 w-64">
<PiModelPanel
models={piModels}
currentModel={currentPiModel ? { provider: currentPiModel.provider, modelId: currentPiModel.modelId } : null}
controlsDisabled={controlsDisabled}
onSelect={(piModel) => {
handleModelChange({ provider: piModel.provider, modelId: piModel.modelId })
}}
onClose={closeAllPanels}
/>
</div>
)
}

// Thinking level panel
if (showPiThinkingPanel && selectedPiModel?.reasoning !== false) {
panels.push(
<div key="thinking" className="absolute bottom-[100%] mb-2 left-2 w-48">
<PiThinkingLevelPanel
currentLevel={effort}
reasoning={selectedPiModel?.reasoning}
thinkingLevelMap={selectedPiModel?.thinkingLevelMap}
controlsDisabled={controlsDisabled}
onSelect={(level) => handleEffortChange(level)}
onClose={closeAllPanels}
/>
</div>
)
}

if (panels.length > 0) return <>{panels}</>
}

// Non-Pi flavors: original unified gear menu
// Unified gear menu
Comment thread
Arnie97 marked this conversation as resolved.
Comment thread
Arnie97 marked this conversation as resolved.
Comment thread
Arnie97 marked this conversation as resolved.
if (showSettings && (showCollaborationSettings || showPermissionSettings || showModelSettings || showModelEffortSettings || showModelReasoningEffortSettings || showEffortSettings || showFastModeSettings)) {
return (
<div className="absolute bottom-[100%] mb-2 w-full">
Expand Down Expand Up @@ -977,9 +897,11 @@ export function HappyComposer(props: {
<div className="px-3 pt-2 pb-0.5 text-xs font-medium text-[var(--app-hint)]">
{group.label}
</div>
{group.models.map((piModel) => (
{group.models.map((piModel) => {
const selected = selectedPiModel?.provider === piModel.provider && selectedPiModel?.modelId === piModel.modelId
return (
<button
key={piModel.modelId}
key={`${piModel.provider}:${piModel.modelId}`}
type="button"
disabled={controlsDisabled}
className={`flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors ${
Expand All @@ -992,20 +914,21 @@ export function HappyComposer(props: {
>
<div
className={`flex h-4 w-4 items-center justify-center rounded-full border-2 ${
model === piModel.modelId
selected
? 'border-[var(--app-link)]'
: 'border-[var(--app-hint)]'
}`}
>
{model === piModel.modelId && (
{selected && (
<div className="h-2 w-2 rounded-full bg-[var(--app-link)]" />
)}
</div>
<span className={model === piModel.modelId ? 'text-[var(--app-link)]' : ''}>
<span className={selected ? 'text-[var(--app-link)]' : ''}>
{piModel.name ?? piModel.modelId}
</span>
</button>
))}
)
})}
</div>
))
) : (
Expand Down Expand Up @@ -1209,12 +1132,9 @@ export function HappyComposer(props: {
return null
}, [
showSettings,
showPiModelPanel,
showPiThinkingPanel,
agentFlavor,
piModels,
selectedPiModel,
closeAllPanels,
showCollaborationSettings,
showPermissionSettings,
showModelSettings,
Expand Down Expand Up @@ -1352,14 +1272,6 @@ export function HappyComposer(props: {
onSchedule={setPendingSchedule}
onClearSchedule={isControlled ? onClearScheduleProp : () => setPendingScheduleLocal(null)}
hasAttachments={hasAttachments}
piModelLabel={piModelLabel}
piModelDisabled={controlsDisabled || !piHasModels}
piModelOpen={showPiModelPanel}
onPiModelToggle={handlePiModelToggle}
piThinkingLabel={piThinkingLabel}
piThinkingDisabled={controlsDisabled || !piHasModels || !selectedPiModel || selectedPiModel.reasoning === false}
piThinkingOpen={showPiThinkingPanel}
onPiThinkingToggle={handlePiThinkingToggle}
scratchlistMode={props.scratchlistMode}
scratchlistCount={props.scratchlistCount}
onScratchlistToggle={props.onScratchlistToggle}
Expand Down
72 changes: 0 additions & 72 deletions web/src/components/AssistantChat/PiModelPanel.tsx

This file was deleted.

Loading
Loading