@@ -5,7 +5,20 @@ import {
55 CardBody ,
66 CardFooter ,
77 DarkModeContext ,
8+ Dialog ,
9+ DialogCloseButton ,
10+ DialogContent ,
11+ DialogHeader ,
12+ DialogModal ,
13+ DialogModalOverlay ,
14+ DialogTitle ,
15+ DialogTrigger ,
16+ FieldGroup ,
17+ Input ,
18+ Link ,
819 Loader ,
20+ SearchField ,
21+ SearchFieldClearButton ,
922 Text ,
1023} from "@stacklok/ui-kit" ;
1124import {
@@ -33,6 +46,9 @@ import {
3346import { v1GetWorkspaceCustomInstructionsQueryKey } from "@/api/generated/@tanstack/react-query.gen" ;
3447import { useQueryGetWorkspaceCustomInstructions } from "../hooks/use-query-get-workspace-custom-instructions" ;
3548import { useMutationSetWorkspaceCustomInstructions } from "../hooks/use-mutation-set-workspace-custom-instructions" ;
49+ import { Bot , Download , Search } from "lucide-react" ;
50+ import Fuse from "fuse.js" ;
51+ import systemPrompts from "../constants/built-in-system-prompts.json" ;
3652
3753type DarkModeContextValue = {
3854 preference : "dark" | "light" | null ;
@@ -129,6 +145,96 @@ function useCustomInstructionsValue({
129145 return { value, setValue } ;
130146}
131147
148+ type PromptPresetPickerProps = {
149+ onActivate : ( text : string ) => void ;
150+ } ;
151+
152+ function PromptPresetPicker ( { onActivate } : PromptPresetPickerProps ) {
153+ const [ query , setQuery ] = useState < string > ( "" ) ;
154+
155+ const fuse = new Fuse ( systemPrompts , {
156+ keys : [ "name" , "text" ] ,
157+ } ) ;
158+
159+ const handleActivate = useCallback (
160+ ( prompt : string ) => {
161+ onActivate ( prompt ) ;
162+ } ,
163+ [ onActivate ] ,
164+ ) ;
165+
166+ return (
167+ < >
168+ < DialogHeader >
169+ < div className = "w-1/3" >
170+ < DialogTitle > Choose a prompt template</ DialogTitle >
171+ </ div >
172+ < div className = "w-1/3" >
173+ < SearchField
174+ className = "w-full max-w-96"
175+ value = { query }
176+ onChange = { setQuery }
177+ >
178+ < FieldGroup >
179+ < Input icon = { < Search /> } placeholder = "Type to search" autoFocus />
180+ { query . length > 0 ? < SearchFieldClearButton /> : null }
181+ </ FieldGroup >
182+ </ SearchField >
183+ </ div >
184+ < div className = "w-1/3 flex justify-end" >
185+ < DialogCloseButton />
186+ </ div >
187+ </ DialogHeader >
188+ < DialogContent >
189+ < div className = "grid grid-flow-row grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 overflow-auto justify-around " >
190+ { fuse . search ( query . length > 0 ? query : " " ) . map ( ( { item } ) => {
191+ return (
192+ < Card className = " flex flex-col" >
193+ < h2 className = "font-bold p-2 flex gap-2 items-center" >
194+ < Bot className = "size-4" />
195+ < div className = "truncate" > { item . name } </ div >
196+ </ h2 >
197+ < pre className = "h-72 overflow-hidden text-wrap text-sm bg-gray-50 p-2 overflow-y-auto" >
198+ { item . text }
199+ </ pre >
200+ < div className = "flex gap-4 justify-between p-2" >
201+ < div className = "h-full items-center" >
202+ < div className = "flex h-full items-center max-w-52 text-clip" >
203+ { item . contributors . map ( ( contributor ) => (
204+ < Link
205+ className = "font-bold text-sm no-underline text-secondary flex gap-1 items-center hover:bg-gray-200 h-full px-2 rounded-md"
206+ target = "_blank"
207+ href = { `https://github.com/${ contributor } /` }
208+ >
209+ < img
210+ className = "size-6 rounded-full"
211+ src = { `https://github.com/${ contributor } .png?size=24` }
212+ />
213+ < span className = "truncate" > { contributor } </ span >
214+ </ Link >
215+ ) ) }
216+ </ div >
217+ </ div >
218+ < Button
219+ isIcon
220+ slot = "close"
221+ variant = "secondary"
222+ onPress = { ( ) => {
223+ handleActivate ( item . text ) ;
224+ } }
225+ >
226+ < Download />
227+ </ Button >
228+ </ div >
229+ </ Card >
230+ ) ;
231+ } ) }
232+ </ div >
233+ </ DialogContent >
234+ </ >
235+ ) ;
236+ }
237+
132238export function WorkspaceCustomInstructions ( {
133239 className,
134240 workspaceName,
@@ -209,6 +315,24 @@ export function WorkspaceCustomInstructions({
209315 </ div >
210316 </ CardBody >
211317 < CardFooter className = "justify-end gap-2" >
318+ < DialogTrigger >
319+ < Button > Use a preset</ Button >
320+ < DialogModalOverlay isDismissable >
321+ < DialogModal isDismissable >
322+ < Dialog
323+ width = "lg"
324+ className = "flex flex-col p-4 gap-4 "
325+ style = { { maxWidth : "min(calc(100vw - 64px), 1200px)" } }
326+ >
327+ < PromptPresetPicker
328+ onActivate = { ( prompt : string ) => {
329+ setValue ( prompt ) ;
330+ } }
331+ />
332+ </ Dialog >
333+ </ DialogModal >
334+ </ DialogModalOverlay >
335+ </ DialogTrigger >
212336 < Button
213337 isPending = { isMutationPending }
214338 isDisabled = { Boolean ( isArchived ?? isCustomInstructionsPending ) }
0 commit comments