Skip to content

Commit 0de128e

Browse files
committed
Rewrite prompt templates
1 parent 0d0f06e commit 0de128e

File tree

8 files changed

+251
-122
lines changed

8 files changed

+251
-122
lines changed

apps/desktop/src/components/codegen/CodegenPage.svelte

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import CodegenClaudeMessage from '$components/codegen/CodegenClaudeMessage.svelte';
1616
import CodegenInput from '$components/codegen/CodegenInput.svelte';
1717
import CodegenMcpConfigModal from '$components/codegen/CodegenMcpConfigModal.svelte';
18+
import CodegenPromptConfigModal from '$components/codegen/CodegenPromptConfigModal.svelte';
1819
import CodegenServiceMessageThinking from '$components/codegen/CodegenServiceMessageThinking.svelte';
1920
import CodegenServiceMessageUseTool from '$components/codegen/CodegenServiceMessageUseTool.svelte';
2021
import CodegenSidebar from '$components/codegen/CodegenSidebar.svelte';
@@ -121,6 +122,7 @@
121122
let templateContextMenu = $state<ContextMenu>();
122123
let templateTrigger = $state<HTMLButtonElement>();
123124
let mcpConfigModal = $state<CodegenMcpConfigModal>();
125+
let promptConfigModal = $state<CodegenPromptConfigModal>();
124126
125127
const modelOptions: { label: string; value: ModelType }[] = [
126128
{ label: 'Sonnet', value: 'sonnet' },
@@ -137,7 +139,19 @@
137139
{ label: 'Accept edits', value: 'acceptEdits' }
138140
];
139141
140-
const promptTemplates = $derived(claudeCodeService.promptTemplates(undefined));
142+
const promptTemplates = $derived(claudeCodeService.promptTemplates(projectId));
143+
const promptDirs = $derived(claudeCodeService.promptDirs(projectId));
144+
145+
async function openPromptConfigDir(path: string) {
146+
await claudeCodeService.createPromptDir({ projectId, path });
147+
148+
const editorUri = getEditorUri({
149+
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
150+
path: [path]
151+
});
152+
153+
urlService.openExternalUrl(editorUri);
154+
}
141155
142156
const projectState = uiState.project(projectId);
143157
const selectedBranch = $derived(projectState.selectedClaudeSession.current);
@@ -334,21 +348,6 @@
334348
templateContextMenu?.close();
335349
}
336350
337-
async function configureTemplates() {
338-
templateContextMenu?.close();
339-
340-
const templatesPath = await claudeCodeService.fetchPromptTemplatesPath(undefined);
341-
342-
if (templatesPath) {
343-
const editorUri = getEditorUri({
344-
schemeId: $userSettings.defaultCodeEditor.schemeIdentifer,
345-
path: [templatesPath]
346-
});
347-
348-
urlService.openExternalUrl(editorUri);
349-
}
350-
}
351-
352351
function showInWorkspace() {
353352
if (!selectedBranch) return;
354353
goto(`${workspacePath(projectId)}?stackId=${selectedBranch.stackId}`);
@@ -1151,7 +1150,7 @@
11511150
<ContextMenuSection>
11521151
<ReduxResult result={promptTemplates.result} {projectId}>
11531152
{#snippet children(promptTemplates, { projectId: _projectId })}
1154-
{#each promptTemplates.templates as template}
1153+
{#each promptTemplates as template}
11551154
<ContextMenuItem
11561155
label={template.label}
11571156
onclick={() => insertTemplate(template.template)}
@@ -1162,13 +1161,21 @@
11621161
</ContextMenuSection>
11631162
<ContextMenuSection>
11641163
<ContextMenuItem
1165-
label="Edit in {$userSettings.defaultCodeEditor.displayName}"
1164+
label="Edit templates"
11661165
icon="open-editor"
1167-
onclick={configureTemplates}
1166+
onclick={() => promptConfigModal?.show()}
11681167
/>
11691168
</ContextMenuSection>
11701169
</ContextMenu>
11711170

1171+
{#if promptDirs.response}
1172+
<CodegenPromptConfigModal
1173+
bind:this={promptConfigModal}
1174+
promptDirs={promptDirs.response}
1175+
{openPromptConfigDir}
1176+
/>
1177+
{/if}
1178+
11721179
<style lang="postcss">
11731180
.page {
11741181
display: flex;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script lang="ts">
2+
import { Button, Modal, SectionCard } from '@gitbutler/ui';
3+
import type { PromptDir } from '$lib/codegen/types';
4+
5+
type Props = {
6+
promptDirs: PromptDir[];
7+
openPromptConfigDir: (path: string) => void;
8+
};
9+
10+
let modal = $state<Modal>();
11+
12+
export function show() {
13+
modal?.show();
14+
}
15+
16+
const { promptDirs, openPromptConfigDir }: Props = $props();
17+
</script>
18+
19+
<Modal bind:this={modal} title="Configure prompt templates">
20+
<div class="flex flex-col gap-16">
21+
<p class="text-13">
22+
We have a tierd prompt configuration setup. Prompts are expected to be found in the following
23+
locations.
24+
</p>
25+
<p class="text-13">
26+
Project prompts take precidence over global prompts, and local prompts take precidence over
27+
project prompts.
28+
</p>
29+
30+
<div>
31+
{#each promptDirs as dir, idx}
32+
<SectionCard
33+
roundedTop={idx === 0}
34+
roundedBottom={idx === promptDirs.length - 1}
35+
orientation="row"
36+
>
37+
{#snippet title()}
38+
{dir.label}
39+
{/snippet}
40+
41+
{#snippet caption()}
42+
<div class="flex flex-col gap-6">
43+
<p class="text-11">{dir.path}{dir.path.endsWith('/') ? '' : '/'}</p>
44+
<p class="text-13">
45+
Looks for files ending with: <span class="clr-text-1"
46+
>{dir.filters.join(' or ')}</span
47+
>
48+
</p>
49+
</div>
50+
{/snippet}
51+
52+
{#snippet actions()}
53+
<Button onclick={() => openPromptConfigDir(dir.path)}>Open in Editor</Button>
54+
{/snippet}
55+
</SectionCard>
56+
{/each}
57+
</div>
58+
</div>
59+
</Modal>

apps/desktop/src/lib/codegen/claude.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
type ThinkingLevel,
77
type ModelType,
88
type PermissionMode,
9-
type PromptTemplates,
9+
type PromptTemplate,
10+
type PromptDir,
1011
type McpConfig,
1112
type SubAgent
1213
} from '$lib/codegen/types';
@@ -88,24 +89,24 @@ export class ClaudeCodeService {
8889
return this.api.endpoints.getSessionDetails.fetch;
8990
}
9091

91-
get promptTemplates() {
92-
return this.api.endpoints.getPromptTemplates.useQuery;
92+
promptTemplates(projectId: string) {
93+
return this.api.endpoints.listPromptTemplates.useQuery({ projectId });
9394
}
9495

9596
get fetchPromptTemplates() {
96-
return this.api.endpoints.getPromptTemplates.fetch;
97+
return this.api.endpoints.listPromptTemplates.fetch;
9798
}
9899

99-
get writePromptTemplates() {
100-
return this.api.endpoints.writePromptTemplates.mutate;
100+
promptDirs(projectId: string) {
101+
return this.api.endpoints.getPromptDirs.useQuery({ projectId });
101102
}
102103

103-
get promptTemplatesPath() {
104-
return this.api.endpoints.getPromptTemplatesPath.useQuery;
104+
get fetchPromptDirs() {
105+
return this.api.endpoints.getPromptDirs.fetch;
105106
}
106107

107-
get fetchPromptTemplatesPath() {
108-
return this.api.endpoints.getPromptTemplatesPath.fetch;
108+
get createPromptDir() {
109+
return this.api.endpoints.createPromptDir.mutate;
109110
}
110111

111112
get mcpConfig() {
@@ -280,26 +281,21 @@ function injectEndpoints(api: ClientState['backendApi']) {
280281
unsubscribe();
281282
}
282283
}),
283-
getPromptTemplates: build.query<PromptTemplates, undefined>({
284-
extraOptions: { command: 'claude_get_prompt_templates' },
285-
query: () => undefined
284+
listPromptTemplates: build.query<PromptTemplate[], { projectId: string }>({
285+
extraOptions: { command: 'claude_list_prompt_templates' },
286+
query: (args) => args
286287
}),
287-
writePromptTemplates: build.mutation<
288-
undefined,
289-
{
290-
templates: PromptTemplates;
291-
}
292-
>({
288+
getPromptDirs: build.query<PromptDir[], { projectId: string }>({
289+
extraOptions: { command: 'claude_get_prompt_dirs' },
290+
query: (args) => args
291+
}),
292+
createPromptDir: build.mutation<undefined, { projectId: string; path: string }>({
293293
extraOptions: {
294-
command: 'claude_write_prompt_templates',
295-
actionName: 'Write Prompt Templates'
294+
command: 'claude_maybe_create_prompt_dir',
295+
actionName: 'Create Prompt Directory'
296296
},
297297
query: (args) => args
298298
}),
299-
getPromptTemplatesPath: build.query<string, undefined>({
300-
extraOptions: { command: 'claude_get_prompt_templates_path' },
301-
query: () => undefined
302-
}),
303299
getMcpConfig: build.query<McpConfig, { projectId: string }>({
304300
extraOptions: { command: 'claude_get_mcp_config' },
305301
query: (args) => args

apps/desktop/src/lib/codegen/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,10 @@ export type PromptTemplate = {
195195
template: string;
196196
};
197197

198-
export type PromptTemplates = {
199-
templates: PromptTemplate[];
198+
export type PromptDir = {
199+
label: string;
200+
path: string;
201+
filters: string[];
200202
};
201203

202204
export type McpConfig = {

crates/but-api/src/commands/claude.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,27 +172,32 @@ pub async fn claude_compact_history(app: &App, params: CompactHistoryParams) ->
172172
#[api_cmd]
173173
#[tauri::command(async)]
174174
#[instrument(err(Debug))]
175-
pub fn claude_get_prompt_templates() -> Result<prompt_templates::PromptTemplates, Error> {
176-
let templates = prompt_templates::load_prompt_templates()?;
175+
pub fn claude_list_prompt_templates(
176+
project_id: ProjectId,
177+
) -> Result<Vec<prompt_templates::PromptTemplate>, Error> {
178+
let project = gitbutler_project::get(project_id)?;
179+
let templates = prompt_templates::list_templates(&project)?;
177180
Ok(templates)
178181
}
179182

180183
#[api_cmd]
181184
#[tauri::command(async)]
182185
#[instrument(err(Debug))]
183-
pub fn claude_write_prompt_templates(
184-
templates: prompt_templates::PromptTemplates,
185-
) -> Result<(), Error> {
186-
prompt_templates::write_prompt_templates(&templates)?;
187-
Ok(())
186+
pub fn claude_get_prompt_dirs(
187+
project_id: ProjectId,
188+
) -> Result<Vec<prompt_templates::PromptDir>, Error> {
189+
let project = gitbutler_project::get(project_id)?;
190+
let dirs = prompt_templates::prompt_dirs(&project)?;
191+
Ok(dirs)
188192
}
189193

190194
#[api_cmd]
191195
#[tauri::command(async)]
192196
#[instrument(err(Debug))]
193-
pub fn claude_get_prompt_templates_path() -> Result<String, Error> {
194-
let path = prompt_templates::get_prompt_templates_path_string()?;
195-
Ok(path)
197+
pub fn claude_maybe_create_prompt_dir(project_id: ProjectId, path: String) -> Result<(), Error> {
198+
let project = gitbutler_project::get(project_id)?;
199+
prompt_templates::maybe_create_dir(&project, &path)?;
200+
Ok(())
196201
}
197202

198203
#[tauri::command(async)]

0 commit comments

Comments
 (0)