Download src/components/permissions/useShellPermissionFeedback.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 4.61 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/permissions/useShellPermissionFeedback.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/permissions/useShellPermissionFeedback.ts
-
curl -L -o useShellPermissionFeedback.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/components/permissions/useShellPermissionFeedback.ts
4.61 kB
| import { useState } from 'react' | |
| import { | |
| type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, | |
| logEvent, | |
| } from '../../services/analytics/index.js' | |
| import { sanitizeToolNameForAnalytics } from '../../services/analytics/metadata.js' | |
| import { useSetAppState } from '../../state/AppState.js' | |
| import type { ToolUseConfirm } from './PermissionRequest.js' | |
| import { logUnaryPermissionEvent } from './utils.js' | |
| /** | |
| * Shared feedback-mode state + handlers for shell permission dialogs (Bash, | |
| * PowerShell). Encapsulates the yes/no input-mode toggle, feedback text state, | |
| * focus tracking, and reject handling. | |
| */ | |
| export function useShellPermissionFeedback({ | |
| toolUseConfirm, | |
| onDone, | |
| onReject, | |
| explainerVisible, | |
| }: { | |
| toolUseConfirm: ToolUseConfirm | |
| onDone: () => void | |
| onReject: () => void | |
| explainerVisible: boolean | |
| }): { | |
| yesInputMode: boolean | |
| noInputMode: boolean | |
| yesFeedbackModeEntered: boolean | |
| noFeedbackModeEntered: boolean | |
| acceptFeedback: string | |
| rejectFeedback: string | |
| setAcceptFeedback: (v: string) => void | |
| setRejectFeedback: (v: string) => void | |
| focusedOption: string | |
| handleInputModeToggle: (option: string) => void | |
| handleReject: (feedback?: string) => void | |
| handleFocus: (value: string) => void | |
| } { | |
| const setAppState = useSetAppState() | |
| const [rejectFeedback, setRejectFeedback] = useState('') | |
| const [acceptFeedback, setAcceptFeedback] = useState('') | |
| const [yesInputMode, setYesInputMode] = useState(false) | |
| const [noInputMode, setNoInputMode] = useState(false) | |
| const [focusedOption, setFocusedOption] = useState('yes') | |
| // Track whether user ever entered feedback mode (persists after collapse) | |
| const [yesFeedbackModeEntered, setYesFeedbackModeEntered] = useState(false) | |
| const [noFeedbackModeEntered, setNoFeedbackModeEntered] = useState(false) | |
| // Handle Tab key toggling input mode for Yes/No options | |
| function handleInputModeToggle(option: string) { | |
| // Notify that user is interacting with the dialog | |
| toolUseConfirm.onUserInteraction() | |
| const analyticsProps = { | |
| toolName: sanitizeToolNameForAnalytics( | |
| toolUseConfirm.tool.name, | |
| ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, | |
| isMcp: toolUseConfirm.tool.isMcp ?? false, | |
| } | |
| if (option === 'yes') { | |
| if (yesInputMode) { | |
| setYesInputMode(false) | |
| logEvent('tengu_accept_feedback_mode_collapsed', analyticsProps) | |
| } else { | |
| setYesInputMode(true) | |
| setYesFeedbackModeEntered(true) | |
| logEvent('tengu_accept_feedback_mode_entered', analyticsProps) | |
| } | |
| } else if (option === 'no') { | |
| if (noInputMode) { | |
| setNoInputMode(false) | |
| logEvent('tengu_reject_feedback_mode_collapsed', analyticsProps) | |
| } else { | |
| setNoInputMode(true) | |
| setNoFeedbackModeEntered(true) | |
| logEvent('tengu_reject_feedback_mode_entered', analyticsProps) | |
| } | |
| } | |
| } | |
| function handleReject(feedback?: string) { | |
| const trimmedFeedback = feedback?.trim() | |
| const hasFeedback = !!trimmedFeedback | |
| // Log escape if no feedback was provided (user pressed ESC) | |
| if (!hasFeedback) { | |
| logEvent('tengu_permission_request_escape', { | |
| explainer_visible: explainerVisible, | |
| }) | |
| // Increment escape count for attribution tracking | |
| setAppState(prev => ({ | |
| ...prev, | |
| attribution: { | |
| ...prev.attribution, | |
| escapeCount: prev.attribution.escapeCount + 1, | |
| }, | |
| })) | |
| } | |
| logUnaryPermissionEvent( | |
| 'tool_use_single', | |
| toolUseConfirm, | |
| 'reject', | |
| hasFeedback, | |
| ) | |
| if (trimmedFeedback) { | |
| toolUseConfirm.onReject(trimmedFeedback) | |
| } else { | |
| toolUseConfirm.onReject() | |
| } | |
| onReject() | |
| onDone() | |
| } | |
| function handleFocus(value: string) { | |
| // Notify that user is interacting with the dialog (only if focus changed) | |
| // This prevents triggering on the initial mount/render | |
| if (value !== focusedOption) { | |
| toolUseConfirm.onUserInteraction() | |
| } | |
| // Reset input mode when navigating away, but only if no text typed | |
| if (value !== 'yes' && yesInputMode && !acceptFeedback.trim()) { | |
| setYesInputMode(false) | |
| } | |
| if (value !== 'no' && noInputMode && !rejectFeedback.trim()) { | |
| setNoInputMode(false) | |
| } | |
| setFocusedOption(value) | |
| } | |
| return { | |
| yesInputMode, | |
| noInputMode, | |
| yesFeedbackModeEntered, | |
| noFeedbackModeEntered, | |
| acceptFeedback, | |
| rejectFeedback, | |
| setAcceptFeedback, | |
| setRejectFeedback, | |
| focusedOption, | |
| handleInputModeToggle, | |
| handleReject, | |
| handleFocus, | |
| } | |
| } | |