-
Notifications
You must be signed in to change notification settings - Fork 11.2k
feat(frontend): classic skin default + frontend UI rework #4914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 29 commits
013edda
8d3b1b2
16422d1
6426419
0f2de46
72b9561
cf3c545
85b2ba0
30e8435
e863f95
441c807
d26a7fd
1a5f32e
3b030e3
d44673a
a4e03d2
9cb7f4e
158be8c
4f14ad9
e433ad0
c46d4a8
6fae72c
acca5a2
112be92
49b5ad7
4c22a5e
78f3d19
91a0872
d8da426
bd73174
bbd2aae
eb7ee01
b5f19c0
bbf1096
e11d5fa
05a1fe0
78dd7a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| "use client"; | ||
|
|
||
| import { ArrowLeftIcon, CalendarClock, TriangleAlertIcon } from "lucide-react"; | ||
| import { useRouter, useSearchParams } from "next/navigation"; | ||
| import { useState } from "react"; | ||
|
|
||
| import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { Input } from "@/components/ui/input"; | ||
| import { Textarea } from "@/components/ui/textarea"; | ||
| import { | ||
| ScheduledTaskScheduleInput, | ||
| type ScheduleValue, | ||
| } from "@/components/workspace/scheduled-task-schedule-input"; | ||
| import { useI18n } from "@/core/i18n/hooks"; | ||
| import { useCreateScheduledTask } from "@/core/scheduled-tasks/hooks"; | ||
|
|
||
| function ReuseThreadNotice({ | ||
| title, | ||
| description, | ||
| }: { | ||
| title: string; | ||
| description: string; | ||
| }) { | ||
| return ( | ||
| <Alert className="border-amber-500/50 bg-amber-500/10"> | ||
| <TriangleAlertIcon className="text-amber-600 dark:text-amber-400" /> | ||
| <AlertTitle>{title}</AlertTitle> | ||
| <AlertDescription>{description}</AlertDescription> | ||
| </Alert> | ||
| ); | ||
| } | ||
|
|
||
| export default function NewScheduledTaskPage() { | ||
| const { t } = useI18n(); | ||
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const initialThreadId = searchParams.get("thread_id"); | ||
| const st = t.scheduledTasks; | ||
| const createTask = useCreateScheduledTask(); | ||
| const [contextMode, setContextMode] = useState< | ||
| "fresh_thread_per_run" | "reuse_thread" | ||
| >(initialThreadId ? "reuse_thread" : "fresh_thread_per_run"); | ||
| const [targetThreadId, setTargetThreadId] = useState(initialThreadId ?? ""); | ||
| const [title, setTitle] = useState(""); | ||
| const [prompt, setPrompt] = useState(""); | ||
| const [createSchedule, setCreateSchedule] = useState<ScheduleValue>({ | ||
| schedule_type: "cron", | ||
| schedule_spec: { cron: "0 9 * * *" }, | ||
| timezone: "", | ||
| }); | ||
|
|
||
| const listHref = initialThreadId | ||
| ? `/workspace/scheduled-tasks?thread_id=${encodeURIComponent(initialThreadId)}` | ||
| : "/workspace/scheduled-tasks"; | ||
|
|
||
| const handleCreate = () => { | ||
| createTask.mutate( | ||
| { | ||
| context_mode: contextMode, | ||
| thread_id: contextMode === "reuse_thread" ? targetThreadId : null, | ||
| title, | ||
| prompt, | ||
| schedule_type: createSchedule.schedule_type, | ||
| schedule_spec: createSchedule.schedule_spec, | ||
| timezone: createSchedule.timezone || "UTC", | ||
| }, | ||
| { onSuccess: () => router.push(listHref) }, | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex size-full flex-col"> | ||
| <header className="flex shrink-0 items-center justify-between gap-3 border-b px-4 py-3"> | ||
| <div className="flex items-center gap-3"> | ||
| <Button | ||
| variant="ghost" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Give the back button an accessible name This icon-only navigation button has no
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in d8da426. The icon-only back button now has a localized |
||
| size="icon-sm" | ||
| aria-label={st.create.back} | ||
| title={st.create.back} | ||
| onClick={() => router.push(listHref)} | ||
| > | ||
| <ArrowLeftIcon className="h-4 w-4" /> | ||
| </Button> | ||
| <h1 className="text-sm font-semibold">{st.create.title}</h1> | ||
| </div> | ||
| </header> | ||
| <main className="flex flex-1 justify-center overflow-y-auto px-4 py-6"> | ||
| <div | ||
| className="my-auto w-full max-w-xl space-y-4" | ||
| data-testid="scheduled-task-create-form" | ||
| > | ||
| <div className="space-y-3 pb-2 text-center"> | ||
| <div className="bg-primary/10 mx-auto flex h-14 w-14 items-center justify-center rounded-full"> | ||
| <CalendarClock className="text-primary h-7 w-7" /> | ||
| </div> | ||
| <div className="space-y-1"> | ||
| <h2 className="text-xl font-semibold">{st.create.title}</h2> | ||
| <p className="text-muted-foreground text-sm">{st.description}</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex flex-wrap gap-2"> | ||
| <Button | ||
| type="button" | ||
| variant={ | ||
| contextMode === "fresh_thread_per_run" ? "default" : "outline" | ||
| } | ||
| size="sm" | ||
| onClick={() => setContextMode("fresh_thread_per_run")} | ||
| > | ||
| {st.context.fresh} | ||
| </Button> | ||
| <Button | ||
| type="button" | ||
| variant={contextMode === "reuse_thread" ? "default" : "outline"} | ||
| size="sm" | ||
| onClick={() => setContextMode("reuse_thread")} | ||
| > | ||
| {st.context.reuse} | ||
| </Button> | ||
| </div> | ||
| {contextMode === "reuse_thread" && ( | ||
| <> | ||
| <Input | ||
| value={targetThreadId} | ||
| onChange={(event) => setTargetThreadId(event.target.value)} | ||
| placeholder={st.context.threadIdPlaceholder} | ||
| /> | ||
| <ReuseThreadNotice | ||
| title={st.context.reuseNoticeTitle} | ||
| description={st.context.reuseNoticeDescription} | ||
| /> | ||
| </> | ||
| )} | ||
| <Input | ||
| value={title} | ||
| onChange={(event) => setTitle(event.target.value)} | ||
| placeholder={st.create.taskTitle} | ||
| /> | ||
| <Textarea | ||
| rows={4} | ||
| value={prompt} | ||
| onChange={(event) => setPrompt(event.target.value)} | ||
| placeholder={st.create.prompt} | ||
| /> | ||
| <ScheduledTaskScheduleInput | ||
| initial={createSchedule} | ||
| onChange={setCreateSchedule} | ||
| /> | ||
| {createSchedule.schedule_type === "once" && | ||
| !createSchedule.schedule_spec.run_at && ( | ||
| <div className="text-muted-foreground text-sm"> | ||
| {st.create.invalidOnce} | ||
| </div> | ||
| )} | ||
| <div className="flex justify-end gap-2"> | ||
| <Button | ||
| variant="outline" | ||
| onClick={() => router.push(listHref)} | ||
| disabled={createTask.isPending} | ||
| > | ||
| {t.common.cancel} | ||
| </Button> | ||
| <Button | ||
| onClick={handleCreate} | ||
| disabled={ | ||
| !title || | ||
| !prompt || | ||
| (!createSchedule.schedule_spec.cron && | ||
| !createSchedule.schedule_spec.run_at) || | ||
| (contextMode === "reuse_thread" && !targetThreadId) || | ||
| createTask.isPending | ||
| } | ||
| > | ||
| {createTask.isPending ? t.common.loading : st.create.submit} | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </main> | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Return to a list that can contain the created task
listHrefis always derived from the inboundinitialThreadId, but this form lets the user switch tofresh_thread_per_runor edittargetThreadId. In those cases the POST creates a task withthread_id: nullor a different thread ID, then success navigates back to the original thread's filtered list, where the new task cannot appear. KeepinglistHreffor Back/Cancel makes sense, but the success destination should follow the submitted context (global for fresh tasks, or the chosen target for reuse tasks). The new round-trip test covers only the unchanged pre-seeded thread ID, so it misses both branches.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in bd73174. Back/Cancel still use inbound
listHref. Create success now follows the submitted context: global list forfresh_thread_per_run, or?thread_id=of the chosentargetThreadIdforreuse_thread. Added e2e for switching to Fresh from a seeded filter, and for editing the reuse thread ID before submit.