@@ -1,5 +1,33 @@
|
||||
import { request } from '../client'
|
||||
|
||||
export interface JobScheduleInterval {
|
||||
kind: 'interval'
|
||||
minutes: number
|
||||
display: string
|
||||
}
|
||||
|
||||
export interface JobScheduleCron {
|
||||
kind: 'cron'
|
||||
expr: string
|
||||
display: string
|
||||
}
|
||||
|
||||
export interface JobScheduleOnce {
|
||||
kind: 'once'
|
||||
run_at: string
|
||||
display: string
|
||||
}
|
||||
|
||||
type UnknownJobSchedule = {
|
||||
kind: string
|
||||
display?: string
|
||||
expr?: string
|
||||
minutes?: number
|
||||
run_at?: string
|
||||
}
|
||||
|
||||
export type JobSchedule = string | JobScheduleInterval | JobScheduleCron | JobScheduleOnce | UnknownJobSchedule
|
||||
|
||||
export interface Job {
|
||||
job_id: string
|
||||
id: string
|
||||
@@ -12,7 +40,7 @@ export interface Job {
|
||||
provider: string | null
|
||||
base_url: string | null
|
||||
script: string | null
|
||||
schedule: string | { kind: string; expr: string; display: string }
|
||||
schedule: JobSchedule
|
||||
schedule_display: string
|
||||
repeat: string | { times: number | null; completed: number }
|
||||
enabled: boolean
|
||||
@@ -45,21 +73,79 @@ export interface CreateJobRequest {
|
||||
|
||||
export interface UpdateJobRequest {
|
||||
name?: string
|
||||
schedule?: string | { kind: string; expr: string; display: string }
|
||||
schedule?: string
|
||||
prompt?: string
|
||||
deliver?: string
|
||||
skills?: string[]
|
||||
skill?: string
|
||||
repeat?: number
|
||||
repeat?: number | null
|
||||
enabled?: boolean
|
||||
model?: string
|
||||
provider?: string
|
||||
}
|
||||
|
||||
export interface JobFormValues {
|
||||
name: string
|
||||
schedule: string
|
||||
prompt: string
|
||||
deliver: string
|
||||
repeat_times: number | null
|
||||
}
|
||||
|
||||
function unwrap(res: { job: Job }): Job {
|
||||
return res.job
|
||||
}
|
||||
|
||||
function isScheduleObject(schedule: JobSchedule | null | undefined): schedule is Exclude<JobSchedule, string> {
|
||||
return typeof schedule === 'object' && schedule !== null
|
||||
}
|
||||
|
||||
export function scheduleToEditableInput(schedule: JobSchedule | null | undefined, fallback = ''): string {
|
||||
if (typeof schedule === 'string') return schedule
|
||||
if (!isScheduleObject(schedule)) return fallback
|
||||
|
||||
if (schedule.kind === 'cron') return schedule.expr || schedule.display || fallback
|
||||
if (schedule.kind === 'once') return schedule.run_at || schedule.display || fallback
|
||||
if (schedule.kind === 'interval') {
|
||||
return schedule.display || (typeof schedule.minutes === 'number' ? `every ${schedule.minutes}m` : fallback)
|
||||
}
|
||||
|
||||
const unknownSchedule = schedule as UnknownJobSchedule
|
||||
return unknownSchedule.expr || unknownSchedule.run_at || unknownSchedule.display || fallback
|
||||
}
|
||||
|
||||
export function scheduleToDisplayText(schedule: JobSchedule | null | undefined, fallback = '—'): string {
|
||||
if (typeof schedule === 'string') return schedule
|
||||
if (!isScheduleObject(schedule)) return fallback
|
||||
|
||||
if (schedule.kind === 'cron') return schedule.expr || schedule.display || fallback
|
||||
if (schedule.kind === 'interval') return schedule.display || scheduleToEditableInput(schedule, fallback)
|
||||
if (schedule.kind === 'once') return schedule.display || scheduleToEditableInput(schedule, fallback)
|
||||
|
||||
const unknownSchedule = schedule as UnknownJobSchedule
|
||||
return unknownSchedule.display || unknownSchedule.expr || unknownSchedule.run_at || fallback
|
||||
}
|
||||
|
||||
export function jobRepeatToEditValue(repeat: Job['repeat']): number | null {
|
||||
if (repeat && typeof repeat === 'object') return repeat.times ?? null
|
||||
return null
|
||||
}
|
||||
|
||||
export function buildJobUpdateRequest(original: Job, form: JobFormValues): UpdateJobRequest {
|
||||
const payload: UpdateJobRequest = {}
|
||||
const originalSchedule = scheduleToEditableInput(original.schedule, original.schedule_display || '')
|
||||
const originalRepeat = jobRepeatToEditValue(original.repeat)
|
||||
const originalDeliver = original.deliver || 'origin'
|
||||
|
||||
if (form.name !== original.name) payload.name = form.name
|
||||
if (form.schedule !== originalSchedule) payload.schedule = form.schedule
|
||||
if (form.prompt !== (original.prompt || '')) payload.prompt = form.prompt
|
||||
if (form.deliver !== originalDeliver) payload.deliver = form.deliver
|
||||
if (form.repeat_times !== originalRepeat) payload.repeat = form.repeat_times
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export async function listJobs(): Promise<Job[]> {
|
||||
const res = await request<{ jobs: Job[] }>('/api/hermes/jobs?include_disabled=true')
|
||||
return res.jobs
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { NButton, NTooltip, useMessage } from 'naive-ui'
|
||||
import type { Job } from '@/api/hermes/jobs'
|
||||
import { scheduleToDisplayText } from '@/api/hermes/jobs'
|
||||
import { useJobsStore } from '@/stores/hermes/jobs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -35,11 +36,7 @@ const statusType = computed(() => {
|
||||
return 'success' as const
|
||||
})
|
||||
|
||||
const scheduleExpr = computed(() => {
|
||||
const s = props.job.schedule
|
||||
if (typeof s === 'string') return s
|
||||
return s?.expr || props.job.schedule_display || '—'
|
||||
})
|
||||
const scheduleExpr = computed(() => scheduleToDisplayText(props.job.schedule, props.job.schedule_display || '—'))
|
||||
|
||||
const formatTime = (t?: string | null) => {
|
||||
if (!t) return '—'
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { NModal, NForm, NFormItem, NInput, NButton, NSelect, NInputNumber, useMessage } from 'naive-ui'
|
||||
import { useJobsStore } from '@/stores/hermes/jobs'
|
||||
import type { CreateJobRequest, UpdateJobRequest } from '@/api/hermes/jobs'
|
||||
import {
|
||||
buildJobUpdateRequest,
|
||||
getJob,
|
||||
jobRepeatToEditValue,
|
||||
scheduleToEditableInput,
|
||||
} from '@/api/hermes/jobs'
|
||||
import type { CreateJobRequest, Job } from '@/api/hermes/jobs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -49,22 +55,19 @@ const targetOptions = computed(() => [
|
||||
{ label: t('jobs.local'), value: 'local' },
|
||||
])
|
||||
|
||||
const originalSchedule = ref<{ kind: string; expr: string; display: string } | null>(null)
|
||||
const originalJob = ref<Job | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.jobId) {
|
||||
try {
|
||||
const { getJob } = await import('@/api/hermes/jobs')
|
||||
const job = await getJob(props.jobId)
|
||||
originalJob.value = job
|
||||
formData.value = {
|
||||
name: job.name,
|
||||
schedule: typeof job.schedule === 'string' ? job.schedule : (job.schedule?.expr || job.schedule_display || ''),
|
||||
schedule: scheduleToEditableInput(job.schedule, job.schedule_display || ''),
|
||||
prompt: job.prompt,
|
||||
deliver: job.deliver || 'origin',
|
||||
repeat_times: typeof job.repeat === 'number' ? job.repeat : (typeof job.repeat === 'object' ? job.repeat.times : null),
|
||||
}
|
||||
if (typeof job.schedule === 'object' && job.schedule) {
|
||||
originalSchedule.value = job.schedule
|
||||
repeat_times: jobRepeatToEditValue(job.repeat),
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(t('jobs.loadFailed') + ': ' + e.message)
|
||||
@@ -85,20 +88,15 @@ async function handleSave() {
|
||||
loading.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
const payload: UpdateJobRequest = {
|
||||
name: formData.value.name,
|
||||
prompt: formData.value.prompt,
|
||||
deliver: formData.value.deliver,
|
||||
repeat: formData.value.repeat_times ?? undefined,
|
||||
if (!originalJob.value) {
|
||||
message.error(t('jobs.loadFailed'))
|
||||
return
|
||||
}
|
||||
if (originalSchedule.value) {
|
||||
payload.schedule = {
|
||||
kind: originalSchedule.value.kind,
|
||||
expr: formData.value.schedule,
|
||||
display: formData.value.schedule,
|
||||
}
|
||||
} else {
|
||||
payload.schedule = formData.value.schedule
|
||||
const payload = buildJobUpdateRequest(originalJob.value, formData.value)
|
||||
if (Object.keys(payload).length === 0) {
|
||||
message.success(t('jobs.jobUpdated'))
|
||||
emit('saved')
|
||||
return
|
||||
}
|
||||
await jobsStore.updateJob(props.jobId!, payload)
|
||||
message.success(t('jobs.jobUpdated'))
|
||||
|
||||
Reference in New Issue
Block a user