mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(frontend): setup datafast custom events (#11231)
## Changes 🏗️ - Add [custom events](https://datafa.st/docs/custom-goals) in **Datafa.st** to track the user journey around core actions - track `add_to_library` - track `download_agent` - track `run_agent` - track `schedule_agent` - Refactor the analytics service to encapsulate both **GA** and **Datafa.st** ## Checklist 📋 ### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Analytics load correctly locally - [x] Events fire in production ### For configuration changes: Once deployed to production we need to verify we are receiving analytics and custom events in [Datafa.st](https://datafa.st/)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import Shepherd from "shepherd.js";
|
||||
import "shepherd.js/dist/css/shepherd.css";
|
||||
import { sendGAEvent } from "@/services/analytics/google-analytics";
|
||||
import { Key, storage } from "@/services/storage/local-storage";
|
||||
import { analytics } from "@/services/analytics";
|
||||
|
||||
export const startTutorial = (
|
||||
emptyNodeList: (forceEmpty: boolean) => boolean,
|
||||
@@ -555,7 +555,7 @@ export const startTutorial = (
|
||||
"use client";
|
||||
console.debug("sendTutorialStep");
|
||||
|
||||
sendGAEvent("event", "tutorial_step_shown", { value: step.id });
|
||||
analytics.sendGAEvent("event", "tutorial_step_shown", { value: step.id });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecu
|
||||
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
|
||||
import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth";
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import { analytics } from "@/services/analytics";
|
||||
|
||||
export type RunVariant =
|
||||
| "manual"
|
||||
@@ -78,6 +79,10 @@ export function useAgentRunModal(
|
||||
agent.graph_id,
|
||||
).queryKey,
|
||||
});
|
||||
analytics.sendDatafastEvent("run_agent", {
|
||||
name: agent.name,
|
||||
id: agent.graph_id,
|
||||
});
|
||||
setIsOpen(false);
|
||||
}
|
||||
},
|
||||
@@ -105,6 +110,11 @@ export function useAgentRunModal(
|
||||
agent.graph_id,
|
||||
),
|
||||
});
|
||||
analytics.sendDatafastEvent("schedule_agent", {
|
||||
name: agent.name,
|
||||
id: agent.graph_id,
|
||||
cronExpression: cronExpression,
|
||||
});
|
||||
setIsOpen(false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ import { useToastOnFail } from "@/components/molecules/Toast/use-toast";
|
||||
import { AgentRunStatus, agentRunStatusMap } from "./agent-run-status-chip";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
import { AgentRunOutputView } from "./agent-run-output-view";
|
||||
import { analytics } from "@/services/analytics";
|
||||
|
||||
export function AgentRunDetailsView({
|
||||
agent,
|
||||
@@ -131,7 +132,13 @@ export function AgentRunDetailsView({
|
||||
run.inputs!,
|
||||
run.credential_inputs!,
|
||||
)
|
||||
.then(({ id }) => onRun(id))
|
||||
.then(({ id }) => {
|
||||
analytics.sendDatafastEvent("run_agent", {
|
||||
name: graph.name,
|
||||
id: graph.id,
|
||||
});
|
||||
onRun(id);
|
||||
})
|
||||
.catch(toastOnFail("execute agent preset"));
|
||||
}
|
||||
|
||||
@@ -142,7 +149,13 @@ export function AgentRunDetailsView({
|
||||
run.inputs!,
|
||||
run.credential_inputs!,
|
||||
)
|
||||
.then(({ id }) => onRun(id))
|
||||
.then(({ id }) => {
|
||||
analytics.sendDatafastEvent("run_agent", {
|
||||
name: graph.name,
|
||||
id: graph.id,
|
||||
});
|
||||
onRun(id);
|
||||
})
|
||||
.catch(toastOnFail("execute agent"));
|
||||
}, [api, graph, run, onRun, toastOnFail]);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
|
||||
import { AgentStatus, AgentStatusChip } from "./agent-status-chip";
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import { analytics } from "@/services/analytics";
|
||||
|
||||
export function AgentRunDraftView({
|
||||
graph,
|
||||
@@ -197,6 +198,12 @@ export function AgentRunDraftView({
|
||||
}
|
||||
// Mark run agent onboarding step as completed
|
||||
completeOnboardingStep("MARKETPLACE_RUN_AGENT");
|
||||
|
||||
analytics.sendDatafastEvent("run_agent", {
|
||||
name: graph.name,
|
||||
id: graph.id,
|
||||
});
|
||||
|
||||
if (runCount > 0) {
|
||||
completeOnboardingStep("RE_RUN_AGENT");
|
||||
}
|
||||
@@ -373,6 +380,12 @@ export function AgentRunDraftView({
|
||||
})
|
||||
.catch(toastOnFail("set up agent run schedule"));
|
||||
|
||||
analytics.sendDatafastEvent("schedule_agent", {
|
||||
name: graph.name,
|
||||
id: graph.id,
|
||||
cronExpression: cronExpression,
|
||||
});
|
||||
|
||||
if (schedule && onCreateSchedule) onCreateSchedule(schedule);
|
||||
},
|
||||
[api, graph, inputValues, inputCredentials, onCreateSchedule, toastOnFail],
|
||||
|
||||
@@ -6,10 +6,10 @@ import Link from "next/link";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAgentInfo } from "./useAgentInfo";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
|
||||
interface AgentInfoProps {
|
||||
user: User | null;
|
||||
agentId: string;
|
||||
name: string;
|
||||
creator: string;
|
||||
shortDescription: string;
|
||||
@@ -20,11 +20,12 @@ interface AgentInfoProps {
|
||||
lastUpdated: string;
|
||||
version: string;
|
||||
storeListingVersionId: string;
|
||||
libraryAgent: LibraryAgent | undefined;
|
||||
isAgentAddedToLibrary: boolean;
|
||||
}
|
||||
|
||||
export const AgentInfo = ({
|
||||
user,
|
||||
agentId,
|
||||
name,
|
||||
creator,
|
||||
shortDescription,
|
||||
@@ -35,7 +36,7 @@ export const AgentInfo = ({
|
||||
lastUpdated,
|
||||
version,
|
||||
storeListingVersionId,
|
||||
libraryAgent,
|
||||
isAgentAddedToLibrary,
|
||||
}: AgentInfoProps) => {
|
||||
const {
|
||||
handleDownload,
|
||||
@@ -82,11 +83,15 @@ export const AgentInfo = ({
|
||||
"transition-colors duration-200 hover:bg-violet-500 disabled:bg-zinc-400",
|
||||
)}
|
||||
data-testid={"agent-add-library-button"}
|
||||
onClick={handleLibraryAction}
|
||||
disabled={isAddingAgentToLibrary}
|
||||
onClick={() =>
|
||||
handleLibraryAction({
|
||||
isAddingAgentFirstTime: !isAgentAddedToLibrary,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span className="justify-start font-sans text-sm font-medium leading-snug text-primary-foreground">
|
||||
{libraryAgent ? "See runs" : "Add to library"}
|
||||
{isAgentAddedToLibrary ? "See runs" : "Add to library"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -96,7 +101,7 @@ export const AgentInfo = ({
|
||||
"transition-colors duration-200 hover:bg-zinc-200/70 disabled:bg-zinc-200/40",
|
||||
)}
|
||||
data-testid={"agent-download-button"}
|
||||
onClick={handleDownload}
|
||||
onClick={() => handleDownload(agentId, name)}
|
||||
disabled={isDownloadingAgent}
|
||||
>
|
||||
<div className="justify-start text-center font-sans text-sm font-medium leading-snug text-zinc-800">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { usePostV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { useRouter } from "next/navigation";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { useGetV2DownloadAgentFile } from "@/app/api/__generated__/endpoints/store/store";
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import { analytics } from "@/services/analytics";
|
||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
|
||||
interface UseAgentInfoProps {
|
||||
storeListingVersionId: string;
|
||||
@@ -16,29 +17,9 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => {
|
||||
const { completeStep } = useOnboarding();
|
||||
|
||||
const {
|
||||
mutate: addMarketplaceAgentToLibrary,
|
||||
mutateAsync: addMarketplaceAgentToLibrary,
|
||||
isPending: isAddingAgentToLibrary,
|
||||
} = usePostV2AddMarketplaceAgent({
|
||||
mutation: {
|
||||
onSuccess: ({ data }) => {
|
||||
completeStep("MARKETPLACE_ADD_AGENT");
|
||||
router.push(`/library/agents/${(data as LibraryAgent).id}`);
|
||||
toast({
|
||||
title: "Agent Added",
|
||||
description: "Redirecting to your library...",
|
||||
duration: 2000,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
Sentry.captureException(error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to add agent to library. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
} = usePostV2AddMarketplaceAgent();
|
||||
|
||||
const { refetch: downloadAgent, isFetching: isDownloadingAgent } =
|
||||
useGetV2DownloadAgentFile(storeListingVersionId, {
|
||||
@@ -50,13 +31,46 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => {
|
||||
},
|
||||
});
|
||||
|
||||
const handleLibraryAction = async () => {
|
||||
addMarketplaceAgentToLibrary({
|
||||
data: { store_listing_version_id: storeListingVersionId },
|
||||
});
|
||||
const handleLibraryAction = async ({
|
||||
isAddingAgentFirstTime,
|
||||
}: {
|
||||
isAddingAgentFirstTime: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const { data: response } = await addMarketplaceAgentToLibrary({
|
||||
data: { store_listing_version_id: storeListingVersionId },
|
||||
});
|
||||
|
||||
const data = response as LibraryAgent;
|
||||
|
||||
if (isAddingAgentFirstTime) {
|
||||
completeStep("MARKETPLACE_ADD_AGENT");
|
||||
|
||||
analytics.sendDatafastEvent("add_to_library", {
|
||||
name: data.name,
|
||||
id: data.id,
|
||||
});
|
||||
}
|
||||
|
||||
router.push(`/library/agents/${data.id}`);
|
||||
|
||||
toast({
|
||||
title: "Agent Added",
|
||||
description: "Redirecting to your library...",
|
||||
duration: 2000,
|
||||
});
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to add agent to library. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
const handleDownload = async (agentId: string, agentName: string) => {
|
||||
try {
|
||||
const { data: file } = await downloadAgent();
|
||||
|
||||
@@ -74,6 +88,11 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => {
|
||||
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
analytics.sendDatafastEvent("download_agent", {
|
||||
name: agentName,
|
||||
id: agentId,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "Download Complete",
|
||||
description: "Your agent has been successfully downloaded.",
|
||||
|
||||
@@ -82,6 +82,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
||||
<div className="w-full md:w-auto md:shrink-0">
|
||||
<AgentInfo
|
||||
user={user}
|
||||
agentId={agent.active_version_id ?? "–"}
|
||||
name={agent.agent_name}
|
||||
creator={agent.creator}
|
||||
shortDescription={agent.sub_heading}
|
||||
@@ -92,7 +93,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
||||
lastUpdated={agent.last_updated.toISOString()}
|
||||
version={agent.versions[agent.versions.length - 1]}
|
||||
storeListingVersionId={agent.store_listing_version_id}
|
||||
libraryAgent={libraryAgent}
|
||||
isAgentAddedToLibrary={Boolean(libraryAgent)}
|
||||
/>
|
||||
</div>
|
||||
<AgentImages
|
||||
|
||||
@@ -6,14 +6,12 @@ import "./globals.css";
|
||||
|
||||
import { Providers } from "@/app/providers";
|
||||
import TallyPopupSimple from "@/components/molecules/TallyPoup/TallyPopup";
|
||||
import { GoogleAnalytics } from "@/services/analytics/google-analytics";
|
||||
import { Toaster } from "@/components/molecules/Toast/toaster";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { Analytics } from "@vercel/analytics/next";
|
||||
import Script from "next/script";
|
||||
import { environment } from "@/services/environment";
|
||||
import { headers } from "next/headers";
|
||||
import { SetupAnalytics } from "@/services/analytics";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "AutoGPT Platform",
|
||||
@@ -27,7 +25,6 @@ export default async function RootLayout({
|
||||
}>) {
|
||||
const headersList = await headers();
|
||||
const host = headersList.get("host") || "";
|
||||
const withAnalytics = environment.areAnalyticsEnabled(host);
|
||||
|
||||
return (
|
||||
<html
|
||||
@@ -36,17 +33,12 @@ export default async function RootLayout({
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<GoogleAnalytics
|
||||
gaId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || "G-FH2XK2W4GN"} // This is the measurement Id for the Google Analytics dev project
|
||||
<SetupAnalytics
|
||||
host={host}
|
||||
ga={{
|
||||
gaId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || "G-FH2XK2W4GN",
|
||||
}}
|
||||
/>
|
||||
{withAnalytics ? (
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
data-website-id="dfid_g5wtBIiHUwSkWKcGz80lu"
|
||||
data-domain="agpt.co"
|
||||
src="https://datafa.st/js/script.js"
|
||||
/>
|
||||
) : null}
|
||||
</head>
|
||||
<body>
|
||||
<Providers
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { StarRatingIcons } from "@/components/__legacy__/ui/icons";
|
||||
import { Separator } from "@/components/__legacy__/ui/separator";
|
||||
import BackendAPI, { LibraryAgent } from "@/lib/autogpt-server-api";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import { User } from "@supabase/supabase-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FC, useCallback, useMemo, useState } from "react";
|
||||
|
||||
interface AgentInfoProps {
|
||||
user: User | null;
|
||||
name: string;
|
||||
creator: string;
|
||||
shortDescription: string;
|
||||
longDescription: string;
|
||||
rating: number;
|
||||
runs: number;
|
||||
categories: string[];
|
||||
lastUpdated: string;
|
||||
version: string;
|
||||
storeListingVersionId: string;
|
||||
libraryAgent: LibraryAgent | null;
|
||||
}
|
||||
|
||||
export const AgentInfo: FC<AgentInfoProps> = ({
|
||||
user,
|
||||
name,
|
||||
creator,
|
||||
shortDescription,
|
||||
longDescription,
|
||||
rating,
|
||||
runs,
|
||||
categories,
|
||||
lastUpdated,
|
||||
version,
|
||||
storeListingVersionId,
|
||||
libraryAgent,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const api = useMemo(() => new BackendAPI(), []);
|
||||
const { toast } = useToast();
|
||||
const { completeStep } = useOnboarding();
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
|
||||
const libraryAction = useCallback(async () => {
|
||||
setAdding(true);
|
||||
if (libraryAgent) {
|
||||
toast({
|
||||
description: "Redirecting to your library...",
|
||||
duration: 2000,
|
||||
});
|
||||
// Redirect to the library agent page
|
||||
router.push(`/library/agents/${libraryAgent.id}`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newLibraryAgent = await api.addMarketplaceAgentToLibrary(
|
||||
storeListingVersionId,
|
||||
);
|
||||
completeStep("MARKETPLACE_ADD_AGENT");
|
||||
router.push(`/library/agents/${newLibraryAgent.id}`);
|
||||
toast({
|
||||
title: "Agent Added",
|
||||
description: "Redirecting to your library...",
|
||||
duration: 2000,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to add agent to library:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to add agent to library. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}, [toast, api, storeListingVersionId, completeStep, router]);
|
||||
|
||||
const handleDownload = useCallback(async () => {
|
||||
const downloadAgent = async (): Promise<void> => {
|
||||
setDownloading(true);
|
||||
try {
|
||||
const file = await api.downloadStoreAgent(storeListingVersionId);
|
||||
|
||||
// Similar to Marketplace v1
|
||||
const jsonData = JSON.stringify(file, null, 2);
|
||||
// Create a Blob from the file content
|
||||
const blob = new Blob([jsonData], { type: "application/json" });
|
||||
|
||||
// Create a temporary URL for the Blob
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `agent_${storeListingVersionId}.json`; // Set the filename
|
||||
|
||||
// Append the anchor to the body, click it, and remove it
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Revoke the temporary URL
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
toast({
|
||||
title: "Download Complete",
|
||||
description: "Your agent has been successfully downloaded.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error downloading agent:`, error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to download agent. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
await downloadAgent();
|
||||
setDownloading(false);
|
||||
}, [setDownloading, api, storeListingVersionId, toast]);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
||||
{/* Title */}
|
||||
<div
|
||||
data-testid="agent-title"
|
||||
className="mb-3 w-full font-poppins text-2xl font-medium leading-normal text-neutral-900 dark:text-neutral-100 sm:text-3xl lg:mb-4 lg:text-[35px] lg:leading-10"
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
|
||||
{/* Creator */}
|
||||
<div className="mb-3 flex w-full items-center gap-1.5 lg:mb-4">
|
||||
<div className="text-base font-normal text-neutral-800 dark:text-neutral-200 sm:text-lg lg:text-xl">
|
||||
by
|
||||
</div>
|
||||
<Link
|
||||
data-testid={"agent-creator"}
|
||||
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
|
||||
className="text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
|
||||
>
|
||||
{creator}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Short Description */}
|
||||
<div className="mb-4 line-clamp-2 w-full text-base font-normal leading-normal text-neutral-600 dark:text-neutral-300 sm:text-lg lg:mb-6 lg:text-xl lg:leading-7">
|
||||
{shortDescription}
|
||||
</div>
|
||||
|
||||
{/* Buttons */}
|
||||
<div className="mb-4 flex w-full gap-3 lg:mb-[60px]">
|
||||
{user && (
|
||||
<button
|
||||
className={cn(
|
||||
"inline-flex min-w-24 items-center justify-center rounded-full bg-violet-600 px-4 py-3",
|
||||
"transition-colors duration-200 hover:bg-violet-500 disabled:bg-zinc-400",
|
||||
)}
|
||||
data-testid={"agent-add-library-button"}
|
||||
onClick={libraryAction}
|
||||
disabled={adding}
|
||||
>
|
||||
<span className="justify-start font-sans text-sm font-medium leading-snug text-primary-foreground">
|
||||
{libraryAgent ? "See runs" : "Add to library"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={cn(
|
||||
"inline-flex min-w-24 items-center justify-center rounded-full bg-zinc-200 px-4 py-3",
|
||||
"transition-colors duration-200 hover:bg-zinc-200/70 disabled:bg-zinc-200/40",
|
||||
)}
|
||||
data-testid={"agent-download-button"}
|
||||
onClick={handleDownload}
|
||||
disabled={downloading}
|
||||
>
|
||||
<div className="justify-start text-center font-sans text-sm font-medium leading-snug text-zinc-800">
|
||||
Download agent
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Rating and Runs */}
|
||||
<div className="mb-4 flex w-full items-center justify-between lg:mb-[44px]">
|
||||
<div className="flex items-center gap-1.5 sm:gap-2">
|
||||
<span className="whitespace-nowrap text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
|
||||
{rating.toFixed(1)}
|
||||
</span>
|
||||
<div className="flex gap-0.5">{StarRatingIcons(rating)}</div>
|
||||
</div>
|
||||
<div className="whitespace-nowrap text-base font-semibold text-neutral-800 dark:text-neutral-200 sm:text-lg">
|
||||
{runs.toLocaleString()} runs
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
<Separator className="mb-4 lg:mb-[44px]" />
|
||||
|
||||
{/* Description Section */}
|
||||
<div className="mb-4 w-full lg:mb-[36px]">
|
||||
<div className="decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
||||
Description
|
||||
</div>
|
||||
<div
|
||||
data-testid={"agent-description"}
|
||||
className="whitespace-pre-line text-base font-normal leading-6 text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{longDescription}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Categories */}
|
||||
<div className="mb-4 flex w-full flex-col gap-1.5 sm:gap-2 lg:mb-[36px]">
|
||||
<div className="decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
||||
Categories
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5 sm:gap-2">
|
||||
{categories.map((category, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="decoration-skip-ink-none whitespace-nowrap rounded-full border border-neutral-600 bg-white px-2 py-0.5 text-base font-normal leading-6 text-neutral-800 underline-offset-[from-font] dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200 sm:px-[16px] sm:py-[10px]"
|
||||
>
|
||||
{category}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Version History */}
|
||||
<div className="flex w-full flex-col gap-0.5 sm:gap-1">
|
||||
<div className="decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
||||
Version history
|
||||
</div>
|
||||
<div className="decoration-skip-ink-none text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400">
|
||||
Last updated {lastUpdated}
|
||||
</div>
|
||||
<div className="text-xs text-neutral-600 dark:text-neutral-400 sm:text-sm">
|
||||
Version {version}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Modified copy of ga.tsx from @next/third-parties/google, with modified gtag.js source URL.
|
||||
* Original source file: https://github.com/vercel/next.js/blob/b304b45e3a6e3e79338568d76e28805e77c03ec9/packages/third-parties/src/google/ga.tsx
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import type { GAParams } from "@/types/google";
|
||||
import Script from "next/script";
|
||||
import { useEffect } from "react";
|
||||
|
||||
let currDataLayerName: string | undefined = undefined;
|
||||
|
||||
export function GoogleAnalytics(props: GAParams) {
|
||||
const { gaId, debugMode, dataLayerName = "dataLayer", nonce } = props;
|
||||
|
||||
if (currDataLayerName === undefined) {
|
||||
currDataLayerName = dataLayerName;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Feature usage signal (same as original implementation)
|
||||
performance.mark("mark_feature_usage", {
|
||||
detail: {
|
||||
feature: "custom-ga",
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
id="_custom-ga-init"
|
||||
// Using "afterInteractive" to avoid blocking the initial page rendering
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window['${dataLayerName}'] = window['${dataLayerName}'] || [];
|
||||
function gtag(){window['${dataLayerName}'].push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${gaId}' ${debugMode ? ",{ 'debug_mode': true }" : ""});
|
||||
`,
|
||||
}}
|
||||
nonce={nonce}
|
||||
/>
|
||||
<Script
|
||||
id="_custom-ga"
|
||||
strategy="afterInteractive"
|
||||
src="/gtag.js"
|
||||
nonce={nonce}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function sendGAEvent(...args: any[]) {
|
||||
if (currDataLayerName === undefined) {
|
||||
console.warn(`Custom GA: GA has not been initialized`);
|
||||
return;
|
||||
}
|
||||
|
||||
const dataLayer = (window as any)[currDataLayerName];
|
||||
if (dataLayer) {
|
||||
dataLayer.push(...args);
|
||||
} else {
|
||||
console.warn(`Custom GA: dataLayer ${currDataLayerName} does not exist`);
|
||||
}
|
||||
}
|
||||
114
autogpt_platform/frontend/src/services/analytics/index.tsx
Normal file
114
autogpt_platform/frontend/src/services/analytics/index.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Modified copy of ga.tsx from @next/third-parties/google, with modified gtag.js source URL.
|
||||
* Original source file: https://github.com/vercel/next.js/blob/b304b45e3a6e3e79338568d76e28805e77c03ec9/packages/third-parties/src/google/ga.tsx
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import type { GAParams } from "@/types/google";
|
||||
import Script from "next/script";
|
||||
import { useEffect } from "react";
|
||||
import { environment } from "../environment";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
datafast: (name: string, metadata: Record<string, unknown>) => void;
|
||||
[key: string]: unknown[] | ((...args: unknown[]) => void) | unknown;
|
||||
}
|
||||
}
|
||||
|
||||
let currDataLayerName: string | undefined = undefined;
|
||||
|
||||
type SetupProps = {
|
||||
ga: GAParams;
|
||||
host: string;
|
||||
};
|
||||
|
||||
export function SetupAnalytics(props: SetupProps) {
|
||||
const { ga, host } = props;
|
||||
const { gaId, debugMode, dataLayerName = "dataLayer", nonce } = ga;
|
||||
const isProductionDomain = host.includes("platform.agpt.co");
|
||||
|
||||
// Datafa.st journey analytics only on production
|
||||
const dataFastEnabled = isProductionDomain;
|
||||
// We collect analytics too for open source developers running the platform locally
|
||||
const googleAnalyticsEnabled = environment.isLocal() || isProductionDomain;
|
||||
|
||||
if (currDataLayerName === undefined) {
|
||||
currDataLayerName = dataLayerName;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!googleAnalyticsEnabled) return;
|
||||
|
||||
// Google Analytics: feature usage signal (same as original implementation)
|
||||
performance.mark("mark_feature_usage", {
|
||||
detail: {
|
||||
feature: "custom-ga",
|
||||
},
|
||||
});
|
||||
}, [googleAnalyticsEnabled]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Google Analytics */}
|
||||
{googleAnalyticsEnabled ? (
|
||||
<>
|
||||
<Script
|
||||
id="_custom-ga-init"
|
||||
strategy="afterInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
window['${dataLayerName}'] = window['${dataLayerName}'] || [];
|
||||
function gtag(){window['${dataLayerName}'].push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${gaId}' ${debugMode ? ",{ 'debug_mode': true }" : ""});
|
||||
`,
|
||||
}}
|
||||
nonce={nonce}
|
||||
/>
|
||||
{/* Google Tag Manager */}
|
||||
<Script
|
||||
id="_custom-ga"
|
||||
strategy="afterInteractive"
|
||||
src="/gtag.js"
|
||||
nonce={nonce}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{/* Datafa.st */}
|
||||
{dataFastEnabled ? (
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
data-website-id="dfid_g5wtBIiHUwSkWKcGz80lu"
|
||||
data-domain="agpt.co"
|
||||
src="https://datafa.st/js/script.js"
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const analytics = {
|
||||
sendGAEvent,
|
||||
sendDatafastEvent,
|
||||
};
|
||||
|
||||
function sendGAEvent(...args: unknown[]) {
|
||||
if (typeof window === "undefined") return;
|
||||
if (currDataLayerName === undefined) return;
|
||||
|
||||
const dataLayer = window[currDataLayerName];
|
||||
if (!dataLayer) return;
|
||||
|
||||
if (Array.isArray(dataLayer)) {
|
||||
dataLayer.push(...args);
|
||||
} else {
|
||||
console.warn(`Custom GA: dataLayer ${currDataLayerName} does not exist`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendDatafastEvent(name: string, metadata: Record<string, unknown>) {
|
||||
if (typeof window === "undefined" || !window.datafast) return;
|
||||
window.datafast(name, metadata);
|
||||
}
|
||||
@@ -92,10 +92,6 @@ function areFeatureFlagsEnabled() {
|
||||
return process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "enabled";
|
||||
}
|
||||
|
||||
function areAnalyticsEnabled(host: string) {
|
||||
return host.includes("platform.agpt.co");
|
||||
}
|
||||
|
||||
export const environment = {
|
||||
// Generic
|
||||
getEnvironmentStr,
|
||||
@@ -115,6 +111,5 @@ export const environment = {
|
||||
isCloud,
|
||||
isLocal,
|
||||
isCAPTCHAEnabled,
|
||||
areAnalyticsEnabled,
|
||||
areFeatureFlagsEnabled,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user