diff --git a/backend/apps/settings/settings.py b/backend/apps/settings/settings.py
index acaac7b6..da747077 100644
--- a/backend/apps/settings/settings.py
+++ b/backend/apps/settings/settings.py
@@ -528,10 +528,15 @@ async def summarize_file(req: _SummarizeRequest):
else:
chunks = [raw[i:i + CHUNK_CHARS] for i in range(0, len(raw), CHUNK_CHARS)]
per_chunk_budget = max(800, req.target_tokens // len(chunks) + 600)
- partials = []
- for i, ch in enumerate(chunks):
- label = f"{os.path.basename(src)} (part {i + 1} of {len(chunks)})"
- partials.append(await _summarize_block(ch, per_chunk_budget, label))
+ # Parallel summarization. Sequential was N chunks * ~60s each
+ # (5+ min wall time for a 4-chunk PDF on Haiku). Aux providers
+ # all handle parallel requests fine; the only ceiling is the
+ # provider's per-key rate limit, and a single user summarizing
+ # one file will never hit that.
+ partials = await asyncio.gather(*[
+ _summarize_block(ch, per_chunk_budget, f"{os.path.basename(src)} (part {i + 1} of {len(chunks)})")
+ for i, ch in enumerate(chunks)
+ ])
merge_input = "\n\n".join(f"## Part {i + 1}\n{p}" for i, p in enumerate(partials))
summary = await _summarize_block(merge_input, req.target_tokens, f"merged summary of {os.path.basename(src)}")
except Exception as e:
diff --git a/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputOverlays.tsx b/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputOverlays.tsx
index b7e22e08..7d80e680 100644
--- a/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputOverlays.tsx
+++ b/frontend/src/app/pages/AgentChat/ChatInput/view/ChatInputOverlays.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Modal from '@mui/material/Modal';
@@ -6,6 +6,37 @@ import Snackbar from '@mui/material/Snackbar';
import CloseIcon from '@mui/icons-material/Close';
import { ClaudeTokens } from '@/shared/styles/claudeTokens';
+const SHRINK_PROGRESS_MESSAGES = [
+ 'Reading the file',
+ 'Pulling out the key bits',
+ 'Compressing',
+ 'Almost done',
+];
+
+function ShrinkingLabel() {
+ const [idx, setIdx] = useState(0);
+ useEffect(() => {
+ const t = setInterval(() => setIdx((i) => (i + 1) % SHRINK_PROGRESS_MESSAGES.length), 4000);
+ return () => clearInterval(t);
+ }, []);
+ return (
+
+
+
+ {SHRINK_PROGRESS_MESSAGES[idx]}…
+
+
+ );
+}
+
interface Props {
c: ClaudeTokens;
lightboxSrc: string | null;
@@ -100,11 +131,12 @@ export const ChatInputOverlays: React.FC = ({
border: 'none', borderRadius: '6px',
px: 1.5, py: 0.7, fontSize: '0.82rem', fontWeight: 500, cursor: 'pointer',
whiteSpace: 'nowrap',
+ transition: 'background 0.15s ease, opacity 0.15s ease',
'&:hover': { bgcolor: c.accent.hover },
- '&:disabled': { opacity: 0.6, cursor: 'wait' },
+ '&:disabled': { opacity: 0.85, cursor: 'wait', bgcolor: c.accent.primary },
}}
>
- {summarizingPath === oversizeQueue[0]?.path ? 'Shrinking…' : 'Shrink it'}
+ {summarizingPath === oversizeQueue[0]?.path ? : 'Shrink it'}