khulnasoft commited on
Commit
2c11877
·
verified ·
1 Parent(s): e3befe8

Create App.tsx

Browse files
Files changed (1) hide show
  1. App.tsx +290 -0
App.tsx ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /* tslint:disable */
6
+ import React, {useCallback, useEffect, useState} from 'react';
7
+ import {GeneratedContent} from './components/GeneratedContent';
8
+ import {Icon} from './components/Icon';
9
+ import {ParametersPanel} from './components/ParametersPanel';
10
+ import {Window} from './components/Window';
11
+ import {APP_DEFINITIONS_CONFIG, INITIAL_MAX_HISTORY_LENGTH} from './constants';
12
+ import {streamAppContent} from './services/geminiService';
13
+ import {AppDefinition, InteractionData} from './types';
14
+
15
+ const DesktopView: React.FC<{onAppOpen: (app: AppDefinition) => void}> = ({
16
+ onAppOpen,
17
+ }) => (
18
+ <div className="flex flex-wrap content-start p-4">
19
+ {APP_DEFINITIONS_CONFIG.map((app) => (
20
+ <Icon key={app.id} app={app} onInteract={() => onAppOpen(app)} />
21
+ ))}
22
+ </div>
23
+ );
24
+
25
+ const App: React.FC = () => {
26
+ const [activeApp, setActiveApp] = useState<AppDefinition | null>(null);
27
+ const [previousActiveApp, setPreviousActiveApp] =
28
+ useState<AppDefinition | null>(null);
29
+ const [llmContent, setLlmContent] = useState<string>('');
30
+ const [isLoading, setIsLoading] = useState<boolean>(false);
31
+ const [error, setError] = useState<string | null>(null);
32
+ const [interactionHistory, setInteractionHistory] = useState<
33
+ InteractionData[]
34
+ >([]);
35
+ const [isParametersOpen, setIsParametersOpen] = useState<boolean>(false);
36
+ const [currentMaxHistoryLength, setCurrentMaxHistoryLength] =
37
+ useState<number>(INITIAL_MAX_HISTORY_LENGTH);
38
+
39
+ // Statefulness feature state
40
+ const [isStatefulnessEnabled, setIsStatefulnessEnabled] =
41
+ useState<boolean>(false);
42
+ const [appContentCache, setAppContentCache] = useState<
43
+ Record<string, string>
44
+ >({});
45
+ const [currentAppPath, setCurrentAppPath] = useState<string[]>([]); // For UI graph statefulness
46
+
47
+ const internalHandleLlmRequest = useCallback(
48
+ async (historyForLlm: InteractionData[], maxHistoryLength: number) => {
49
+ if (historyForLlm.length === 0) {
50
+ setError('No interaction data to process.');
51
+ return;
52
+ }
53
+
54
+ setIsLoading(true);
55
+ setError(null);
56
+
57
+ let accumulatedContent = '';
58
+ // Clear llmContent before streaming new content only if not loading from cache
59
+ // This is now handled before this function is called (in handleAppOpen/handleInteraction)
60
+ // setLlmContent(''); // Removed from here, set by caller if needed
61
+
62
+ try {
63
+ const stream = streamAppContent(historyForLlm, maxHistoryLength);
64
+ for await (const chunk of stream) {
65
+ accumulatedContent += chunk;
66
+ setLlmContent((prev) => prev + chunk);
67
+ }
68
+ } catch (e: any) {
69
+ setError('Failed to stream content from the API.');
70
+ console.error(e);
71
+ accumulatedContent = `<div class="p-4 text-red-600 bg-red-100 rounded-md">Error loading content.</div>`;
72
+ setLlmContent(accumulatedContent);
73
+ } finally {
74
+ setIsLoading(false);
75
+ // Caching logic is now in useEffect watching llmContent, isLoading, activeApp, currentAppPath etc.
76
+ }
77
+ },
78
+ [],
79
+ );
80
+
81
+ // Effect to cache content when loading finishes and statefulness is enabled
82
+ useEffect(() => {
83
+ if (
84
+ !isLoading &&
85
+ currentAppPath.length > 0 &&
86
+ isStatefulnessEnabled &&
87
+ llmContent
88
+ ) {
89
+ const cacheKey = currentAppPath.join('__');
90
+ // Update cache if content is different or not yet cached for this path
91
+ if (appContentCache[cacheKey] !== llmContent) {
92
+ setAppContentCache((prevCache) => ({
93
+ ...prevCache,
94
+ [cacheKey]: llmContent,
95
+ }));
96
+ }
97
+ }
98
+ }, [
99
+ llmContent,
100
+ isLoading,
101
+ currentAppPath,
102
+ isStatefulnessEnabled,
103
+ appContentCache,
104
+ ]);
105
+
106
+ const handleInteraction = useCallback(
107
+ async (interactionData: InteractionData) => {
108
+ if (interactionData.id === 'app_close_button') {
109
+ // This specific ID might not be generated by LLM
110
+ handleCloseAppView(); // Use existing close logic
111
+ return;
112
+ }
113
+
114
+ const newHistory = [
115
+ interactionData,
116
+ ...interactionHistory.slice(0, currentMaxHistoryLength - 1),
117
+ ];
118
+ setInteractionHistory(newHistory);
119
+
120
+ const newPath = activeApp
121
+ ? [...currentAppPath, interactionData.id]
122
+ : [interactionData.id];
123
+ setCurrentAppPath(newPath);
124
+ const cacheKey = newPath.join('__');
125
+
126
+ setLlmContent('');
127
+ setError(null);
128
+
129
+ if (isStatefulnessEnabled && appContentCache[cacheKey]) {
130
+ setLlmContent(appContentCache[cacheKey]);
131
+ setIsLoading(false);
132
+ } else {
133
+ internalHandleLlmRequest(newHistory, currentMaxHistoryLength);
134
+ }
135
+ },
136
+ [
137
+ interactionHistory,
138
+ internalHandleLlmRequest,
139
+ activeApp,
140
+ currentMaxHistoryLength,
141
+ currentAppPath,
142
+ isStatefulnessEnabled,
143
+ appContentCache,
144
+ ],
145
+ );
146
+
147
+ const handleAppOpen = (app: AppDefinition) => {
148
+ const initialInteraction: InteractionData = {
149
+ id: app.id,
150
+ type: 'app_open',
151
+ elementText: app.name,
152
+ elementType: 'icon',
153
+ appContext: app.id,
154
+ };
155
+
156
+ const newHistory = [initialInteraction];
157
+ setInteractionHistory(newHistory);
158
+
159
+ const appPath = [app.id];
160
+ setCurrentAppPath(appPath);
161
+ const cacheKey = appPath.join('__');
162
+
163
+ if (isParametersOpen) {
164
+ setIsParametersOpen(false);
165
+ }
166
+ setActiveApp(app);
167
+ setLlmContent('');
168
+ setError(null);
169
+
170
+ if (isStatefulnessEnabled && appContentCache[cacheKey]) {
171
+ setLlmContent(appContentCache[cacheKey]);
172
+ setIsLoading(false);
173
+ } else {
174
+ internalHandleLlmRequest(newHistory, currentMaxHistoryLength);
175
+ }
176
+ };
177
+
178
+ const handleCloseAppView = () => {
179
+ setActiveApp(null);
180
+ setLlmContent('');
181
+ setError(null);
182
+ setInteractionHistory([]);
183
+ setCurrentAppPath([]);
184
+ };
185
+
186
+ const handleToggleParametersPanel = () => {
187
+ setIsParametersOpen((prevIsOpen) => {
188
+ const nowOpeningParameters = !prevIsOpen;
189
+ if (nowOpeningParameters) {
190
+ // Store the currently active app (if any) so it can be restored,
191
+ // or null if no app is active (desktop view).
192
+ setPreviousActiveApp(activeApp);
193
+ setActiveApp(null); // Clear active app to show parameters panel
194
+ setLlmContent('');
195
+ setError(null);
196
+ // Interaction history and current path are not cleared here,
197
+ // as they might be relevant if the user returns to an app.
198
+ } else {
199
+ // Closing parameters panel - always go back to desktop view
200
+ setPreviousActiveApp(null); // Clear any stored previous app
201
+ setActiveApp(null); // Ensure desktop view
202
+ setLlmContent('');
203
+ setError(null);
204
+ setInteractionHistory([]); // Clear history when returning to desktop from parameters
205
+ setCurrentAppPath([]); // Clear app path
206
+ }
207
+ return nowOpeningParameters;
208
+ });
209
+ };
210
+
211
+ const handleUpdateHistoryLength = (newLength: number) => {
212
+ setCurrentMaxHistoryLength(newLength);
213
+ // Trim interaction history if new length is shorter
214
+ setInteractionHistory((prev) => prev.slice(0, newLength));
215
+ };
216
+
217
+ const handleSetStatefulness = (enabled: boolean) => {
218
+ setIsStatefulnessEnabled(enabled);
219
+ if (!enabled) {
220
+ setAppContentCache({});
221
+ }
222
+ };
223
+
224
+ const windowTitle = isParametersOpen
225
+ ? 'Gemini Computer'
226
+ : activeApp
227
+ ? activeApp.name
228
+ : 'Gemini Computer';
229
+ const contentBgColor = '#ffffff';
230
+
231
+ const handleMasterClose = () => {
232
+ if (isParametersOpen) {
233
+ handleToggleParametersPanel();
234
+ } else if (activeApp) {
235
+ handleCloseAppView();
236
+ }
237
+ };
238
+
239
+ return (
240
+ <div className="bg-white w-full min-h-screen flex items-center justify-center p-4">
241
+ <Window
242
+ title={windowTitle}
243
+ onClose={handleMasterClose}
244
+ isAppOpen={!!activeApp && !isParametersOpen}
245
+ appId={activeApp?.id}
246
+ onToggleParameters={handleToggleParametersPanel}
247
+ onExitToDesktop={handleCloseAppView}
248
+ isParametersPanelOpen={isParametersOpen}>
249
+ <div
250
+ className="w-full h-full"
251
+ style={{backgroundColor: contentBgColor}}>
252
+ {isParametersOpen ? (
253
+ <ParametersPanel
254
+ currentLength={currentMaxHistoryLength}
255
+ onUpdateHistoryLength={handleUpdateHistoryLength}
256
+ onClosePanel={handleToggleParametersPanel}
257
+ isStatefulnessEnabled={isStatefulnessEnabled}
258
+ onSetStatefulness={handleSetStatefulness}
259
+ />
260
+ ) : !activeApp ? (
261
+ <DesktopView onAppOpen={handleAppOpen} />
262
+ ) : (
263
+ <>
264
+ {isLoading && llmContent.length === 0 && (
265
+ <div className="flex justify-center items-center h-full">
266
+ <div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
267
+ </div>
268
+ )}
269
+ {error && (
270
+ <div className="p-4 text-red-600 bg-red-100 rounded-md">
271
+ {error}
272
+ </div>
273
+ )}
274
+ {(!isLoading || llmContent) && (
275
+ <GeneratedContent
276
+ htmlContent={llmContent}
277
+ onInteract={handleInteraction}
278
+ appContext={activeApp.id}
279
+ isLoading={isLoading}
280
+ />
281
+ )}
282
+ </>
283
+ )}
284
+ </div>
285
+ </Window>
286
+ </div>
287
+ );
288
+ };
289
+
290
+ export default App;