diff --git a/docs/product/explore/logs/getting-started/index.mdx b/docs/product/explore/logs/getting-started/index.mdx index a743b1fe17cd0..10d82fbc7a1fd 100644 --- a/docs/product/explore/logs/getting-started/index.mdx +++ b/docs/product/explore/logs/getting-started/index.mdx @@ -14,156 +14,187 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s platform="javascript.browser" label="Browser JavaScript" url="/platforms/javascript/logs/" + skill="sentry-browser-sdk" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ### Java @@ -190,40 +221,47 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s platform="android" label="Android" url="/platforms/android/logs/" + skill="sentry-android-sdk" /> - - - - ### PHP -- +- - - ### Python @@ -232,6 +270,7 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s platform="python" label="Python" url="/platforms/python/logs/" + skill="sentry-python-sdk" /> ### Ruby @@ -240,16 +279,18 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s platform="ruby" label="Ruby" url="/platforms/ruby/logs/" + skill="sentry-ruby-sdk" /> - ### Go -- +- ### Rust @@ -265,26 +306,31 @@ To set up Sentry Logs, use the links below for supported SDKs. After it's been s platform="dotnet" label=".NET" url="/platforms/dotnet/logs/" + skill="sentry-dotnet-sdk" /> - - - - ### Native diff --git a/docs/product/explore/metrics/getting-started/index.mdx b/docs/product/explore/metrics/getting-started/index.mdx index c15e6d8c0bef2..49352cc718de3 100644 --- a/docs/product/explore/metrics/getting-started/index.mdx +++ b/docs/product/explore/metrics/getting-started/index.mdx @@ -14,156 +14,187 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="javascript.browser" label="Browser JavaScript" url="/platforms/javascript/metrics/" + skill="sentry-browser-sdk" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ### Java @@ -190,6 +221,7 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="elixir" label="Elixir" url="/platforms/elixir/metrics/" + skill="sentry-elixir-sdk" /> ### Mobile @@ -198,41 +230,49 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="android" label="Android" url="/platforms/android/metrics/" + skill="sentry-android-sdk" /> - - - - - - - ### Python @@ -241,41 +281,49 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="python" label="Python" url="/platforms/python/metrics/" + skill="sentry-python-sdk" /> - - - - - - - ### PHP @@ -284,16 +332,19 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="php" label="PHP" url="/platforms/php/metrics/" + skill="sentry-php-sdk" /> - - ### Ruby @@ -302,6 +353,7 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="ruby" label="Ruby (incl. Rails)" url="/platforms/ruby/metrics/" + skill="sentry-ruby-sdk" /> ### Go @@ -310,6 +362,7 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="go" label="Go" url="/platforms/go/metrics/" + skill="sentry-go-sdk" /> ### .NET @@ -318,91 +371,109 @@ To set up Sentry's Application Metrics, use the links below for supported SDKs. platform="dotnet" label=".NET" url="/platforms/dotnet/metrics/" + skill="sentry-dotnet-sdk" /> - - - - - - - - - - - - - - - - - ### Native diff --git a/docs/product/explore/profiling/getting-started.mdx b/docs/product/explore/profiling/getting-started.mdx index 3561477c4c9c2..847f18be738bf 100644 --- a/docs/product/explore/profiling/getting-started.mdx +++ b/docs/product/explore/profiling/getting-started.mdx @@ -26,11 +26,13 @@ Continuous Profiling can be used both independently and as a complement to the t platform="python" label="Python (since version 2.24.1)" url="/platforms/python/profiling/" + skill="sentry-python-sdk" /> - ### UI Profiling @@ -45,21 +47,25 @@ UI Profiling can be used both independently and as a complement to the tracing p platform="apple" label="iOS & macOS (since version 8.49.0)" url="/platforms/apple/profiling/" + skill="sentry-cocoa-sdk" /> - - - ### Transaction-based Profiling @@ -78,16 +84,19 @@ Transaction-based Profiling requires Sentry's tracing product being enabled befo platform="react-native" label="React Native (beta)" url="/platforms/react-native/profiling/" + skill="sentry-react-native-sdk" /> - - #### Standalone and server apps @@ -96,14 +105,17 @@ Transaction-based Profiling requires Sentry's tracing product being enabled befo platform="php" label="PHP" url="/platforms/php/profiling/" + skill="sentry-php-sdk" /> - - diff --git a/src/components/agentSkillsCallout/index.tsx b/src/components/agentSkillsCallout/index.tsx index a1672cb68d9b2..3cea70876d787 100644 --- a/src/components/agentSkillsCallout/index.tsx +++ b/src/components/agentSkillsCallout/index.tsx @@ -9,11 +9,9 @@ import {DocMetrics} from 'sentry-docs/metrics'; import {CodeBlock} from '../codeBlock'; import {CodeTabs} from '../codeTabs'; +import {buildDotagentsCommand, buildNpxSkillsCommand, buildPrompt} from './shared'; import styles from './style.module.scss'; -const SKILLS_BASE_URL = 'https://skills.sentry.dev'; -const SKILLS_REPO = 'getsentry/sentry-for-ai'; - type Props = { /** Display name for the platform, e.g. "Next.js", "Python". Omit for generic. */ platformName?: string; @@ -21,32 +19,6 @@ type Props = { skill?: string; }; -function buildPromptUrl(skill?: string): string { - if (!skill) { - return SKILLS_BASE_URL + '/'; - } - return `${SKILLS_BASE_URL}/${skill}/SKILL.md`; -} - -function buildPrompt(skill?: string): string { - const url = buildPromptUrl(skill); - return `Use curl to download, read and follow: ${url}`; -} - -function buildDotagentsCommand(skill?: string): string { - if (!skill) { - return `npx @sentry/dotagents add ${SKILLS_REPO} --all`; - } - return `npx @sentry/dotagents add ${SKILLS_REPO} --name ${skill}`; -} - -function buildNpxSkillsCommand(skill?: string): string { - if (!skill) { - return `npx skills add ${SKILLS_REPO}`; - } - return `npx skills add ${SKILLS_REPO} --skill ${skill}`; -} - export function AgentSkillsCallout({skill, platformName}: Props) { const [copied, setCopied] = useState(false); const [isExpanded, setIsExpanded] = useState(false); diff --git a/src/components/agentSkillsCallout/shared.ts b/src/components/agentSkillsCallout/shared.ts new file mode 100644 index 0000000000000..3decc8ede4243 --- /dev/null +++ b/src/components/agentSkillsCallout/shared.ts @@ -0,0 +1,33 @@ +/** + * Shared constants and utilities for agent skills prompt building. + * Used by AgentSkillsCallout (full banner) and inline copy-prompt buttons. + */ + +export const SKILLS_BASE_URL = 'https://skills.sentry.dev'; +export const SKILLS_REPO = 'getsentry/sentry-for-ai'; + +export function buildPromptUrl(skill?: string): string { + if (!skill) { + return SKILLS_BASE_URL + '/'; + } + return `${SKILLS_BASE_URL}/${skill}/SKILL.md`; +} + +export function buildPrompt(skill?: string): string { + const url = buildPromptUrl(skill); + return `Use curl to download, read and follow: ${url}`; +} + +export function buildDotagentsCommand(skill?: string): string { + if (!skill) { + return `npx @sentry/dotagents add ${SKILLS_REPO} --all`; + } + return `npx @sentry/dotagents add ${SKILLS_REPO} --name ${skill}`; +} + +export function buildNpxSkillsCommand(skill?: string): string { + if (!skill) { + return `npx skills add ${SKILLS_REPO}`; + } + return `npx skills add ${SKILLS_REPO} --skill ${skill}`; +} diff --git a/src/components/copyPromptButton/index.tsx b/src/components/copyPromptButton/index.tsx new file mode 100644 index 0000000000000..b010c98c41971 --- /dev/null +++ b/src/components/copyPromptButton/index.tsx @@ -0,0 +1,132 @@ +'use client'; + +import * as Tooltip from '@radix-ui/react-tooltip'; +import {Theme} from '@radix-ui/themes'; +import * as Sentry from '@sentry/nextjs'; +import {useTheme} from 'next-themes'; +import {useCallback, useMemo, useState} from 'react'; +import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; +import {DocMetrics} from 'sentry-docs/metrics'; + +import {buildPrompt} from '../agentSkillsCallout/shared'; +import styles from './style.module.scss'; + +type Props = { + /** Skill package name, e.g. "sentry-nextjs-sdk". */ + skill: string; +}; + +export function CopyPromptButton({skill}: Props) { + const [copied, setCopied] = useState(false); + const {emit} = usePlausibleEvent(); + const {resolvedTheme} = useTheme(); + const isDark = resolvedTheme === 'dark'; + + const prompt = buildPrompt(skill); + + /** + * Tooltip styles matching the onboarding tooltip pattern. + * Inline styles are required because Radix portals the tooltip to document.body, + * outside the Theme wrapper, so CSS module classes don't reliably apply. + */ + const tooltipContentStyle: React.CSSProperties = useMemo( + () => ({ + borderRadius: '4px', + padding: '8px 12px', + fontSize: '12px', + lineHeight: 1.2, + textAlign: 'center' as const, + color: isDark ? 'var(--foreground)' : 'var(--gray-11)', + backgroundColor: isDark ? 'var(--gray-4)' : 'white', + boxShadow: '0px 4px 16px 0px rgba(31, 22, 51, 0.1)', + zIndex: 9999, + position: 'relative' as const, + pointerEvents: 'none' as const, + userSelect: 'none' as const, + whiteSpace: 'nowrap' as const, + animationDuration: '100ms', + animationTimingFunction: 'ease-in', + }), + [isDark] + ); + + const tooltipArrowStyle: React.CSSProperties = useMemo( + () => ({ + fill: isDark ? 'var(--gray-4)' : 'white', + }), + [isDark] + ); + + const copyPrompt = useCallback( + async (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + + emit('Copy AI Prompt', { + props: {page: window.location.pathname, title: 'Inline Platform Link'}, + }); + + try { + setCopied(false); + await navigator.clipboard.writeText(prompt); + setCopied(true); + DocMetrics.copyAIPrompt(window.location.pathname, skill, true, 'inline_link'); + setTimeout(() => setCopied(false), 1500); + } catch (error) { + Sentry.captureException(error); + DocMetrics.copyAIPrompt(window.location.pathname, skill, false, 'inline_link'); + setCopied(false); + } + }, + [prompt, emit, skill] + ); + + return ( + + | + + + + + + {!copied && ( + + + + Copy setup prompt for AI agents + + + + + )} + + + + ); +} + +function CopyIcon() { + return ( + + + + + ); +} diff --git a/src/components/copyPromptButton/style.module.scss b/src/components/copyPromptButton/style.module.scss new file mode 100644 index 0000000000000..4bc6f92171583 --- /dev/null +++ b/src/components/copyPromptButton/style.module.scss @@ -0,0 +1,70 @@ +.wrapper { + display: inline-flex; + align-items: center; + margin-left: 0.4rem; + vertical-align: middle; + + @media (max-width: 768px) { + display: none; + } +} + +.divider { + color: var(--gray-200); + font-size: 0.85rem; + margin-right: 0.4rem; + user-select: none; +} + +:global(.dark) .divider { + color: var(--gray-600); +} + +.button { + display: inline-flex; + align-items: center; + gap: 0.3rem; + padding: 0.15rem 0.5rem; + border: 1px solid color-mix(in srgb, var(--accent-purple), transparent 60%); + border-radius: 4px; + background: color-mix(in srgb, var(--accent-purple), transparent 92%); + color: var(--accent-purple); + font-size: 0.72rem; + font-weight: 500; + white-space: nowrap; + cursor: pointer; + transition: + background-color 150ms, + border-color 150ms; + line-height: 1.4; + + &:hover { + background: color-mix(in srgb, var(--accent-purple), transparent 82%); + border-color: color-mix(in srgb, var(--accent-purple), transparent 40%); + } + + &:active { + background: color-mix(in srgb, var(--accent-purple), transparent 72%); + } +} + +:global(.dark) .button { + background: color-mix(in srgb, var(--accent-purple), transparent 85%); + border-color: color-mix(in srgb, var(--accent-purple), transparent 50%); + color: var(--accent-purple-light, var(--accent-purple)); + + &:hover { + background: color-mix(in srgb, var(--accent-purple), transparent 75%); + border-color: color-mix(in srgb, var(--accent-purple), transparent 30%); + } + + &:active { + background: color-mix(in srgb, var(--accent-purple), transparent 65%); + } +} + +.label { + letter-spacing: 0.01em; +} + + diff --git a/src/components/linkWithPlatformIcon.tsx b/src/components/linkWithPlatformIcon.tsx index 84b22326de675..17ae9d8b53e1a 100644 --- a/src/components/linkWithPlatformIcon.tsx +++ b/src/components/linkWithPlatformIcon.tsx @@ -1,13 +1,16 @@ +import {CopyPromptButton} from './copyPromptButton'; import {PlatformIcon} from './platformIcon'; import {SmartLink} from './smartLink'; type Props = { label?: string; platform?: string; + /** Agent skill name, e.g. "sentry-react-sdk". When provided, renders an inline "Agent Setup" copy-prompt button. */ + skill?: string; url?: string; }; -export function LinkWithPlatformIcon({platform, label, url}: Props) { +export function LinkWithPlatformIcon({platform, label, url, skill}: Props) { if (!platform) { return null; } @@ -27,6 +30,7 @@ export function LinkWithPlatformIcon({platform, label, url}: Props) { /> {label ?? platform} + {skill && } ); } diff --git a/src/metrics.ts b/src/metrics.ts index d4b34347e6c1d..8e0343e3f7064 100644 --- a/src/metrics.ts +++ b/src/metrics.ts @@ -166,13 +166,20 @@ export const DocMetrics = { * @param pathname - Page where the prompt was copied * @param skill - Skill name if present (e.g., "sentry-nextjs-sdk") * @param success - Whether the clipboard copy succeeded + * @param source - Where the copy was triggered from ('callout' for the full banner, 'inline_link' for platform list buttons) */ - copyAIPrompt: (pathname: string, skill: string | undefined, success: boolean) => { + copyAIPrompt: ( + pathname: string, + skill: string | undefined, + success: boolean, + source: 'callout' | 'inline_link' = 'callout' + ) => { Sentry.metrics.count('docs.copy_ai_prompt', 1, { attributes: { page_path: pathname.split('/').slice(0, 3).join('/'), // First 3 segments skill: skill || 'generic', success, + source, }, }); },