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:
1 parent
5b569b6835
commit
ec1b9b4925
22 files changed
+1036
-48
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { base64Size, FileChip, FileFigure, fileType } from "./FileFigure";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { del, get, patch, post } from "../api";
|
||||
import { useModels } from "../hooks";
|
||||
@@ -12,7 +13,7 @@ import { ShareDialog } from "./ShareDialog";
|
||||
import { QueueList } from "./QueueList";
|
||||
import { BudgetChip } from "./Budget";
|
||||
import { PermissionCard, QuestionCard } from "./Interaction";
|
||||
import { ImageRefs, Markdown } from "./Markdown";
|
||||
import { ImageRefs, Markdown, projectFileUrl } from "./Markdown";
|
||||
import { UsageOverlay } from "./UsageOverlay";
|
||||
import { RewindDialog } from "./RewindDialog";
|
||||
import { ShellsChip } from "./ShellsPanel";
|
||||
@@ -238,7 +239,7 @@ export function ChatView({ sessionId, onMenu }: { sessionId: string; onMenu: ()
|
||||
</div>
|
||||
{ctxShare !== undefined && (
|
||||
<span className={`chip hide-mobile ${ctxShare > 0.7 ? "warn" : ""}`} title={t("chat.contextHint", { tokens: fmtNumber(ctxTokens) })}>
|
||||
{t("chat.context")} {fmtPercent(ctxShare)}
|
||||
{t("chat.context")} {fmtK(ctxTokens)}/{fmtK(modelInfo!.context!)} · {fmtPercent(ctxShare)}
|
||||
</span>
|
||||
)}
|
||||
{stats && (
|
||||
@@ -325,11 +326,13 @@ export function ChatView({ sessionId, onMenu }: { sessionId: string; onMenu: ()
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{texts.map((p) => p.text).join("\n\n")}
|
||||
<UserText text={texts.map((p) => p.text).join("\n\n")} />
|
||||
{imgs.length > 0 && (
|
||||
<div>
|
||||
<div className="user-files">
|
||||
{imgs.map((p, i) => (
|
||||
<img key={i} src={`data:${p.image.media_type};base64,${p.image.data}`} alt="" />
|
||||
<FileFigure key={i} name={`image-${i + 1}.${fileType(undefined, p.image.media_type).toLowerCase()}`} mime={p.image.media_type} size={base64Size(p.image.data)}>
|
||||
<img src={`data:${p.image.media_type};base64,${p.image.data}`} alt="" />
|
||||
</FileFigure>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -399,3 +402,41 @@ export function ChatView({ sessionId, onMenu }: { sessionId: string; onMenu: ()
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Attached-file lines ("- /path (12.3 KB)") of a user message become file chips. */
|
||||
const ATTACHED_RE = /^- (\/\S+) \(([\d.]+ (?:B|KB|MB))\)$/;
|
||||
|
||||
function UserText({ text }: { text: string }) {
|
||||
const lines = text.split("\n");
|
||||
const files: { path: string; size: string }[] = [];
|
||||
const rest: string[] = [];
|
||||
for (const l of lines) {
|
||||
const m = l.match(ATTACHED_RE);
|
||||
if (m) files.push({ path: m[1], size: m[2] });
|
||||
else rest.push(l);
|
||||
}
|
||||
// Drop the heading line ("Attached files …:") right before the list.
|
||||
while (files.length && rest.length && (rest[rest.length - 1].trim() === "" || rest[rest.length - 1].trim().endsWith(":"))) {
|
||||
const last = rest.pop()!;
|
||||
if (last.trim().endsWith(":")) break;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{rest.join("\n").trimEnd()}
|
||||
{files.length > 0 && (
|
||||
<div className="user-files">
|
||||
{files.map((f) => (
|
||||
<FileChip key={f.path} url={projectFileUrl(f.path)} name={f.path} sizeText={f.size} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Token counts like "221k" / "1.2M" (same in every language). */
|
||||
function fmtK(n: number) {
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1).replace(/\.0$/, "")}M`;
|
||||
if (n >= 1000) return `${Math.round(n / 1000)}k`;
|
||||
return String(n);
|
||||
}
|
||||
Reference in new issue
Block a user