Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion app/_components/integration-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Card, CardContent } from "@arcadeai/design-system";
import { cn } from "@arcadeai/design-system/lib/utils";
import type { LucideIcon } from "lucide-react";
import { usePostHog } from "posthog-js/react";

type IntegrationCardProps = {
id: string;
Expand All @@ -13,12 +14,24 @@ type IntegrationCardProps = {
};

export function IntegrationCard({
id,
icon: Icon,
title,
description,
isActive = false,
onClick,
}: IntegrationCardProps) {
const posthog = usePostHog();

const handleClick = () => {
posthog?.capture("integration_card_clicked", {
integration_id: id,
integration_title: title,
is_active: isActive,
});
onClick();
};

return (
<Card
className={cn(
Expand All @@ -27,7 +40,7 @@ export function IntegrationCard({
? "border-primary/50 bg-primary/5"
: "border-zinc-800 bg-zinc-900/50 hover:bg-zinc-900"
)}
onClick={onClick}
onClick={handleClick}
>
<div
className={cn(
Expand Down
17 changes: 12 additions & 5 deletions app/_components/posthog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ export const PostHog = ({ children }: { children: React.ReactNode }) => {
process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
ui_host:
process.env.NEXT_PUBLIC_POSTHOG_UI_HOST || "https://us.posthog.com",
disable_session_recording: true,
// Enable session recording for user behavior analysis
disable_session_recording: false,
session_recording: {
maskAllInputs: true, // Privacy: mask sensitive input fields
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maskAllInputs and maskTextContent seem to be in conflict - which one wins?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the envoy completely hallucinated maskTextContent and Claude Code was just going with it. I had a word with them.

blockClass: "ph-no-capture", // Allow opting out specific elements
recordCrossOriginIframes: false, // Don't record third-party iframes
},
// Enable heatmaps for click tracking
enable_heatmaps: true,
// Enable surveys for CSAT feedback
disable_surveys: false,
loaded: (posthogInstance) => {
if (
process.env.NODE_ENV === "development" ||
process.env.NEXT_PUBLIC_POSTHOG_DEBUG
) {
if (process.env.NEXT_PUBLIC_POSTHOG_DEBUG === "true") {
posthogInstance.debug();
}
},
Expand Down
20 changes: 18 additions & 2 deletions app/_components/quick-start-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "@arcadeai/design-system";
import { motion } from "motion/react";
import Link from "next/link";
import { usePostHog } from "posthog-js/react";

type QuickStartCardProps = {
icon: React.ElementType;
Expand All @@ -25,6 +26,16 @@ export function QuickStartCard({
onClick,
code,
}: QuickStartCardProps) {
const posthog = usePostHog();

const handleCardClick = () => {
posthog?.capture("quickstart_card_clicked", {
card_title: title,
card_href: href || null,
has_custom_onclick: !!onClick,
});
};

const content = (
<>
<CardHeader className="flex flex-row items-center gap-3 p-6">
Expand All @@ -48,20 +59,25 @@ export function QuickStartCard({
</>
);

const handleClick = () => {
handleCardClick();
onClick?.();
};

let wrapper: React.ReactElement | null = null;
if (onClick) {
wrapper = (
<button
className="block h-full w-full text-left"
onClick={onClick}
onClick={handleClick}
type="button"
>
{content}
</button>
);
} else if (href) {
wrapper = (
<Link className="block h-full" href={href}>
<Link className="block h-full" href={href} onClick={handleCardClick}>
{content}
</Link>
);
Expand Down
18 changes: 17 additions & 1 deletion app/_components/sample-app-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { Card, CardContent } from "@arcadeai/design-system";
import Link from "next/link";
import { usePostHog } from "posthog-js/react";

type SampleAppCardProps = {
title: string;
Expand All @@ -21,8 +22,23 @@ export function SampleAppCard({
tags = [],
date,
}: SampleAppCardProps) {
const posthog = usePostHog();

const handleClick = () => {
posthog?.capture("sample_app_clicked", {
app_title: title,
app_href: href,
tags,
opens_in_new_tab: blank,
});
};

return (
<Link href={href} target={blank ? "_blank" : undefined}>
<Link
href={href}
onClick={handleClick}
target={blank ? "_blank" : undefined}
>
<Card className="flex h-full flex-col gap-1.5 border border-gray-600/20 bg-white/90 py-3 backdrop-blur-sm transition-all duration-300 hover:border-primary hover:bg-gray-600/[0.03] hover:shadow-lg dark:bg-gray-900/80">
<CardContent className="p-0">
<div className="space-y-2 p-6">
Expand Down
23 changes: 23 additions & 0 deletions app/_components/scope-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { usePostHog } from "posthog-js/react";
import { useState } from "react";

type Tool = {
Expand All @@ -13,23 +14,45 @@ type ScopePickerProps = {

export default function ScopePicker({ tools }: ScopePickerProps) {
const [selectedTools, setSelectedTools] = useState<Set<string>>(new Set());
const posthog = usePostHog();

const trackScopeCalculatorUsed = (
action: string,
toolName: string | undefined,
newSelectedCount: number
) => {
posthog?.capture("scope_calculator_used", {
action,
tool_name: toolName || null,
selected_tools_count: newSelectedCount,
total_tools_available: tools.length,
});
};

const toggleTool = (toolName: string) => {
const newSelected = new Set(selectedTools);
const isSelecting = !newSelected.has(toolName);
if (newSelected.has(toolName)) {
newSelected.delete(toolName);
} else {
newSelected.add(toolName);
}
setSelectedTools(newSelected);
trackScopeCalculatorUsed(
isSelecting ? "tool_selected" : "tool_deselected",
toolName,
newSelected.size
);
};

const selectAll = () => {
setSelectedTools(new Set(tools.map((t) => t.name)));
trackScopeCalculatorUsed("select_all", undefined, tools.length);
};

const clearAll = () => {
setSelectedTools(new Set());
trackScopeCalculatorUsed("clear_all", undefined, 0);
};

// Get unique scopes from selected tools
Expand Down
12 changes: 11 additions & 1 deletion app/_components/tabbed-code-block.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { Button } from "@arcadeai/design-system";
import { ChevronDown } from "lucide-react";
import { usePostHog } from "posthog-js/react";
import React, { useMemo, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {
Expand All @@ -27,6 +28,7 @@ const CodeTabSwitcher = ({ tabs }: CodeTabSwitcherProps) => {
const [activeTab, setActiveTab] = useState(0);
const [selectedLanguage, setSelectedLanguage] = useState("Python");
const [isDarkMode, setIsDarkMode] = useState(false);
const posthog = usePostHog();

// Effect to monitor theme changes
React.useEffect(() => {
Expand Down Expand Up @@ -104,11 +106,19 @@ const CodeTabSwitcher = ({ tabs }: CodeTabSwitcherProps) => {
fileName
);

const handleExpandExample = () => {
setIsExpanded(true);
posthog?.capture("code_example_expanded", {
tab_count: tabs.length,
initial_language: selectedLanguage,
});
};

if (!isExpanded) {
return (
<Button
className="mt-2"
onClick={() => setIsExpanded(true)}
onClick={handleExpandExample}
size="sm"
variant="outline"
>
Expand Down
10 changes: 9 additions & 1 deletion app/_components/tabbed-code-block/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { Button } from "@arcadeai/design-system";
import { Check, Copy } from "lucide-react";
import { usePostHog } from "posthog-js/react";
import { useCallback, useState } from "react";

const COPY_TIMEOUT_MS = 1500;
Expand All @@ -13,6 +14,7 @@ export function CopyButton({
disabled: boolean;
}) {
const [copied, setCopied] = useState(false);
const posthog = usePostHog();

const handleCopy = useCallback(async () => {
if (!content) {
Expand All @@ -22,10 +24,16 @@ export function CopyButton({
await navigator.clipboard.writeText(content);
setCopied(true);
setTimeout(() => setCopied(false), COPY_TIMEOUT_MS);

// Track code copy event
posthog?.capture("code_copied", {
content_length: content.length,
content_preview: content.substring(0, 100),
});
} catch {
// Silent fail
}
}, [content]);
}, [content, posthog]);

return (
<Button
Expand Down
2 changes: 1 addition & 1 deletion app/en/get-started/quickstarts/call-tool-agent/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ console.log(respose_send_email.output?.value);

## Next Steps

In this simple example, we call the tool methods directly. In your real applications and agents, you'll likely be letting the LLM decide which tools to call. Learn more about using Arcade with Frameworks in the [Frameworks](/guides/agent-frameworks/langchain/python section, or [how to build your own tools](/guides/create-tools/tool-basics/build-mcp-server).
In this simple example, we call the tool methods directly. In your real applications and agents, you'll likely be letting the LLM decide which tools to call. Learn more about using Arcade with Frameworks in the [Frameworks](/guides/agent-frameworks) section, or [how to build your own tools](/guides/create-tools/tool-basics/build-mcp-server).

## Example Code

Expand Down
2 changes: 1 addition & 1 deletion app/en/get-started/quickstarts/call-tool-client/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,6 @@ As you interact with the agent, it will call the tools from the MCP Gateway. You

- Learn more about [MCP Gateways](/guides/create-tools/mcp-gateways).
- Learn how to use MCP Gateways with:
- [Cursor](/guides/tool-calling/mcp-clients/visual-studio-code)
- [Cursor](/guides/tool-calling/mcp-clients/cursor)
- [Visual Studio Code](/guides/tool-calling/mcp-clients/visual-studio-code)
- Build your own MCP servers with [arcade-mcp](/get-started/quickstarts/mcp-server-quickstart).
4 changes: 2 additions & 2 deletions app/en/guides/deployment-hosting/on-prem/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ You can now test your MCP Server by making requests using the Playground, or an
1. Use an app that supports MCP clients, like AI assistants and IDEs:
- [Visual Studio Code](/guides/tool-calling/mcp-clients/visual-studio-code)
- [Claude Desktop](/guides/tool-calling/mcp-clients/visual-studio-code)
- [Cursor](/guides/tool-calling/mcp-clients/visual-studio-code)
- [Claude Desktop](/guides/tool-calling/mcp-clients/claude-desktop)
- [Cursor](/guides/tool-calling/mcp-clients/cursor)
1. Enable your MCP Server from the list of available MCP Servers
1. Verify that the response is correct and you see request logs in your MCP Server
Expand Down
21 changes: 19 additions & 2 deletions app/en/home/landing-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { motion } from "motion/react";
import Image from "next/image";
import Link from "next/link";
import { usePostHog } from "posthog-js/react";
// import { ChallengeSolution } from "./ChallengeSolution";
import { QuickStartCard } from "../../_components/quick-start-card";
import { SampleAppCard } from "../../_components/sample-app-card";
Expand All @@ -35,6 +36,16 @@ const ANIMATION_DELAYS = {
} as const;

export function LandingPage() {
const posthog = usePostHog();

const trackHeroClick =
(eventName: string) => (e: React.MouseEvent<HTMLAnchorElement>) => {
posthog?.capture(eventName, {
button_location: "landing_page_hero",
destination: e.currentTarget.getAttribute("href"),
});
};

return (
<div>
<section className="relative isolate px-6 lg:px-8">
Expand Down Expand Up @@ -108,7 +119,10 @@ export function LandingPage() {
className="h-12 bg-primary px-6 text-white hover:bg-primary/90"
size="lg"
>
<Link href="/get-started/quickstarts/call-tool-agent">
<Link
href="/get-started/quickstarts/call-tool-agent"
onClick={trackHeroClick("get_started_clicked")}
>
<Rocket className="mr-2 h-5 w-5" />
Get Started
</Link>
Expand All @@ -119,7 +133,10 @@ export function LandingPage() {
size="lg"
variant="outline"
>
<Link href="/guides/create-tools/tool-basics/build-mcp-server">
<Link
href="/guides/create-tools/tool-basics/build-mcp-server"
onClick={trackHeroClick("build_tool_clicked")}
>
<Wrench className="mr-2 h-5 w-5" />
Build a tool
</Link>
Expand Down
19 changes: 9 additions & 10 deletions app/en/references/auth-providers/airtable/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Tabs, Callout, Steps } from "nextra/components";
The Airtable auth provider enables tools and agents to call [Airtable APIs](https://airtable.com/developers/web/api/introduction) on behalf of a user using OAuth 2.0 authentication.

<Callout>
Want to quickly get started with Airtable in your agent or AI app? The pre-built
[Arcade Airtable MCP Server](/resources/integrations/productivity/airtable-api) is what you
want!
Want to quickly get started with Airtable in your agent or AI app? The
pre-built [Arcade Airtable MCP
Server](/resources/integrations/productivity/airtable-api) is what you want!
</Callout>

### What's documented here
Expand Down Expand Up @@ -115,7 +115,7 @@ Hit the **Create** button and the provider will be ready to be used.

<Callout type="info">
This method is only available when you are [self-hosting the
engine](/guides/deployment-hosting/on-prem
engine](/guides/deployment-hosting/on-prem)
</Callout>

<Steps>
Expand Down Expand Up @@ -224,11 +224,11 @@ const client = new Arcade();
const userId = "{arcade_user_id}";

// Start the authorization process
const authResponse = await client.auth.start(
userId,
"arcade-airtable",
["data.records:read", "data.records:write", "schema.bases:read"]
);
const authResponse = await client.auth.start(userId, "arcade-airtable", [
"data.records:read",
"data.records:write",
"schema.bases:read",
]);

if (authResponse.status !== "completed") {
console.log("Please complete the authorization challenge in your browser:");
Expand Down Expand Up @@ -299,4 +299,3 @@ Airtable supports various OAuth scopes that determine the level of access your a
- `user.email:read` - Read user email address

For a complete list of available scopes, refer to the [Airtable Scopes documentation](https://airtable.com/developers/web/api/scopes).

Loading