Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

756 lines
30 KiB
TypeScript

import { createPortal } from "react-dom";
import {
Popover,
PopoverAnchor,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "@tutti-os/ui-system";
import { cn } from "../../../app/renderer/lib/utils";
import styles from "../AgentGUINode.styles";
import { AgentInteractivePromptSurface } from "../AgentInteractivePromptSurface";
import { AgentQueuedPromptPanel } from "../AgentQueuedPromptPanel";
import {
AgentProjectDropdown,
AgentProjectMissingStatusProbe
} from "../AgentComposerSettingsMenus";
import { AgentChromeNotice } from "../AgentSessionChrome";
import { AgentFullAccessRestoredWarning } from "../AgentFullAccessRestoredWarning";
import { AgentRichTextEditor } from "../agentRichText/AgentRichTextEditor";
import { AgentFileMentionPalette } from "../AgentFileMentionPalette";
import { AgentReferenceProvenanceFilterControl } from "../AgentReferenceProvenanceFilterControl";
import type { AgentMentionFilterId } from "../AgentMentionSearchController";
import { AgentSlashCommandPalette } from "../AgentSlashCommandPalette";
import { AgentSlashStatusPanel } from "../AgentSlashStatusPanel";
import { AgentReviewPickerPanel } from "../AgentReviewPickerPanel";
import { ComposerFloatingMenuSurface } from "../composerFloatingMenu/ComposerFloatingMenuSurface";
import type { AgentComposerViewProps } from "./AgentComposerView.types";
import {
EMPTY_PROVIDER_SKILLS,
EMPTY_WORKSPACE_APP_ICONS,
SLASH_PALETTE_HEIGHT_PX,
composerStyles
} from "./AgentComposerChrome";
import { ComposerDraftAttachments } from "./ComposerDraftAttachments";
import { ComposerFooter } from "./ComposerFooter";
import { AgentSessionLaunchModeSelect } from "./AgentSessionLaunchModeSelect";
import { AgentQuickPromptPopover } from "./quickPrompts/AgentQuickPromptPopover";
import {
agentComposerDraftHasContent,
agentComposerDraftConnectors,
agentComposerDraftImages,
agentComposerDraftPrompt,
agentComposerDraftQuotes,
updateAgentComposerDraft
} from "../model/agentComposerDraft";
export function AgentComposerView(
input: AgentComposerViewProps
): React.JSX.Element {
const {
provider,
slashStatus = null,
usage = null,
draftContent,
engagement,
availableSkills = EMPTY_PROVIDER_SKILLS,
composerSettings,
workspaceId,
queueStatus = "active",
queuedPrompts,
drainingQueuedPromptId,
workspaceAppIcons = EMPTY_WORKSPACE_APP_ICONS,
activePromptDisabledReason = null,
activePromptKeyboardShortcutsEnabled = true,
promptImagesSupported = true,
layoutMode = "dock",
providerSelectLabel = "",
composerActionAccessory,
labels,
workspaceUserProjectI18n,
isSubmittingPrompt,
onSendQueuedPromptNext,
onRemoveQueuedPrompt,
onEditQueuedPrompt,
onPromptImagesUnsupported,
onRequestWorkspaceReferences,
referenceProvenanceFilters,
selectProjectDirectory,
projectSelectOptions,
userProjectApi,
onProjectPathChange = () => {},
onSettingsChange,
onLinkAction,
onSubmit,
onProviderSelect,
onHandoffConversation,
onSlashStatusRefresh,
compactSupported = null,
hasCompactableContext = true
} = input.props;
const draftImages = agentComposerDraftImages(draftContent);
const draftQuotes = agentComposerDraftQuotes(draftContent);
const draftConnectors = agentComposerDraftConnectors(draftContent);
const slashStatusAgentSessionId = slashStatus?.agentSessionId ?? null;
const draftPrompt = agentComposerDraftPrompt(draftContent);
const { availableCapabilities, slashPaletteEntries, slashQuery } =
input.paletteCatalog;
const { mentionPaletteFrame, mentionPaletteHeightPx, mentionPaletteStyle } =
input.mentionFrame;
const {
closeReviewPicker,
closeSlashFloatingMenu,
closeSlashStatusPanel,
composerControlsHardDisabled,
permissionModeControlsDisabled,
reviewBranchLoader,
selectCapability,
selectCapabilitySettings,
selectCommand,
selectSkill,
settingsControlsDisabled,
submit,
submitCurrentPrompt,
submitReviewCommand
} = input.slashActions;
const {
handleFileMentionSuggestionChange,
handleFileMentionKeyDown,
handlePaletteKeyDown,
handleMentionHighlightChange,
navigateFileMentionHierarchy,
navigateIntoFileMentionItem,
selectFileMention
} = input.mentionActions;
const {
clearGoalModeBadge,
expandDraftLargeTextToPrompt,
handleDraftChange,
handleLinkClick,
handleOpenReferencesForEntity,
handlePastedLargeText,
handleWorkspaceReferencePicker,
removeDraftImage,
removeDraftLargeText,
removeDraftQuotes,
setDraftConnectorSelected
} = input.attachments;
const {
composerClassName,
effectiveHandoffLabel,
effectiveHandoffMenuLabel,
handoffDisabled,
handoffMenuTargets,
inputDisabled,
inputShellClassName,
isHeroLayout,
providerMenuTargets,
providerSelectDisabled,
selectedProviderLabel,
selectedProviderSwitchTarget,
showHandoffSelect,
showProviderSelect
} = input.providerState;
const { handleMentionPaletteButton, handlePastedImages } = input.focusAndDrop;
const {
activePromptTip,
activePromptTipText,
composerStyle,
inputShellStyle,
promptInputAreaStyle,
showEdgeGlow,
showHeroProjectSelector,
showProjectMissingProbe,
showProjectRow
} = input.layout;
const {
composerActionButton,
disabledReasonText,
effectivePlaceholder,
fileDropOverlay,
promptTipNode,
submitInteractivePromptAndDismiss,
visibleActivePrompt,
visibleDraftLargeTexts
} = input.presentation;
const composerActionNode = composerActionAccessory ? (
<div className="inline-flex shrink-0 items-center gap-2">
{composerActionAccessory}
{composerActionButton}
</div>
) : (
composerActionButton
);
const showComposerActionInFooter =
isHeroLayout || input.props.composerActionPlacement === "footer";
const showProjectSelectorInFooter =
!isHeroLayout && input.props.showProjectSelectorInFooter === true;
const projectSelectorNode =
showHeroProjectSelector || showProjectSelectorInFooter ? (
<AgentProjectDropdown
composerSettings={composerSettings}
i18n={workspaceUserProjectI18n}
labels={{
projectLocked: labels.projectLocked,
projectMissingDescription: labels.projectMissingDescription
}}
selectProjectDirectory={selectProjectDirectory}
options={projectSelectOptions}
userProjectApi={userProjectApi}
onDismissAutoFocus={input.onDismissProjectMenuAutoFocus}
onProjectMissingChange={input.setIsSelectedProjectMissing}
onProjectPathChange={onProjectPathChange}
/>
) : null;
const sessionLaunchModeNode =
input.sessionWorktreeLaunch.visible &&
input.props.labels.sessionLaunchModeLabel &&
input.props.labels.sessionLaunchModeLocal &&
input.props.labels.sessionLaunchModeWorktree ? (
<AgentSessionLaunchModeSelect
labels={{
launchMode: input.props.labels.sessionLaunchModeLabel,
local: input.props.labels.sessionLaunchModeLocal,
worktree: input.props.labels.sessionLaunchModeWorktree
}}
mode={input.sessionWorktreeLaunch.mode}
onModeChange={input.sessionWorktreeLaunch.onModeChange}
/>
) : null;
const projectControlsNode =
projectSelectorNode || sessionLaunchModeNode ? (
<div className={styles.composerProjectControls}>
{projectSelectorNode}
{sessionLaunchModeNode}
</div>
) : null;
return (
<form
ref={input.composerRef}
className={composerClassName}
data-testid={
input.props.activeTurnId?.trim()
? "agent-gui-composer-active-turn"
: undefined
}
data-agent-turn-id={input.props.activeTurnId?.trim() || undefined}
data-fill-available-height={
input.props.fillAvailableHeight ? "true" : undefined
}
data-layout={layoutMode}
style={composerStyle}
onSubmit={submit}
>
{fileDropOverlay}
{visibleActivePrompt ? (
<div
className={styles.composerFloatingPrompt}
data-testid="agent-gui-composer-floating-prompt"
>
<AgentInteractivePromptSurface
prompt={visibleActivePrompt}
conversationReturn={labels.conversationReturn}
embedded={true}
edgeGlow={true}
keyboardShortcuts={activePromptKeyboardShortcutsEnabled}
isSubmitting={isSubmittingPrompt}
isInteractionDisabled={Boolean(activePromptDisabledReason)}
interactionDisabledReason={activePromptDisabledReason}
onSubmit={submitInteractivePromptAndDismiss}
labels={{
approvalLead: labels.approvalLead,
fileChangeApprovalLead: labels.fileChangeApprovalLead,
planLead: labels.planLead,
planModes: labels.planModes,
stayInPlan: labels.stayInPlan,
sendFeedback: labels.sendFeedback,
feedbackPlaceholder: labels.feedbackPlaceholder,
previousQuestion: labels.previousQuestion,
nextQuestion: labels.nextQuestion,
submitAnswers: labels.submitAnswers,
answerPlaceholder: labels.answerPlaceholder,
waitingForAnswer: labels.waitingForAnswer,
planImplementationLead: labels.planImplementationLead,
planImplementationConfirm: labels.planImplementationConfirm,
planImplementationFeedbackPlaceholder:
labels.planImplementationFeedbackPlaceholder,
planImplementationSend: labels.planImplementationSend,
planImplementationSkip: labels.planImplementationSkip
}}
/>
</div>
) : null}
{queuedPrompts.length > 0 ? (
<div
className={cn(
styles.composerFloatingPrompt,
styles.composerQueuedPromptFloating
)}
data-testid="agent-gui-composer-queued-prompts"
>
<AgentQueuedPromptPanel
queueStatus={queueStatus}
queuedPrompts={queuedPrompts}
drainingQueuedPromptId={drainingQueuedPromptId}
labels={{
queuedLabel: labels.queuedLabel,
queuePausedByUserLabel: labels.queuePausedByUserLabel,
sendQueuedPromptNext: labels.sendQueuedPromptNext,
editQueuedPrompt: labels.editQueuedPrompt,
deleteQueuedPrompt: labels.deleteQueuedPrompt,
queuedPromptMoreActions: labels.queuedPromptMoreActions
}}
onSendQueuedPromptNext={onSendQueuedPromptNext}
onRemoveQueuedPrompt={onRemoveQueuedPrompt}
onEditQueuedPrompt={onEditQueuedPrompt}
agentSessionId={slashStatusAgentSessionId}
onLinkClick={handleLinkClick}
workspaceId={workspaceId}
workspaceAppIcons={workspaceAppIcons}
/>
</div>
) : null}
{showProjectMissingProbe ? (
<AgentProjectMissingStatusProbe
composerSettings={composerSettings}
onProjectMissingChange={input.setIsSelectedProjectMissing}
/>
) : null}
<AgentFullAccessRestoredWarning
isSettingsLoading={composerSettings.isSettingsLoading}
permissionModeId={
composerSettings.selectedPermissionModeValue ??
composerSettings.draftSettings.permissionModeId
}
provider={provider}
visibleOnHome={isHeroLayout}
/>
<div
className={cn(
styles.composerInputGroup,
layoutMode === "hero" && styles.composerInputGroupHero
)}
data-edge-glow={showEdgeGlow ? "true" : undefined}
>
{input.isSelectedProjectMissing ? (
<AgentChromeNotice
tone="danger"
role="alert"
testId="agent-gui-missing-project-notice"
title={workspaceUserProjectI18n.tFirst([
"projectSelect.projectMissingTitle"
])}
description={labels.projectMissingDescription}
/>
) : null}
<div
ref={input.inputShellRef}
className={cn(inputShellClassName, "relative")}
data-testid="agent-gui-composer-input-shell"
data-input-disabled={inputDisabled ? "true" : undefined}
title={
inputDisabled && disabledReasonText ? disabledReasonText : undefined
}
style={inputShellStyle}
>
<Popover
open={input.showFileMentionPalette}
onOpenChange={input.setIsPaletteOpen}
modal={false}
>
<PopoverAnchor asChild>
<div
ref={input.promptInputAreaRef}
className={cn(
"w-full min-w-0 self-start",
!isHeroLayout && "agent-gui-node__composer-prompt-input-area",
isHeroLayout &&
"agent-gui-node__composer-hero-prompt-input-area"
)}
data-has-draft-images={
draftImages.length > 0 ? "true" : undefined
}
data-has-draft-attachments={
draftImages.length > 0 ||
visibleDraftLargeTexts.length > 0 ||
draftQuotes.length > 0
? "true"
: undefined
}
style={promptInputAreaStyle}
>
<ComposerDraftAttachments
draftImages={draftImages}
draftLargeTexts={visibleDraftLargeTexts}
draftQuotes={draftQuotes}
removeLabel={labels.removeMention}
onRemoveImage={removeDraftImage}
onRemoveLargeText={removeDraftLargeText}
onExpandLargeText={expandDraftLargeTextToPrompt}
onRemoveQuotes={removeDraftQuotes}
/>
<div
className={cn(
"w-full min-w-0 self-start",
!isHeroLayout &&
"agent-gui-node__composer-prompt-input-line"
)}
>
<AgentRichTextEditor
ref={input.editorHandleRef}
value={input.paletteDraftPrompt}
contentScopeKey={input.props.draftScopeKey}
placeholder={effectivePlaceholder}
disabled={inputDisabled}
className={styles.composerTextarea}
testId="agent-gui-composer-editor"
onChange={handleDraftChange}
onContentLayoutInvalidated={
input.layout.invalidateComposerMeasurement
}
onFocus={(method) => engagement?.focused(method)}
onUserContentChange={(nextPrompt) => {
if (
agentComposerDraftHasContent(
updateAgentComposerDraft(draftContent, {
prompt: nextPrompt
})
)
) {
engagement?.contentEntered({
contentType: "text",
hadPrefill: agentComposerDraftHasContent(draftContent)
});
}
}}
onSubmit={submitCurrentPrompt}
onSubmitGuidance={() =>
submitCurrentPrompt({ guidance: true })
}
availableSkills={availableSkills}
availableCapabilities={availableCapabilities}
removeMentionLabel={labels.removeMention}
onKeyDownForPalette={handlePaletteKeyDown}
onHistoryNavigation={input.onHistoryNavigation}
onFileMentionSuggestionChange={
handleFileMentionSuggestionChange
}
onFileMentionSuggestionKeyDown={handleFileMentionKeyDown}
onLinkClick={handleLinkClick}
promptImagesSupported={promptImagesSupported}
onPromptImagesUnsupported={onPromptImagesUnsupported}
onPasteImages={handlePastedImages}
onPasteLargeText={handlePastedLargeText}
onPasteFiles={
input.externalPromptEntriesSupported
? input.addExternalPromptEntries
: undefined
}
onDropFiles={
input.externalPromptEntriesSupported
? input.addExternalPromptEntries
: undefined
}
onResolvePastedPath={
input.props.resolvePastedPath ?? undefined
}
/>
{!isHeroLayout && !showComposerActionInFooter
? composerActionNode
: null}
</div>
</div>
</PopoverAnchor>
{input.showFileMentionPalette && mentionPaletteFrame
? createPortal(
<div
data-testid="agent-gui-mention-palette-surface"
ref={input.paletteContentRef}
className={cn(
composerStyles.dropdownSurface,
"max-h-[320px] overflow-hidden border-[var(--line-1)] p-0"
)}
style={mentionPaletteStyle}
>
<AgentFileMentionPalette
state={input.mentionSearchState}
highlightedKey={input.mentionHighlightedKey}
label={labels.fileMentionPalette}
loadingLabel={labels.fileMentionLoading}
emptyLabel={labels.fileMentionEmpty}
errorLabel={labels.fileMentionError}
tabHintLabel={labels.fileMentionTabHint}
maxHeightPx={mentionPaletteHeightPx}
shouldCenterHighlightedItem={
input.shouldCenterMentionHighlight
}
onHighlightChange={handleMentionHighlightChange}
onSelectItem={selectFileMention}
onSelectCategory={(filter) =>
input.mentionControllerRef.current?.setFilter(filter)
}
onSelectFilter={(filter) =>
input.mentionControllerRef.current?.setFilter(filter)
}
onExpandGroup={(groupId) =>
input.mentionControllerRef.current?.expandGroup(groupId)
}
onNavigateHierarchy={navigateFileMentionHierarchy}
onNavigateIntoItem={navigateIntoFileMentionItem}
onOpenReferences={
onRequestWorkspaceReferences
? handleOpenReferencesForEntity
: undefined
}
provenanceFilterControl={
referenceProvenanceFilters ? (
<AgentReferenceProvenanceFilterControl
filter={
referenceProvenanceFilters.byFilter[
input.mentionSearchState
.filter as AgentMentionFilterId
]
}
/>
) : undefined
}
/>
</div>,
mentionPaletteFrame.portalTarget
)
: null}
<ComposerFloatingMenuSurface
anchorRef={input.inputShellRef}
className={cn(
composerStyles.dropdownSurface,
"max-h-[320px] overflow-hidden border-[var(--line-1)] p-0"
)}
contentClassName="h-full min-h-0"
dismissBoundaryRef={input.promptInputAreaRef}
maxHeight={SLASH_PALETTE_HEIGHT_PX}
onDismiss={closeSlashFloatingMenu}
open={input.showSlashPalette}
placement="fixed-height"
surfaceRef={input.paletteContentRef}
testId="agent-gui-slash-palette-surface"
>
<AgentSlashCommandPalette
entries={slashPaletteEntries}
highlightedIndex={input.activeHighlight}
label={
slashQuery === null
? labels.skillPickerPalette
: labels.slashCommandPalette
}
commandsGroupLabel={labels.slashPaletteCommandsGroup}
capabilitiesGroupLabel={labels.slashPaletteCapabilitiesGroup}
capabilitiesLoading={
composerSettings.isCapabilityOptionsLoading === true
}
capabilitiesLoadingLabel={
labels.slashPaletteCapabilitiesLoading
}
skillsGroupLabel={labels.slashPaletteSkillsGroup}
pluginsGroupLabel={labels.slashPalettePluginsGroup}
connectorsGroupLabel={labels.slashPaletteConnectorsGroup}
connectorConnectedLabel={labels.slashPaletteConnectorConnected}
connectorNotConnectedLabel={
labels.slashPaletteConnectorNotConnected
}
connectorUnsupportedLabel={
labels.slashPaletteConnectorUnsupported
}
mcpGroupLabel={labels.slashPaletteMcpGroup}
onHighlightChange={input.setHighlightedIndex}
onSelect={selectCommand}
onSelectCapability={selectCapability}
onSelectCapabilitySettings={selectCapabilitySettings}
onSelectSkill={selectSkill}
/>
</ComposerFloatingMenuSurface>
<ComposerFloatingMenuSurface
anchorRef={input.inputShellRef}
className="border-0 p-0"
dismissBoundaryRef={input.promptInputAreaRef}
maxHeight={SLASH_PALETTE_HEIGHT_PX}
onDismiss={closeSlashFloatingMenu}
open={input.isSlashStatusPanelOpen}
placement="dynamic-above"
surfaceRef={input.paletteContentRef}
testId="agent-gui-command-menu-surface"
>
<AgentSlashStatusPanel
status={slashStatus}
labels={{
slashStatusTitle: labels.slashStatusTitle,
slashStatusSession: labels.slashStatusSession,
slashStatusBaseUrl: labels.slashStatusBaseUrl,
slashStatusContext: labels.slashStatusContext,
slashStatusLimits: labels.slashStatusLimits,
slashStatusClose: labels.slashStatusClose,
slashStatusContextValue: labels.slashStatusContextValue,
slashStatusContextUnavailable:
labels.slashStatusContextUnavailable,
slashStatusLimitsUnavailable:
labels.slashStatusLimitsUnavailable,
slashStatusEmptyValue: labels.slashStatusEmptyValue,
slashStatusUsageJustUpdated:
labels.slashStatusUsageJustUpdated,
slashStatusUsageMinutesAgo: labels.slashStatusUsageMinutesAgo,
slashStatusUsageHoursAgo: labels.slashStatusUsageHoursAgo,
slashStatusUsageUpdating: labels.slashStatusUsageUpdating,
slashStatusUsageRefreshFailed:
labels.slashStatusUsageRefreshFailed,
slashStatusUsageRefreshAria:
labels.slashStatusUsageRefreshAria
}}
onClose={closeSlashStatusPanel}
onRefresh={onSlashStatusRefresh}
/>
</ComposerFloatingMenuSurface>
<ComposerFloatingMenuSurface
anchorRef={input.inputShellRef}
className="border-0 p-0"
dismissBoundaryRef={input.promptInputAreaRef}
maxHeight={SLASH_PALETTE_HEIGHT_PX}
onDismiss={closeSlashFloatingMenu}
open={input.isReviewPickerOpen}
placement="dynamic-above"
surfaceRef={input.paletteContentRef}
testId="agent-gui-command-menu-surface"
>
<AgentReviewPickerPanel
labels={labels.reviewPicker}
onRequestGitBranches={reviewBranchLoader}
onSubmitReview={submitReviewCommand}
onClose={closeReviewPicker}
/>
</ComposerFloatingMenuSurface>
</Popover>
<ComposerFooter
workspaceId={workspaceId}
labels={labels}
provider={provider}
composerSettings={composerSettings}
usage={usage}
compactSupported={compactSupported}
hasCompactableContext={hasCompactableContext}
composerControlsHardDisabled={composerControlsHardDisabled}
inputDisabled={inputDisabled}
settingsControlsDisabled={settingsControlsDisabled}
codexSaverModeDisabled={
settingsControlsDisabled || Boolean(input.props.agentSessionId)
}
rtkSaverModeDisabled={
settingsControlsDisabled || Boolean(input.props.agentSessionId)
}
permissionModeControlsDisabled={permissionModeControlsDisabled}
isSendingTurn={input.props.isSendingTurn}
showComposerAction={showComposerActionInFooter}
isGoalModeActive={input.isGoalModeActive}
isPlanModeActive={input.isPlanModeActive}
isTuttiModeActive={input.isTuttiModeActive}
isTuttiModeUpdating={input.isTuttiModeUpdating}
tuttiModeSupported={
input.props.capabilityMenuState?.tuttiMode?.enabled === true
}
connectorsVisible={
input.props.capabilityMenuState?.connectors?.enabled !== false
}
connectorsReadOnly={
input.props.capabilityControlsReadOnly === true ||
input.props.capabilityMenuState?.connectors?.readOnly === true
}
showConnectorViewMore={
input.props.capabilityMenuState?.connectors?.showViewMore !==
false
}
onTuttiModeChange={input.props.onTuttiModeChange}
onClearPlanMode={input.onClearPlanMode}
composerAction={composerActionNode}
projectControl={
showProjectSelectorInFooter ? projectControlsNode : null
}
quickPromptControl={
<AgentQuickPromptPopover
controller={input.quickPromptLibrary}
disabled={composerControlsHardDisabled || inputDisabled}
/>
}
footerAccessory={input.props.footerAccessory}
showHandoffSelect={showHandoffSelect}
handoffDisabled={handoffDisabled}
effectiveHandoffLabel={effectiveHandoffLabel}
effectiveHandoffMenuLabel={effectiveHandoffMenuLabel}
handoffMenuTargets={handoffMenuTargets}
onHandoffConversation={onHandoffConversation}
showHandoffTargetOwnershipLabels={
input.props.showHandoffTargetOwnershipLabels
}
showProviderSelect={showProviderSelect}
selectedProviderSwitchTarget={selectedProviderSwitchTarget}
providerSelectDisabled={providerSelectDisabled}
providerSelectLabel={providerSelectLabel}
selectedProviderLabel={selectedProviderLabel}
providerMenuTargets={providerMenuTargets}
menuViewportTopInset={input.props.menuViewportTopInset}
onProviderSelect={onProviderSelect}
onLinkAction={onLinkAction}
availableSkills={availableSkills}
selectedConnectorKeys={draftConnectors.map(
(connector) => connector.connectorKey
)}
onConnectorSelected={setDraftConnectorSelected}
onRetryComposerOptions={input.props.onRetryComposerOptions}
onCapabilitySettingsRequest={
input.props.onCapabilitySettingsRequest
}
onRequestWorkspaceReferences={onRequestWorkspaceReferences}
onWorkspaceReferencePicker={handleWorkspaceReferencePicker}
onMentionPaletteButton={handleMentionPaletteButton}
onSettingsChange={onSettingsChange}
onSubmit={onSubmit}
onClearGoalMode={clearGoalModeBadge}
draftPrompt={draftPrompt}
/>
</div>
{showProjectRow ? (
<div
className={styles.composerProjectRow}
data-project-missing={
input.isSelectedProjectMissing ? "true" : undefined
}
>
{showHeroProjectSelector ? projectControlsNode : null}
{activePromptTip ? (
<div
className={styles.composerPromptTips}
data-testid="agent-gui-prompt-tips"
>
{input.isPromptTipOverflowing && promptTipNode ? (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>{promptTipNode}</TooltipTrigger>
<TooltipContent
align="end"
className={styles.composerPromptTipTooltip}
side="bottom"
>
{activePromptTipText}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
promptTipNode
)}
</div>
) : null}
</div>
) : null}
</div>
</form>
);
}