mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
e3c677fc5a
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Add telemetry for `PromptPanel` to help us understand how people interact with our AI prompts better. Relates to DOCS-1393 Dashboard(restricted access): [Docs: AI prompt affordances](https://eu.posthog.com/project/34344/dashboard/957235) ## What is the current behavior? The docs homepage cover renders a setup panel with "AI Prompt" and "CLI" tabs, and guides render `AiPrompt` blocks. Both are built on the shared `PromptPanel`, whose copy button called `copyToClipboard` and nothing else. Copying was therefore unmeasured, while the neighbouring affordances (`ask_ai_clicked`, `agent_setup_clicked`, `copy_as_markdown_clicked`) are already instrumented. ## What is the new behavior? `PromptPanel` takes an optional `telemetry` prop. When it is set, the panel sends a new docs-owned event after a **successful** clipboard write, so instrumentation lives in the shared component instead of a forked homepage copy button. New event in `packages/common/telemetry-constants.ts`: | | | | --- | --- | | `action` | `docs_ai_prompt_copied` | | `source` | `homepage` \| `guide` \| `agent_setup` | | `tab` | `prompt` \| `cli` (omitted for panes outside that set) | | `promptId` | prompt id, when the panel comes from an `AiPrompt` block | Wired consumers: `HomePageCover` (`homepage`), `AiPrompt` (`guide` by default, plus `promptId`), and `AgentSetup` (`agent_setup`). No prompt body text and no PII is sent. Studio's existing `ai_prompt_copied` event is deliberately left alone: it has a different owner and surface, and merging the two would blend unrelated funnels. ### Proof it works ``` $ pnpm run test:local:unwatch features/ui/PromptPanel.telemetry.test.ts RUN v5.0.0 /apps/docs Test Files 1 passed (1) Tests 4 passed (4) Duration 775ms ``` ## Additional context Test plan, run against a local docs server with a stub telemetry endpoint so the request bodies could be read directly: | Case | Observed payload | | --- | --- | | Homepage, AI Prompt tab | `{"source":"homepage","tab":"prompt"}` | | Homepage, CLI tab | `{"source":"homepage","tab":"cli"}` | | Next.js quickstart `AiPrompt` | `{"source":"guide","tab":"prompt","promptId":"nextjs"}` | | `automate-with-agents/health` `AgentSetup` | `{"source":"agent_setup","tab":"prompt","promptId":"monitoring-agent-health"}` | | Clipboard write rejected | no request sent, error toast shown, button does not flip to "copied" | The failure case was re-checked with a control click on the same page after restoring a working clipboard, which did send the event, so the negative result is not just a missed handler. Also run: `turbo typecheck --filter=docs --filter=common` (passes), Prettier check on the touched files (passes), and ESLint on the touched docs files (no new findings; the one warning on `HomePageCover` is the pre-existing default export). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Successful prompt copies are now tracked across the homepage, documentation guides, and agent setup experiences. * Copy activity records the prompt’s source, selected format, and associated prompt when available, providing more complete usage insights. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Nik Richers <nik@validmind.ai>
109 lines
2.8 KiB
TypeScript
109 lines
2.8 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
getMonitoringAgent,
|
|
getMonitoringAgentHarnesses,
|
|
type MonitoringAgentHarnessSetup,
|
|
} from '~/data/monitoring-agents.utils'
|
|
import { Sparkles } from 'lucide-react'
|
|
import { useTheme } from 'next-themes'
|
|
import { type ReactNode } from 'react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import { ConnectionIcon } from 'ui-patterns/McpUrlBuilder'
|
|
|
|
import { AiPrompt } from './AiPrompt'
|
|
import { PromptCode } from './PromptPanel'
|
|
import { TabPanel, Tabs } from './Tabs'
|
|
|
|
type AgentSetupProps = {
|
|
id: string
|
|
}
|
|
|
|
const markdownComponents = {
|
|
p: ({ children }: { children?: ReactNode }) => <>{children}</>,
|
|
a: ({ href, children }: { href?: string; children?: ReactNode }) => {
|
|
if (!href) return <>{children}</>
|
|
const external = /^(?:[a-z][a-z0-9+\-.]*:|\/\/)/i.test(href)
|
|
return (
|
|
<a
|
|
href={href}
|
|
className="text-brand-link hover:underline"
|
|
{...(external ? { target: '_blank', rel: 'noreferrer noopener' } : {})}
|
|
>
|
|
{children}
|
|
</a>
|
|
)
|
|
},
|
|
code: PromptCode,
|
|
}
|
|
|
|
function HarnessBody({ harness }: { harness: MonitoringAgentHarnessSetup }) {
|
|
return (
|
|
<>
|
|
<p>{harness.intro}</p>
|
|
<ol>
|
|
{harness.steps.map((step) => (
|
|
<li key={step}>
|
|
<ReactMarkdown components={markdownComponents}>{step}</ReactMarkdown>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
{harness.note && (
|
|
<p>
|
|
<ReactMarkdown components={markdownComponents}>{harness.note}</ReactMarkdown>
|
|
</p>
|
|
)}
|
|
<p>
|
|
<a
|
|
href={harness.docsUrl}
|
|
className="text-brand-link hover:underline"
|
|
target="_blank"
|
|
rel="noreferrer noopener"
|
|
>
|
|
{harness.label} docs
|
|
</a>
|
|
</p>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function AgentSetup({ id }: AgentSetupProps) {
|
|
const agent = getMonitoringAgent(id)
|
|
const harnesses = getMonitoringAgentHarnesses(agent)
|
|
const { resolvedTheme } = useTheme()
|
|
const theme = resolvedTheme?.includes('dark') ? 'dark' : 'light'
|
|
|
|
return (
|
|
<Tabs
|
|
defaultActiveId="prompt"
|
|
type="underlined"
|
|
size="small"
|
|
wrappable
|
|
queryGroup="agent-setup"
|
|
>
|
|
<TabPanel id="prompt" label="Prompt" icon={<Sparkles size={14} />}>
|
|
<AiPrompt id={agent.promptId} telemetry={{ source: 'agent_setup' }} />
|
|
</TabPanel>
|
|
{harnesses.map((harness) => (
|
|
<TabPanel
|
|
key={harness.key}
|
|
id={harness.key}
|
|
label={harness.label}
|
|
icon={
|
|
<ConnectionIcon
|
|
theme={theme}
|
|
connection={harness.icon}
|
|
hasDistinctDarkIcon={harness.hasDistinctDarkIcon}
|
|
/>
|
|
}
|
|
>
|
|
<HarnessBody harness={harness} />
|
|
</TabPanel>
|
|
))}
|
|
</Tabs>
|
|
)
|
|
}
|
|
|
|
export { AgentSetup }
|
|
export type { AgentSetupProps }
|