Add file badges, menu icons, LSP diagnostics, robust compaction

- Files in chat: preview with type/size badges and file name; attachment chips
- Menu and settings tabs with icons
- Heatmap: distinct colour for empty hours, hourly labels
- Context chip shows used/total tokens (e.g. 221k/240k) plus percent
- LSP client: diagnostics appended to edit/write results and a diagnostics tool
  (typescript-language-server, pyright, rust-analyzer, gopls, clangd; sandboxed)
- Compaction summarizes a plain transcript and retries once on empty output
- files/stat endpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 19:50:12 +01:00
1 parent 5b569b6835
commit ec1b9b4925
22 files changed
+1036 -48

No files matched your search

+14 -10
View File
@@ -2,6 +2,7 @@ import { memo, useState, type ReactNode } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypeHighlight from "rehype-highlight";
import { FileChip, FileFigure } from "./FileFigure";
export const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|svg)$/i;
@@ -31,7 +32,9 @@ export function SvgBlock({ code, children }: { code: string; children?: ReactNod
const url = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(code)))}`;
return (
<div className="svg-block">
<img src={url} alt="SVG" />
<FileFigure name="inline.svg" size={new Blob([code]).size} mime="image/svg+xml">
<img src={url} alt="SVG" />
</FileFigure>
<button className="small ghost" onClick={() => setShowCode(!showCode)}>
{showCode ? "▾ SVG" : "▸ SVG"}
</button>
@@ -53,13 +56,14 @@ export const Markdown = memo(function Markdown({ text, baseDir }: { text: string
if (!url) return <span>{alt}</span>;
// Non-image files (3D models, documents) become links.
if (typeof src === "string" && !/^(data:|https?:)/i.test(src) && !IMAGE_EXT.test(src)) {
return (
<a href={url} target="_blank" rel="noreferrer">
📎 {alt || src}
</a>
);
return <FileChip url={url} name={src} />;
}
return <img className="inline-image" src={url} alt={alt ?? ""} loading="lazy" />;
const name = typeof src === "string" && !src.startsWith("data:") ? decodeURIComponent(src.split("?")[0]) : alt || undefined;
return (
<FileFigure url={url} name={name} mime={typeof src === "string" ? src.match(/^data:([^;,]+)/)?.[1] : undefined}>
<img className="inline-image" src={url} alt={alt ?? ""} loading="lazy" />
</FileFigure>
);
},
pre: ({ children }) => {
const code = textOf(children);
@@ -105,9 +109,9 @@ export function ImageRefs({ text, baseDir }: { text: string; baseDir?: string })
{paths.map((p) => {
const url = projectFileUrl(p, baseDir)!;
return (
<a key={p} href={url} target="_blank" rel="noreferrer" title={p}>
<img src={url} alt={p} loading="lazy" onError={(e) => ((e.currentTarget.parentElement as HTMLElement).style.display = "none")} />
</a>
<FileFigure key={p} url={url} name={p} className="thumb">
<img src={url} alt={p} loading="lazy" onError={(e) => ((e.currentTarget.closest("figure") as HTMLElement).style.display = "none")} />
</FileFigure>
);
})}
</div>