feat(frontend): streamline task creation and preview workflows
- remove prompt and preview generation from task creation - create tasks as inactive and route directly to edit page - add generated message history UX to edit task - update entity/task views and related test coverage
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { getEntity } from '../api/entitiesApi'
|
||||
@@ -35,6 +35,12 @@ interface RegularitySuggestion {
|
||||
cronParts: CronParts
|
||||
}
|
||||
|
||||
interface GeneratedMessageItem {
|
||||
id: string
|
||||
label: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const DEFAULT_CRON_PARTS: CronParts = {
|
||||
minute: '0',
|
||||
hour: '9',
|
||||
@@ -115,8 +121,10 @@ export default function EditTaskPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const [cronParts, setCronParts] = useState<CronParts>(DEFAULT_CRON_PARTS)
|
||||
const [taskForm, setTaskForm] = useState<TaskFormState>(DEFAULT_TASK_FORM)
|
||||
const [preview, setPreview] = useState('')
|
||||
const [generatedMessages, setGeneratedMessages] = useState<GeneratedMessageItem[]>([])
|
||||
const [selectedMessageId, setSelectedMessageId] = useState('')
|
||||
const [previewError, setPreviewError] = useState('')
|
||||
const generatedMessageCounter = useRef(0)
|
||||
|
||||
const { data: entity, isLoading: isLoadingEntity } = useQuery({
|
||||
queryKey: ['entity', entityId],
|
||||
@@ -187,10 +195,19 @@ export default function EditTaskPage() {
|
||||
const previewMutation = useMutation({
|
||||
mutationFn: generateTaskPreview,
|
||||
onMutate: () => {
|
||||
setPreview('')
|
||||
setPreviewError('')
|
||||
},
|
||||
onSuccess: (value) => setPreview(value),
|
||||
onSuccess: (value) => {
|
||||
generatedMessageCounter.current += 1
|
||||
const nextMessage: GeneratedMessageItem = {
|
||||
id: `message-${generatedMessageCounter.current}`,
|
||||
label: `Message #${generatedMessageCounter.current}`,
|
||||
content: value,
|
||||
}
|
||||
|
||||
setGeneratedMessages((prev) => [nextMessage, ...prev])
|
||||
setSelectedMessageId(nextMessage.id)
|
||||
},
|
||||
onError: (error) => {
|
||||
setPreviewError(
|
||||
error instanceof Error
|
||||
@@ -224,6 +241,11 @@ export default function EditTaskPage() {
|
||||
return buildTaskPreviewPrompt(entity, currentTask)
|
||||
}, [currentTask, entity])
|
||||
|
||||
const selectedMessage = useMemo(
|
||||
() => generatedMessages.find((message) => message.id === selectedMessageId),
|
||||
[generatedMessages, selectedMessageId]
|
||||
)
|
||||
|
||||
const applyCronParts = (nextCronParts: CronParts) => {
|
||||
setCronParts(nextCronParts)
|
||||
setTaskForm((prev) => ({ ...prev, scheduleCron: buildCron(nextCronParts) }))
|
||||
@@ -256,7 +278,7 @@ export default function EditTaskPage() {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen overflow-y-auto bg-slate-950 py-8">
|
||||
<div className="mx-auto max-w-2xl px-4 sm:px-6">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6">
|
||||
<nav className="mb-6">
|
||||
<Link
|
||||
to={`/entities/${entityId}`}
|
||||
@@ -443,13 +465,72 @@ export default function EditTaskPage() {
|
||||
{previewMutation.isPending ? 'Generating…' : 'Generate Test Message'}
|
||||
</button>
|
||||
|
||||
<div className="mt-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||
Final Prompt
|
||||
</p>
|
||||
<pre className="mt-2 whitespace-pre-wrap rounded-md bg-slate-950 p-4 text-xs text-slate-200">
|
||||
{finalPrompt}
|
||||
</pre>
|
||||
<div className="mt-4 grid gap-4 lg:grid-cols-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||
Final Prompt
|
||||
</p>
|
||||
<pre className="mt-2 h-full min-h-56 whitespace-pre-wrap rounded-md bg-slate-950 p-4 text-xs text-slate-200">
|
||||
{finalPrompt}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||
Generated Message
|
||||
</p>
|
||||
<pre className="mt-2 min-h-56 whitespace-pre-wrap rounded-md bg-slate-950 p-4 text-xs text-slate-200">
|
||||
{selectedMessage?.content ?? 'Generate a message and it will appear here.'}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||
Generated Message History
|
||||
</p>
|
||||
<ul
|
||||
aria-label="Generated message history"
|
||||
className="mt-2 space-y-2 rounded-md border border-slate-800 bg-slate-950 p-3"
|
||||
>
|
||||
{generatedMessages.length === 0 && (
|
||||
<li className="text-xs text-slate-400">No generated messages yet.</li>
|
||||
)}
|
||||
{generatedMessages.map((message) => (
|
||||
<li
|
||||
key={message.id}
|
||||
className="flex items-start gap-2 rounded-md border border-slate-800 bg-slate-900 p-2"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedMessageId(message.id)}
|
||||
className="flex-1 text-left text-xs text-slate-200 hover:text-cyan-300"
|
||||
aria-label={message.label}
|
||||
>
|
||||
<span className="block font-medium">{message.label}</span>
|
||||
<span className="mt-1 block line-clamp-2 text-slate-400">
|
||||
{message.content.split('\n')[0]}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setGeneratedMessages((prev) => {
|
||||
const nextMessages = prev.filter((item) => item.id !== message.id)
|
||||
if (selectedMessageId === message.id) {
|
||||
setSelectedMessageId(nextMessages[0]?.id ?? '')
|
||||
}
|
||||
return nextMessages
|
||||
})
|
||||
}}
|
||||
className="rounded border border-red-500/40 px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-red-300 hover:bg-red-500/10"
|
||||
aria-label={`Delete ${message.label.toLowerCase()}`}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{previewError && (
|
||||
@@ -458,16 +539,6 @@ export default function EditTaskPage() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{preview && (
|
||||
<div className="mt-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
||||
Generated Message
|
||||
</p>
|
||||
<pre className="mt-2 whitespace-pre-wrap rounded-md bg-slate-950 p-4 text-xs text-slate-200">
|
||||
{preview}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pb-8">
|
||||
|
||||
Reference in New Issue
Block a user