/**
* @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;