@@ -36,7 +36,6 @@ const form = ref({
3636 expr: ' 30m' ,
3737 promptFile: ' heartbeat/default.md' ,
3838 target: null as number | null ,
39- notifyTargets: [] as number [],
4039 enabled: true ,
4140 activeHoursEnabled: false ,
4241 activeHoursStart: 9 ,
@@ -74,7 +73,6 @@ function openAdd() {
7473 expr: ' 30m' ,
7574 promptFile: ' heartbeat/default.md' ,
7675 target: null ,
77- notifyTargets: [],
7876 enabled: true ,
7977 activeHoursEnabled: false ,
8078 activeHoursStart: 9 ,
@@ -93,7 +91,6 @@ function openEdit(id: string) {
9391 expr: hb .expr || ' 30m' ,
9492 promptFile: hb .promptFile || ' heartbeat/default.md' ,
9593 target: hb .target ?? null ,
96- notifyTargets: hb .notifyTargets ?? [],
9794 enabled: hb .enabled !== false ,
9895 activeHoursEnabled: !! hb .activeHours ,
9996 activeHoursStart: hb .activeHours ?.start ?? 9 ,
@@ -113,9 +110,6 @@ function buildBody() {
113110 enabled: form .value .enabled ,
114111 pruneIdle: form .value .pruneIdle ,
115112 }
116- if (form .value .notifyTargets .length > 0 ) {
117- body .notifyTargets = form .value .notifyTargets
118- }
119113 if (form .value .activeHoursEnabled ) {
120114 body .activeHours = {
121115 start: form .value .activeHoursStart ,
@@ -180,6 +174,40 @@ async function loadSessions() {
180174 } catch {}
181175}
182176
177+ const heartbeatPrompts = ref <{ path: string ; isUserOnly? : boolean }[]>([])
178+
179+ async function loadHeartbeatPrompts() {
180+ try {
181+ const res = await apiFetch (' /api/prompts/files?prefix=heartbeat' )
182+ heartbeatPrompts .value = res .data || []
183+ } catch {}
184+ }
185+
186+ const showCreatePrompt = ref (false )
187+ const newPromptName = ref (' ' )
188+ const newPromptContent = ref (' ' )
189+
190+ function openCreatePrompt() {
191+ newPromptName .value = ' '
192+ newPromptContent .value = ' '
193+ showCreatePrompt .value = true
194+ }
195+
196+ async function createPrompt() {
197+ const name = newPromptName .value .trim ()
198+ if (! name ) { show (t (' common.name_required' ), ' error' ); return }
199+ const filePath = ` heartbeat/${name }${name .endsWith (' .md' ) ? ' ' : ' .md' } `
200+ try {
201+ await apiFetch (' /api/prompts/content' , ' PUT' , { path: filePath , content: newPromptContent .value })
202+ show (t (' common.created' ))
203+ showCreatePrompt .value = false
204+ await loadHeartbeatPrompts ()
205+ form .value .promptFile = filePath
206+ } catch (e : any ) {
207+ show (e .message , ' error' )
208+ }
209+ }
210+
183211async function refresh() {
184212 try {
185213 const res = await apiFetch (' /api/settings' )
@@ -191,7 +219,7 @@ async function refresh() {
191219}
192220
193221onMounted (async () => {
194- await Promise .all ([loadStatus (), loadSessions ()])
222+ await Promise .all ([loadStatus (), loadSessions (), loadHeartbeatPrompts () ])
195223})
196224 </script >
197225
@@ -298,7 +326,14 @@ onMounted(async () => {
298326 </div >
299327 <div class =" form-group" >
300328 <label >{{ t('heartbeats.promptFile') }}</label >
301- <input v-model =" form.promptFile" placeholder =" heartbeat/default.md" />
329+ <div style =" display :flex ;gap :6px ;align-items :center " >
330+ <select v-model =" form.promptFile" style =" flex :1 " >
331+ <option v-for =" p in heartbeatPrompts" :key =" p.path" :value =" p.path" >
332+ {{ p.path }}
333+ </option >
334+ </select >
335+ <button type =" button" class =" btn-outline btn-sm" @click =" openCreatePrompt" title =" +" >+</button >
336+ </div >
302337 <div class =" hint" >{{ t('heartbeats.promptFile_hint') }}</div >
303338 </div >
304339 <div class =" form-group" >
@@ -311,15 +346,6 @@ onMounted(async () => {
311346 </select >
312347 <div class =" hint" >{{ t('heartbeats.target_hint') }}</div >
313348 </div >
314- <div class =" form-group" >
315- <label >{{ t('heartbeats.notifyTargets') }}</label >
316- <select v-model =" form.notifyTargets" multiple style =" min-height :60px " >
317- <option v-for =" s in channelSessions" :key =" s.id" :value =" s.id" >
318- {{ s.sessionName || `${s.channelId} / ${s.sessionId}` }} (#{{ s.id }})
319- </option >
320- </select >
321- <div class =" hint" >{{ t('heartbeats.notifyTargets_hint') }}</div >
322- </div >
323349 <div class =" form-group" >
324350 <label class =" checkbox-label" >
325351 <input type =" checkbox" v-model =" form.enabled" />
@@ -363,5 +389,32 @@ onMounted(async () => {
363389 </div >
364390 </div >
365391 </div >
392+
393+ <!-- Quick create prompt sub-dialog -->
394+ <div v-if =" showCreatePrompt" class =" modal-overlay" style =" z-index :1001 " @click.self =" showCreatePrompt = false" >
395+ <div class =" modal-box" style =" width :440px " >
396+ <div class =" modal-header" >
397+ <h3 >{{ t('heartbeats.create_prompt') }}</h3 >
398+ <button class =" modal-close" @click =" showCreatePrompt = false" >× ; </button >
399+ </div >
400+ <div class =" modal-body" >
401+ <div class =" form-group" >
402+ <label >{{ t('heartbeats.prompt_filename') }}</label >
403+ <div style =" display :flex ;align-items :center ;gap :4px " >
404+ <span style =" color :#9b9b9b ;font-size :13px ;flex-shrink :0 " >heartbeat/</span >
405+ <input v-model =" newPromptName" placeholder =" my-prompt.md" style =" flex :1 " @keyup.enter =" createPrompt" />
406+ </div >
407+ </div >
408+ <div class =" form-group" >
409+ <label >{{ t('heartbeats.prompt_content') }}</label >
410+ <textarea v-model =" newPromptContent" rows =" 8" style =" font-family :' Consolas' ,' Monaco' ,monospace ;font-size :13px " />
411+ </div >
412+ </div >
413+ <div class =" modal-footer" >
414+ <button class =" btn-outline" @click =" showCreatePrompt = false" >{{ t('common.cancel') }}</button >
415+ <button class =" btn-primary" @click =" createPrompt" >{{ t('common.create') }}</button >
416+ </div >
417+ </div >
418+ </div >
366419 </div >
367420</template >
0 commit comments