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.
- {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/METADATA +2 -1
- {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/RECORD +51 -78
- agents/adk_base/app/__init__.py +17 -0
- agents/adk_base/notebooks/adk_app_testing.ipynb +4 -1
- agents/adk_base/tests/integration/test_agent.py +1 -1
- agents/agentic_rag/app/__init__.py +17 -0
- agents/agentic_rag/app/agent.py +2 -2
- agents/agentic_rag/notebooks/adk_app_testing.ipynb +4 -1
- agents/agentic_rag/tests/integration/test_agent.py +2 -2
- agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
- agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
- agents/live_api/tests/unit/test_server.py +6 -6
- llm.txt +15 -4
- src/base_template/Makefile +5 -5
- src/base_template/README.md +4 -4
- src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +2 -2
- src/base_template/pyproject.toml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -1
- src/cli/commands/create.py +30 -2
- src/cli/commands/enhance.py +98 -15
- src/cli/commands/list.py +6 -1
- src/cli/utils/remote_template.py +5 -1
- src/cli/utils/template.py +120 -41
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +3 -3
- src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py +10 -10
- src/deployment_targets/cloud_run/Dockerfile +2 -2
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +3 -3
- src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +2 -2
- {agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py +186 -7
- src/frontends/live_api_react/frontend/package-lock.json +9 -9
- src/resources/docs/adk-cheatsheet.md +3 -3
- src/resources/locks/uv-adk_base-agent_engine.lock +452 -452
- src/resources/locks/uv-adk_base-cloud_run.lock +571 -568
- src/resources/locks/uv-agentic_rag-agent_engine.lock +565 -566
- src/resources/locks/uv-agentic_rag-cloud_run.lock +716 -713
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +729 -735
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +923 -940
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +658 -664
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +852 -869
- src/resources/locks/uv-live_api-cloud_run.lock +758 -775
- src/base_template/app/__init__.py +0 -3
- src/deployment_targets/cloud_run/app/server.py +0 -206
- src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
- src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
- src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
- src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
- src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
- src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
- src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
- src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
- src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
- src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
- src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
- src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
- src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
- src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
- src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
- src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
- src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +0 -3938
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +0 -4501
- {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/licenses/LICENSE +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
- /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
@@ -1,244 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
Card,
|
3
|
-
CardContent,
|
4
|
-
CardDescription,
|
5
|
-
CardHeader,
|
6
|
-
} from "@/components/ui/card";
|
7
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
8
|
-
import {
|
9
|
-
Loader2,
|
10
|
-
Activity,
|
11
|
-
Info,
|
12
|
-
Search,
|
13
|
-
TextSearch,
|
14
|
-
Brain,
|
15
|
-
Pen,
|
16
|
-
ChevronDown,
|
17
|
-
ChevronUp,
|
18
|
-
Link,
|
19
|
-
} from "lucide-react";
|
20
|
-
import { useEffect, useState } from "react";
|
21
|
-
import ReactMarkdown from "react-markdown";
|
22
|
-
|
23
|
-
export interface ProcessedEvent {
|
24
|
-
title: string;
|
25
|
-
data: any;
|
26
|
-
}
|
27
|
-
|
28
|
-
interface ActivityTimelineProps {
|
29
|
-
processedEvents: ProcessedEvent[];
|
30
|
-
isLoading: boolean;
|
31
|
-
websiteCount: number;
|
32
|
-
}
|
33
|
-
|
34
|
-
export function ActivityTimeline({
|
35
|
-
processedEvents,
|
36
|
-
isLoading,
|
37
|
-
websiteCount,
|
38
|
-
}: ActivityTimelineProps) {
|
39
|
-
const [isTimelineCollapsed, setIsTimelineCollapsed] =
|
40
|
-
useState<boolean>(false);
|
41
|
-
|
42
|
-
const formatEventData = (data: any): string => {
|
43
|
-
// Handle new structured data types
|
44
|
-
if (typeof data === "object" && data !== null && data.type) {
|
45
|
-
switch (data.type) {
|
46
|
-
case 'functionCall':
|
47
|
-
return `Calling function: ${data.name}\nArguments: ${JSON.stringify(data.args, null, 2)}`;
|
48
|
-
case 'functionResponse':
|
49
|
-
return `Function ${data.name} response:\n${JSON.stringify(data.response, null, 2)}`;
|
50
|
-
case 'text':
|
51
|
-
return data.content;
|
52
|
-
case 'sources':
|
53
|
-
const sources = data.content as Record<string, { title: string; url: string }>;
|
54
|
-
if (Object.keys(sources).length === 0) {
|
55
|
-
return "No sources found.";
|
56
|
-
}
|
57
|
-
return Object.values(sources)
|
58
|
-
.map(source => `[${source.title || 'Untitled Source'}](${source.url})`).join(', ');
|
59
|
-
default:
|
60
|
-
return JSON.stringify(data, null, 2);
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
// Existing logic for backward compatibility
|
65
|
-
if (typeof data === "string") {
|
66
|
-
// Try to parse as JSON first
|
67
|
-
try {
|
68
|
-
const parsed = JSON.parse(data);
|
69
|
-
return JSON.stringify(parsed, null, 2);
|
70
|
-
} catch {
|
71
|
-
// If not JSON, return as string (could be markdown)
|
72
|
-
return data;
|
73
|
-
}
|
74
|
-
} else if (Array.isArray(data)) {
|
75
|
-
return data.join(", ");
|
76
|
-
} else if (typeof data === "object" && data !== null) {
|
77
|
-
return JSON.stringify(data, null, 2);
|
78
|
-
}
|
79
|
-
return String(data);
|
80
|
-
};
|
81
|
-
|
82
|
-
const isJsonData = (data: any): boolean => {
|
83
|
-
// Handle new structured data types
|
84
|
-
if (typeof data === "object" && data !== null && data.type) {
|
85
|
-
if (data.type === 'sources') {
|
86
|
-
return false; // Let ReactMarkdown handle this
|
87
|
-
}
|
88
|
-
return data.type === 'functionCall' || data.type === 'functionResponse';
|
89
|
-
}
|
90
|
-
|
91
|
-
// Existing logic
|
92
|
-
if (typeof data === "string") {
|
93
|
-
try {
|
94
|
-
JSON.parse(data);
|
95
|
-
return true;
|
96
|
-
} catch {
|
97
|
-
return false;
|
98
|
-
}
|
99
|
-
}
|
100
|
-
return typeof data === "object" && data !== null;
|
101
|
-
};
|
102
|
-
const getEventIcon = (title: string, index: number) => {
|
103
|
-
if (index === 0 && isLoading && processedEvents.length === 0) {
|
104
|
-
return <Loader2 className="h-4 w-4 text-neutral-400 animate-spin" />;
|
105
|
-
}
|
106
|
-
if (title.toLowerCase().includes("function call")) {
|
107
|
-
return <Activity className="h-4 w-4 text-blue-400" />;
|
108
|
-
} else if (title.toLowerCase().includes("function response")) {
|
109
|
-
return <Activity className="h-4 w-4 text-green-400" />;
|
110
|
-
} else if (title.toLowerCase().includes("generating")) {
|
111
|
-
return <TextSearch className="h-4 w-4 text-neutral-400" />;
|
112
|
-
} else if (title.toLowerCase().includes("thinking")) {
|
113
|
-
return <Loader2 className="h-4 w-4 text-neutral-400 animate-spin" />;
|
114
|
-
} else if (title.toLowerCase().includes("reflection")) {
|
115
|
-
return <Brain className="h-4 w-4 text-neutral-400" />;
|
116
|
-
} else if (title.toLowerCase().includes("research")) {
|
117
|
-
return <Search className="h-4 w-4 text-neutral-400" />;
|
118
|
-
} else if (title.toLowerCase().includes("finalizing")) {
|
119
|
-
return <Pen className="h-4 w-4 text-neutral-400" />;
|
120
|
-
} else if (title.toLowerCase().includes("retrieved sources")) {
|
121
|
-
return <Link className="h-4 w-4 text-yellow-400" />;
|
122
|
-
}
|
123
|
-
return <Activity className="h-4 w-4 text-neutral-400" />;
|
124
|
-
};
|
125
|
-
|
126
|
-
useEffect(() => {
|
127
|
-
if (!isLoading && processedEvents.length !== 0) {
|
128
|
-
setIsTimelineCollapsed(true);
|
129
|
-
}
|
130
|
-
}, [isLoading, processedEvents]);
|
131
|
-
return (
|
132
|
-
<Card className={`border-none rounded-lg bg-neutral-700 ${isTimelineCollapsed ? "h-10 py-2" : "max-h-96 py-2"}`}>
|
133
|
-
<CardHeader className="py-0">
|
134
|
-
<CardDescription className="flex items-center justify-between">
|
135
|
-
<div
|
136
|
-
className="flex items-center justify-start text-sm w-full cursor-pointer gap-2 text-neutral-100"
|
137
|
-
onClick={() => setIsTimelineCollapsed(!isTimelineCollapsed)}
|
138
|
-
>
|
139
|
-
<span>Research</span>
|
140
|
-
{websiteCount > 0 && (
|
141
|
-
<span className="text-xs bg-neutral-600 px-2 py-0.5 rounded-full">
|
142
|
-
{websiteCount} websites
|
143
|
-
</span>
|
144
|
-
)}
|
145
|
-
{isTimelineCollapsed ? (
|
146
|
-
<ChevronDown className="h-4 w-4 mr-2" />
|
147
|
-
) : (
|
148
|
-
<ChevronUp className="h-4 w-4 mr-2" />
|
149
|
-
)}
|
150
|
-
</div>
|
151
|
-
</CardDescription>
|
152
|
-
</CardHeader>
|
153
|
-
{!isTimelineCollapsed && (
|
154
|
-
<ScrollArea className="max-h-80 overflow-y-auto">
|
155
|
-
<CardContent>
|
156
|
-
{isLoading && processedEvents.length === 0 && (
|
157
|
-
<div className="relative pl-8 pb-4">
|
158
|
-
<div className="absolute left-3 top-3.5 h-full w-0.5 bg-neutral-800" />
|
159
|
-
<div className="absolute left-0.5 top-2 h-5 w-5 rounded-full bg-neutral-800 flex items-center justify-center ring-4 ring-neutral-900">
|
160
|
-
<Loader2 className="h-3 w-3 text-neutral-400 animate-spin" />
|
161
|
-
</div>
|
162
|
-
<div>
|
163
|
-
<p className="text-sm text-neutral-300 font-medium">
|
164
|
-
Thinking...
|
165
|
-
</p>
|
166
|
-
</div>
|
167
|
-
</div>
|
168
|
-
)}
|
169
|
-
{processedEvents.length > 0 ? (
|
170
|
-
<div className="space-y-0">
|
171
|
-
{processedEvents.map((eventItem, index) => (
|
172
|
-
<div key={index} className="relative pl-8 pb-4">
|
173
|
-
{index < processedEvents.length - 1 ||
|
174
|
-
(isLoading && index === processedEvents.length - 1) ? (
|
175
|
-
<div className="absolute left-3 top-3.5 h-full w-0.5 bg-neutral-600" />
|
176
|
-
) : null}
|
177
|
-
<div className="absolute left-0.5 top-2 h-6 w-6 rounded-full bg-neutral-600 flex items-center justify-center ring-4 ring-neutral-700">
|
178
|
-
{getEventIcon(eventItem.title, index)}
|
179
|
-
</div>
|
180
|
-
<div>
|
181
|
-
<p className="text-sm text-neutral-200 font-medium mb-0.5">
|
182
|
-
{eventItem.title}
|
183
|
-
</p>
|
184
|
-
<div className="text-xs text-neutral-300 leading-relaxed">
|
185
|
-
{isJsonData(eventItem.data) ? (
|
186
|
-
<pre className="bg-neutral-800 p-2 rounded text-xs overflow-x-auto whitespace-pre-wrap">
|
187
|
-
{formatEventData(eventItem.data)}
|
188
|
-
</pre>
|
189
|
-
) : (
|
190
|
-
<ReactMarkdown
|
191
|
-
components={{
|
192
|
-
p: ({ children }) => <span>{children}</span>,
|
193
|
-
a: ({ href, children }) => (
|
194
|
-
<a
|
195
|
-
href={href}
|
196
|
-
target="_blank"
|
197
|
-
rel="noopener noreferrer"
|
198
|
-
className="text-blue-400 hover:text-blue-300 underline"
|
199
|
-
>
|
200
|
-
{children}
|
201
|
-
</a>
|
202
|
-
),
|
203
|
-
code: ({ children }) => (
|
204
|
-
<code className="bg-neutral-800 px-1 py-0.5 rounded text-xs">
|
205
|
-
{children}
|
206
|
-
</code>
|
207
|
-
),
|
208
|
-
}}
|
209
|
-
>
|
210
|
-
{formatEventData(eventItem.data)}
|
211
|
-
</ReactMarkdown>
|
212
|
-
)}
|
213
|
-
</div>
|
214
|
-
</div>
|
215
|
-
</div>
|
216
|
-
))}
|
217
|
-
{isLoading && processedEvents.length > 0 && (
|
218
|
-
<div className="relative pl-8 pb-4">
|
219
|
-
<div className="absolute left-0.5 top-2 h-5 w-5 rounded-full bg-neutral-600 flex items-center justify-center ring-4 ring-neutral-700">
|
220
|
-
<Loader2 className="h-3 w-3 text-neutral-400 animate-spin" />
|
221
|
-
</div>
|
222
|
-
<div>
|
223
|
-
<p className="text-sm text-neutral-300 font-medium">
|
224
|
-
Thinking...
|
225
|
-
</p>
|
226
|
-
</div>
|
227
|
-
</div>
|
228
|
-
)}
|
229
|
-
</div>
|
230
|
-
) : !isLoading ? ( // Only show "No activity" if not loading and no events
|
231
|
-
<div className="flex flex-col items-center justify-center h-full text-neutral-500 pt-10">
|
232
|
-
<Info className="h-6 w-6 mb-3" />
|
233
|
-
<p className="text-sm">No activity to display.</p>
|
234
|
-
<p className="text-xs text-neutral-600 mt-1">
|
235
|
-
Timeline will update during processing.
|
236
|
-
</p>
|
237
|
-
</div>
|
238
|
-
) : null}
|
239
|
-
</CardContent>
|
240
|
-
</ScrollArea>
|
241
|
-
)}
|
242
|
-
</Card>
|
243
|
-
);
|
244
|
-
}
|
@@ -1,420 +0,0 @@
|
|
1
|
-
import type React from "react";
|
2
|
-
import { ScrollArea } from "@/components/ui/scroll-area";
|
3
|
-
import { Loader2, Copy, CopyCheck } from "lucide-react";
|
4
|
-
import { InputForm } from "@/components/InputForm";
|
5
|
-
import { Button } from "@/components/ui/button";
|
6
|
-
import { useState, ReactNode } from "react";
|
7
|
-
import ReactMarkdown from "react-markdown";
|
8
|
-
import remarkGfm from 'remark-gfm';
|
9
|
-
import { cn } from "@/utils";
|
10
|
-
import { Badge } from "@/components/ui/badge";
|
11
|
-
import { ActivityTimeline } from "@/components/ActivityTimeline";
|
12
|
-
|
13
|
-
// Markdown component props type from former ReportView
|
14
|
-
type MdComponentProps = {
|
15
|
-
className?: string;
|
16
|
-
children?: ReactNode;
|
17
|
-
[key: string]: any;
|
18
|
-
};
|
19
|
-
|
20
|
-
interface ProcessedEvent {
|
21
|
-
title: string;
|
22
|
-
data: any;
|
23
|
-
}
|
24
|
-
|
25
|
-
// Markdown components (from former ReportView.tsx)
|
26
|
-
const mdComponents = {
|
27
|
-
h1: ({ className, children, ...props }: MdComponentProps) => (
|
28
|
-
<h1 className={cn("text-2xl font-bold mt-4 mb-2", className)} {...props}>
|
29
|
-
{children}
|
30
|
-
</h1>
|
31
|
-
),
|
32
|
-
h2: ({ className, children, ...props }: MdComponentProps) => (
|
33
|
-
<h2 className={cn("text-xl font-bold mt-3 mb-2", className)} {...props}>
|
34
|
-
{children}
|
35
|
-
</h2>
|
36
|
-
),
|
37
|
-
h3: ({ className, children, ...props }: MdComponentProps) => (
|
38
|
-
<h3 className={cn("text-lg font-bold mt-3 mb-1", className)} {...props}>
|
39
|
-
{children}
|
40
|
-
</h3>
|
41
|
-
),
|
42
|
-
p: ({ className, children, ...props }: MdComponentProps) => (
|
43
|
-
<p className={cn("mb-3 leading-7", className)} {...props}>
|
44
|
-
{children}
|
45
|
-
</p>
|
46
|
-
),
|
47
|
-
a: ({ className, children, href, ...props }: MdComponentProps) => (
|
48
|
-
<Badge className="text-xs mx-0.5">
|
49
|
-
<a
|
50
|
-
className={cn("text-blue-400 hover:text-blue-300 text-xs", className)}
|
51
|
-
href={href}
|
52
|
-
target="_blank"
|
53
|
-
rel="noopener noreferrer"
|
54
|
-
{...props}
|
55
|
-
>
|
56
|
-
{children}
|
57
|
-
</a>
|
58
|
-
</Badge>
|
59
|
-
),
|
60
|
-
ul: ({ className, children, ...props }: MdComponentProps) => (
|
61
|
-
<ul className={cn("list-disc pl-6 mb-3", className)} {...props}>
|
62
|
-
{children}
|
63
|
-
</ul>
|
64
|
-
),
|
65
|
-
ol: ({ className, children, ...props }: MdComponentProps) => (
|
66
|
-
<ol className={cn("list-decimal pl-6 mb-3", className)} {...props}>
|
67
|
-
{children}
|
68
|
-
</ol>
|
69
|
-
),
|
70
|
-
li: ({ className, children, ...props }: MdComponentProps) => (
|
71
|
-
<li className={cn("mb-1", className)} {...props}>
|
72
|
-
{children}
|
73
|
-
</li>
|
74
|
-
),
|
75
|
-
blockquote: ({ className, children, ...props }: MdComponentProps) => (
|
76
|
-
<blockquote
|
77
|
-
className={cn(
|
78
|
-
"border-l-4 border-neutral-600 pl-4 italic my-3 text-sm",
|
79
|
-
className
|
80
|
-
)}
|
81
|
-
{...props}
|
82
|
-
>
|
83
|
-
{children}
|
84
|
-
</blockquote>
|
85
|
-
),
|
86
|
-
code: ({ className, children, ...props }: MdComponentProps) => (
|
87
|
-
<code
|
88
|
-
className={cn(
|
89
|
-
"bg-neutral-900 rounded px-1 py-0.5 font-mono text-xs",
|
90
|
-
className
|
91
|
-
)}
|
92
|
-
{...props}
|
93
|
-
>
|
94
|
-
{children}
|
95
|
-
</code>
|
96
|
-
),
|
97
|
-
pre: ({ className, children, ...props }: MdComponentProps) => (
|
98
|
-
<pre
|
99
|
-
className={cn(
|
100
|
-
"bg-neutral-900 p-3 rounded-lg overflow-x-auto font-mono text-xs my-3",
|
101
|
-
className
|
102
|
-
)}
|
103
|
-
{...props}
|
104
|
-
>
|
105
|
-
{children}
|
106
|
-
</pre>
|
107
|
-
),
|
108
|
-
hr: ({ className, ...props }: MdComponentProps) => (
|
109
|
-
<hr className={cn("border-neutral-600 my-4", className)} {...props} />
|
110
|
-
),
|
111
|
-
table: ({ className, children, ...props }: MdComponentProps) => (
|
112
|
-
<div className="my-3 overflow-x-auto">
|
113
|
-
<table className={cn("border-collapse w-full", className)} {...props}>
|
114
|
-
{children}
|
115
|
-
</table>
|
116
|
-
</div>
|
117
|
-
),
|
118
|
-
th: ({ className, children, ...props }: MdComponentProps) => (
|
119
|
-
<th
|
120
|
-
className={cn(
|
121
|
-
"border border-neutral-600 px-3 py-2 text-left font-bold",
|
122
|
-
className
|
123
|
-
)}
|
124
|
-
{...props}
|
125
|
-
>
|
126
|
-
{children}
|
127
|
-
</th>
|
128
|
-
),
|
129
|
-
td: ({ className, children, ...props }: MdComponentProps) => (
|
130
|
-
<td
|
131
|
-
className={cn("border border-neutral-600 px-3 py-2", className)}
|
132
|
-
{...props}
|
133
|
-
>
|
134
|
-
{children}
|
135
|
-
</td>
|
136
|
-
),
|
137
|
-
};
|
138
|
-
|
139
|
-
// Props for HumanMessageBubble
|
140
|
-
interface HumanMessageBubbleProps {
|
141
|
-
message: { content: string; id: string };
|
142
|
-
mdComponents: typeof mdComponents;
|
143
|
-
}
|
144
|
-
|
145
|
-
// HumanMessageBubble Component
|
146
|
-
const HumanMessageBubble: React.FC<HumanMessageBubbleProps> = ({
|
147
|
-
message,
|
148
|
-
mdComponents,
|
149
|
-
}) => {
|
150
|
-
return (
|
151
|
-
<div className="text-white rounded-3xl break-words min-h-7 bg-neutral-700 max-w-[100%] sm:max-w-[90%] px-4 pt-3 rounded-br-lg">
|
152
|
-
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
|
153
|
-
{message.content}
|
154
|
-
</ReactMarkdown>
|
155
|
-
</div>
|
156
|
-
);
|
157
|
-
};
|
158
|
-
|
159
|
-
// Props for AiMessageBubble
|
160
|
-
interface AiMessageBubbleProps {
|
161
|
-
message: { content: string; id: string };
|
162
|
-
mdComponents: typeof mdComponents;
|
163
|
-
handleCopy: (text: string, messageId: string) => void;
|
164
|
-
copiedMessageId: string | null;
|
165
|
-
agent?: string;
|
166
|
-
finalReportWithCitations?: boolean;
|
167
|
-
processedEvents: ProcessedEvent[];
|
168
|
-
websiteCount: number;
|
169
|
-
isLoading: boolean;
|
170
|
-
}
|
171
|
-
|
172
|
-
// AiMessageBubble Component
|
173
|
-
const AiMessageBubble: React.FC<AiMessageBubbleProps> = ({
|
174
|
-
message,
|
175
|
-
mdComponents,
|
176
|
-
handleCopy,
|
177
|
-
copiedMessageId,
|
178
|
-
agent,
|
179
|
-
finalReportWithCitations,
|
180
|
-
processedEvents,
|
181
|
-
websiteCount,
|
182
|
-
isLoading,
|
183
|
-
}) => {
|
184
|
-
// Show ActivityTimeline if we have processedEvents (this will be the first AI message)
|
185
|
-
const shouldShowTimeline = processedEvents.length > 0;
|
186
|
-
|
187
|
-
// Condition for DIRECT DISPLAY (interactive_planner_agent OR final report)
|
188
|
-
const shouldDisplayDirectly =
|
189
|
-
agent === "interactive_planner_agent" ||
|
190
|
-
(agent === "report_composer_with_citations" && finalReportWithCitations);
|
191
|
-
|
192
|
-
if (shouldDisplayDirectly) {
|
193
|
-
// Direct display - show content with copy button, and timeline if available
|
194
|
-
return (
|
195
|
-
<div className="relative break-words flex flex-col w-full">
|
196
|
-
{/* Show timeline for interactive_planner_agent if available */}
|
197
|
-
{shouldShowTimeline && agent === "interactive_planner_agent" && (
|
198
|
-
<div className="w-full mb-2">
|
199
|
-
<ActivityTimeline
|
200
|
-
processedEvents={processedEvents}
|
201
|
-
isLoading={isLoading}
|
202
|
-
websiteCount={websiteCount}
|
203
|
-
/>
|
204
|
-
</div>
|
205
|
-
)}
|
206
|
-
<div className="flex items-start gap-3">
|
207
|
-
<div className="flex-1">
|
208
|
-
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
|
209
|
-
{message.content}
|
210
|
-
</ReactMarkdown>
|
211
|
-
</div>
|
212
|
-
<button
|
213
|
-
onClick={() => handleCopy(message.content, message.id)}
|
214
|
-
className="p-1 hover:bg-neutral-700 rounded"
|
215
|
-
>
|
216
|
-
{copiedMessageId === message.id ? (
|
217
|
-
<CopyCheck className="h-4 w-4 text-green-500" />
|
218
|
-
) : (
|
219
|
-
<Copy className="h-4 w-4 text-neutral-400" />
|
220
|
-
)}
|
221
|
-
</button>
|
222
|
-
</div>
|
223
|
-
</div>
|
224
|
-
);
|
225
|
-
} else if (shouldShowTimeline) {
|
226
|
-
// First AI message with timeline only (no direct content display)
|
227
|
-
return (
|
228
|
-
<div className="relative break-words flex flex-col w-full">
|
229
|
-
<div className="w-full">
|
230
|
-
<ActivityTimeline
|
231
|
-
processedEvents={processedEvents}
|
232
|
-
isLoading={isLoading}
|
233
|
-
websiteCount={websiteCount}
|
234
|
-
/>
|
235
|
-
</div>
|
236
|
-
{/* Only show accumulated content if it's not empty and not from research agents */}
|
237
|
-
{message.content && message.content.trim() && agent !== "interactive_planner_agent" && (
|
238
|
-
<div className="flex items-start gap-3 mt-2">
|
239
|
-
<div className="flex-1">
|
240
|
-
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
|
241
|
-
{message.content}
|
242
|
-
</ReactMarkdown>
|
243
|
-
</div>
|
244
|
-
<button
|
245
|
-
onClick={() => handleCopy(message.content, message.id)}
|
246
|
-
className="p-1 hover:bg-neutral-700 rounded"
|
247
|
-
>
|
248
|
-
{copiedMessageId === message.id ? (
|
249
|
-
<CopyCheck className="h-4 w-4 text-green-500" />
|
250
|
-
) : (
|
251
|
-
<Copy className="h-4 w-4 text-neutral-400" />
|
252
|
-
)}
|
253
|
-
</button>
|
254
|
-
</div>
|
255
|
-
)}
|
256
|
-
</div>
|
257
|
-
);
|
258
|
-
} else {
|
259
|
-
// Fallback for other messages - just show content
|
260
|
-
return (
|
261
|
-
<div className="relative break-words flex flex-col w-full">
|
262
|
-
<div className="flex items-start gap-3">
|
263
|
-
<div className="flex-1">
|
264
|
-
<ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
|
265
|
-
{message.content}
|
266
|
-
</ReactMarkdown>
|
267
|
-
</div>
|
268
|
-
<button
|
269
|
-
onClick={() => handleCopy(message.content, message.id)}
|
270
|
-
className="p-1 hover:bg-neutral-700 rounded"
|
271
|
-
>
|
272
|
-
{copiedMessageId === message.id ? (
|
273
|
-
<CopyCheck className="h-4 w-4 text-green-500" />
|
274
|
-
) : (
|
275
|
-
<Copy className="h-4 w-4 text-neutral-400" />
|
276
|
-
)}
|
277
|
-
</button>
|
278
|
-
</div>
|
279
|
-
</div>
|
280
|
-
);
|
281
|
-
}
|
282
|
-
};
|
283
|
-
|
284
|
-
interface ChatMessagesViewProps {
|
285
|
-
messages: { type: "human" | "ai"; content: string; id: string; agent?: string; finalReportWithCitations?: boolean }[];
|
286
|
-
isLoading: boolean;
|
287
|
-
scrollAreaRef: React.RefObject<HTMLDivElement | null>;
|
288
|
-
onSubmit: (query: string) => void;
|
289
|
-
onCancel: () => void;
|
290
|
-
displayData: string | null;
|
291
|
-
messageEvents: Map<string, ProcessedEvent[]>;
|
292
|
-
websiteCount: number;
|
293
|
-
}
|
294
|
-
|
295
|
-
export function ChatMessagesView({
|
296
|
-
messages,
|
297
|
-
isLoading,
|
298
|
-
scrollAreaRef,
|
299
|
-
onSubmit,
|
300
|
-
onCancel,
|
301
|
-
messageEvents,
|
302
|
-
websiteCount,
|
303
|
-
}: ChatMessagesViewProps) {
|
304
|
-
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
|
305
|
-
|
306
|
-
const handleCopy = async (text: string, messageId: string) => {
|
307
|
-
try {
|
308
|
-
await navigator.clipboard.writeText(text);
|
309
|
-
setCopiedMessageId(messageId);
|
310
|
-
setTimeout(() => setCopiedMessageId(null), 2000);
|
311
|
-
} catch (err) {
|
312
|
-
console.error("Failed to copy text:", err);
|
313
|
-
}
|
314
|
-
};
|
315
|
-
|
316
|
-
const handleNewChat = () => {
|
317
|
-
window.location.reload();
|
318
|
-
};
|
319
|
-
|
320
|
-
// Find the ID of the last AI message
|
321
|
-
const lastAiMessage = messages.slice().reverse().find(m => m.type === "ai");
|
322
|
-
const lastAiMessageId = lastAiMessage?.id;
|
323
|
-
|
324
|
-
return (
|
325
|
-
<div className="flex flex-col h-full w-full">
|
326
|
-
{/* Header with New Chat button */}
|
327
|
-
<div className="border-b border-neutral-700 p-4 bg-neutral-800">
|
328
|
-
<div className="max-w-4xl mx-auto flex justify-between items-center">
|
329
|
-
<h1 className="text-lg font-semibold text-neutral-100">Chat</h1>
|
330
|
-
<Button
|
331
|
-
onClick={handleNewChat}
|
332
|
-
variant="outline"
|
333
|
-
className="bg-neutral-700 hover:bg-neutral-600 text-neutral-100 border-neutral-600 hover:border-neutral-500"
|
334
|
-
>
|
335
|
-
New Chat
|
336
|
-
</Button>
|
337
|
-
</div>
|
338
|
-
</div>
|
339
|
-
<div className="flex-1 flex flex-col w-full">
|
340
|
-
<ScrollArea ref={scrollAreaRef} className="flex-1 w-full">
|
341
|
-
<div className="p-4 md:p-6 space-y-2 max-w-4xl mx-auto">
|
342
|
-
{messages.map((message) => { // Removed index as it's not directly used for this logic
|
343
|
-
const eventsForMessage = message.type === "ai" ? (messageEvents.get(message.id) || []) : [];
|
344
|
-
|
345
|
-
// Determine if the current AI message is the last one
|
346
|
-
const isCurrentMessageTheLastAiMessage = message.type === "ai" && message.id === lastAiMessageId;
|
347
|
-
|
348
|
-
return (
|
349
|
-
<div
|
350
|
-
key={message.id}
|
351
|
-
className={`flex ${message.type === "human" ? "justify-end" : "justify-start"}`}
|
352
|
-
>
|
353
|
-
{message.type === "human" ? (
|
354
|
-
<HumanMessageBubble
|
355
|
-
message={message}
|
356
|
-
mdComponents={mdComponents}
|
357
|
-
/>
|
358
|
-
) : (
|
359
|
-
<AiMessageBubble
|
360
|
-
message={message}
|
361
|
-
mdComponents={mdComponents}
|
362
|
-
handleCopy={handleCopy}
|
363
|
-
copiedMessageId={copiedMessageId}
|
364
|
-
agent={message.agent}
|
365
|
-
finalReportWithCitations={message.finalReportWithCitations}
|
366
|
-
processedEvents={eventsForMessage}
|
367
|
-
// MODIFIED: Pass websiteCount only if it's the last AI message
|
368
|
-
websiteCount={isCurrentMessageTheLastAiMessage ? websiteCount : 0}
|
369
|
-
// MODIFIED: Pass isLoading only if it's the last AI message and global isLoading is true
|
370
|
-
isLoading={isCurrentMessageTheLastAiMessage && isLoading}
|
371
|
-
/>
|
372
|
-
)}
|
373
|
-
</div>
|
374
|
-
);
|
375
|
-
})}
|
376
|
-
{/* This global "Thinking..." indicator appears below all messages if isLoading is true */}
|
377
|
-
{/* It's independent of the per-timeline isLoading state */}
|
378
|
-
{isLoading && !lastAiMessage && messages.some(m => m.type === 'human') && (
|
379
|
-
<div className="flex justify-start">
|
380
|
-
<div className="flex items-center gap-2 text-neutral-400">
|
381
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
382
|
-
<span>Thinking...</span>
|
383
|
-
</div>
|
384
|
-
</div>
|
385
|
-
)}
|
386
|
-
{/* Show "Thinking..." if the last message is human and we are loading,
|
387
|
-
or if there's an active AI message that is the last one and we are loading.
|
388
|
-
The AiMessageBubble's internal isLoading will handle its own spinner.
|
389
|
-
This one is for the general loading state at the bottom.
|
390
|
-
*/}
|
391
|
-
{isLoading && messages.length > 0 && messages[messages.length -1].type === 'human' && (
|
392
|
-
<div className="flex justify-start pl-10 pt-2"> {/* Adjusted padding to align similarly to AI bubble */}
|
393
|
-
<div className="flex items-center gap-2 text-neutral-400">
|
394
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
395
|
-
<span>Thinking...</span>
|
396
|
-
</div>
|
397
|
-
</div>
|
398
|
-
)}
|
399
|
-
</div>
|
400
|
-
</ScrollArea>
|
401
|
-
</div>
|
402
|
-
<div className="border-t border-neutral-700 p-4 w-full">
|
403
|
-
<div className="max-w-3xl mx-auto">
|
404
|
-
<InputForm onSubmit={onSubmit} isLoading={isLoading} context="chat" />
|
405
|
-
{isLoading && (
|
406
|
-
<div className="mt-4 flex justify-center">
|
407
|
-
<Button
|
408
|
-
variant="outline"
|
409
|
-
onClick={onCancel}
|
410
|
-
className="text-red-400 hover:text-red-300"
|
411
|
-
>
|
412
|
-
Cancel
|
413
|
-
</Button>
|
414
|
-
</div>
|
415
|
-
)}
|
416
|
-
</div>
|
417
|
-
</div>
|
418
|
-
</div>
|
419
|
-
);
|
420
|
-
}
|