Copilot apply all and status bar (#173)

* Club context with status bar copilot

Club copilot cards and add apply all

Remove ability to delete context and club context with status bar copilot

* Show apply all disabled in copilot
This commit is contained in:
Akhilesh Sudhakar 2025-07-15 18:33:14 +05:30 committed by GitHub
parent bbd112f80a
commit 44a951c5d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 654 additions and 179 deletions

View file

@ -1,6 +1,6 @@
'use client';
import { Button, Spinner } from "@heroui/react";
import { Button, Spinner, Tooltip } from "@heroui/react";
import { useRef, useState, useEffect } from "react";
import { Textarea } from "@/components/ui/textarea";
@ -22,6 +22,7 @@ interface ComposeBoxCopilotProps {
shouldAutoFocus?: boolean;
onFocus?: () => void;
onCancel?: () => void;
statusBar?: any;
}
export function ComposeBoxCopilot({
@ -32,6 +33,7 @@ export function ComposeBoxCopilot({
shouldAutoFocus = false,
onFocus,
onCancel,
statusBar,
}: ComposeBoxCopilotProps) {
const [input, setInput] = useState('');
const [isFocused, setIsFocused] = useState(false);
@ -75,13 +77,14 @@ export function ComposeBoxCopilot({
};
return (
<div className="relative group">
<div className="relative group z-10">
{/* Status bar above the input */}
{statusBar && <CopilotStatusBar {...statusBar} />}
{/* Keyboard shortcut hint */}
<div className="absolute -top-6 right-0 text-xs text-gray-500 dark:text-gray-400 opacity-0
group-hover:opacity-100 transition-opacity">
Press + Enter to send
</div>
{/* Outer container with padding */}
<div className="rounded-2xl border-[1.5px] border-gray-200 dark:border-[#2a2d31] p-3 relative
bg-white dark:bg-[#1e2023] flex items-end gap-2">
@ -113,7 +116,6 @@ export function ComposeBoxCopilot({
`}
/>
</div>
{/* Send button */}
<Button
size="sm"
@ -183,3 +185,132 @@ function StopIcon({ size, className }: { size: number, className?: string }) {
</svg>
);
}
function CopilotStatusBar({
allCardsLoaded,
allApplied,
appliedCount,
pendingCount,
streamingLine,
completedSummary,
hasPanelWarning,
handleApplyAll,
context,
onCloseContext
}: {
allCardsLoaded?: boolean;
allApplied?: boolean;
appliedCount?: number;
pendingCount?: number;
streamingLine?: string;
completedSummary?: string;
hasPanelWarning?: boolean;
handleApplyAll?: () => void;
context?: any;
onCloseContext?: () => void;
}) {
// Context label rendering
const renderContext = () => {
if (!context) return null;
let icon = null;
if (context.type === 'chat') icon = <svg className="w-3.5 h-3.5 text-blue-500 dark:text-blue-300 mr-1" fill="currentColor" viewBox="0 0 20 20"><path d="M18 10c0 3.866-3.582 7-8 7a8.96 8.96 0 01-4.39-1.11L2 17l1.11-2.61A8.96 8.96 0 012 10c0-3.866 3.582-7 8-7s8 3.134 8 7z" /></svg>;
if (context.type === 'agent') icon = <svg className="w-3.5 h-3.5 text-green-500 dark:text-green-300 mr-1" fill="currentColor" viewBox="0 0 20 20"><path d="M10 2a6 6 0 016 6c0 2.21-1.343 4.09-3.25 5.25A4.992 4.992 0 0110 18a4.992 4.992 0 01-2.75-4.75C5.343 12.09 4 10.21 4 8a6 6 0 016-6z" /></svg>;
if (context.type === 'tool') icon = <svg className="w-3.5 h-3.5 text-yellow-500 dark:text-yellow-300 mr-1" fill="currentColor" viewBox="0 0 20 20"><path d="M13.293 2.293a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-8.5 8.5a1 1 0 01-.293.207l-4 2a1 1 0 01-1.316-1.316l2-4a1 1 0 01.207-.293l8.5-8.5z" /></svg>;
if (context.type === 'prompt') icon = <svg className="w-3.5 h-3.5 text-purple-500 dark:text-purple-300 mr-1" fill="currentColor" viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" /></svg>;
let label = '';
if (context.type === 'chat') label = 'Chat';
if (context.type === 'agent') label = `Agent: ${context.name}`;
if (context.type === 'tool') label = `Tool: ${context.name}`;
if (context.type === 'prompt') label = `Prompt: ${context.name}`;
return (
<div className="flex items-center gap-1 px-2 py-1 rounded-md border border-zinc-200 dark:border-zinc-700 bg-zinc-50/70 dark:bg-zinc-800/40 shadow-sm text-xs font-medium text-zinc-700 dark:text-zinc-200 max-w-[180px] truncate">
{icon}
<span className="truncate max-w-[110px]">{label}</span>
</div>
);
};
// Status/ticker rendering
const renderStatus = () => {
if (!allCardsLoaded && !streamingLine && !hasPanelWarning && !completedSummary) return null;
return (
<div className="flex flex-col min-w-0">
{hasPanelWarning && (
<span className="text-xs text-yellow-600 dark:text-yellow-400 font-semibold flex items-center">
<span className="mr-1"></span> Some changes could not be applied
</span>
)}
{allCardsLoaded && completedSummary ? (
<span className="font-semibold text-xs text-gray-900 dark:text-gray-100 truncate">{completedSummary}</span>
) : streamingLine && (
<span className="font-semibold text-xs text-gray-900 dark:text-gray-100 truncate">{streamingLine}</span>
)}
<span className="text-xs text-gray-500 dark:text-gray-400">{appliedCount ?? 0} applied, {pendingCount ?? 0} pending</span>
</div>
);
};
// Apply All button
const renderApplyAll = () => {
// Show disabled button with tooltip while streaming
if (!allCardsLoaded) {
return (
<Tooltip content="Apply all will be available when all changes are ready" placement="top">
<div className="inline-block">
<button
disabled
className="flex items-center gap-2 px-3 py-1 rounded-full font-medium text-xs transition-colors duration-200 bg-zinc-100 dark:bg-zinc-800 text-zinc-400 cursor-not-allowed border border-zinc-200 dark:border-zinc-700 shadow-none"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M9 12l2 2l4 -4" strokeLinecap="round" strokeLinejoin="round" /></svg>
Apply all
</button>
</div>
</Tooltip>
);
}
// Show real button when ready
return (
<button
onClick={handleApplyAll}
disabled={allApplied}
className={`flex items-center gap-2 px-3 py-1 rounded-full font-medium text-xs transition-colors duration-200
${
allApplied
? 'bg-zinc-100 dark:bg-zinc-800 text-zinc-400 cursor-not-allowed border border-zinc-200 dark:border-zinc-700 shadow-none'
: 'bg-blue-100 dark:bg-zinc-900 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-zinc-800 border border-blue-200 dark:border-zinc-800 shadow-sm'
}
`}
>
{allApplied ? (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M9 12l2 2l4 -4" strokeLinecap="round" strokeLinejoin="round" /></svg>
All applied!
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path d="M9 12l2 2l4 -4" strokeLinecap="round" strokeLinejoin="round" /></svg>
Apply all
</>
)}
</button>
);
};
return (
<div className="w-auto max-w-[calc(100%-16px)] mx-auto flex items-center px-3 py-1 pt-2.5 pb-5 mt-2 -mb-3 rounded-xl bg-zinc-50 dark:bg-zinc-900/90 border border-zinc-300 dark:border-zinc-700 shadow-md dark:shadow-zinc-950/10 backdrop-blur-sm transition-all z-0 relative mx-2 overflow-visible">
{/* Left: context + status/ticker, flex-1, truncate as needed */}
<div className="flex items-center gap-2 flex-1 min-w-0 overflow-visible">
{renderContext()}
{renderStatus() && (
<div className="ml-2 min-w-0 overflow-visible">{renderStatus()}</div>
)}
</div>
{/* Divider and rightmost Apply All button */}
{renderApplyAll() && (
<>
<div className="mx-2 h-5 border-l border-gray-200 dark:border-gray-700 flex-shrink-0" />
<div className="flex-shrink-0 flex items-center overflow-visible">{renderApplyAll()}</div>
</>
)}
{/* Optional: subtle shadow at the bottom for extra depth */}
<div className="absolute left-0 right-0 bottom-0 h-2 pointer-events-none rounded-b-xl shadow-[0_6px_12px_-6px_rgba(0,0,0,0.10)]" />
</div>
);
}