Initial CodeCraft: web-based coding harness for AI models
Rust backend (axum/tokio): agent loop with streaming, tools (fs, bash, web_fetch, todo, ask_user, subagents, skills, craft snapshots/compare), permissions with modes and rules, Anthropic + OpenAI-compatible providers, MCP client (stdio/HTTP, elicitation), SQLite sessions, usage statistics, compaction, commented TOML config editable via toml_edit. React PWA: chat with live streaming, permission/question cards, modes (build/plan/craft), settings for providers/models/prices/MCP/permissions, raw config editor, statistics dashboard, i18n (en/de). Docker setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
f3a2fe3816
62 files changed
+21835
No files matched your search
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ApiError, connectEvents, get, getToken } from "./api";
|
||||
import { useT } from "./i18n";
|
||||
import { navigate, useRoute } from "./router";
|
||||
import { applyEvent, loadSessions, resync, setState } from "./store";
|
||||
import { Sidebar } from "./components/Sidebar";
|
||||
import { Login } from "./components/Login";
|
||||
import { ChatView } from "./components/ChatView";
|
||||
import { NewSession } from "./components/NewSession";
|
||||
import { Settings } from "./components/Settings";
|
||||
import { Stats } from "./components/Stats";
|
||||
|
||||
type AuthState = "checking" | "login" | "ok";
|
||||
|
||||
export function App() {
|
||||
const t = useT();
|
||||
const route = useRoute();
|
||||
const [auth, setAuth] = useState<AuthState>("checking");
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
// Check whether a token is needed and whether ours works.
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const info = await get<{ auth_required: boolean }>("/info");
|
||||
if (info.auth_required && !getToken()) return setAuth("login");
|
||||
await loadSessions();
|
||||
setAuth("ok");
|
||||
} catch (e) {
|
||||
setAuth(e instanceof ApiError && e.status === 401 ? "login" : "ok");
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (auth !== "ok") return;
|
||||
return connectEvents({
|
||||
onEvent: applyEvent,
|
||||
onOpen: () => {
|
||||
setState(() => ({ connected: true }));
|
||||
void resync().catch(() => undefined);
|
||||
},
|
||||
onClose: () => setState(() => ({ connected: false })),
|
||||
});
|
||||
}, [auth]);
|
||||
|
||||
// Close the mobile menu on navigation.
|
||||
useEffect(() => setMenuOpen(false), [route.join("/")]);
|
||||
|
||||
if (auth === "checking") return <div className="empty">{t("common.loading")}</div>;
|
||||
if (auth === "login") return <Login onSuccess={() => setAuth("ok")} />;
|
||||
|
||||
const [section, arg] = route;
|
||||
let content;
|
||||
if (section === "s" && arg) content = <ChatView key={arg} sessionId={arg} onMenu={() => setMenuOpen(true)} />;
|
||||
else if (section === "settings") content = <Settings tab={arg} onMenu={() => setMenuOpen(true)} />;
|
||||
else if (section === "stats") content = <Stats onMenu={() => setMenuOpen(true)} />;
|
||||
else content = <NewSession onMenu={() => setMenuOpen(true)} onCreated={(id) => navigate(`/s/${id}`)} />;
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
{menuOpen && <div className="backdrop" onClick={() => setMenuOpen(false)} />}
|
||||
<Sidebar open={menuOpen} activeId={section === "s" ? arg : undefined} section={section ?? ""} />
|
||||
<main className="main">{content}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user