Add worktrees, per-hunk review, effort per message, regenerate, voice input
- 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>
This commit is contained in:
1 parent
df1924b0a1
commit
fca1c41087
22 files changed
+821
-32
No files matched your search
@@ -0,0 +1,62 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user