Skip to content

Commit 0d9d752

Browse files
feat: workspace settings route & system prompt editor (#114)
* feat: monaco text editor for system prompt * feat: further work on editing system prompt, and workspace route * fix: add `legacy-peer-deps` to `.npmrc` * chore: declare dev deps * fix: add @ts-expect-error to mocked function
1 parent bfc3820 commit 0d9d752

File tree

11 files changed

+1295
-20
lines changed

11 files changed

+1295
-20
lines changed

.gitignore

-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ yarn-error.log*
77
pnpm-debug.log*
88
lerna-debug.log*
99

10-
# NPM
11-
.npmrc
12-
1310
node_modules
1411
dist
1512
dist-ssr

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
legacy-peer-deps=true

package-lock.json

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

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"dependencies": {
2121
"@hey-api/client-fetch": "^0.6.0",
22+
"@monaco-editor/react": "^4.6.0",
2223
"@radix-ui/react-dialog": "^1.1.4",
2324
"@radix-ui/react-separator": "^1.1.0",
2425
"@radix-ui/react-slot": "^1.1.0",
@@ -48,8 +49,9 @@
4849
"@faker-js/faker": "^9.4.0",
4950
"@hey-api/openapi-ts": "^0.61.2",
5051
"@tailwindcss/typography": "^0.5.16",
52+
"@testing-library/dom": "^10.4.0",
5153
"@testing-library/jest-dom": "^6.6.3",
52-
"@testing-library/react": "^16.1.0",
54+
"@testing-library/react": "^16.2.0",
5355
"@testing-library/user-event": "^14.6.0",
5456
"@types/node": "^22.10.1",
5557
"@types/react": "^19.0.2",

src/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
BreadcrumbPage,
1818
} from "./components/ui/breadcrumb";
1919
import { useBreadcrumb } from "./hooks/useBreadcrumb";
20+
import { RouteWorkspace } from "./routes/route-workspace";
2021

2122
function App() {
2223
const { data: prompts, isLoading } = usePromptsData();
@@ -57,6 +58,7 @@ function App() {
5758
<Route path="/prompt/:id" element={<Chat />} />
5859
<Route path="/help/:section" element={<Help />} />
5960
<Route path="/certificates" element={<Certificates />} />
61+
<Route path="/workspace/:id" element={<RouteWorkspace />} />
6062
<Route
6163
path="/certificates/security"
6264
element={<CertificateSecurity />}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import Editor, { type Theme } from "@monaco-editor/react";
2+
import {
3+
Button,
4+
Card,
5+
CardBody,
6+
CardFooter,
7+
DarkModeContext,
8+
LinkButton,
9+
Text,
10+
} from "@stacklok/ui-kit";
11+
import {
12+
Dispatch,
13+
SetStateAction,
14+
useContext,
15+
useEffect,
16+
useState,
17+
} from "react";
18+
import { usePostSystemPrompt } from "../hooks/use-post-system-prompt";
19+
import { Check } from "lucide-react";
20+
21+
type DarkModeContextValue = {
22+
preference: "dark" | "light" | null;
23+
override: "dark" | "light" | null;
24+
};
25+
26+
const DEFAULT_VALUE = `You are a security expert conducting a thorough code review.\nIdentify potential security vulnerabilities, suggest improvements, and explain security best practices.`;
27+
28+
function inferDarkMode(
29+
contextValue:
30+
| null
31+
| [DarkModeContextValue, Dispatch<SetStateAction<DarkModeContextValue>>],
32+
): Theme {
33+
if (contextValue === null) return "light";
34+
35+
// Handle override
36+
if (contextValue[0].override === "dark") return "vs-dark";
37+
if (contextValue[0].override === "light") return "light";
38+
39+
// Handle preference
40+
if (contextValue[0].preference === "dark") return "vs-dark";
41+
return "light";
42+
}
43+
44+
function useSavedStatus() {
45+
const [saved, setSaved] = useState<boolean>(false);
46+
47+
useEffect(() => {
48+
const id = setTimeout(() => setSaved(false), 2000);
49+
return () => clearTimeout(id);
50+
}, [saved]);
51+
52+
return { saved, setSaved };
53+
}
54+
55+
export function SystemPromptEditor({ className }: { className?: string }) {
56+
const context = useContext(DarkModeContext);
57+
const theme: Theme = inferDarkMode(context);
58+
59+
const [value, setValue] = useState<string>(() => DEFAULT_VALUE);
60+
61+
const { mutate, isPending } = usePostSystemPrompt();
62+
63+
const { saved, setSaved } = useSavedStatus();
64+
65+
return (
66+
<Card className={className}>
67+
<CardBody>
68+
<Text className="text-primary">Custom prompt</Text>
69+
<Text className="text-secondary mb-4">
70+
Pass custom instructions to your LLM to augment it's behavior, and
71+
save time & tokens.
72+
</Text>
73+
<div className="border border-gray-200 rounded overflow-hidden">
74+
<Editor
75+
options={{
76+
minimap: { enabled: false },
77+
}}
78+
value={value}
79+
onChange={(v) => setValue(v ?? "")}
80+
height="20rem"
81+
defaultLanguage="Markdown"
82+
theme={theme}
83+
className="bg-base"
84+
defaultValue="<!-- Add any additional prompts you would like to pass to your LLM here. -->"
85+
/>
86+
</div>
87+
</CardBody>
88+
<CardFooter className="justify-end gap-2">
89+
<LinkButton variant="secondary">Cancel</LinkButton>
90+
<Button
91+
isPending={isPending}
92+
isDisabled={saved}
93+
onPress={() => {
94+
mutate(value, {
95+
onSuccess: () => setSaved(true),
96+
});
97+
}}
98+
>
99+
{saved ? (
100+
<>
101+
<span>Saved</span> <Check />
102+
</>
103+
) : (
104+
"Save changes"
105+
)}
106+
</Button>
107+
</CardFooter>
108+
</Card>
109+
);
110+
}

0 commit comments

Comments
 (0)