Claude Code engine, image viewer, colour tags, checks per turn and more
- Claude Code as an engine: chats on a "claude-code/<alias>" model run through the claude CLI (stream-json); tool approvals go through our permission system. Sign in from the web UI (URL + pasted code), CLI version and update from Settings; the Docker image contains the CLI. - Small models (8k context) for side tasks: input is cut to the model's context window; one-line chat status, "What does this do?" on approvals, "Plan as automation" (job draft from a chat). - Mobile: top bar no longer widens the page, image viewer (zoom, swipe, mark up, use in message), jump to previous own message, message menu, time under every message. - Approvals: push actions (allow/reject), "always allow in this project" (never for dangerous commands) with revoke. - Per turn: changed files with diffs and rollback; project check command with a green/red badge and "let the agent fix it". - Colour tags for chats in the colours of the active theme. - Project defaults for new chats, project page (timeline), offline queue. Tested in a throwaway container with a mock model (8k small model): 26 API checks passed (permissions, diffs, checks, defaults, job draft, side-task budget, Claude Code status/login URL, unsigned-in chat). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
084452abf6
commit
62fe5bf467
42 files changed
+3734
-55
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { useState } from "react";
|
||||
import { get } from "../api";
|
||||
import { useT } from "../i18n";
|
||||
import type { Message } from "../types";
|
||||
import { Diff } from "./Diff";
|
||||
import { Icon } from "./Icon";
|
||||
import { basename } from "./FileFigure";
|
||||
|
||||
interface Change {
|
||||
path: string;
|
||||
created: boolean;
|
||||
deleted?: boolean;
|
||||
diff?: string;
|
||||
truncated?: boolean;
|
||||
}
|
||||
|
||||
const count = (diff: string | undefined, sign: "+" | "-") => (diff ?? "").split("\n").filter((l) => l.startsWith(sign) && !l.startsWith(sign.repeat(3))).length;
|
||||
|
||||
/**
|
||||
* What one turn changed: files with their diffs (from the checkpoints the agent leaves before
|
||||
* every write/edit) and a button to roll the chat and the files back to before the turn.
|
||||
*/
|
||||
export function TurnChanges({
|
||||
sessionId,
|
||||
fromId,
|
||||
untilId,
|
||||
fileCount,
|
||||
userMessage,
|
||||
onRewind,
|
||||
}: {
|
||||
sessionId: string;
|
||||
fromId: string;
|
||||
untilId?: string;
|
||||
fileCount: number;
|
||||
userMessage?: Message;
|
||||
onRewind?: (m: Message) => void;
|
||||
}) {
|
||||
const t = useT();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [changes, setChanges] = useState<Change[]>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const toggle = () => {
|
||||
const next = !open;
|
||||
setOpen(next);
|
||||
if (next && !changes) {
|
||||
const q = `since_message=${encodeURIComponent(fromId)}&diff=true${untilId ? `&until_message=${encodeURIComponent(untilId)}` : ""}`;
|
||||
get<Change[]>(`/sessions/${sessionId}/changes?${q}`).then(setChanges, (e) => setError(e.message));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="turn-changes">
|
||||
<button className="small ghost" onClick={toggle} aria-expanded={open}>
|
||||
<Icon name={open ? "chevron-down" : "chevron-right"} className="tiny" /> {t("turn.changes", { n: fileCount })}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="turn-changes-body">
|
||||
{error && <div className="notice err">{error}</div>}
|
||||
{!changes && !error && <div className="hint">…</div>}
|
||||
{changes?.length === 0 && <div className="hint">{t("turn.noChanges")}</div>}
|
||||
{changes?.map((c) => (
|
||||
<details key={c.path} className="turn-file">
|
||||
<summary title={c.path}>
|
||||
<span className="mono">{basename(c.path)}</span>
|
||||
{c.created && <span className="chip">{t("turn.new")}</span>}
|
||||
{c.deleted && <span className="chip warn">{t("turn.deleted")}</span>}
|
||||
<span className="turn-stat">
|
||||
<span className="add">+{count(c.diff, "+")}</span> <span className="del">−{count(c.diff, "-")}</span>
|
||||
</span>
|
||||
</summary>
|
||||
{c.diff ? <Diff diff={c.diff} /> : <div className="hint">{t("turn.noDiff")}</div>}
|
||||
{c.truncated && <div className="hint">{t("turn.truncated")}</div>}
|
||||
</details>
|
||||
))}
|
||||
{userMessage && onRewind && changes && changes.length > 0 && (
|
||||
<button className="small" onClick={() => onRewind(userMessage)}>
|
||||
<Icon name="undo" /> {t("turn.rollback")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user