Download packages/cli/src/ui/hooks/useConsoleMessages.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 4.74 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/useConsoleMessages.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/ui/hooks/useConsoleMessages.ts
-
curl -L -o useConsoleMessages.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/useConsoleMessages.ts
4.74 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { useCallback, useSyncExternalStore } from 'react'; | |
| import type { ConsoleMessageItem } from '../types.js'; | |
| import { | |
| coreEvents, | |
| CoreEvent, | |
| type ConsoleLogPayload, | |
| } from '@google/gemini-cli-core'; | |
| export interface UseErrorCountReturn { | |
| errorCount: number; | |
| clearErrorCount: () => void; | |
| } | |
| // --- Global Console Store --- | |
| const MAX_CONSOLE_MESSAGES = 1000; | |
| let globalConsoleMessages: ConsoleMessageItem[] = []; | |
| let globalErrorCount = 0; | |
| const listeners = new Set<() => void>(); | |
| let messageQueue: ConsoleMessageItem[] = []; | |
| let timeoutId: NodeJS.Timeout | null = null; | |
| /** | |
| * Initializes the global console store and subscribes to coreEvents. | |
| * Acts as a safe reset function, making it idempotent and useful for test isolation. | |
| * Must be called during application startup. | |
| */ | |
| export function initializeConsoleStore() { | |
| if (timeoutId) { | |
| clearTimeout(timeoutId); | |
| timeoutId = null; | |
| } | |
| messageQueue = []; | |
| globalConsoleMessages = []; | |
| globalErrorCount = 0; | |
| notifyListeners(); | |
| // Safely detach first to ensure idempotency and prevent listener leaks | |
| coreEvents.off(CoreEvent.ConsoleLog, handleConsoleLog); | |
| coreEvents.off(CoreEvent.Output, handleOutput); | |
| coreEvents.on(CoreEvent.ConsoleLog, handleConsoleLog); | |
| coreEvents.on(CoreEvent.Output, handleOutput); | |
| } | |
| function notifyListeners() { | |
| for (const listener of listeners) { | |
| listener(); | |
| } | |
| } | |
| function processQueue() { | |
| if (messageQueue.length === 0) return; | |
| // Create a new array to trigger React updates | |
| const newMessages = [...globalConsoleMessages]; | |
| for (const queuedMessage of messageQueue) { | |
| if (queuedMessage.type === 'error') { | |
| globalErrorCount++; | |
| } | |
| // Coalesce consecutive identical messages | |
| const prev = newMessages[newMessages.length - 1]; | |
| if ( | |
| prev && | |
| prev.type === queuedMessage.type && | |
| prev.content === queuedMessage.content | |
| ) { | |
| newMessages[newMessages.length - 1] = { | |
| ...prev, | |
| count: prev.count + 1, | |
| }; | |
| } else { | |
| newMessages.push({ ...queuedMessage, count: 1 }); | |
| } | |
| } | |
| globalConsoleMessages = | |
| newMessages.length > MAX_CONSOLE_MESSAGES | |
| ? newMessages.slice(-MAX_CONSOLE_MESSAGES) | |
| : newMessages; | |
| messageQueue = []; | |
| timeoutId = null; | |
| notifyListeners(); | |
| } | |
| function handleNewMessage(message: ConsoleMessageItem) { | |
| messageQueue.push(message); | |
| if (!timeoutId) { | |
| // Batch updates using a timeout. 50ms is a reasonable delay to batch | |
| // rapid-fire messages without noticeable lag while avoiding React update | |
| // queue flooding. | |
| timeoutId = setTimeout(processQueue, 50); | |
| } | |
| } | |
| // --- Subscription API for useSyncExternalStore --- | |
| function subscribe(listener: () => void) { | |
| listeners.add(listener); | |
| return () => { | |
| listeners.delete(listener); | |
| }; | |
| } | |
| function getConsoleMessagesSnapshot() { | |
| return globalConsoleMessages; | |
| } | |
| function getErrorCountSnapshot() { | |
| return globalErrorCount; | |
| } | |
| // --- Core Event Listeners (Always active at module level) --- | |
| const handleConsoleLog = (payload: ConsoleLogPayload) => { | |
| let content = payload.content; | |
| const MAX_CONSOLE_MSG_LENGTH = 10000; | |
| if (content.length > MAX_CONSOLE_MSG_LENGTH) { | |
| content = | |
| content.slice(0, MAX_CONSOLE_MSG_LENGTH) + | |
| `... [Truncated ${content.length - MAX_CONSOLE_MSG_LENGTH} characters]`; | |
| } | |
| handleNewMessage({ | |
| type: payload.type, | |
| content, | |
| count: 1, | |
| }); | |
| }; | |
| const handleOutput = (payload: { | |
| isStderr: boolean; | |
| chunk: Uint8Array | string; | |
| }) => { | |
| let content = | |
| typeof payload.chunk === 'string' | |
| ? payload.chunk | |
| : new TextDecoder().decode(payload.chunk); | |
| const MAX_OUTPUT_CHUNK_LENGTH = 10000; | |
| if (content.length > MAX_OUTPUT_CHUNK_LENGTH) { | |
| content = | |
| content.slice(0, MAX_OUTPUT_CHUNK_LENGTH) + | |
| `... [Truncated ${content.length - MAX_OUTPUT_CHUNK_LENGTH} characters]`; | |
| } | |
| handleNewMessage({ type: 'log', content, count: 1 }); | |
| }; | |
| /** | |
| * Hook to access the global console message history. | |
| * Decoupled from any component lifecycle to ensure history is preserved even | |
| * when the UI is unmounted. | |
| */ | |
| export function useConsoleMessages(): ConsoleMessageItem[] { | |
| return useSyncExternalStore(subscribe, getConsoleMessagesSnapshot); | |
| } | |
| /** | |
| * Hook to access the global error count. | |
| * Uses the same external store as useConsoleMessages for consistency. | |
| */ | |
| export function useErrorCount(): UseErrorCountReturn { | |
| const errorCount = useSyncExternalStore(subscribe, getErrorCountSnapshot); | |
| const clearErrorCount = useCallback(() => { | |
| globalErrorCount = 0; | |
| notifyListeners(); | |
| }, []); | |
| return { errorCount, clearErrorCount }; | |
| } | |