refactor: enhance ThreadContent layout and improve navigation handling in LayoutDataProvider

This commit is contained in:
Anish Sarkar 2026-04-14 03:57:37 +05:30
parent 43744713f7
commit 26695b949e
3 changed files with 29 additions and 31 deletions

View file

@ -123,6 +123,10 @@ const ThreadContent: FC = () => {
}}
/>
<AuiIf condition={({ thread }) => !thread.isEmpty}>
<div className="grow" />
</AuiIf>
<ThreadPrimitive.ViewportFooter
className="aui-thread-viewport-footer sticky bottom-0 z-10 mx-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-main-panel pb-4 md:pb-6"
style={{ paddingBottom: "max(1rem, env(safe-area-inset-bottom))" }}

View file

@ -532,16 +532,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
const isOutOfSync = currentThreadState.id !== null && !params?.chat_id;
if (isOutOfSync) {
// First sync Next.js router by navigating to the current chat's actual URL
// This updates the router's internal state to match the browser URL
resetCurrentThread();
router.replace(`/dashboard/${searchSpaceId}/new-chat/${currentThreadState.id}`);
// Allow router to sync, then navigate to fresh new-chat
setTimeout(() => {
router.push(`/dashboard/${searchSpaceId}/new-chat`);
}, 0);
// Immediately set the browser URL so the page remounts with a clean /new-chat path
window.history.replaceState(null, "", `/dashboard/${searchSpaceId}/new-chat`);
// Force-remount the page component to reset all React state synchronously
setChatResetKey((k) => k + 1);
// Sync Next.js router internals so useParams/usePathname stay correct going forward
router.replace(`/dashboard/${searchSpaceId}/new-chat`);
} else {
// Normal navigation - router is in sync
router.push(`/dashboard/${searchSpaceId}/new-chat`);
}
}, [router, searchSpaceId, currentThreadState.id, params?.chat_id, resetCurrentThread]);

View file

@ -1,7 +1,7 @@
"use client";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { CornerDownLeftIcon, ShieldAlertIcon, ShieldCheckIcon } from "lucide-react";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button";
@ -134,17 +134,14 @@ function GenericApprovalCard({
return (
<div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300">
{/* Header */}
<div className="flex items-start gap-3 px-5 pt-5 pb-4 select-none">
<div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-amber-500/10">
<ShieldAlertIcon className="size-4 text-amber-600" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between px-5 pt-5 pb-4 select-none">
<div>
<p className="text-sm font-semibold text-foreground">
{phase === "rejected"
? `${displayName} — Rejected`
: phase === "processing" || phase === "complete"
? `${displayName} — Approved`
: `Approve: ${displayName}`}
: displayName}
</p>
{phase === "processing" ? (
<TextShimmerLoader text="Executing..." size="sm" />
@ -163,13 +160,24 @@ function GenericApprovalCard({
</p>
)}
</div>
{phase === "pending" && canEdit && !isEditing && (
<Button
size="sm"
variant="ghost"
className="rounded-lg text-muted-foreground -mt-1 -mr-2"
onClick={() => setIsEditing(true)}
>
<Pen className="size-3.5" />
Edit
</Button>
)}
</div>
{/* Description */}
{toolDescription && phase === "pending" && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-2">
<div className="px-5 py-3">
<p className="text-xs text-muted-foreground">{toolDescription}</p>
</div>
</>
@ -179,7 +187,7 @@ function GenericApprovalCard({
{Object.keys(args).length > 0 && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-3 space-y-2">
<div className="px-5 py-4 space-y-2">
<p className="text-xs font-medium text-muted-foreground">Parameters</p>
{phase === "pending" && isEditing ? (
<ParamEditor
@ -200,7 +208,7 @@ function GenericApprovalCard({
{phase === "pending" && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-3 flex items-center gap-2 select-none flex-wrap">
<div className="px-5 py-4 flex items-center gap-2 select-none">
{allowedDecisions.includes("approve") && (
<Button size="sm" className="rounded-lg gap-1.5" onClick={handleApprove}>
{isEditing && hasChanged ? "Approve with edits" : "Approve"}
@ -210,24 +218,12 @@ function GenericApprovalCard({
{isMCPTool && (
<Button
size="sm"
variant="outline"
className="rounded-lg gap-1.5"
className="rounded-lg"
onClick={handleAlwaysAllow}
>
<ShieldCheckIcon className="size-3" />
Always Allow
</Button>
)}
{canEdit && !isEditing && (
<Button
size="sm"
variant="outline"
className="rounded-lg"
onClick={() => setIsEditing(true)}
>
Edit
</Button>
)}
{allowedDecisions.includes("reject") && (
<Button
size="sm"