mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-13 21:27:41 +02:00
[eric] ui: native files up to 98pct of window, compaction resets every context surface not just tokens, and the context ring eases down with a soft settle-flash when room is freed
This commit is contained in:
@@ -4,15 +4,16 @@ import { API_BASE, getAuthToken } from '@/shared/config';
|
||||
import { ForcedToolGroup } from '../types';
|
||||
import { basename } from '../helpers';
|
||||
|
||||
// Only auto-shrink a file when it would eat 85%+ of the window on its own. Below
|
||||
// that, send it NATIVELY to the model (base64 document block) the way claude.ai /
|
||||
// Only auto-shrink a file when it literally won't fit (98%+ of the window on its
|
||||
// own). Below that, send it NATIVELY (base64 document block) the way claude.ai /
|
||||
// OpenAI / Gemini do — the model reads the PDF server-side, instantly, no separate
|
||||
// summarize round-trip. The old 50% trigger was force-summarizing files that fit
|
||||
// fine, which is the entire reason our file flow felt 60s-slow vs their instant: we
|
||||
// were doing pre-processing work the big providers simply don't do. Leaves ~15% for
|
||||
// the conversation; if it grows past the window later, auto-compact handles it.
|
||||
// were doing pre-processing work the big providers simply don't do. 98% (not 100%)
|
||||
// leaves a sliver for the prompt itself so a barely-fitting file doesn't 4xx; if the
|
||||
// conversation later grows past the window, auto-compact handles it.
|
||||
function shrinkThreshold(modelCtx: number): number {
|
||||
return Math.floor(modelCtx * 0.85);
|
||||
return Math.floor(modelCtx * 0.98);
|
||||
}
|
||||
|
||||
export type SendBlock = null | {
|
||||
|
||||
@@ -4,6 +4,21 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import { formatTokenCount } from '../helpers';
|
||||
|
||||
export const ContextRing: React.FC<{ used: number; limit: number; accentColor: string; trackColor: string }> = ({ used, limit, accentColor, trackColor }) => {
|
||||
// Track the previous fill so a DROP (compaction freed space) can play a brief
|
||||
// "settle" cue: the ring eases down AND flashes once toward the track color,
|
||||
// signaling "we just made room" without a loud banner. A rise just eases up.
|
||||
const prevUsed = React.useRef(used);
|
||||
const [justCompacted, setJustCompacted] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (used < prevUsed.current - 1) {
|
||||
setJustCompacted(true);
|
||||
const t = setTimeout(() => setJustCompacted(false), 700);
|
||||
prevUsed.current = used;
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
prevUsed.current = used;
|
||||
}, [used]);
|
||||
|
||||
if (used === 0) return null;
|
||||
const pct = Math.min((used / limit) * 100, 100);
|
||||
const size = 20;
|
||||
@@ -24,6 +39,12 @@ export const ContextRing: React.FC<{ used: number; limit: number; accentColor: s
|
||||
strokeDasharray={circumference} strokeDashoffset={dashOffset}
|
||||
strokeLinecap="round"
|
||||
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
||||
style={{
|
||||
// 600ms cubic-bezier ease on the fill: a rise glides up, a compaction
|
||||
// glides down. The one-shot opacity dip is the "settle" flash on drop.
|
||||
transition: 'stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s ease',
|
||||
opacity: justCompacted ? 0.35 : 1,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function ContextDrawer() {
|
||||
{Math.round(ctxPct * 100)}%
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: c.text.tertiary }}>
|
||||
{(session.tokens?.input || 0).toLocaleString()} / 200K tokens
|
||||
{(session.tokens?.input || 0).toLocaleString()} / {session.context_window ? `${Math.round(session.context_window / 1000)}K` : '200K'} tokens
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 1.5, borderRadius: 1, border: `1px solid ${c.border.medium}` }}>
|
||||
|
||||
@@ -744,13 +744,16 @@ const agentsSlice = createSlice({
|
||||
const session = state.sessions[action.payload.sessionId];
|
||||
if (!session) return;
|
||||
session.compacted_through_msg_id = action.payload.throughMsgId;
|
||||
// The next preflight check looks at tokens.input to estimate "history used".
|
||||
// After a compaction the real number is unknown until the next turn round-trips,
|
||||
// but the OLD number is definitely wrong (it counts messages we just dropped).
|
||||
// Zero it so preflight falls back to the char/4 estimate of remaining content,
|
||||
// which is closer than holding the stale pre-compact value.
|
||||
// After compaction the OLD context numbers are wrong (they count messages we
|
||||
// just dropped). Reset EVERY context-derived field so all surfaces that show
|
||||
// it — the toolbar ContextRing, the /context drawer %, the pre-send guard —
|
||||
// reflect the post-compaction state, not just the token count. The real values
|
||||
// refill on the next turn's round-trip; until then zero is closer than stale.
|
||||
if (action.payload.throughMsgId) {
|
||||
session.tokens = { input: 0, output: session.tokens?.output ?? 0 };
|
||||
session.ctx_used_pct = 0;
|
||||
session.cache_read_pct = 0;
|
||||
session.cache_read_tokens = 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user