- Isolated git worktree per session (create, status, merge, discard) - Permission card: accept file-edit hunks individually; agent learns what was rejected - Reasoning effort per message; regenerate the last answer with another model/effort - Voice input via the Web Speech API - Folder-up button in the file browser; ssh, rsync and mc in the image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
import { useState } from "react";
|
|
import { post } from "../api";
|
|
import { useT } from "../i18n";
|
|
import { toast } from "../dialogs";
|
|
import { Icon } from "./Icon";
|
|
import { ModelSelect } from "./ModelSelect";
|
|
|
|
export const EFFORTS = ["low", "medium", "high", "max"] as const;
|
|
|
|
/** Effort choice for one turn ("" = the model's default). */
|
|
export function EffortSelect({ value, onChange, compact }: { value: string; onChange: (v: string) => void; compact?: boolean }) {
|
|
const t = useT();
|
|
return (
|
|
<select className={compact ? "effort-select" : ""} value={value} onChange={(e) => onChange(e.target.value)} title={t("effort.title")} aria-label={t("effort.title")}>
|
|
<option value="">{compact ? t("effort.autoShort") : t("effort.auto")}</option>
|
|
{EFFORTS.map((e) => (
|
|
<option key={e} value={e}>
|
|
{t(`effort.${e}` as any)}
|
|
</option>
|
|
))}
|
|
</select>
|
|
);
|
|
}
|
|
|
|
/** Redo the last turn, optionally with another model and effort. */
|
|
export function RegenerateButton({ sessionId, messageId, model }: { sessionId: string; messageId: string; model: string }) {
|
|
const t = useT();
|
|
const [open, setOpen] = useState(false);
|
|
const [m, setM] = useState(model);
|
|
const [effort, setEffort] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
const go = async () => {
|
|
setBusy(true);
|
|
try {
|
|
await post(`/sessions/${sessionId}/regenerate`, { message_id: messageId, model: m !== model ? m : undefined, effort: effort || undefined });
|
|
setOpen(false);
|
|
} catch (e: any) {
|
|
toast(e.message);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
return (
|
|
<div className="regen">
|
|
<button className="small ghost" onClick={() => setOpen(!open)} title={t("regen.title")}>
|
|
<Icon name="refresh" /> {t("regen.button")}
|
|
</button>
|
|
{open && (
|
|
<div className="regen-panel">
|
|
<div className="hint">{t("regen.hint")}</div>
|
|
<div className="row" style={{ gap: 6, flexWrap: "wrap" }}>
|
|
<ModelSelect value={m} onChange={setM} />
|
|
<EffortSelect value={effort} onChange={setEffort} />
|
|
<button className="small primary" disabled={busy} onClick={go}>
|
|
<Icon name="refresh" /> {t("regen.go")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|