CopyPromptAdmonition.tsx152 lines · main
1import { type RefObject } from 'react'
2import { Admonition } from 'ui-patterns/admonition'
3
4import CopyButton from '@/components/ui/CopyButton'
5import { BASE_PATH } from '@/lib/constants'
6
7interface CopyPromptAdmonitionProps {
8 stepsContainerRef: RefObject<HTMLDivElement | null>
9}
10
11export function CopyPromptAdmonition({ stepsContainerRef }: CopyPromptAdmonitionProps) {
12 const normalizeTextLines = (value: string) => {
13 return value
14 .split('\n')
15 .map((line) => line.trim())
16 .filter(Boolean)
17 .join('\n')
18 }
19
20 const getStepTextContent = (contentElement: HTMLElement) => {
21 const clone = contentElement.cloneNode(true) as HTMLElement
22 clone
23 .querySelectorAll('pre, button, svg, input, textarea, select, [aria-hidden="true"]')
24 .forEach((element) => {
25 element.remove()
26 })
27
28 clone.querySelectorAll('p, div').forEach((el) => {
29 el.appendChild(document.createTextNode('\n'))
30 })
31
32 const text = clone.textContent ?? ''
33 return normalizeTextLines(text)
34 }
35
36 const getStepCodeSnippets = (contentElement: HTMLElement) => {
37 const snippets: Array<{ label: string; snippet: string }> = []
38 const seen = new Set<string>()
39
40 const addSnippet = (label: string, snippet: string) => {
41 if (!snippet || seen.has(snippet)) return
42 seen.add(snippet)
43 snippets.push({ label, snippet })
44 }
45
46 const tabContents = Array.from(
47 contentElement.querySelectorAll('[data-connect-tab-content]')
48 ) as HTMLElement[]
49
50 tabContents.forEach((tabContent) => {
51 const label = tabContent.getAttribute('data-tab-label') || 'Code'
52 const tabSnippets = Array.from(tabContent.querySelectorAll('pre'))
53 .map((pre) => pre.textContent?.trim())
54 .filter((snippet): snippet is string => Boolean(snippet))
55
56 if (tabSnippets.length === 0) {
57 const inlineSnippets = Array.from(tabContent.querySelectorAll('code'))
58 .filter((code) => !code.closest('pre') && code.closest('.font-mono'))
59 .map((code) => code.textContent?.trim())
60 .filter((snippet): snippet is string => Boolean(snippet))
61 inlineSnippets.forEach((snippet, index) => {
62 const inlineLabel = inlineSnippets.length > 1 ? `${label} (part ${index + 1})` : label
63 addSnippet(inlineLabel, snippet)
64 })
65 return
66 }
67
68 tabSnippets.forEach((snippet, index) => {
69 const tabLabel = tabSnippets.length > 1 ? `${label} (part ${index + 1})` : label
70 addSnippet(tabLabel, snippet)
71 })
72 })
73
74 contentElement.querySelectorAll('pre').forEach((pre) => {
75 if (pre.closest('[data-connect-tab-content]')) return
76 const snippet = pre.textContent?.trim()
77 if (snippet) addSnippet('Code', snippet)
78 })
79
80 contentElement.querySelectorAll('code').forEach((code) => {
81 if (code.closest('pre')) return
82 if (code.closest('[data-connect-tab-content]')) return
83 if (!code.closest('.font-mono')) return
84 const snippet = code.textContent?.trim()
85 if (snippet) addSnippet('Code', snippet)
86 })
87
88 return snippets
89 }
90
91 const handleCopyPrompt = () => {
92 const stepElements = stepsContainerRef.current?.querySelectorAll('[data-connect-step]')
93 if (!stepElements?.length) return ''
94
95 const promptContent = Array.from(stepElements)
96 .map((stepElement, index) => {
97 const title = stepElement.getAttribute('data-step-title') ?? `Step ${index + 1}`
98 const description = stepElement.getAttribute('data-step-description') ?? ''
99 const contentElement = stepElement.querySelector(
100 '[data-step-content]'
101 ) as HTMLElement | null
102
103 const details = contentElement ? getStepTextContent(contentElement) : ''
104 const codeSnippets = contentElement ? getStepCodeSnippets(contentElement) : []
105
106 const sections = [
107 `${index + 1}. ${title}`,
108 description,
109 details ? `Details:\n${details}` : null,
110 codeSnippets.length
111 ? `Code:\n${codeSnippets
112 .map(({ label, snippet }) => `File: ${label}\n\`\`\`\n${snippet}\n\`\`\``)
113 .join('\n\n')}`
114 : null,
115 ].filter(Boolean)
116
117 return sections.join('\n')
118 })
119 .join('\n\n')
120
121 return promptContent
122 }
123
124 return (
125 <Admonition
126 type="tip"
127 showIcon={false}
128 layout="horizontal"
129 actions={<CopyButton type="default" copyLabel="Copy prompt" asyncText={handleCopyPrompt} />}
130 >
131 <div className="absolute -inset-16 z-0 opacity-50">
132 <img
133 src={`${BASE_PATH}/img/reports/bg-grafana-dark.svg`}
134 alt="Briven Grafana"
135 className="w-full h-full object-cover object-right hidden dark:block"
136 />
137 <img
138 src={`${BASE_PATH}/img/reports/bg-grafana-light.svg`}
139 alt="Briven Grafana"
140 className="w-full h-full object-cover object-right dark:hidden"
141 />
142 <div className="absolute inset-0 bg-linear-to-r from-background-alternative to-transparent" />
143 </div>
144
145 <div className="relative flex flex-col md:flex-row md:items-center gap-y-2 md:gap-x-8 justify-between">
146 <div className="flex flex-col gap-y-0.5">
147 <p className="heading-default">Give your agent everything it needs</p>
148 </div>
149 </div>
150 </Admonition>
151 )
152}