Add Web Push, guided start screen, SVG icons, chunked uploads, rename
- Web Push (VAPID + RFC 8291, pure Rust): permission/question/finished notifications while no app window is visible; settings with test button - Guided new-session flow: task, craft target (website/app/game/old software/fresh), clean room or deep, description, model, folder (new, existing or cloned git repository); originals upload to original/ - Craft phase bar (craft_phase tool) for study/specify/build/check/ready - Chunked uploads (8 MB) up to server.max_upload_mb (default 500 MB) - All UI glyphs and emojis replaced by themeable SVG icons - Rename dialog with manual name or model-generated suggestion Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
013695722a
commit
a0d0e707f2
48 files changed
+2724
-228
No files matched your search
@@ -1,4 +1,6 @@
|
||||
import { Icon } from "./Icon";
|
||||
import { formatSize } from "./FileFigure";
|
||||
import { uploadFile } from "../api";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useAgents, useFetch } from "../hooks";
|
||||
import { useT, type Key } from "../i18n";
|
||||
@@ -20,7 +22,7 @@ const MAX_SIDE = 2000;
|
||||
|
||||
|
||||
/** Read an image and downscale it: models don't use more detail, and uploads stay small. */
|
||||
async function readImage(file: File): Promise<ImageData> {
|
||||
export async function readImage(file: File): Promise<ImageData> {
|
||||
const bitmap = await createImageBitmap(file);
|
||||
const scale = Math.min(1, MAX_SIDE / Math.max(bitmap.width, bitmap.height));
|
||||
const canvas = document.createElement("canvas");
|
||||
@@ -179,8 +181,9 @@ export function Composer({
|
||||
await fetch(`/api/files/create?`, { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: `${session.cwd}/.codecraft/uploads`, dir: true }) });
|
||||
// Keep uploads out of git status without touching the project's .gitignore.
|
||||
await fetch(`/api/files/upload?path=${encodeURIComponent(`${session.cwd}/.codecraft/uploads/.gitignore`)}`, { method: "PUT", body: "*\n", credentials: "same-origin" });
|
||||
const res = await fetch(`/api/files/upload?path=${encodeURIComponent(path)}`, { method: "PUT", body: f, credentials: "same-origin" });
|
||||
if (!res.ok) throw new Error(`${f.name}: HTTP ${res.status}${res.status === 413 ? " – too large for the reverse proxy" : ""}`);
|
||||
await uploadFile(path, f).catch((e) => {
|
||||
throw new Error(`${f.name}: ${e.message}`);
|
||||
});
|
||||
setFiles((cur) => [...cur, { name: f.name, path, size: f.size }]);
|
||||
} catch (e: any) {
|
||||
toast(e.message ?? String(e));
|
||||
@@ -229,10 +232,10 @@ export function Composer({
|
||||
<div className="attachments">
|
||||
{files.map((f) => (
|
||||
<div className="att file" key={f.path} title={f.path}>
|
||||
<span>📄 {f.name}</span>
|
||||
<span><Icon name="file" /> {f.name}</span>
|
||||
<small>{formatSize(f.size)}</small>
|
||||
<button onClick={() => removeFile(f.path)} aria-label={t("common.remove")}>
|
||||
×
|
||||
<Icon name="close" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -245,7 +248,7 @@ export function Composer({
|
||||
<div className="att" key={i}>
|
||||
<img src={`data:${img.media_type};base64,${img.data}`} alt="" />
|
||||
<button onClick={() => setImages((cur) => cur.filter((_, j) => j !== i))} aria-label={t("common.remove")}>
|
||||
×
|
||||
<Icon name="close" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -310,14 +313,14 @@ export function Composer({
|
||||
</select>
|
||||
<ModelSelect value={session.model} onChange={(m) => onUpdate({ model: m })} title={t("composer.model")} />
|
||||
<label className="ghost" style={{ cursor: "pointer", padding: "3px 6px" }} title={t("composer.attach")}>
|
||||
📎
|
||||
<Icon name="clip" />
|
||||
<input type="file" multiple hidden onChange={(e) => e.target.files && (addFiles(e.target.files), (e.target.value = ""))} />
|
||||
</label>
|
||||
</div>
|
||||
<div className="composer-actions">
|
||||
{busy && (
|
||||
<button className="danger" onClick={onAbort} title="Esc">
|
||||
■ {t("composer.stop")}
|
||||
<Icon name="stop" /> {t("composer.stop")}
|
||||
</button>
|
||||
)}
|
||||
<button className="primary" onClick={send} disabled={uploading > 0 || (!text.trim() && images.length === 0 && files.length === 0)}>
|
||||
|
||||
Reference in new issue
Block a user