/** * @license * SPDX-License-Identifier: Apache-2.0 */ /* tslint:disable */ import React, {useCallback, useEffect, useState} from 'react'; import {GeneratedContent} from './components/GeneratedContent'; import {Icon} from './components/Icon'; import {ParametersPanel} from './components/ParametersPanel'; import {Window} from './components/Window'; import {APP_DEFINITIONS_CONFIG, INITIAL_MAX_HISTORY_LENGTH} from './constants'; import {streamAppContent} from './services/geminiService'; import {AppDefinition, InteractionData} from './types'; const DesktopView: React.FC<{onAppOpen: (app: AppDefinition) => void}> = ({ onAppOpen, }) => (
{APP_DEFINITIONS_CONFIG.map((app) => ( onAppOpen(app)} /> ))}
); const App: React.FC = () => { const [activeApp, setActiveApp] = useState(null); const [previousActiveApp, setPreviousActiveApp] = useState(null); const [llmContent, setLlmContent] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [interactionHistory, setInteractionHistory] = useState< InteractionData[] >([]); const [isParametersOpen, setIsParametersOpen] = useState(false); const [currentMaxHistoryLength, setCurrentMaxHistoryLength] = useState(INITIAL_MAX_HISTORY_LENGTH); // Statefulness feature state const [isStatefulnessEnabled, setIsStatefulnessEnabled] = useState(false); const [appContentCache, setAppContentCache] = useState< Record >({}); const [currentAppPath, setCurrentAppPath] = useState([]); // For UI graph statefulness const internalHandleLlmRequest = useCallback( async (historyForLlm: InteractionData[], maxHistoryLength: number) => { if (historyForLlm.length === 0) { setError('No interaction data to process.'); return; } setIsLoading(true); setError(null); let accumulatedContent = ''; // Clear llmContent before streaming new content only if not loading from cache // This is now handled before this function is called (in handleAppOpen/handleInteraction) // setLlmContent(''); // Removed from here, set by caller if needed try { const stream = streamAppContent(historyForLlm, maxHistoryLength); for await (const chunk of stream) { accumulatedContent += chunk; setLlmContent((prev) => prev + chunk); } } catch (e: any) { setError('Failed to stream content from the API.'); console.error(e); accumulatedContent = `
Error loading content.
`; setLlmContent(accumulatedContent); } finally { setIsLoading(false); // Caching logic is now in useEffect watching llmContent, isLoading, activeApp, currentAppPath etc. } }, [], ); // Effect to cache content when loading finishes and statefulness is enabled useEffect(() => { if ( !isLoading && currentAppPath.length > 0 && isStatefulnessEnabled && llmContent ) { const cacheKey = currentAppPath.join('__'); // Update cache if content is different or not yet cached for this path if (appContentCache[cacheKey] !== llmContent) { setAppContentCache((prevCache) => ({ ...prevCache, [cacheKey]: llmContent, })); } } }, [ llmContent, isLoading, currentAppPath, isStatefulnessEnabled, appContentCache, ]); const handleInteraction = useCallback( async (interactionData: InteractionData) => { if (interactionData.id === 'app_close_button') { // This specific ID might not be generated by LLM handleCloseAppView(); // Use existing close logic return; } const newHistory = [ interactionData, ...interactionHistory.slice(0, currentMaxHistoryLength - 1), ]; setInteractionHistory(newHistory); const newPath = activeApp ? [...currentAppPath, interactionData.id] : [interactionData.id]; setCurrentAppPath(newPath); const cacheKey = newPath.join('__'); setLlmContent(''); setError(null); if (isStatefulnessEnabled && appContentCache[cacheKey]) { setLlmContent(appContentCache[cacheKey]); setIsLoading(false); } else { internalHandleLlmRequest(newHistory, currentMaxHistoryLength); } }, [ interactionHistory, internalHandleLlmRequest, activeApp, currentMaxHistoryLength, currentAppPath, isStatefulnessEnabled, appContentCache, ], ); const handleAppOpen = (app: AppDefinition) => { const initialInteraction: InteractionData = { id: app.id, type: 'app_open', elementText: app.name, elementType: 'icon', appContext: app.id, }; const newHistory = [initialInteraction]; setInteractionHistory(newHistory); const appPath = [app.id]; setCurrentAppPath(appPath); const cacheKey = appPath.join('__'); if (isParametersOpen) { setIsParametersOpen(false); } setActiveApp(app); setLlmContent(''); setError(null); if (isStatefulnessEnabled && appContentCache[cacheKey]) { setLlmContent(appContentCache[cacheKey]); setIsLoading(false); } else { internalHandleLlmRequest(newHistory, currentMaxHistoryLength); } }; const handleCloseAppView = () => { setActiveApp(null); setLlmContent(''); setError(null); setInteractionHistory([]); setCurrentAppPath([]); }; const handleToggleParametersPanel = () => { setIsParametersOpen((prevIsOpen) => { const nowOpeningParameters = !prevIsOpen; if (nowOpeningParameters) { // Store the currently active app (if any) so it can be restored, // or null if no app is active (desktop view). setPreviousActiveApp(activeApp); setActiveApp(null); // Clear active app to show parameters panel setLlmContent(''); setError(null); // Interaction history and current path are not cleared here, // as they might be relevant if the user returns to an app. } else { // Closing parameters panel - always go back to desktop view setPreviousActiveApp(null); // Clear any stored previous app setActiveApp(null); // Ensure desktop view setLlmContent(''); setError(null); setInteractionHistory([]); // Clear history when returning to desktop from parameters setCurrentAppPath([]); // Clear app path } return nowOpeningParameters; }); }; const handleUpdateHistoryLength = (newLength: number) => { setCurrentMaxHistoryLength(newLength); // Trim interaction history if new length is shorter setInteractionHistory((prev) => prev.slice(0, newLength)); }; const handleSetStatefulness = (enabled: boolean) => { setIsStatefulnessEnabled(enabled); if (!enabled) { setAppContentCache({}); } }; const windowTitle = isParametersOpen ? 'Gemini Computer' : activeApp ? activeApp.name : 'Gemini Computer'; const contentBgColor = '#ffffff'; const handleMasterClose = () => { if (isParametersOpen) { handleToggleParametersPanel(); } else if (activeApp) { handleCloseAppView(); } }; return (
{isParametersOpen ? ( ) : !activeApp ? ( ) : ( <> {isLoading && llmContent.length === 0 && (
)} {error && (
{error}
)} {(!isLoading || llmContent) && ( )} )}
); }; export default App;