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,84 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { get, post } from "../api";
|
||||
import { useT } from "../i18n";
|
||||
import { confirmDialog, toast } from "../dialogs";
|
||||
import { Icon } from "./Icon";
|
||||
import { Spinner } from "./Activity";
|
||||
import type { Session } from "../types";
|
||||
|
||||
interface WtStatus {
|
||||
worktree: { repo: string; path: string; branch: string; base: string } | null;
|
||||
dirty_files?: number;
|
||||
commits?: string[];
|
||||
diffstat?: string;
|
||||
}
|
||||
|
||||
/** Chip + panel for a session working in an isolated git worktree. */
|
||||
export function WorktreeChip({ session, busy }: { session: Session; busy: boolean }) {
|
||||
const t = useT();
|
||||
const wt = session.worktree as WtStatus["worktree"] | undefined;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [status, setStatus] = useState<WtStatus>();
|
||||
const [working, setWorking] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
get<WtStatus>(`/sessions/${session.id}/worktree`)
|
||||
.then(setStatus)
|
||||
.catch((e) => toast(e.message));
|
||||
}, [open, session.id, busy]);
|
||||
|
||||
if (!wt) return null;
|
||||
|
||||
const act = async (action: "merge" | "remove") => {
|
||||
const msg = action === "merge" ? t("worktree.mergeConfirm", { branch: wt.branch, base: wt.base }) : t("worktree.removeConfirm", { branch: wt.branch });
|
||||
if (!(await confirmDialog({ message: msg, danger: action === "remove" }))) return;
|
||||
setWorking(action);
|
||||
try {
|
||||
await post(`/sessions/${session.id}/worktree`, { action });
|
||||
toast(t(action === "merge" ? "worktree.merged" : "worktree.removed", { base: wt.base }), "success");
|
||||
if (action === "merge") setStatus(await get<WtStatus>(`/sessions/${session.id}/worktree`));
|
||||
else setOpen(false);
|
||||
} catch (e: any) {
|
||||
toast(e.message);
|
||||
} finally {
|
||||
setWorking(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span className="wt-wrap">
|
||||
<button className="chip wt-chip" onClick={() => setOpen(!open)} title={t("worktree.title")}>
|
||||
<Icon name="fork" /> {wt.branch}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="menu-pop-body wt-panel">
|
||||
<div className="hint">{t("worktree.hint", { base: wt.base })}</div>
|
||||
{!status ? (
|
||||
<Spinner small />
|
||||
) : (
|
||||
<>
|
||||
<div className="wt-stats">
|
||||
<span>{t("worktree.dirty", { n: status.dirty_files ?? 0 })}</span>
|
||||
<span>{t("worktree.commits", { n: status.commits?.length ?? 0 })}</span>
|
||||
{status.diffstat && <span className="mono">{status.diffstat}</span>}
|
||||
</div>
|
||||
{!!status.commits?.length && (
|
||||
<pre className="wt-log">{status.commits.slice(0, 8).join("\n")}</pre>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="row" style={{ gap: 6, marginTop: 8 }}>
|
||||
<button className="small primary" disabled={busy || !!working} onClick={() => act("merge")}>
|
||||
{working === "merge" ? <Spinner small /> : <Icon name="check" />} {t("worktree.merge", { base: wt.base })}
|
||||
</button>
|
||||
<button className="small danger" disabled={busy || !!working} onClick={() => act("remove")}>
|
||||
<Icon name="trash" /> {t("worktree.remove")}
|
||||
</button>
|
||||
</div>
|
||||
{busy && <div className="hint">{t("worktree.busy")}</div>}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user