PromptsView.tsx 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303
  1. import React, { useState, useEffect, useMemo, useCallback } from "react"
  2. import {
  3. VSCodeButton,
  4. VSCodeTextArea,
  5. VSCodeDropdown,
  6. VSCodeOption,
  7. VSCodeTextField,
  8. VSCodeCheckbox,
  9. VSCodeRadioGroup,
  10. VSCodeRadio,
  11. } from "@vscode/webview-ui-toolkit/react"
  12. import { useExtensionState } from "../../context/ExtensionStateContext"
  13. import {
  14. Mode,
  15. PromptComponent,
  16. getRoleDefinition,
  17. getCustomInstructions,
  18. getAllModes,
  19. ModeConfig,
  20. GroupEntry,
  21. } from "../../../../src/shared/modes"
  22. import {
  23. supportPrompt,
  24. SupportPromptType,
  25. supportPromptLabels,
  26. supportPromptDescriptions,
  27. } from "../../../../src/shared/support-prompt"
  28. import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
  29. import { vscode } from "../../utils/vscode"
  30. // Get all available groups that should show in prompts view
  31. const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable)
  32. type ModeSource = "global" | "project"
  33. type PromptsViewProps = {
  34. onDone: () => void
  35. }
  36. // Helper to get group name regardless of format
  37. function getGroupName(group: GroupEntry): ToolGroup {
  38. return Array.isArray(group) ? group[0] : group
  39. }
  40. const PromptsView = ({ onDone }: PromptsViewProps) => {
  41. const {
  42. customModePrompts,
  43. customSupportPrompts,
  44. listApiConfigMeta,
  45. currentApiConfigName,
  46. enhancementApiConfigId,
  47. setEnhancementApiConfigId,
  48. mode,
  49. customInstructions,
  50. setCustomInstructions,
  51. preferredLanguage,
  52. setPreferredLanguage,
  53. customModes,
  54. } = useExtensionState()
  55. // Memoize modes to preserve array order
  56. const modes = useMemo(() => getAllModes(customModes), [customModes])
  57. const [testPrompt, setTestPrompt] = useState("")
  58. const [isEnhancing, setIsEnhancing] = useState(false)
  59. const [isDialogOpen, setIsDialogOpen] = useState(false)
  60. const [selectedPromptContent, setSelectedPromptContent] = useState("")
  61. const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
  62. const [isToolsEditMode, setIsToolsEditMode] = useState(false)
  63. const [showConfigMenu, setShowConfigMenu] = useState(false)
  64. const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false)
  65. const [activeSupportTab, setActiveSupportTab] = useState<SupportPromptType>("ENHANCE")
  66. // Direct update functions
  67. const updateAgentPrompt = useCallback(
  68. (mode: Mode, promptData: PromptComponent) => {
  69. const existingPrompt = customModePrompts?.[mode] as PromptComponent
  70. const updatedPrompt = { ...existingPrompt, ...promptData }
  71. // Only include properties that differ from defaults
  72. if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) {
  73. delete updatedPrompt.roleDefinition
  74. }
  75. vscode.postMessage({
  76. type: "updatePrompt",
  77. promptMode: mode,
  78. customPrompt: updatedPrompt,
  79. })
  80. },
  81. [customModePrompts],
  82. )
  83. const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => {
  84. const source = modeConfig.source || "global"
  85. vscode.postMessage({
  86. type: "updateCustomMode",
  87. slug,
  88. modeConfig: {
  89. ...modeConfig,
  90. source, // Ensure source is set
  91. },
  92. })
  93. }, [])
  94. // Helper function to find a mode by slug
  95. const findModeBySlug = useCallback(
  96. (searchSlug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined => {
  97. if (!modes) return undefined
  98. const isModeWithSlug = (mode: ModeConfig): mode is ModeConfig => mode.slug === searchSlug
  99. return modes.find(isModeWithSlug)
  100. },
  101. [],
  102. )
  103. const switchMode = useCallback((slug: string) => {
  104. vscode.postMessage({
  105. type: "mode",
  106. text: slug,
  107. })
  108. }, [])
  109. // Handle mode switching with explicit state initialization
  110. const handleModeSwitch = useCallback(
  111. (modeConfig: ModeConfig) => {
  112. if (modeConfig.slug === mode) return // Prevent unnecessary updates
  113. // First switch the mode
  114. switchMode(modeConfig.slug)
  115. // Exit tools edit mode when switching modes
  116. setIsToolsEditMode(false)
  117. },
  118. [mode, switchMode, setIsToolsEditMode],
  119. )
  120. // Helper function to get current mode's config
  121. const getCurrentMode = useCallback((): ModeConfig | undefined => {
  122. const findMode = (m: ModeConfig): boolean => m.slug === mode
  123. return customModes?.find(findMode) || modes.find(findMode)
  124. }, [mode, customModes, modes])
  125. // Helper function to safely access mode properties
  126. const getModeProperty = <T extends keyof ModeConfig>(
  127. mode: ModeConfig | undefined,
  128. property: T,
  129. ): ModeConfig[T] | undefined => {
  130. return mode?.[property]
  131. }
  132. // State for create mode dialog
  133. const [newModeName, setNewModeName] = useState("")
  134. const [newModeSlug, setNewModeSlug] = useState("")
  135. const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("")
  136. const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("")
  137. const [newModeGroups, setNewModeGroups] = useState<GroupEntry[]>(availableGroups)
  138. const [newModeSource, setNewModeSource] = useState<ModeSource>("global")
  139. // Reset form fields when dialog opens
  140. useEffect(() => {
  141. if (isCreateModeDialogOpen) {
  142. setNewModeGroups(availableGroups)
  143. setNewModeRoleDefinition("")
  144. setNewModeCustomInstructions("")
  145. setNewModeSource("global")
  146. }
  147. }, [isCreateModeDialogOpen])
  148. // Helper function to generate a unique slug from a name
  149. const generateSlug = useCallback((name: string, attempt = 0): string => {
  150. const baseSlug = name
  151. .toLowerCase()
  152. .replace(/[^a-z0-9-]+/g, "-")
  153. .replace(/^-+|-+$/g, "")
  154. return attempt === 0 ? baseSlug : `${baseSlug}-${attempt}`
  155. }, [])
  156. // Handler for name changes
  157. const handleNameChange = useCallback(
  158. (name: string) => {
  159. setNewModeName(name)
  160. setNewModeSlug(generateSlug(name))
  161. },
  162. [generateSlug],
  163. )
  164. const handleCreateMode = useCallback(() => {
  165. if (!newModeName.trim() || !newModeSlug.trim()) return
  166. const source = newModeSource
  167. const newMode: ModeConfig = {
  168. slug: newModeSlug,
  169. name: newModeName,
  170. roleDefinition: newModeRoleDefinition.trim() || "",
  171. customInstructions: newModeCustomInstructions.trim() || undefined,
  172. groups: newModeGroups,
  173. source,
  174. }
  175. updateCustomMode(newModeSlug, newMode)
  176. switchMode(newModeSlug)
  177. setIsCreateModeDialogOpen(false)
  178. setNewModeName("")
  179. setNewModeSlug("")
  180. setNewModeRoleDefinition("")
  181. setNewModeCustomInstructions("")
  182. setNewModeGroups(availableGroups)
  183. setNewModeSource("global")
  184. // eslint-disable-next-line react-hooks/exhaustive-deps
  185. }, [
  186. newModeName,
  187. newModeSlug,
  188. newModeRoleDefinition,
  189. newModeCustomInstructions,
  190. newModeGroups,
  191. newModeSource,
  192. updateCustomMode,
  193. ])
  194. const isNameOrSlugTaken = useCallback(
  195. (name: string, slug: string) => {
  196. return modes.some((m) => m.slug === slug || m.name === name)
  197. },
  198. [modes],
  199. )
  200. const openCreateModeDialog = useCallback(() => {
  201. const baseNamePrefix = "New Custom Mode"
  202. // Find unique name and slug
  203. let attempt = 0
  204. let name = baseNamePrefix
  205. let slug = generateSlug(name)
  206. while (isNameOrSlugTaken(name, slug)) {
  207. attempt++
  208. name = `${baseNamePrefix} ${attempt + 1}`
  209. slug = generateSlug(name)
  210. }
  211. setNewModeName(name)
  212. setNewModeSlug(slug)
  213. setIsCreateModeDialogOpen(true)
  214. }, [generateSlug, isNameOrSlugTaken])
  215. // Handler for group checkbox changes
  216. const handleGroupChange = useCallback(
  217. (group: ToolGroup, isCustomMode: boolean, customMode: ModeConfig | undefined) =>
  218. (e: Event | React.FormEvent<HTMLElement>) => {
  219. if (!isCustomMode) return // Prevent changes to built-in modes
  220. const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
  221. const checked = target.checked
  222. const oldGroups = customMode?.groups || []
  223. let newGroups: GroupEntry[]
  224. if (checked) {
  225. newGroups = [...oldGroups, group]
  226. } else {
  227. newGroups = oldGroups.filter((g) => getGroupName(g) !== group)
  228. }
  229. if (customMode) {
  230. const source = customMode.source || "global"
  231. updateCustomMode(customMode.slug, {
  232. ...customMode,
  233. groups: newGroups,
  234. source,
  235. })
  236. }
  237. },
  238. [updateCustomMode],
  239. )
  240. // Handle clicks outside the config menu
  241. useEffect(() => {
  242. const handleClickOutside = (event: MouseEvent) => {
  243. if (showConfigMenu) {
  244. setShowConfigMenu(false)
  245. }
  246. }
  247. document.addEventListener("click", handleClickOutside)
  248. return () => document.removeEventListener("click", handleClickOutside)
  249. }, [showConfigMenu])
  250. useEffect(() => {
  251. const handler = (event: MessageEvent) => {
  252. const message = event.data
  253. if (message.type === "enhancedPrompt") {
  254. if (message.text) {
  255. setTestPrompt(message.text)
  256. }
  257. setIsEnhancing(false)
  258. } else if (message.type === "systemPrompt") {
  259. if (message.text) {
  260. setSelectedPromptContent(message.text)
  261. setSelectedPromptTitle(`System Prompt (${message.mode} mode)`)
  262. setIsDialogOpen(true)
  263. }
  264. }
  265. }
  266. window.addEventListener("message", handler)
  267. return () => window.removeEventListener("message", handler)
  268. }, [])
  269. const updateSupportPrompt = (type: SupportPromptType, value: string | undefined) => {
  270. vscode.postMessage({
  271. type: "updateSupportPrompt",
  272. values: {
  273. [type]: value,
  274. },
  275. })
  276. }
  277. const handleAgentReset = (modeSlug: string, type: "roleDefinition" | "customInstructions") => {
  278. // Only reset for built-in modes
  279. const existingPrompt = customModePrompts?.[modeSlug] as PromptComponent
  280. const updatedPrompt = { ...existingPrompt }
  281. delete updatedPrompt[type] // Remove the field entirely to ensure it reloads from defaults
  282. vscode.postMessage({
  283. type: "updatePrompt",
  284. promptMode: modeSlug,
  285. customPrompt: updatedPrompt,
  286. })
  287. }
  288. const handleSupportReset = (type: SupportPromptType) => {
  289. vscode.postMessage({
  290. type: "resetSupportPrompt",
  291. text: type,
  292. })
  293. }
  294. const getSupportPromptValue = (type: SupportPromptType): string => {
  295. return supportPrompt.get(customSupportPrompts, type)
  296. }
  297. const handleTestEnhancement = () => {
  298. if (!testPrompt.trim()) return
  299. setIsEnhancing(true)
  300. vscode.postMessage({
  301. type: "enhancePrompt",
  302. text: testPrompt,
  303. })
  304. }
  305. return (
  306. <div className="fixed inset-0 flex flex-col">
  307. <div className="flex justify-between items-center px-5 py-2.5">
  308. <h3 className="text-vscode-foreground m-0">Prompts</h3>
  309. <VSCodeButton onClick={onDone}>Done</VSCodeButton>
  310. </div>
  311. <div className="flex-1 overflow-auto px-5">
  312. <div className="pb-5 border-b border-vscode-input-border">
  313. <div className="mb-5">
  314. <div className="font-bold mb-1">Preferred Language</div>
  315. <select
  316. value={preferredLanguage}
  317. onChange={(e) => {
  318. setPreferredLanguage(e.target.value)
  319. vscode.postMessage({
  320. type: "preferredLanguage",
  321. text: e.target.value,
  322. })
  323. }}
  324. className="w-full px-2 py-1 h-7 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded">
  325. <option value="English">English</option>
  326. <option value="Arabic">Arabic - العربية</option>
  327. <option value="Brazilian Portuguese">Portuguese - Português (Brasil)</option>
  328. <option value="Czech">Czech - Čeština</option>
  329. <option value="French">French - Français</option>
  330. <option value="German">German - Deutsch</option>
  331. <option value="Hindi">Hindi - हिन्दी</option>
  332. <option value="Hungarian">Hungarian - Magyar</option>
  333. <option value="Italian">Italian - Italiano</option>
  334. <option value="Japanese">Japanese - 日本語</option>
  335. <option value="Korean">Korean - 한국어</option>
  336. <option value="Polish">Polish - Polski</option>
  337. <option value="Portuguese">Portuguese - Português (Portugal)</option>
  338. <option value="Russian">Russian - Русский</option>
  339. <option value="Simplified Chinese">Simplified Chinese - 简体中文</option>
  340. <option value="Spanish">Spanish - Español</option>
  341. <option value="Traditional Chinese">Traditional Chinese - 繁體中文</option>
  342. <option value="Turkish">Turkish - Türkçe</option>
  343. </select>
  344. <p className="text-xs mt-1.5 text-vscode-descriptionForeground">
  345. Select the language that Cline should use for communication.
  346. </p>
  347. </div>
  348. <div className="font-bold mb-1">Custom Instructions for All Modes</div>
  349. <div className="text-sm text-vscode-descriptionForeground mb-2">
  350. These instructions apply to all modes. They provide a base set of behaviors that can be enhanced
  351. by mode-specific instructions below.
  352. </div>
  353. <VSCodeTextArea
  354. value={customInstructions ?? ""}
  355. onChange={(e) => {
  356. const value =
  357. (e as CustomEvent)?.detail?.target?.value ||
  358. ((e as any).target as HTMLTextAreaElement).value
  359. setCustomInstructions(value || undefined)
  360. vscode.postMessage({
  361. type: "customInstructions",
  362. text: value.trim() || undefined,
  363. })
  364. }}
  365. rows={4}
  366. resize="vertical"
  367. className="w-full"
  368. data-testid="global-custom-instructions-textarea"
  369. />
  370. <div className="text-xs text-vscode-descriptionForeground mt-1.5 mb-10">
  371. Instructions can also be loaded from{" "}
  372. <span
  373. className="text-vscode-textLink-foreground cursor-pointer underline"
  374. onClick={() =>
  375. vscode.postMessage({
  376. type: "openFile",
  377. text: "./.clinerules",
  378. values: {
  379. create: true,
  380. content: "",
  381. },
  382. })
  383. }>
  384. .clinerules
  385. </span>{" "}
  386. in your workspace.
  387. </div>
  388. </div>
  389. <div className="mt-5">
  390. <div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
  391. <h3 className="text-vscode-foreground m-0">Mode-Specific Prompts</h3>
  392. <div className="flex gap-2">
  393. <VSCodeButton appearance="icon" onClick={openCreateModeDialog} title="Create new mode">
  394. <span className="codicon codicon-add"></span>
  395. </VSCodeButton>
  396. <div className="relative inline-block">
  397. <VSCodeButton
  398. appearance="icon"
  399. title="Edit modes configuration"
  400. className="flex"
  401. onClick={(e: React.MouseEvent) => {
  402. e.preventDefault()
  403. e.stopPropagation()
  404. setShowConfigMenu((prev) => !prev)
  405. }}
  406. onBlur={() => {
  407. // Add slight delay to allow menu item clicks to register
  408. setTimeout(() => setShowConfigMenu(false), 200)
  409. }}>
  410. <span className="codicon codicon-json"></span>
  411. </VSCodeButton>
  412. {showConfigMenu && (
  413. <div
  414. onClick={(e) => e.stopPropagation()}
  415. onMouseDown={(e) => e.stopPropagation()}
  416. className="absolute top-full right-0 w-[200px] mt-1 bg-vscode-editor-background border border-vscode-input-border rounded shadow-md z-[1000]">
  417. <div
  418. className="p-2 cursor-pointer text-vscode-foreground text-sm"
  419. onMouseDown={(e) => {
  420. e.preventDefault() // Prevent blur
  421. vscode.postMessage({
  422. type: "openCustomModesSettings",
  423. })
  424. setShowConfigMenu(false)
  425. }}
  426. onClick={(e) => e.preventDefault()}>
  427. Edit Global Modes
  428. </div>
  429. <div
  430. className="p-2 cursor-pointer text-vscode-foreground text-sm border-t border-vscode-input-border"
  431. onMouseDown={(e) => {
  432. e.preventDefault() // Prevent blur
  433. vscode.postMessage({
  434. type: "openFile",
  435. text: "./.roomodes",
  436. values: {
  437. create: true,
  438. content: JSON.stringify({ customModes: [] }, null, 2),
  439. },
  440. })
  441. setShowConfigMenu(false)
  442. }}
  443. onClick={(e) => e.preventDefault()}>
  444. Edit Project Modes (.roomodes)
  445. </div>
  446. </div>
  447. )}
  448. </div>
  449. </div>
  450. </div>
  451. <div className="text-sm text-vscode-descriptionForeground mb-3">
  452. Hit the + to create a new custom mode, or just ask Roo in chat to create one for you!
  453. </div>
  454. <div className="flex gap-2 items-center mb-3 flex-wrap py-1">
  455. {modes.map((modeConfig) => {
  456. const isActive = mode === modeConfig.slug
  457. return (
  458. <button
  459. key={modeConfig.slug}
  460. data-testid={`${modeConfig.slug}-tab`}
  461. data-active={isActive ? "true" : "false"}
  462. onClick={() => handleModeSwitch(modeConfig)}
  463. className={`px-2 py-1 border-none rounded cursor-pointer font-bold ${
  464. isActive
  465. ? "bg-vscode-button-background text-vscode-button-foreground opacity-100"
  466. : "bg-transparent text-vscode-foreground opacity-80"
  467. }`}>
  468. {modeConfig.name}
  469. </button>
  470. )
  471. })}
  472. </div>
  473. </div>
  474. <div style={{ marginBottom: "20px" }}>
  475. {/* Only show name and delete for custom modes */}
  476. {mode && findModeBySlug(mode, customModes) && (
  477. <div className="flex gap-3 mb-4">
  478. <div className="flex-1">
  479. <div className="font-bold mb-1">Name</div>
  480. <div className="flex gap-2">
  481. <VSCodeTextField
  482. value={getModeProperty(findModeBySlug(mode, customModes), "name") ?? ""}
  483. onChange={(e: Event | React.FormEvent<HTMLElement>) => {
  484. const target =
  485. (e as CustomEvent)?.detail?.target ||
  486. ((e as any).target as HTMLInputElement)
  487. const customMode = findModeBySlug(mode, customModes)
  488. if (customMode) {
  489. updateCustomMode(mode, {
  490. ...customMode,
  491. name: target.value,
  492. source: customMode.source || "global",
  493. })
  494. }
  495. }}
  496. className="w-full"
  497. />
  498. <VSCodeButton
  499. appearance="icon"
  500. title="Delete mode"
  501. onClick={() => {
  502. vscode.postMessage({
  503. type: "deleteCustomMode",
  504. slug: mode,
  505. })
  506. }}>
  507. <span className="codicon codicon-trash"></span>
  508. </VSCodeButton>
  509. </div>
  510. </div>
  511. </div>
  512. )}
  513. <div style={{ marginBottom: "16px" }}>
  514. <div className="flex justify-between items-center mb-1">
  515. <div className="font-bold">Role Definition</div>
  516. {!findModeBySlug(mode, customModes) && (
  517. <VSCodeButton
  518. appearance="icon"
  519. onClick={() => {
  520. const currentMode = getCurrentMode()
  521. if (currentMode?.slug) {
  522. handleAgentReset(currentMode.slug, "roleDefinition")
  523. }
  524. }}
  525. title="Reset to default"
  526. data-testid="role-definition-reset">
  527. <span className="codicon codicon-discard"></span>
  528. </VSCodeButton>
  529. )}
  530. </div>
  531. <div className="text-sm text-vscode-descriptionForeground mb-2">
  532. Define Roo's expertise and personality for this mode. This description shapes how Roo
  533. presents itself and approaches tasks.
  534. </div>
  535. <VSCodeTextArea
  536. value={(() => {
  537. const customMode = findModeBySlug(mode, customModes)
  538. const prompt = customModePrompts?.[mode] as PromptComponent
  539. return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
  540. })()}
  541. onChange={(e) => {
  542. const value =
  543. (e as CustomEvent)?.detail?.target?.value ||
  544. ((e as any).target as HTMLTextAreaElement).value
  545. const customMode = findModeBySlug(mode, customModes)
  546. if (customMode) {
  547. // For custom modes, update the JSON file
  548. updateCustomMode(mode, {
  549. ...customMode,
  550. roleDefinition: value.trim() || "",
  551. source: customMode.source || "global",
  552. })
  553. } else {
  554. // For built-in modes, update the prompts
  555. updateAgentPrompt(mode, {
  556. roleDefinition: value.trim() || undefined,
  557. })
  558. }
  559. }}
  560. rows={4}
  561. resize="vertical"
  562. style={{ width: "100%" }}
  563. data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`}
  564. />
  565. </div>
  566. {/* Mode settings */}
  567. <>
  568. <div style={{ marginBottom: "12px" }}>
  569. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>API Configuration</div>
  570. <div style={{ marginBottom: "8px" }}>
  571. <VSCodeDropdown
  572. value={currentApiConfigName || ""}
  573. onChange={(e: any) => {
  574. const value = e.detail?.target?.value || e.target?.value
  575. vscode.postMessage({
  576. type: "loadApiConfiguration",
  577. text: value,
  578. })
  579. }}
  580. className="w-full">
  581. {(listApiConfigMeta || []).map((config) => (
  582. <VSCodeOption key={config.id} value={config.name}>
  583. {config.name}
  584. </VSCodeOption>
  585. ))}
  586. </VSCodeDropdown>
  587. <div className="text-xs mt-1.5 text-vscode-descriptionForeground">
  588. Select which API configuration to use for this mode
  589. </div>
  590. </div>
  591. </div>
  592. {/* Show tools for all modes */}
  593. <div className="mb-4">
  594. <div className="flex justify-between items-center mb-1">
  595. <div className="font-bold">Available Tools</div>
  596. {findModeBySlug(mode, customModes) && (
  597. <VSCodeButton
  598. appearance="icon"
  599. onClick={() => setIsToolsEditMode(!isToolsEditMode)}
  600. title={isToolsEditMode ? "Done editing" : "Edit tools"}>
  601. <span
  602. className={`codicon codicon-${isToolsEditMode ? "check" : "edit"}`}></span>
  603. </VSCodeButton>
  604. )}
  605. </div>
  606. {!findModeBySlug(mode, customModes) && (
  607. <div className="text-sm text-vscode-descriptionForeground mb-2">
  608. Tools for built-in modes cannot be modified
  609. </div>
  610. )}
  611. {isToolsEditMode && findModeBySlug(mode, customModes) ? (
  612. <div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
  613. {availableGroups.map((group) => {
  614. const currentMode = getCurrentMode()
  615. const isCustomMode = findModeBySlug(mode, customModes)
  616. const customMode = isCustomMode
  617. const isGroupEnabled = isCustomMode
  618. ? customMode?.groups?.some((g) => getGroupName(g) === group)
  619. : currentMode?.groups?.some((g) => getGroupName(g) === group)
  620. return (
  621. <VSCodeCheckbox
  622. key={group}
  623. checked={isGroupEnabled}
  624. onChange={handleGroupChange(group, Boolean(isCustomMode), customMode)}
  625. disabled={!isCustomMode}>
  626. {GROUP_DISPLAY_NAMES[group]}
  627. {group === "edit" && (
  628. <div className="text-xs text-vscode-descriptionForeground mt-0.5">
  629. Allowed files:{" "}
  630. {(() => {
  631. const currentMode = getCurrentMode()
  632. const editGroup = currentMode?.groups?.find(
  633. (g) =>
  634. Array.isArray(g) &&
  635. g[0] === "edit" &&
  636. g[1]?.fileRegex,
  637. )
  638. if (!Array.isArray(editGroup)) return "all files"
  639. return (
  640. editGroup[1].description ||
  641. `/${editGroup[1].fileRegex}/`
  642. )
  643. })()}
  644. </div>
  645. )}
  646. </VSCodeCheckbox>
  647. )
  648. })}
  649. </div>
  650. ) : (
  651. <div className="text-sm text-vscode-foreground mb-2 leading-relaxed">
  652. {(() => {
  653. const currentMode = getCurrentMode()
  654. const enabledGroups = currentMode?.groups || []
  655. return enabledGroups
  656. .map((group) => {
  657. const groupName = getGroupName(group)
  658. const displayName = GROUP_DISPLAY_NAMES[groupName]
  659. if (Array.isArray(group) && group[1]?.fileRegex) {
  660. const description =
  661. group[1].description || `/${group[1].fileRegex}/`
  662. return `${displayName} (${description})`
  663. }
  664. return displayName
  665. })
  666. .join(", ")
  667. })()}
  668. </div>
  669. )}
  670. </div>
  671. </>
  672. {/* Role definition for both built-in and custom modes */}
  673. <div style={{ marginBottom: "8px" }}>
  674. <div
  675. style={{
  676. display: "flex",
  677. justifyContent: "space-between",
  678. alignItems: "center",
  679. marginBottom: "4px",
  680. }}>
  681. <div style={{ fontWeight: "bold" }}>Mode-specific Custom Instructions</div>
  682. {!findModeBySlug(mode, customModes) && (
  683. <VSCodeButton
  684. appearance="icon"
  685. onClick={() => {
  686. const currentMode = getCurrentMode()
  687. if (currentMode?.slug) {
  688. handleAgentReset(currentMode.slug, "customInstructions")
  689. }
  690. }}
  691. title="Reset to default"
  692. data-testid="custom-instructions-reset">
  693. <span className="codicon codicon-discard"></span>
  694. </VSCodeButton>
  695. )}
  696. </div>
  697. <div
  698. style={{
  699. fontSize: "13px",
  700. color: "var(--vscode-descriptionForeground)",
  701. marginBottom: "8px",
  702. }}>
  703. Add behavioral guidelines specific to {getCurrentMode()?.name || "Code"} mode.
  704. </div>
  705. <VSCodeTextArea
  706. value={(() => {
  707. const customMode = findModeBySlug(mode, customModes)
  708. const prompt = customModePrompts?.[mode] as PromptComponent
  709. return (
  710. customMode?.customInstructions ??
  711. prompt?.customInstructions ??
  712. getCustomInstructions(mode, customModes)
  713. )
  714. })()}
  715. onChange={(e) => {
  716. const value =
  717. (e as CustomEvent)?.detail?.target?.value ||
  718. ((e as any).target as HTMLTextAreaElement).value
  719. const customMode = findModeBySlug(mode, customModes)
  720. if (customMode) {
  721. // For custom modes, update the JSON file
  722. updateCustomMode(mode, {
  723. ...customMode,
  724. customInstructions: value.trim() || undefined,
  725. source: customMode.source || "global",
  726. })
  727. } else {
  728. // For built-in modes, update the prompts
  729. const existingPrompt = customModePrompts?.[mode] as PromptComponent
  730. updateAgentPrompt(mode, {
  731. ...existingPrompt,
  732. customInstructions: value.trim(),
  733. })
  734. }
  735. }}
  736. rows={4}
  737. resize="vertical"
  738. style={{ width: "100%" }}
  739. data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`}
  740. />
  741. <div
  742. style={{
  743. fontSize: "12px",
  744. color: "var(--vscode-descriptionForeground)",
  745. marginTop: "5px",
  746. }}>
  747. Custom instructions specific to {getCurrentMode()?.name || "Code"} mode can also be loaded
  748. from{" "}
  749. <span
  750. style={{
  751. color: "var(--vscode-textLink-foreground)",
  752. cursor: "pointer",
  753. textDecoration: "underline",
  754. }}
  755. onClick={() => {
  756. const currentMode = getCurrentMode()
  757. if (!currentMode) return
  758. // Open or create an empty file
  759. vscode.postMessage({
  760. type: "openFile",
  761. text: `./.clinerules-${currentMode.slug}`,
  762. values: {
  763. create: true,
  764. content: "",
  765. },
  766. })
  767. }}>
  768. .clinerules-{getCurrentMode()?.slug || "code"}
  769. </span>{" "}
  770. in your workspace.
  771. </div>
  772. </div>
  773. </div>
  774. <div
  775. style={{
  776. paddingBottom: "40px",
  777. marginBottom: "20px",
  778. borderBottom: "1px solid var(--vscode-input-border)",
  779. }}>
  780. <div style={{ display: "flex", gap: "8px" }}>
  781. <VSCodeButton
  782. appearance="primary"
  783. onClick={() => {
  784. const currentMode = getCurrentMode()
  785. if (currentMode) {
  786. vscode.postMessage({
  787. type: "getSystemPrompt",
  788. mode: currentMode.slug,
  789. })
  790. }
  791. }}
  792. data-testid="preview-prompt-button">
  793. Preview System Prompt
  794. </VSCodeButton>
  795. <VSCodeButton
  796. appearance="icon"
  797. title="Copy system prompt to clipboard"
  798. onClick={() => {
  799. vscode.postMessage({
  800. type: "copySystemPrompt",
  801. text: selectedPromptContent,
  802. })
  803. }}
  804. data-testid="copy-prompt-button">
  805. <span className="codicon codicon-copy"></span>
  806. </VSCodeButton>
  807. </div>
  808. </div>
  809. <div
  810. style={{
  811. marginTop: "20px",
  812. paddingBottom: "60px",
  813. borderBottom: "1px solid var(--vscode-input-border)",
  814. }}>
  815. <h3 style={{ color: "var(--vscode-foreground)", marginBottom: "12px" }}>Support Prompts</h3>
  816. <div
  817. style={{
  818. display: "flex",
  819. gap: "8px",
  820. alignItems: "center",
  821. marginBottom: "12px",
  822. flexWrap: "wrap",
  823. padding: "4px 0",
  824. }}>
  825. {Object.keys(supportPrompt.default).map((type) => (
  826. <button
  827. key={type}
  828. data-testid={`${type}-tab`}
  829. data-active={activeSupportTab === type ? "true" : "false"}
  830. onClick={() => setActiveSupportTab(type as SupportPromptType)}
  831. style={{
  832. padding: "4px 8px",
  833. border: "none",
  834. background: activeSupportTab === type ? "var(--vscode-button-background)" : "none",
  835. color:
  836. activeSupportTab === type
  837. ? "var(--vscode-button-foreground)"
  838. : "var(--vscode-foreground)",
  839. cursor: "pointer",
  840. opacity: activeSupportTab === type ? 1 : 0.8,
  841. borderRadius: "3px",
  842. fontWeight: "bold",
  843. }}>
  844. {supportPromptLabels[type as SupportPromptType]}
  845. </button>
  846. ))}
  847. </div>
  848. {/* Support prompt description */}
  849. <div
  850. style={{
  851. fontSize: "13px",
  852. color: "var(--vscode-descriptionForeground)",
  853. margin: "8px 0 16px",
  854. }}>
  855. {supportPromptDescriptions[activeSupportTab]}
  856. </div>
  857. {/* Show active tab content */}
  858. <div key={activeSupportTab}>
  859. <div
  860. style={{
  861. display: "flex",
  862. justifyContent: "space-between",
  863. alignItems: "center",
  864. marginBottom: "4px",
  865. }}>
  866. <div style={{ fontWeight: "bold" }}>Prompt</div>
  867. <VSCodeButton
  868. appearance="icon"
  869. onClick={() => handleSupportReset(activeSupportTab)}
  870. title={`Reset ${activeSupportTab} prompt to default`}>
  871. <span className="codicon codicon-discard"></span>
  872. </VSCodeButton>
  873. </div>
  874. <VSCodeTextArea
  875. value={getSupportPromptValue(activeSupportTab)}
  876. onChange={(e) => {
  877. const value =
  878. (e as CustomEvent)?.detail?.target?.value ||
  879. ((e as any).target as HTMLTextAreaElement).value
  880. const trimmedValue = value.trim()
  881. updateSupportPrompt(activeSupportTab, trimmedValue || undefined)
  882. }}
  883. rows={6}
  884. resize="vertical"
  885. style={{ width: "100%" }}
  886. />
  887. {activeSupportTab === "ENHANCE" && (
  888. <>
  889. <div>
  890. <div
  891. style={{
  892. color: "var(--vscode-foreground)",
  893. fontSize: "13px",
  894. marginBottom: "20px",
  895. marginTop: "5px",
  896. }}></div>
  897. <div style={{ marginBottom: "12px" }}>
  898. <div style={{ marginBottom: "8px" }}>
  899. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>
  900. API Configuration
  901. </div>
  902. <div
  903. style={{
  904. fontSize: "13px",
  905. color: "var(--vscode-descriptionForeground)",
  906. }}>
  907. You can select an API configuration to always use for enhancing prompts,
  908. or just use whatever is currently selected
  909. </div>
  910. </div>
  911. <VSCodeDropdown
  912. value={enhancementApiConfigId || ""}
  913. data-testid="api-config-dropdown"
  914. onChange={(e: any) => {
  915. const value = e.detail?.target?.value || e.target?.value
  916. setEnhancementApiConfigId(value)
  917. vscode.postMessage({
  918. type: "enhancementApiConfigId",
  919. text: value,
  920. })
  921. }}
  922. style={{ width: "300px" }}>
  923. <VSCodeOption value="">
  924. Use currently selected API configuration
  925. </VSCodeOption>
  926. {(listApiConfigMeta || []).map((config) => (
  927. <VSCodeOption key={config.id} value={config.id}>
  928. {config.name}
  929. </VSCodeOption>
  930. ))}
  931. </VSCodeDropdown>
  932. </div>
  933. </div>
  934. <div style={{ marginTop: "12px" }}>
  935. <VSCodeTextArea
  936. value={testPrompt}
  937. onChange={(e) => setTestPrompt((e.target as HTMLTextAreaElement).value)}
  938. placeholder="Enter a prompt to test the enhancement"
  939. rows={3}
  940. resize="vertical"
  941. style={{ width: "100%" }}
  942. data-testid="test-prompt-textarea"
  943. />
  944. <div
  945. style={{
  946. marginTop: "8px",
  947. display: "flex",
  948. justifyContent: "flex-start",
  949. alignItems: "center",
  950. gap: 8,
  951. }}>
  952. <VSCodeButton
  953. onClick={handleTestEnhancement}
  954. disabled={isEnhancing}
  955. appearance="primary">
  956. Preview Prompt Enhancement
  957. </VSCodeButton>
  958. </div>
  959. </div>
  960. </>
  961. )}
  962. </div>
  963. </div>
  964. </div>
  965. {isCreateModeDialogOpen && (
  966. <div
  967. style={{
  968. position: "fixed",
  969. inset: 0,
  970. display: "flex",
  971. justifyContent: "flex-end",
  972. backgroundColor: "rgba(0, 0, 0, 0.5)",
  973. zIndex: 1000,
  974. }}>
  975. <div
  976. style={{
  977. width: "calc(100vw - 100px)",
  978. height: "100%",
  979. backgroundColor: "var(--vscode-editor-background)",
  980. boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.2)",
  981. display: "flex",
  982. flexDirection: "column",
  983. position: "relative",
  984. }}>
  985. <div
  986. style={{
  987. flex: 1,
  988. padding: "20px",
  989. overflowY: "auto",
  990. minHeight: 0,
  991. }}>
  992. <VSCodeButton
  993. appearance="icon"
  994. onClick={() => setIsCreateModeDialogOpen(false)}
  995. style={{
  996. position: "absolute",
  997. top: "20px",
  998. right: "20px",
  999. }}>
  1000. <span className="codicon codicon-close"></span>
  1001. </VSCodeButton>
  1002. <h2 style={{ margin: "0 0 16px" }}>Create New Mode</h2>
  1003. <div style={{ marginBottom: "16px" }}>
  1004. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Name</div>
  1005. <VSCodeTextField
  1006. value={newModeName}
  1007. onChange={(e: Event | React.FormEvent<HTMLElement>) => {
  1008. const target =
  1009. (e as CustomEvent)?.detail?.target ||
  1010. ((e as any).target as HTMLInputElement)
  1011. handleNameChange(target.value)
  1012. }}
  1013. style={{ width: "100%" }}
  1014. />
  1015. </div>
  1016. <div style={{ marginBottom: "16px" }}>
  1017. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Slug</div>
  1018. <VSCodeTextField
  1019. value={newModeSlug}
  1020. onChange={(e: Event | React.FormEvent<HTMLElement>) => {
  1021. const target =
  1022. (e as CustomEvent)?.detail?.target ||
  1023. ((e as any).target as HTMLInputElement)
  1024. setNewModeSlug(target.value)
  1025. }}
  1026. style={{ width: "100%" }}
  1027. />
  1028. <div
  1029. style={{
  1030. fontSize: "12px",
  1031. color: "var(--vscode-descriptionForeground)",
  1032. marginTop: "4px",
  1033. }}>
  1034. The slug is used in URLs and file names. It should be lowercase and contain only
  1035. letters, numbers, and hyphens.
  1036. </div>
  1037. </div>
  1038. <div style={{ marginBottom: "16px" }}>
  1039. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Save Location</div>
  1040. <div className="text-sm text-vscode-descriptionForeground mb-2">
  1041. Choose where to save this mode. Project-specific modes take precedence over global
  1042. modes.
  1043. </div>
  1044. <VSCodeRadioGroup
  1045. value={newModeSource}
  1046. onChange={(e: Event | React.FormEvent<HTMLElement>) => {
  1047. const target = ((e as CustomEvent)?.detail?.target ||
  1048. (e.target as HTMLInputElement)) as HTMLInputElement
  1049. setNewModeSource(target.value as ModeSource)
  1050. }}>
  1051. <VSCodeRadio value="global">
  1052. Global
  1053. <div
  1054. style={{
  1055. fontSize: "12px",
  1056. color: "var(--vscode-descriptionForeground)",
  1057. marginTop: "2px",
  1058. }}>
  1059. Available in all workspaces
  1060. </div>
  1061. </VSCodeRadio>
  1062. <VSCodeRadio value="project">
  1063. Project-specific (.roomodes)
  1064. <div className="text-xs text-vscode-descriptionForeground mt-0.5">
  1065. Only available in this workspace, takes precedence over global
  1066. </div>
  1067. </VSCodeRadio>
  1068. </VSCodeRadioGroup>
  1069. </div>
  1070. <div style={{ marginBottom: "16px" }}>
  1071. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Role Definition</div>
  1072. <div
  1073. style={{
  1074. fontSize: "13px",
  1075. color: "var(--vscode-descriptionForeground)",
  1076. marginBottom: "8px",
  1077. }}>
  1078. Define Roo's expertise and personality for this mode.
  1079. </div>
  1080. <VSCodeTextArea
  1081. value={newModeRoleDefinition}
  1082. onChange={(e) => {
  1083. const value =
  1084. (e as CustomEvent)?.detail?.target?.value ||
  1085. ((e as any).target as HTMLTextAreaElement).value
  1086. setNewModeRoleDefinition(value)
  1087. }}
  1088. rows={4}
  1089. resize="vertical"
  1090. style={{ width: "100%" }}
  1091. />
  1092. </div>
  1093. <div style={{ marginBottom: "16px" }}>
  1094. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Available Tools</div>
  1095. <div
  1096. style={{
  1097. fontSize: "13px",
  1098. color: "var(--vscode-descriptionForeground)",
  1099. marginBottom: "8px",
  1100. }}>
  1101. Select which tools this mode can use.
  1102. </div>
  1103. <div
  1104. style={{
  1105. display: "grid",
  1106. gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
  1107. gap: "8px",
  1108. }}>
  1109. {availableGroups.map((group) => (
  1110. <VSCodeCheckbox
  1111. key={group}
  1112. checked={newModeGroups.some((g) => getGroupName(g) === group)}
  1113. onChange={(e: Event | React.FormEvent<HTMLElement>) => {
  1114. const target =
  1115. (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
  1116. const checked = target.checked
  1117. if (checked) {
  1118. setNewModeGroups([...newModeGroups, group])
  1119. } else {
  1120. setNewModeGroups(
  1121. newModeGroups.filter((g) => getGroupName(g) !== group),
  1122. )
  1123. }
  1124. }}>
  1125. {GROUP_DISPLAY_NAMES[group]}
  1126. </VSCodeCheckbox>
  1127. ))}
  1128. </div>
  1129. </div>
  1130. <div style={{ marginBottom: "16px" }}>
  1131. <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Custom Instructions</div>
  1132. <div
  1133. style={{
  1134. fontSize: "13px",
  1135. color: "var(--vscode-descriptionForeground)",
  1136. marginBottom: "8px",
  1137. }}>
  1138. Add behavioral guidelines specific to this mode.
  1139. </div>
  1140. <VSCodeTextArea
  1141. value={newModeCustomInstructions}
  1142. onChange={(e) => {
  1143. const value =
  1144. (e as CustomEvent)?.detail?.target?.value ||
  1145. ((e as any).target as HTMLTextAreaElement).value
  1146. setNewModeCustomInstructions(value)
  1147. }}
  1148. rows={4}
  1149. resize="vertical"
  1150. style={{ width: "100%" }}
  1151. />
  1152. </div>
  1153. </div>
  1154. <div
  1155. style={{
  1156. display: "flex",
  1157. justifyContent: "flex-end",
  1158. padding: "12px 20px",
  1159. gap: "8px",
  1160. borderTop: "1px solid var(--vscode-editor-lineHighlightBorder)",
  1161. backgroundColor: "var(--vscode-editor-background)",
  1162. }}>
  1163. <VSCodeButton onClick={() => setIsCreateModeDialogOpen(false)}>Cancel</VSCodeButton>
  1164. <VSCodeButton
  1165. appearance="primary"
  1166. onClick={handleCreateMode}
  1167. disabled={!newModeName.trim() || !newModeSlug.trim()}>
  1168. Create Mode
  1169. </VSCodeButton>
  1170. </div>
  1171. </div>
  1172. </div>
  1173. )}
  1174. {isDialogOpen && (
  1175. <div
  1176. style={{
  1177. position: "fixed",
  1178. inset: 0,
  1179. display: "flex",
  1180. justifyContent: "flex-end",
  1181. backgroundColor: "rgba(0, 0, 0, 0.5)",
  1182. zIndex: 1000,
  1183. }}>
  1184. <div
  1185. style={{
  1186. width: "calc(100vw - 100px)",
  1187. height: "100%",
  1188. backgroundColor: "var(--vscode-editor-background)",
  1189. boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.2)",
  1190. display: "flex",
  1191. flexDirection: "column",
  1192. position: "relative",
  1193. }}>
  1194. <div
  1195. style={{
  1196. flex: 1,
  1197. padding: "20px",
  1198. overflowY: "auto",
  1199. minHeight: 0,
  1200. }}>
  1201. <VSCodeButton
  1202. appearance="icon"
  1203. onClick={() => setIsDialogOpen(false)}
  1204. style={{
  1205. position: "absolute",
  1206. top: "20px",
  1207. right: "20px",
  1208. }}>
  1209. <span className="codicon codicon-close"></span>
  1210. </VSCodeButton>
  1211. <h2 style={{ margin: "0 0 16px" }}>{selectedPromptTitle}</h2>
  1212. <pre
  1213. style={{
  1214. padding: "8px",
  1215. whiteSpace: "pre-wrap",
  1216. wordBreak: "break-word",
  1217. fontFamily: "var(--vscode-editor-font-family)",
  1218. fontSize: "var(--vscode-editor-font-size)",
  1219. color: "var(--vscode-editor-foreground)",
  1220. backgroundColor: "var(--vscode-editor-background)",
  1221. border: "1px solid var(--vscode-editor-lineHighlightBorder)",
  1222. borderRadius: "4px",
  1223. overflowY: "auto",
  1224. }}>
  1225. {selectedPromptContent}
  1226. </pre>
  1227. </div>
  1228. <div
  1229. style={{
  1230. display: "flex",
  1231. justifyContent: "flex-end",
  1232. padding: "12px 20px",
  1233. borderTop: "1px solid var(--vscode-editor-lineHighlightBorder)",
  1234. backgroundColor: "var(--vscode-editor-background)",
  1235. }}>
  1236. <VSCodeButton onClick={() => setIsDialogOpen(false)}>Close</VSCodeButton>
  1237. </div>
  1238. </div>
  1239. </div>
  1240. )}
  1241. </div>
  1242. )
  1243. }
  1244. export default PromptsView