Stats history with year filter, folding and XLSX/CSV export; snippets, read-only links, commit suggestions, weekly review, offline copies, server speech-to-text, planning model, job reports; fal.ai health fix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Sonnet 5.5 committed 2026-11-15 08:20:52 +01:00
1 parent c08c6c7436
commit 388ca96419
31 files changed
+1518 -82

No files matched your search

+38 -1
View File
@@ -146,8 +146,11 @@ function VersionInfo() {
function DigestToggle() {
const t = useT();
const [on, setOn] = useState<boolean>();
const [weekly, setWeekly] = useState(false);
useEffect(() => {
get<{ enabled: boolean }>("/push/digest").then((r) => setOn(r.enabled)).catch(() => {});
get<{ enabled: boolean; weekly?: boolean }>("/push/digest")
.then((r) => (setOn(r.enabled), setWeekly(!!r.weekly)))
.catch(() => {});
}, []);
if (on === undefined) return null;
return (
@@ -164,6 +167,30 @@ function DigestToggle() {
/>
{t("settings.digest")}
</label>
<label className="row" style={{ gap: 6, alignItems: "center" }}>
<input
type="checkbox"
checked={weekly}
onChange={(e) => {
const v = e.target.checked;
setWeekly(v);
put("/push/digest", { weekly: v }).catch((err) => (setWeekly(!v), toast(err.message)));
}}
/>
{t("settings.weeklyDigest")}
</label>
{weekly && (
<button
className="small ghost"
onClick={() =>
post<{ sent: number; errors: string[] }>("/push/digest?weekly=true")
.then((r) => toast(r.sent ? t("settings.pushTestSent") : r.errors.join("; ") || t("settings.pushTestNone"), r.sent ? "success" : "error"))
.catch((e) => toast(e.message))
}
>
{t("settings.digestNow")}
</button>
)}
{on && (
<button
className="small ghost"
@@ -246,6 +273,8 @@ function General({ cfg, reload }: { cfg: ConfigResponse; reload: () => void }) {
const [form, setForm] = useState({
default_model: s.default_model ?? "",
small_model: s.small_model ?? "",
plan_model: ((cfg.config as any).agents?.plan?.model ?? "") as string,
transcribe_model: ((s as any).transcribe_model ?? "") as string,
currency: s.currency ?? "USD",
auto_compact_threshold: s.auto_compact_threshold ?? 0.85,
commit_trailer: ((s as any).commit_trailer ?? "Co-Authored-By: {model} ({provider}) via CodeCraft") as string,
@@ -270,6 +299,8 @@ function General({ cfg, reload }: { cfg: ConfigResponse; reload: () => void }) {
savePatches([
{ path: ["server", "default_model"], value: form.default_model },
{ path: ["server", "small_model"], value: form.small_model },
{ path: ["agents", "plan", "model"], value: form.plan_model || null },
{ path: ["server", "transcribe_model"], value: form.transcribe_model.trim() },
{ path: ["server", "currency"], value: form.currency },
{ path: ["server", "auto_compact_threshold"], value: Number(form.auto_compact_threshold) },
{ path: ["server", "commit_trailer"], value: form.commit_trailer.trim() },
@@ -302,6 +333,12 @@ function General({ cfg, reload }: { cfg: ConfigResponse; reload: () => void }) {
<label>{t("settings.smallModel")}</label>
<ModelSelect value={form.small_model} onChange={(v) => setForm({ ...form, small_model: v })} />
<div className="hint">{t("settings.smallModelHint")}</div>
<label>{t("settings.planModel")}</label>
<ModelSelect value={form.plan_model} emptyLabel={t("settings.sameAsChat")} onChange={(v) => setForm({ ...form, plan_model: v })} />
<div className="hint">{t("settings.planModelHint")}</div>
<label>{t("settings.transcribeModel")}</label>
<input value={form.transcribe_model} placeholder="openai/whisper-1" onChange={(e) => setForm({ ...form, transcribe_model: e.target.value })} />
<div className="hint">{t("settings.transcribeHint")}</div>
<label>{t("settings.currency")}</label>
<input value={form.currency} onChange={(e) => setForm({ ...form, currency: e.target.value.toUpperCase() })} maxLength={3} />
<label>{t("settings.autoCompact")}</label>