Skip to content

Commit 25a531e

Browse files
authored
feat: add prompt picker (#212)
* create basic prompt picker * add actual prompts * . * add authors to system prompts * reuse card components * eye candy * add header * eye candy * use grid instead of flex for built-in prompts * make design responsive * center search field * simpler way to center the search bar
1 parent d9652bb commit 25a531e

File tree

4 files changed

+1065
-0
lines changed

4 files changed

+1065
-0
lines changed

package-lock.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@types/react-syntax-highlighter": "^15.5.13",
3232
"clsx": "^2.1.1",
3333
"date-fns": "^4.1.0",
34+
"fuse.js": "^7.0.0",
3435
"highlight.js": "^11.11.1",
3536
"lucide-react": "^0.471.1",
3637
"prismjs": "^1.29.0",

src/features/workspace/components/workspace-custom-instructions.tsx

+124
Original file line numberDiff line numberDiff line change
@@ -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";
1124
import {
@@ -33,6 +46,9 @@ import {
3346
import { v1GetWorkspaceCustomInstructionsQueryKey } from "@/api/generated/@tanstack/react-query.gen";
3447
import { useQueryGetWorkspaceCustomInstructions } from "../hooks/use-query-get-workspace-custom-instructions";
3548
import { 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

3753
type 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+
132238
export 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

Comments
 (0)