diff --git a/vibn-frontend/app/api/chat/route.ts b/vibn-frontend/app/api/chat/route.ts
index ed96fb3b..d4b4f677 100644
--- a/vibn-frontend/app/api/chat/route.ts
+++ b/vibn-frontend/app/api/chat/route.ts
@@ -71,6 +71,7 @@ function classifyTurnIntent(message: string): TurnIntent {
// High-agency directives
if (
+ /^(yes|yep|yeah|yup|sure|ok|okay|kk|k|please|fix it)\b/.test(m) ||
/(keep going|continue|build it|do it|go ahead|proceed|autonomous)/.test(m)
)
return "autonomous";
@@ -134,7 +135,7 @@ function classifyTurnIntent(message: string): TurnIntent {
m,
) ||
// Acknowledgements / pleasantries
- /^(thanks|thank you|ty|ok|okay|kk|k|yes|yep|yeah|yup|no|nope|nah|sure|cool|nice|great|awesome|perfect|got it|sounds good|will do|nvm|never mind)\b/.test(
+ /^(thanks|thank you|ty|no|nope|nah|cool|nice|great|awesome|perfect|got it|sounds good|will do|nvm|never mind)\b/.test(
m,
) ||
/(what'?s up|how are you|how'?s it going|good to see you)/.test(m) ||
diff --git a/vibn-frontend/components/vibn-chat/chat-panel.tsx b/vibn-frontend/components/vibn-chat/chat-panel.tsx
index c0eb0aef..e10b2862 100644
--- a/vibn-frontend/components/vibn-chat/chat-panel.tsx
+++ b/vibn-frontend/components/vibn-chat/chat-panel.tsx
@@ -640,7 +640,7 @@ function Timeline({ entries, isActiveStream }: { entries: TimelineEntry[], isAct
{items.map((item, i) => {
const isLast = i === items.length - 1;
if (item.kind === "thought") {
- return ;
+ return ;
}
if (item.kind === "text") {
return ;
@@ -665,6 +665,92 @@ function Timeline({ entries, isActiveStream }: { entries: TimelineEntry[], isAct
* bubble so each round of multi-tool-loop output reads as a discrete
* step instead of concatenating into a wall of text.
*/
+
+function TimelineThought({ text, isStreaming }: { text: string; isStreaming?: boolean }) {
+ const [expanded, setExpanded] = React.useState(true);
+ const textLenRef = React.useRef(text.length);
+
+ React.useEffect(() => {
+ // If not streaming, auto-collapse after a short delay so the user isn't stuck with huge thinking blocks
+ if (!isStreaming) {
+ const t = setTimeout(() => setExpanded(false), 500);
+ return () => clearTimeout(t);
+ }
+ }, [isStreaming]);
+
+ const proseWrap: React.CSSProperties = {
+ overflowWrap: "anywhere",
+ wordBreak: "break-word",
+ minWidth: 0,
+ };
+
+ return (
+
+
+ {expanded && (
+
+ ` : ""),
+ }}
+ />
+
+ )}
+
+ );
+}
+
function TimelineText({ text, isStreaming }: { text: string; isStreaming?: boolean }) {
const proseWrap: React.CSSProperties = {
overflowWrap: "anywhere",
@@ -1060,6 +1146,7 @@ export function ChatPanel({
.catch(() => {});
}, [projectId, workspace, status]);
const [sending, setSending] = useState(false);
+ const [showScrollButton, setShowScrollButton] = useState(false);
const [currentPhaseLabel, setCurrentPhaseLabel] = useState(
null,
);
@@ -1890,6 +1977,11 @@ export function ChatPanel({
{/* Messages */}
{
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
+ const distanceToBottom = scrollHeight - scrollTop - clientHeight;
+ setShowScrollButton(distanceToBottom > 150);
+ }}
style={{
flex: 1,
minWidth: 0,
@@ -2119,6 +2211,35 @@ export function ChatPanel({
+ {/* Scroll to bottom button */}
+ {showScrollButton && (
+
+ )}
+
{(selectToggle) => (