-
Notifications
You must be signed in to change notification settings - Fork 67.1k
Expand file tree
/
Copy pathcopilot-prompt.ts
More file actions
104 lines (91 loc) · 3.25 KB
/
copilot-prompt.ts
File metadata and controls
104 lines (91 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Adds a runnable prompt button in the header of Copilot Chat blocks.
*/
import { find } from 'unist-util-find'
import { h } from 'hastscript'
import octicons from '@primer/octicons'
import { parse } from 'parse5'
import { fromParse5 } from 'hast-util-from-parse5'
import { getPreMeta } from './code-header'
import { createLogger } from '@/observability/logger'
import { generatePromptId } from '../lib/prompt-id'
import type { Element, Root } from 'hast'
const logger = createLogger(import.meta.url)
export function getPrompt(
node: Element,
tree: Root,
code: string,
): { element: Element; promptContent: string } | null {
const hasPrompt = Boolean(getPreMeta(node).prompt)
if (!hasPrompt) return null
const { promptContent, ariaLabel } = buildPromptData(node, tree, code)
const promptLink = `https://github.com/copilot?prompt=${encodeURIComponent(promptContent.trim())}`
// Use murmur hash for deterministic ID (avoids hydration mismatch)
const promptId: string = generatePromptId(promptContent)
const element = h(
'a',
{
href: promptLink,
target: '_blank',
class: ['btn', 'btn-sm', 'mr-1', 'tooltipped', 'tooltipped-nw', 'no-underline'],
'aria-label': ariaLabel,
'aria-describedby': promptId,
},
copilotIcon(),
)
return { element, promptContent }
}
function buildPromptData(
node: Element,
tree: Root,
code: string,
): { promptContent: string; ariaLabel: string } {
// Find a ref meta in the format 'ref=<id>'
const ref = getPreMeta(node).ref
if (!ref) {
// If no 'ref=<id>' meta is found, use just the current code for the prompt link.
return promptOnly(code)
}
// If the 'ref=<id>' meta is found, find a matching code block to include as context in the prompt link.
const matchingCodeEl = findMatchingCode(ref as string, tree)
if (!matchingCodeEl) {
logger.warn('Cannot find referenced code block', { ref })
return promptOnly(code)
}
// AST structure: element -> code -> text node with value property
const codeChild = matchingCodeEl.children[0] as Element | undefined
const textNode = codeChild?.children[0] as { value?: string } | undefined
const matchingCode = textNode?.value || null
if (!matchingCode) {
return promptOnly(code)
}
return promptAndContext(code, matchingCode)
}
function promptOnly(code: string): { promptContent: string; ariaLabel: string } {
return {
promptContent: code,
ariaLabel: 'Run this prompt in Copilot Chat',
}
}
function promptAndContext(
code: string,
matchingCode: string,
): { promptContent: string; ariaLabel: string } {
return {
promptContent: `${matchingCode}\n${code}`,
ariaLabel: 'Run this prompt with context in Copilot Chat',
}
}
function findMatchingCode(ref: string, tree: Root): Element | undefined {
return find<Element>(tree, ((node: { type: string; tagName?: string }) => {
return (
node.type === 'element' && node.tagName === 'pre' && getPreMeta(node as Element).id === ref
)
}) as Parameters<typeof find>[1])
}
function copilotIcon(): Element {
const copilotIconHtml = octicons.copilot.toSVG()
const copilotIconAst = parse(String(copilotIconHtml), { sourceCodeLocationInfo: true })
const copilotIconElement = fromParse5(copilotIconAst)
return copilotIconElement as Element
}