agent-starter-pack 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/METADATA +2 -1
  2. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/RECORD +51 -78
  3. agents/adk_base/app/__init__.py +17 -0
  4. agents/adk_base/notebooks/adk_app_testing.ipynb +4 -1
  5. agents/adk_base/tests/integration/test_agent.py +1 -1
  6. agents/agentic_rag/app/__init__.py +17 -0
  7. agents/agentic_rag/app/agent.py +2 -2
  8. agents/agentic_rag/notebooks/adk_app_testing.ipynb +4 -1
  9. agents/agentic_rag/tests/integration/test_agent.py +2 -2
  10. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  11. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  12. agents/live_api/tests/unit/test_server.py +6 -6
  13. llm.txt +15 -4
  14. src/base_template/Makefile +5 -5
  15. src/base_template/README.md +4 -4
  16. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +2 -2
  17. src/base_template/pyproject.toml +2 -2
  18. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -1
  19. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
  20. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +2 -2
  21. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -1
  22. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -1
  23. src/cli/commands/create.py +30 -2
  24. src/cli/commands/enhance.py +98 -15
  25. src/cli/commands/list.py +6 -1
  26. src/cli/utils/remote_template.py +5 -1
  27. src/cli/utils/template.py +120 -41
  28. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +3 -3
  29. src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py +10 -10
  30. src/deployment_targets/cloud_run/Dockerfile +2 -2
  31. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +3 -3
  32. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  33. src/deployment_targets/cloud_run/tests/load_test/load_test.py +2 -2
  34. {agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py +186 -7
  35. src/frontends/live_api_react/frontend/package-lock.json +9 -9
  36. src/resources/docs/adk-cheatsheet.md +3 -3
  37. src/resources/locks/uv-adk_base-agent_engine.lock +452 -452
  38. src/resources/locks/uv-adk_base-cloud_run.lock +571 -568
  39. src/resources/locks/uv-agentic_rag-agent_engine.lock +565 -566
  40. src/resources/locks/uv-agentic_rag-cloud_run.lock +716 -713
  41. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +729 -735
  42. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +923 -940
  43. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +658 -664
  44. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +852 -869
  45. src/resources/locks/uv-live_api-cloud_run.lock +758 -775
  46. src/base_template/app/__init__.py +0 -3
  47. src/deployment_targets/cloud_run/app/server.py +0 -206
  48. src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
  49. src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
  50. src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
  51. src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
  52. src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
  53. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
  54. src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
  55. src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
  56. src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
  57. src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
  58. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
  59. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
  60. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
  61. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
  62. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
  63. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
  64. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
  65. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
  66. src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
  67. src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
  68. src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
  69. src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
  70. src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
  71. src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
  72. src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
  73. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +0 -3938
  74. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +0 -4501
  75. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/WHEEL +0 -0
  76. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/entry_points.txt +0 -0
  77. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/licenses/LICENSE +0 -0
  78. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
  79. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
  80. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
@@ -1,47 +0,0 @@
1
- {
2
- "name": "frontend",
3
- "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "tsc -b && vite build",
9
- "lint": "eslint .",
10
- "preview": "vite preview"
11
- },
12
- "dependencies": {
13
- "@langchain/core": "^0.3.55",
14
- "@langchain/langgraph-sdk": "^0.0.74",
15
- "@radix-ui/react-scroll-area": "^1.2.8",
16
- "@radix-ui/react-select": "^2.2.4",
17
- "@radix-ui/react-slot": "^1.2.2",
18
- "@radix-ui/react-tabs": "^1.1.11",
19
- "@radix-ui/react-tooltip": "^1.2.6",
20
- "@tailwindcss/vite": "^4.1.5",
21
- "class-variance-authority": "^0.7.1",
22
- "clsx": "^2.1.1",
23
- "lucide-react": "^0.508.0",
24
- "react": "^19.0.0",
25
- "react-dom": "^19.0.0",
26
- "react-markdown": "^9.0.3",
27
- "react-router-dom": "^7.5.3",
28
- "remark-gfm": "^4.0.1",
29
- "tailwind-merge": "^3.2.0",
30
- "tailwindcss": "^4.1.5"
31
- },
32
- "devDependencies": {
33
- "@eslint/js": "^9.22.0",
34
- "@types/node": "^22.15.17",
35
- "@types/react": "^19.1.2",
36
- "@types/react-dom": "^19.1.3",
37
- "@vitejs/plugin-react-swc": "^3.9.0",
38
- "eslint": "^9.22.0",
39
- "eslint-plugin-react-hooks": "^5.2.0",
40
- "eslint-plugin-react-refresh": "^0.4.19",
41
- "globals": "^16.0.0",
42
- "tw-animate-css": "^1.2.9",
43
- "typescript": "~5.7.2",
44
- "typescript-eslint": "^8.26.1",
45
- "vite": "^6.3.4"
46
- }
47
- }
@@ -1,564 +0,0 @@
1
- import { useState, useRef, useCallback, useEffect } from "react";
2
- import { v4 as uuidv4 } from 'uuid';
3
- import { WelcomeScreen } from "@/components/WelcomeScreen";
4
- import { ChatMessagesView } from "@/components/ChatMessagesView";
5
-
6
- // Update DisplayData to be a string type
7
- type DisplayData = string | null;
8
- interface MessageWithAgent {
9
- type: "human" | "ai";
10
- content: string;
11
- id: string;
12
- agent?: string;
13
- finalReportWithCitations?: boolean;
14
- }
15
-
16
- interface AgentMessage {
17
- parts: { text: string }[];
18
- role: string;
19
- }
20
-
21
- interface AgentResponse {
22
- content: AgentMessage;
23
- usageMetadata: {
24
- candidatesTokenCount: number;
25
- promptTokenCount: number;
26
- totalTokenCount: number;
27
- };
28
- author: string;
29
- actions: {
30
- stateDelta: {
31
- research_plan?: string;
32
- final_report_with_citations?: boolean;
33
- };
34
- };
35
- }
36
-
37
- interface ProcessedEvent {
38
- title: string;
39
- data: any;
40
- }
41
-
42
- export default function App() {
43
- const [userId, setUserId] = useState<string | null>(null);
44
- const [sessionId, setSessionId] = useState<string | null>(null);
45
- const [appName, setAppName] = useState<string | null>(null);
46
- const [messages, setMessages] = useState<MessageWithAgent[]>([]);
47
- const [displayData, setDisplayData] = useState<DisplayData | null>(null);
48
- const [isLoading, setIsLoading] = useState(false);
49
- const [messageEvents, setMessageEvents] = useState<Map<string, ProcessedEvent[]>>(new Map());
50
- const [websiteCount, setWebsiteCount] = useState<number>(0);
51
- const [isBackendReady, setIsBackendReady] = useState(false);
52
- const [isCheckingBackend, setIsCheckingBackend] = useState(true);
53
- const currentAgentRef = useRef('');
54
- const accumulatedTextRef = useRef("");
55
- const scrollAreaRef = useRef<HTMLDivElement>(null);
56
-
57
- const retryWithBackoff = async (
58
- fn: () => Promise<any>,
59
- maxRetries: number = 10,
60
- maxDuration: number = 120000 // 2 minutes
61
- ): Promise<any> => {
62
- const startTime = Date.now();
63
- let lastError: Error;
64
-
65
- for (let attempt = 0; attempt < maxRetries; attempt++) {
66
- if (Date.now() - startTime > maxDuration) {
67
- throw new Error(`Retry timeout after ${maxDuration}ms`);
68
- }
69
-
70
- try {
71
- return await fn();
72
- } catch (error) {
73
- lastError = error as Error;
74
- const delay = Math.min(1000 * Math.pow(2, attempt), 5000); // Exponential backoff, max 5s
75
- console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error);
76
- await new Promise(resolve => setTimeout(resolve, delay));
77
- }
78
- }
79
-
80
- throw lastError!;
81
- };
82
-
83
- const createSession = async (): Promise<{userId: string, sessionId: string, appName: string}> => {
84
- const response = await fetch(`/api/apps/app/users/u_999/sessions`, {
85
- method: "POST",
86
- headers: {
87
- "Content-Type": "application/json"
88
- },
89
- });
90
-
91
- if (!response.ok) {
92
- throw new Error(`Failed to create session: ${response.status} ${response.statusText}`);
93
- }
94
-
95
- const data = await response.json();
96
- return {
97
- userId: data.userId,
98
- sessionId: data.id,
99
- appName: data.appName
100
- };
101
- };
102
-
103
- const checkBackendHealth = async (): Promise<boolean> => {
104
- try {
105
- // Use the docs endpoint or root endpoint to check if backend is ready
106
- const response = await fetch("/api/docs", {
107
- method: "GET",
108
- headers: {
109
- "Content-Type": "application/json"
110
- }
111
- });
112
- return response.ok;
113
- } catch (error) {
114
- console.log("Backend not ready yet:", error);
115
- return false;
116
- }
117
- };
118
-
119
- // Function to extract text and metadata from SSE data
120
- const extractDataFromSSE = (data: string) => {
121
- try {
122
- const parsed = JSON.parse(data);
123
- console.log('[SSE PARSED EVENT]:', JSON.stringify(parsed, null, 2)); // DEBUG: Log parsed event
124
-
125
- let textParts: string[] = [];
126
- let agent = '';
127
- let finalReportWithCitations = undefined;
128
- let functionCall = null;
129
- let functionResponse = null;
130
- let sources = null;
131
-
132
- // Check if content.parts exists and has text
133
- if (parsed.content && parsed.content.parts) {
134
- textParts = parsed.content.parts
135
- .filter((part: any) => part.text)
136
- .map((part: any) => part.text);
137
-
138
- // Check for function calls
139
- const functionCallPart = parsed.content.parts.find((part: any) => part.functionCall);
140
- if (functionCallPart) {
141
- functionCall = functionCallPart.functionCall;
142
- }
143
-
144
- // Check for function responses
145
- const functionResponsePart = parsed.content.parts.find((part: any) => part.functionResponse);
146
- if (functionResponsePart) {
147
- functionResponse = functionResponsePart.functionResponse;
148
- }
149
- }
150
-
151
- // Extract agent information
152
- if (parsed.author) {
153
- agent = parsed.author;
154
- console.log('[SSE EXTRACT] Agent:', agent); // DEBUG: Log agent
155
- }
156
-
157
- if (
158
- parsed.actions &&
159
- parsed.actions.stateDelta &&
160
- parsed.actions.stateDelta.final_report_with_citations
161
- ) {
162
- finalReportWithCitations = parsed.actions.stateDelta.final_report_with_citations;
163
- }
164
-
165
- // Extract website count from research agents
166
- let sourceCount = 0;
167
- if ((parsed.author === 'section_researcher' || parsed.author === 'enhanced_search_executor')) {
168
- console.log('[SSE EXTRACT] Relevant agent for source count:', parsed.author); // DEBUG
169
- if (parsed.actions?.stateDelta?.url_to_short_id) {
170
- console.log('[SSE EXTRACT] url_to_short_id found:', parsed.actions.stateDelta.url_to_short_id); // DEBUG
171
- sourceCount = Object.keys(parsed.actions.stateDelta.url_to_short_id).length;
172
- console.log('[SSE EXTRACT] Calculated sourceCount:', sourceCount); // DEBUG
173
- } else {
174
- console.log('[SSE EXTRACT] url_to_short_id NOT found for agent:', parsed.author); // DEBUG
175
- }
176
- }
177
-
178
- // Extract sources if available
179
- if (parsed.actions?.stateDelta?.sources) {
180
- sources = parsed.actions.stateDelta.sources;
181
- console.log('[SSE EXTRACT] Sources found:', sources); // DEBUG
182
- }
183
-
184
-
185
- return { textParts, agent, finalReportWithCitations, functionCall, functionResponse, sourceCount, sources };
186
- } catch (error) {
187
- // Log the error and a truncated version of the problematic data for easier debugging.
188
- const truncatedData = data.length > 200 ? data.substring(0, 200) + "..." : data;
189
- console.error('Error parsing SSE data. Raw data (truncated): "', truncatedData, '". Error details:', error);
190
- return { textParts: [], agent: '', finalReportWithCitations: undefined, functionCall: null, functionResponse: null, sourceCount: 0, sources: null };
191
- }
192
- };
193
-
194
- // Define getEventTitle here or ensure it's in scope from where it's used
195
- const getEventTitle = (agentName: string): string => {
196
- switch (agentName) {
197
- case "plan_generator":
198
- return "Planning Research Strategy";
199
- case "section_planner":
200
- return "Structuring Report Outline";
201
- case "section_researcher":
202
- return "Initial Web Research";
203
- case "research_evaluator":
204
- return "Evaluating Research Quality";
205
- case "EscalationChecker":
206
- return "Quality Assessment";
207
- case "enhanced_search_executor":
208
- return "Enhanced Web Research";
209
- case "research_pipeline":
210
- return "Executing Research Pipeline";
211
- case "iterative_refinement_loop":
212
- return "Refining Research";
213
- case "interactive_planner_agent":
214
- case "root_agent":
215
- return "Interactive Planning";
216
- default:
217
- return `Processing (${agentName || 'Unknown Agent'})`;
218
- }
219
- };
220
-
221
- const processSseEventData = (jsonData: string, aiMessageId: string) => {
222
- const { textParts, agent, finalReportWithCitations, functionCall, functionResponse, sourceCount, sources } = extractDataFromSSE(jsonData);
223
-
224
- if (sourceCount > 0) {
225
- console.log('[SSE HANDLER] Updating websiteCount. Current sourceCount:', sourceCount);
226
- setWebsiteCount(prev => Math.max(prev, sourceCount));
227
- }
228
-
229
- if (agent && agent !== currentAgentRef.current) {
230
- currentAgentRef.current = agent;
231
- }
232
-
233
- if (functionCall) {
234
- const functionCallTitle = `Function Call: ${functionCall.name}`;
235
- console.log('[SSE HANDLER] Adding Function Call timeline event:', functionCallTitle);
236
- setMessageEvents(prev => new Map(prev).set(aiMessageId, [...(prev.get(aiMessageId) || []), {
237
- title: functionCallTitle,
238
- data: { type: 'functionCall', name: functionCall.name, args: functionCall.args, id: functionCall.id }
239
- }]));
240
- }
241
-
242
- if (functionResponse) {
243
- const functionResponseTitle = `Function Response: ${functionResponse.name}`;
244
- console.log('[SSE HANDLER] Adding Function Response timeline event:', functionResponseTitle);
245
- setMessageEvents(prev => new Map(prev).set(aiMessageId, [...(prev.get(aiMessageId) || []), {
246
- title: functionResponseTitle,
247
- data: { type: 'functionResponse', name: functionResponse.name, response: functionResponse.response, id: functionResponse.id }
248
- }]));
249
- }
250
-
251
- if (textParts.length > 0 && agent !== "report_composer_with_citations") {
252
- if (agent !== "interactive_planner_agent") {
253
- const eventTitle = getEventTitle(agent);
254
- console.log('[SSE HANDLER] Adding Text timeline event for agent:', agent, 'Title:', eventTitle, 'Data:', textParts.join(" "));
255
- setMessageEvents(prev => new Map(prev).set(aiMessageId, [...(prev.get(aiMessageId) || []), {
256
- title: eventTitle,
257
- data: { type: 'text', content: textParts.join(" ") }
258
- }]));
259
- } else { // interactive_planner_agent text updates the main AI message
260
- for (const text of textParts) {
261
- accumulatedTextRef.current += text + " ";
262
- setMessages(prev => prev.map(msg =>
263
- msg.id === aiMessageId ? { ...msg, content: accumulatedTextRef.current.trim(), agent: currentAgentRef.current || msg.agent } : msg
264
- ));
265
- setDisplayData(accumulatedTextRef.current.trim());
266
- }
267
- }
268
- }
269
-
270
- if (sources) {
271
- console.log('[SSE HANDLER] Adding Retrieved Sources timeline event:', sources);
272
- setMessageEvents(prev => new Map(prev).set(aiMessageId, [...(prev.get(aiMessageId) || []), {
273
- title: "Retrieved Sources", data: { type: 'sources', content: sources }
274
- }]));
275
- }
276
-
277
- if (agent === "report_composer_with_citations" && finalReportWithCitations) {
278
- const finalReportMessageId = Date.now().toString() + "_final";
279
- setMessages(prev => [...prev, { type: "ai", content: finalReportWithCitations as string, id: finalReportMessageId, agent: currentAgentRef.current, finalReportWithCitations: true }]);
280
- setDisplayData(finalReportWithCitations as string);
281
- }
282
- };
283
-
284
- const handleSubmit = useCallback(async (query: string, model: string, effort: string) => {
285
- if (!query.trim()) return;
286
-
287
- setIsLoading(true);
288
- try {
289
- // Create session if it doesn't exist
290
- let currentUserId = userId;
291
- let currentSessionId = sessionId;
292
- let currentAppName = appName;
293
-
294
- if (!currentSessionId || !currentUserId || !currentAppName) {
295
- console.log('Creating new session...');
296
- const sessionData = await retryWithBackoff(createSession);
297
- currentUserId = sessionData.userId;
298
- currentSessionId = sessionData.sessionId;
299
- currentAppName = sessionData.appName;
300
-
301
- setUserId(currentUserId);
302
- setSessionId(currentSessionId);
303
- setAppName(currentAppName);
304
- console.log('Session created successfully:', { currentUserId, currentSessionId, currentAppName });
305
- }
306
-
307
- // Add user message to chat
308
- const userMessageId = Date.now().toString();
309
- setMessages(prev => [...prev, { type: "human", content: query, id: userMessageId }]);
310
-
311
- // Create AI message placeholder
312
- const aiMessageId = Date.now().toString() + "_ai";
313
- currentAgentRef.current = ''; // Reset current agent
314
- accumulatedTextRef.current = ''; // Reset accumulated text
315
-
316
- setMessages(prev => [...prev, {
317
- type: "ai",
318
- content: "",
319
- id: aiMessageId,
320
- agent: '',
321
- }]);
322
-
323
- // Send the message with retry logic
324
- const sendMessage = async () => {
325
- const response = await fetch("/api/run_sse", {
326
- method: "POST",
327
- headers: {
328
- "Content-Type": "application/json",
329
- },
330
- body: JSON.stringify({
331
- appName: currentAppName,
332
- userId: currentUserId,
333
- sessionId: currentSessionId,
334
- newMessage: {
335
- parts: [{ text: query }],
336
- role: "user"
337
- },
338
- streaming: false
339
- }),
340
- });
341
-
342
- if (!response.ok) {
343
- throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
344
- }
345
-
346
- return response;
347
- };
348
-
349
- const response = await retryWithBackoff(sendMessage);
350
-
351
- // Handle SSE streaming
352
- const reader = response.body?.getReader();
353
- const decoder = new TextDecoder();
354
- let lineBuffer = "";
355
- let eventDataBuffer = "";
356
-
357
- if (reader) {
358
- // eslint-disable-next-line no-constant-condition
359
- while (true) {
360
- const { done, value } = await reader.read();
361
-
362
- if (value) {
363
- lineBuffer += decoder.decode(value, { stream: true });
364
- }
365
-
366
- let eolIndex;
367
- // Process all complete lines in the buffer, or the remaining buffer if 'done'
368
- while ((eolIndex = lineBuffer.indexOf('\n')) >= 0 || (done && lineBuffer.length > 0)) {
369
- let line: string;
370
- if (eolIndex >= 0) {
371
- line = lineBuffer.substring(0, eolIndex);
372
- lineBuffer = lineBuffer.substring(eolIndex + 1);
373
- } else { // Only if done and lineBuffer has content without a trailing newline
374
- line = lineBuffer;
375
- lineBuffer = "";
376
- }
377
-
378
- if (line.trim() === "") { // Empty line: dispatch event
379
- if (eventDataBuffer.length > 0) {
380
- // Remove trailing newline before parsing
381
- const jsonDataToParse = eventDataBuffer.endsWith('\n') ? eventDataBuffer.slice(0, -1) : eventDataBuffer;
382
- console.log('[SSE DISPATCH EVENT]:', jsonDataToParse.substring(0, 200) + "..."); // DEBUG
383
- processSseEventData(jsonDataToParse, aiMessageId);
384
- eventDataBuffer = ""; // Reset for next event
385
- }
386
- } else if (line.startsWith('data:')) {
387
- eventDataBuffer += line.substring(5).trimStart() + '\n'; // Add newline as per spec for multi-line data
388
- } else if (line.startsWith(':')) {
389
- // Comment line, ignore
390
- } // Other SSE fields (event, id, retry) can be handled here if needed
391
- }
392
-
393
- if (done) {
394
- // If the loop exited due to 'done', and there's still data in eventDataBuffer
395
- // (e.g., stream ended after data lines but before an empty line delimiter)
396
- if (eventDataBuffer.length > 0) {
397
- const jsonDataToParse = eventDataBuffer.endsWith('\n') ? eventDataBuffer.slice(0, -1) : eventDataBuffer;
398
- console.log('[SSE DISPATCH FINAL EVENT]:', jsonDataToParse.substring(0,200) + "..."); // DEBUG
399
- processSseEventData(jsonDataToParse, aiMessageId);
400
- eventDataBuffer = ""; // Clear buffer
401
- }
402
- break; // Exit the while(true) loop
403
- }
404
- }
405
- }
406
-
407
- setIsLoading(false);
408
-
409
- } catch (error) {
410
- console.error("Error:", error);
411
- // Update the AI message placeholder with an error message
412
- const aiMessageId = Date.now().toString() + "_ai_error";
413
- setMessages(prev => [...prev, {
414
- type: "ai",
415
- content: `Sorry, there was an error processing your request: ${error instanceof Error ? error.message : 'Unknown error'}`,
416
- id: aiMessageId
417
- }]);
418
- setIsLoading(false);
419
- }
420
- }, [processSseEventData]);
421
-
422
- useEffect(() => {
423
- if (scrollAreaRef.current) {
424
- const scrollViewport = scrollAreaRef.current.querySelector(
425
- "[data-radix-scroll-area-viewport]"
426
- );
427
- if (scrollViewport) {
428
- scrollViewport.scrollTop = scrollViewport.scrollHeight;
429
- }
430
- }
431
- }, [messages]);
432
-
433
- useEffect(() => {
434
- const checkBackend = async () => {
435
- setIsCheckingBackend(true);
436
-
437
- // Check if backend is ready with retry logic
438
- const maxAttempts = 60; // 2 minutes with 2-second intervals
439
- let attempts = 0;
440
-
441
- while (attempts < maxAttempts) {
442
- const isReady = await checkBackendHealth();
443
- if (isReady) {
444
- setIsBackendReady(true);
445
- setIsCheckingBackend(false);
446
- return;
447
- }
448
-
449
- attempts++;
450
- await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds between checks
451
- }
452
-
453
- // If we get here, backend didn't come up in time
454
- setIsCheckingBackend(false);
455
- console.error("Backend failed to start within 2 minutes");
456
- };
457
-
458
- checkBackend();
459
- }, []);
460
-
461
- const handleCancel = useCallback(() => {
462
- setMessages([]);
463
- setDisplayData(null);
464
- setMessageEvents(new Map());
465
- setWebsiteCount(0);
466
- window.location.reload();
467
- }, []);
468
-
469
- // Scroll to bottom when messages update
470
- const scrollToBottom = useCallback(() => {
471
- if (scrollAreaRef.current) {
472
- const scrollViewport = scrollAreaRef.current.querySelector(
473
- "[data-radix-scroll-area-viewport]"
474
- );
475
- if (scrollViewport) {
476
- scrollViewport.scrollTop = scrollViewport.scrollHeight;
477
- }
478
- }
479
- }, []);
480
-
481
- const BackendLoadingScreen = () => (
482
- <div className="flex-1 flex flex-col items-center justify-center p-4 overflow-hidden relative">
483
- <div className="w-full max-w-2xl z-10
484
- bg-neutral-900/50 backdrop-blur-md
485
- p-8 rounded-2xl border border-neutral-700
486
- shadow-2xl shadow-black/60">
487
-
488
- <div className="text-center space-y-6">
489
- <h1 className="text-4xl font-bold text-white flex items-center justify-center gap-3">
490
- ✨ Gemini FullStack - ADK 🚀
491
- </h1>
492
-
493
- <div className="flex flex-col items-center space-y-4">
494
- {/* Spinning animation */}
495
- <div className="relative">
496
- <div className="w-16 h-16 border-4 border-neutral-600 border-t-blue-500 rounded-full animate-spin"></div>
497
- <div className="absolute inset-0 w-16 h-16 border-4 border-transparent border-r-purple-500 rounded-full animate-spin" style={{animationDirection: 'reverse', animationDuration: '1.5s'}}></div>
498
- </div>
499
-
500
- <div className="space-y-2">
501
- <p className="text-xl text-neutral-300">
502
- Waiting for backend to be ready...
503
- </p>
504
- <p className="text-sm text-neutral-400">
505
- This may take a moment on first startup
506
- </p>
507
- </div>
508
-
509
- {/* Animated dots */}
510
- <div className="flex space-x-1">
511
- <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" style={{animationDelay: '0ms'}}></div>
512
- <div className="w-2 h-2 bg-purple-500 rounded-full animate-bounce" style={{animationDelay: '150ms'}}></div>
513
- <div className="w-2 h-2 bg-pink-500 rounded-full animate-bounce" style={{animationDelay: '300ms'}}></div>
514
- </div>
515
- </div>
516
- </div>
517
- </div>
518
- </div>
519
- );
520
-
521
- return (
522
- <div className="flex h-screen bg-neutral-800 text-neutral-100 font-sans antialiased">
523
- <main className="flex-1 flex flex-col overflow-hidden w-full">
524
- <div className={`flex-1 overflow-y-auto ${(messages.length === 0 || isCheckingBackend) ? "flex" : ""}`}>
525
- {isCheckingBackend ? (
526
- <BackendLoadingScreen />
527
- ) : !isBackendReady ? (
528
- <div className="flex-1 flex flex-col items-center justify-center p-4">
529
- <div className="text-center space-y-4">
530
- <h2 className="text-2xl font-bold text-red-400">Backend Unavailable</h2>
531
- <p className="text-neutral-300">
532
- Unable to connect to backend services at localhost:8000
533
- </p>
534
- <button
535
- onClick={() => window.location.reload()}
536
- className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
537
- >
538
- Retry
539
- </button>
540
- </div>
541
- </div>
542
- ) : messages.length === 0 ? (
543
- <WelcomeScreen
544
- handleSubmit={handleSubmit}
545
- isLoading={isLoading}
546
- onCancel={handleCancel}
547
- />
548
- ) : (
549
- <ChatMessagesView
550
- messages={messages}
551
- isLoading={isLoading}
552
- scrollAreaRef={scrollAreaRef}
553
- onSubmit={handleSubmit}
554
- onCancel={handleCancel}
555
- displayData={displayData}
556
- messageEvents={messageEvents}
557
- websiteCount={websiteCount}
558
- />
559
- )}
560
- </div>
561
- </main>
562
- </div>
563
- );
564
- }