diff --git a/README.md b/README.md index fabd18416..f72402116 100644 --- a/README.md +++ b/README.md @@ -1372,6 +1372,7 @@ Current MVP capabilities: - Manage tasks at `/workspace/scheduled-tasks` - Choose whether each scheduled task reuses a thread and its conversation history or creates a fresh thread per run +- Duplicate an existing task into the create form as an editable draft without copying its run history - Support `once` and `cron` schedules - Run background scheduled executions as non-interactive DeerFlow runs (`ask_clarification` is not exposed there) - Persist a due execution as `queued` when its reused thread or the global execution budget is busy, then launch it when capacity is available; queued occurrences survive Gateway restarts and fail after `scheduler.queue_timeout_seconds` diff --git a/README_zh.md b/README_zh.md index ae7f89e94..2018e9880 100644 --- a/README_zh.md +++ b/README_zh.md @@ -730,6 +730,7 @@ DeerFlow 现在在 workspace 里内置了一个一等的定时任务(scheduled - 在 `/workspace/scheduled-tasks` 管理任务 - 每个定时任务可以选择复用同一个 thread 及其历史对话,也可以选择每次运行新建一个 thread +- 将现有任务复制到创建表单中作为可编辑草稿,不复制运行历史 - 支持 `once` 和 `cron` 两种调度方式 - 后台定时执行以非交互式 DeerFlow run 运行(那里不会暴露 `ask_clarification`) - 当所复用的 thread 或全局执行配额正忙时,到期执行会持久化为 `queued`,并在可用后启动;队列项在 Gateway 重启后保留,超过 `scheduler.queue_timeout_seconds` 后标记为失败 diff --git a/frontend/src/app/workspace/scheduled-tasks/page.tsx b/frontend/src/app/workspace/scheduled-tasks/page.tsx index 1f9559da4..45dddd5a0 100644 --- a/frontend/src/app/workspace/scheduled-tasks/page.tsx +++ b/frontend/src/app/workspace/scheduled-tasks/page.tsx @@ -1,8 +1,8 @@ "use client"; -import { TriangleAlertIcon } from "lucide-react"; +import { CopyIcon, TriangleAlertIcon } from "lucide-react"; import { useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; @@ -118,6 +118,8 @@ export default function ScheduledTasksPage() { timezone: "UTC", }); const [createNonce, setCreateNonce] = useState(0); + const createFormRef = useRef(null); + const createTitleRef = useRef(null); const filteredData = (data ?? []).filter((task) => { const statusPass = statusFilter === "all" || task.status === statusFilter; const typePass = typeFilter === "all" || task.schedule_type === typeFilter; @@ -163,6 +165,24 @@ export default function ScheduledTasksPage() { setContextMode("fresh_thread_per_run"); setCreateNonce((n) => n + 1); }; + const duplicateTask = (task: ScheduledTask) => { + setTitle(`${task.title}${st.actions.duplicateTitleSuffix}`); + setPrompt(task.prompt); + setContextMode(task.context_mode); + setTargetThreadId(task.thread_id ?? ""); + setCreateSchedule({ + schedule_type: task.schedule_type, + schedule_spec: { ...task.schedule_spec }, + timezone: task.timezone, + }); + setFormError(null); + setCreateNonce((nonce) => nonce + 1); + createFormRef.current?.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + createTitleRef.current?.focus(); + }; useEffect(() => { document.title = `${t.sidebar.scheduledTasks} - ${t.pages.appName}`; @@ -212,6 +232,7 @@ export default function ScheduledTasksPage() {

{t.sidebar.scheduledTasks}

@@ -267,6 +288,7 @@ export default function ScheduledTasksPage() { )} setTitle(event.target.value)} placeholder={st.create.taskTitle} @@ -546,6 +568,14 @@ export default function ScheduledTasksPage() { > {st.actions.trigger} +