/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import { Box, Text, useIsScreenReaderEnabled } from 'ink'; import type { FC } from 'react'; import { useEffect, useState } from 'react'; import { useUIState } from '../contexts/UIStateContext.js'; import { theme } from '../semantic-colors.js'; import { Colors } from '../colors.js'; import { StreamingState } from '../types.js'; import { UpdateNotification } from './UpdateNotification.js'; import type { UpdateObject } from '../utils/updateCheck.js'; import type { HistoryItem } from '../types.js'; import { debugLogger } from '@vybestack/llxprt-code-telemetry'; import { Storage } from '@vybestack/llxprt-code-settings'; import * as fs from 'node:fs/promises'; import path from 'node:path'; import { getBorderStyle } from '../contexts/UnicodeRenderingContext.js'; const settingsPath = Storage.getGlobalSettingsPath(); const screenReaderNudgeFilePath = path.join( Storage.getGlobalTempDir(), 'seen_screen_reader_nudge.json', ); interface NotificationsProps { startupWarnings: string[]; updateInfo: UpdateObject | null; history: HistoryItem[]; } function useScreenReaderNudge( isScreenReaderEnabled: boolean, ): [boolean | undefined, () => void] { const [hasSeenScreenReaderNudge, setHasSeenScreenReaderNudge] = useState< boolean | undefined >(undefined); useEffect(() => { const checkScreenReaderNudge = async () => { try { await fs.access(screenReaderNudgeFilePath); setHasSeenScreenReaderNudge(true); } catch { setHasSeenScreenReaderNudge(false); } }; void checkScreenReaderNudge(); }, []); useEffect(() => { const writeScreenReaderNudgeFile = async () => { if (isScreenReaderEnabled && hasSeenScreenReaderNudge === false) { try { await fs.mkdir(path.dirname(screenReaderNudgeFilePath), { recursive: true, }); await fs.writeFile(screenReaderNudgeFilePath, 'true'); } catch (error) { debugLogger.error('Error storing screen reader nudge', error); } } }; void writeScreenReaderNudgeFile(); }, [isScreenReaderEnabled, hasSeenScreenReaderNudge]); return [hasSeenScreenReaderNudge, () => setHasSeenScreenReaderNudge(true)]; } const ScreenReaderNudge: FC = () => ( You are currently in screen reader-friendly view. To switch out, open{' '} {settingsPath} and remove the entry for {'"screenReader"'}. This will disappear on next run. ); interface StartupWarningsBoxProps { warnings: string[]; } const StartupWarningsBox: FC = ({ warnings }) => ( {warnings.map((warning, index) => ( {warning} ))} ); interface InitErrorBoxProps { initError: string; history: HistoryItem[]; } const InitErrorBox: FC = ({ initError, history }) => { const matchingHistoryError = history.find( (item) => item.type === 'error' && item.text.includes(initError), ); if (matchingHistoryError?.text) { return ( {matchingHistoryError.text} ); } return ( Initialization Error: {initError} {' '} Please check API key and configuration. ); }; export const Notifications: FC = ({ startupWarnings, updateInfo, history, }) => { const { initError, streamingState } = useUIState(); const isScreenReaderEnabled = useIsScreenReaderEnabled(); const [hasSeenScreenReaderNudge] = useScreenReaderNudge( isScreenReaderEnabled, ); const showStartupWarnings = startupWarnings.length > 0; const showInitError = initError != null && streamingState !== StreamingState.Responding; const showScreenReaderNudge = isScreenReaderEnabled && hasSeenScreenReaderNudge === false; if ( !showStartupWarnings && !showInitError && updateInfo == null && !showScreenReaderNudge ) { return null; } return ( <> {showScreenReaderNudge && } {updateInfo && } {showStartupWarnings && } {showInitError && ( )} ); };