Automation editor with graphical schedule and assistant; movable chat list groups
Automations
- Job files hold only the instructions; name, schedule, mode, model, approval
mode and time limit are settings in the database (jobs.settings). Existing
files are adopted on start-up (frontmatter into the database and removed from
the file); plain Markdown files registered later get default settings and stay
switched off until scheduled.
- New automation: name, project and a starting point (blank example structure or
a template) create .codecraft/jobs/<name>.md and open the editor: settings with
a graphical cron builder (next runs in server time via /jobs/schedule), the
instructions in the Markdown editor and an assistant chat
(POST /jobs/{id}/assistant) whose model edits the file; the editor reloads it
after each turn and warns about conflicts with unsaved edits.
- The schedule tool writes prompt-only files with settings.
Chat list
- Folders and projects keep a user-defined order and can be pinned (drag and
drop, or the group's menu), stored per user via /account/sidebar. A new
message moves the chat to the top of its group, no longer the project itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
872ebb94dd
commit
05c215f685
19 files changed
+1661
-356
No files matched your search
@@ -0,0 +1,128 @@
|
||||
import { useState } from "react";
|
||||
import { post } from "../api";
|
||||
import { getLang, useT } from "../i18n";
|
||||
import { JOB_TEMPLATES, type JobTemplate } from "../jobTemplates";
|
||||
import { navigate } from "../router";
|
||||
import { Icon } from "./Icon";
|
||||
import { FolderPicker } from "./NewSession";
|
||||
|
||||
/** Starting point of a new instructions file: headings to fill in (with the assistant). */
|
||||
function example(name: string, de: boolean) {
|
||||
return de
|
||||
? `# ${name}
|
||||
|
||||
## Ziel
|
||||
Was soll diese Automatisierung erreichen? (ein, zwei Sätze)
|
||||
|
||||
## Vorgehen
|
||||
1. …
|
||||
2. …
|
||||
|
||||
## Ergebnis
|
||||
Was am Ende vorliegen soll – z. B. ein kurzer Bericht, geänderte Dateien, ein Commit.
|
||||
|
||||
## Grenzen
|
||||
- Was der Agent nicht tun darf (z. B. nichts pushen, nichts löschen).
|
||||
`
|
||||
: `# ${name}
|
||||
|
||||
## Goal
|
||||
What should this automation achieve? (one or two sentences)
|
||||
|
||||
## Steps
|
||||
1. …
|
||||
2. …
|
||||
|
||||
## Result
|
||||
What should exist at the end – e.g. a short report, changed files, a commit.
|
||||
|
||||
## Limits
|
||||
- What the agent must not do (e.g. no pushing, no deleting).
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* New automation: name, project and a starting point (example or template). Creates the
|
||||
* instructions file (<project>/.codecraft/jobs/<name>.md, switched off) and opens the
|
||||
* editor, where the schedule is set and the instructions are worked out.
|
||||
*/
|
||||
export function NewJobDialog({ onClose }: { onClose: () => void }) {
|
||||
const t = useT();
|
||||
const de = getLang() === "de";
|
||||
const [tpl, setTpl] = useState<JobTemplate>();
|
||||
const [name, setName] = useState("");
|
||||
const [cwd, setCwd] = useState(localStorage.getItem("codecraft.lastDir") ?? "");
|
||||
const [error, setError] = useState<string>();
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const choose = (x?: JobTemplate) => {
|
||||
setTpl(x);
|
||||
if (x && (!name.trim() || JOB_TEMPLATES.some((y) => name === y.name.de || name === y.name.en))) setName(de ? x.name.de : x.name.en);
|
||||
};
|
||||
|
||||
const create = async () => {
|
||||
setBusy(true);
|
||||
setError(undefined);
|
||||
try {
|
||||
const n = name.trim();
|
||||
const job = await post<{ id: string }>("/jobs/create", {
|
||||
cwd,
|
||||
name: n,
|
||||
prompt: tpl ? `# ${n}\n\n${tpl.prompt}\n` : example(n, de),
|
||||
schedule: tpl?.schedule,
|
||||
agent: tpl?.agent,
|
||||
});
|
||||
onClose();
|
||||
navigate(`/jobs/${job.id}`);
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" onClick={onClose}>
|
||||
<div className="modal job-tpl" role="dialog" aria-label={t("jobs.new")} onClick={(e) => e.stopPropagation()}>
|
||||
<h3>{t("jobs.new")}</h3>
|
||||
<label className="field">
|
||||
<span>{t("jobs.tplName")}</span>
|
||||
<input autoFocus value={name} placeholder={t("jobs.namePlaceholder")} onChange={(e) => setName(e.target.value)} />
|
||||
</label>
|
||||
<div className="field">
|
||||
<span>{t("jobs.tplProject")}</span>
|
||||
<FolderPicker path={cwd} onPick={setCwd} />
|
||||
<small className="hint">{t("jobs.fileHint")}</small>
|
||||
</div>
|
||||
<div className="field">
|
||||
<span>{t("jobs.startWith")}</span>
|
||||
<div className="job-tpl-grid">
|
||||
<button className={`job-tpl-card ${!tpl ? "active" : ""}`} onClick={() => choose(undefined)}>
|
||||
<Icon name="file-text" />
|
||||
<span>
|
||||
<b>{t("jobs.blank")}</b>
|
||||
<small>{t("jobs.blankDesc")}</small>
|
||||
</span>
|
||||
</button>
|
||||
{JOB_TEMPLATES.map((x) => (
|
||||
<button key={x.id} className={`job-tpl-card ${tpl?.id === x.id ? "active" : ""}`} onClick={() => choose(x)}>
|
||||
<Icon name={x.icon} />
|
||||
<span>
|
||||
<b>{de ? x.name.de : x.name.en}</b>
|
||||
<small>{de ? x.desc.de : x.desc.en}</small>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{error && <div className="notice err">{error}</div>}
|
||||
<div className="row" style={{ marginTop: 12, justifyContent: "flex-end" }}>
|
||||
<button onClick={onClose}>{t("common.cancel")}</button>
|
||||
<button className="primary" disabled={busy || !cwd || !name.trim()} onClick={create}>
|
||||
{t("jobs.createAndEdit")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user