Skip to content

Commit 8a095e5

Browse files
Kasper Jungeclaude
authored andcommitted
feat: add prompt picker to New Run modal so users can select named prompts
The modal now fetches available prompts from the API and displays them as selectable chips. When a prompt is selected, it replaces the raw file path input. The run overview also shows a prompt tag badge when a named prompt was used. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2e84071 commit 8a095e5

2 files changed

Lines changed: 129 additions & 8 deletions

File tree

src/ralphify/ui/static/dashboard.css

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,80 @@ body {
10651065
margin-top: 24px;
10661066
}
10671067

1068+
/* ── Prompt picker ──────────────────────────────────────────────── */
1069+
1070+
.prompt-picker {
1071+
display: flex;
1072+
flex-wrap: wrap;
1073+
gap: 8px;
1074+
margin-bottom: 10px;
1075+
}
1076+
1077+
.prompt-chip {
1078+
display: flex;
1079+
flex-direction: column;
1080+
align-items: flex-start;
1081+
padding: 8px 14px;
1082+
background: var(--bg);
1083+
border: 1.5px solid var(--border);
1084+
border-radius: var(--radius);
1085+
cursor: pointer;
1086+
transition: all 0.15s ease;
1087+
font-family: var(--font-sans);
1088+
font-size: 13px;
1089+
text-align: left;
1090+
}
1091+
1092+
.prompt-chip:hover {
1093+
border-color: var(--primary-border);
1094+
background: var(--primary-light);
1095+
}
1096+
1097+
.prompt-chip.selected {
1098+
border-color: var(--primary);
1099+
background: var(--primary-light);
1100+
box-shadow: 0 0 0 3px rgba(109, 74, 232, 0.1);
1101+
}
1102+
1103+
.prompt-chip-name {
1104+
font-weight: 600;
1105+
color: var(--text);
1106+
}
1107+
1108+
.prompt-chip-desc {
1109+
font-size: 11px;
1110+
color: var(--text-secondary);
1111+
margin-top: 2px;
1112+
}
1113+
1114+
.prompt-selected-note {
1115+
font-size: 13px;
1116+
color: var(--text-secondary);
1117+
padding: 8px 12px;
1118+
background: var(--primary-light);
1119+
border-radius: var(--radius);
1120+
border: 1px solid var(--primary-border);
1121+
}
1122+
1123+
.prompt-selected-note strong {
1124+
color: var(--primary);
1125+
font-family: var(--font-mono);
1126+
font-size: 12px;
1127+
}
1128+
1129+
.run-prompt-tag {
1130+
display: inline-flex;
1131+
align-items: center;
1132+
padding: 2px 10px;
1133+
background: var(--accent-light);
1134+
color: var(--accent);
1135+
border-radius: 20px;
1136+
font-size: 11px;
1137+
font-weight: 600;
1138+
font-family: var(--font-mono);
1139+
letter-spacing: 0.02em;
1140+
}
1141+
10681142
/* ── Scrollbar ───────────────────────────────────────────────────── */
10691143

10701144
::-webkit-scrollbar {

src/ralphify/ui/static/dashboard.js

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,9 @@ function App() {
189189
}, []);
190190

191191
return html`
192-
<div id="app">
193-
<${Sidebar} />
194-
<${Main} />
195-
${showNewRunModal.value && html`<${NewRunModal} />`}
196-
</div>
192+
<${Sidebar} />
193+
<${Main} />
194+
${showNewRunModal.value && html`<${NewRunModal} />`}
197195
`;
198196
}
199197

@@ -391,6 +389,9 @@ function RunOverview({ run }) {
391389
<div class="run-overview-title">
392390
<h2>${run.run_id}</h2>
393391
<span class="run-status-badge ${run.status}">${run.status}</span>
392+
${run.prompt_name && html`
393+
<span class="run-prompt-tag">${run.prompt_name}</span>
394+
`}
394395
</div>
395396
</div>
396397
<div class="run-overview-body">
@@ -650,22 +651,46 @@ function NewRunModal() {
650651
args: ['-p', '--dangerously-skip-permissions'],
651652
prompt_file: 'PROMPT.md',
652653
prompt_text: '',
654+
prompt_name: null,
653655
max_iterations: '',
654656
delay: '0',
655657
timeout: '',
656658
stop_on_error: false,
657659
project_dir: '.',
658660
});
661+
const [prompts, setPrompts] = useState([]);
662+
const [promptsLoaded, setPromptsLoaded] = useState(false);
663+
664+
useEffect(() => {
665+
const projectDir = btoa(config.project_dir);
666+
api('GET', `/projects/${projectDir}/primitives`)
667+
.then(data => {
668+
const found = (data || []).filter(p => p.kind === 'prompts' && p.enabled);
669+
setPrompts(found);
670+
setPromptsLoaded(true);
671+
})
672+
.catch(() => setPromptsLoaded(true));
673+
}, []);
659674

660675
const updateField = (field) => (e) => {
661676
setConfig({ ...config, [field]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
662677
};
663678

679+
const selectPrompt = (name) => {
680+
if (config.prompt_name === name) {
681+
// Deselect — go back to default prompt file
682+
setConfig({ ...config, prompt_name: null, prompt_file: 'PROMPT.md' });
683+
} else {
684+
setConfig({ ...config, prompt_name: name, prompt_file: '' });
685+
}
686+
};
687+
664688
const handleSubmit = () => {
665689
const body = {
666690
command: config.command,
667691
args: config.args,
668-
prompt_file: config.prompt_file,
692+
prompt_file: config.prompt_name ? '' : config.prompt_file,
693+
prompt_name: config.prompt_name || null,
669694
prompt_text: config.prompt_text || null,
670695
max_iterations: config.max_iterations ? parseInt(config.max_iterations) : null,
671696
delay: parseFloat(config.delay) || 0,
@@ -685,8 +710,30 @@ function NewRunModal() {
685710
<input class="form-input" value=${config.command} onInput=${updateField('command')} />
686711
</div>
687712
<div class="form-group">
688-
<label class="form-label">Prompt File</label>
689-
<input class="form-input" value=${config.prompt_file} onInput=${updateField('prompt_file')} />
713+
<label class="form-label">Prompt</label>
714+
${promptsLoaded && prompts.length > 0 && html`
715+
<div class="prompt-picker">
716+
${prompts.map(p => html`
717+
<button key=${p.name}
718+
class="prompt-chip ${config.prompt_name === p.name ? 'selected' : ''}"
719+
onClick=${() => selectPrompt(p.name)}
720+
title=${p.description || p.name}>
721+
<span class="prompt-chip-name">${p.name}</span>
722+
${p.description && html`<span class="prompt-chip-desc">${p.description}</span>`}
723+
</button>
724+
`)}
725+
</div>
726+
`}
727+
${!config.prompt_name && html`
728+
<input class="form-input" value=${config.prompt_file}
729+
onInput=${updateField('prompt_file')}
730+
placeholder="PROMPT.md" />
731+
`}
732+
${config.prompt_name && html`
733+
<div class="prompt-selected-note">
734+
Using prompt: <strong>${config.prompt_name}</strong>
735+
</div>
736+
`}
690737
</div>
691738
<div class="form-group">
692739
<label class="form-label">Project Directory</label>

0 commit comments

Comments
 (0)