Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ export default function App() {
const [extensionConfirmTitle, setExtensionConfirmTitle] = useState<string>('');
const [isLoadingSession, setIsLoadingSession] = useState(false);
const [isGoosehintsModalOpen, setIsGoosehintsModalOpen] = useState(false);
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);

// Add separate state for pair chat to maintain its own conversation
const [pairChat, setPairChat] = useState<ChatType>({
Expand Down Expand Up @@ -449,16 +450,19 @@ export default function App() {
getExtensions,
addExtension,
setPairChat,
setMessage: setAgentWaitingMessage,
provider: provider as string,
model: model as string,
});
};

initialize().catch((error) => {
console.error('Fatal error during initialization:', error);
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
});
}, [getExtensions, addExtension, read, setPairChat]);
initialize()
.then(() => setAgentWaitingMessage(null))
.catch((error) => {
console.error('Fatal error during initialization:', error);
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
});
}, [getExtensions, addExtension, read, setPairChat, setAgentWaitingMessage]);

useEffect(() => {
console.log('Sending reactReady signal to Electron');
Expand Down Expand Up @@ -838,7 +842,12 @@ export default function App() {
<Route
path="/"
element={
<ChatProvider chat={chat} setChat={setChat} contextKey="hub">
<ChatProvider
chat={chat}
setChat={setChat}
contextKey="hub"
agentWaitingMessage={agentWaitingMessage}
>
<AppLayout setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
</ChatProvider>
}
Expand All @@ -864,6 +873,7 @@ export default function App() {
chat={pairChat}
setChat={setPairChat}
contextKey={`pair-${pairChat.id}`}
agentWaitingMessage={agentWaitingMessage}
key={pairChat.id}
>
<PairRouteWrapper
Expand Down
93 changes: 53 additions & 40 deletions ui/desktop/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export default function ChatInput({
// Draft functionality - get chat context and global draft context
// We need to handle the case where ChatInput is used without ChatProvider (e.g., in Hub)
const chatContext = useChatContext(); // This should always be available now
const agentIsReady = chatContext === null || chatContext.agentWaitingMessage === null;
const draftLoadedRef = useRef(false);

// Debug logging for draft context
Expand Down Expand Up @@ -1060,6 +1061,7 @@ export default function ChatInput({
const canSubmit =
!isLoading &&
!isLoadingCompaction &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
Expand All @@ -1074,6 +1076,7 @@ export default function ChatInput({
const canSubmit =
!isLoading &&
!isLoadingCompaction &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
Expand Down Expand Up @@ -1337,46 +1340,56 @@ export default function ChatInput({
<Stop />
</Button>
) : (
<Button
type="submit"
size="sm"
shape="round"
variant="outline"
disabled={
!hasSubmittableContent ||
isAnyImageLoading ||
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction
}
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
!hasSubmittableContent ||
isAnyImageLoading ||
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
}`}
title={
isLoadingCompaction
? 'Summarizing conversation...'
: isAnyImageLoading
? 'Waiting for images to save...'
: isAnyDroppedFileLoading
? 'Processing dropped files...'
: isRecording
? 'Recording...'
: isTranscribing
? 'Transcribing...'
: 'Send'
}
>
<Send className="w-4 h-4" />
<span className="text-sm">Send</span>
</Button>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button
type="submit"
size="sm"
shape="round"
variant="outline"
disabled={
!hasSubmittableContent ||
isAnyImageLoading ||
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction ||
!agentIsReady
}
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
!hasSubmittableContent ||
isAnyImageLoading ||
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction ||
!agentIsReady
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
}`}
>
<Send className="w-4 h-4" />
<span className="text-sm">Send</span>
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
<p>
{isLoadingCompaction
? 'Summarizing conversation...'
: isAnyImageLoading
? 'Waiting for images to save...'
: isAnyDroppedFileLoading
? 'Processing dropped files...'
: isRecording
? 'Recording...'
: isTranscribing
? 'Transcribing...'
: (chatContext?.agentWaitingMessage ?? 'Send')}
</p>
</TooltipContent>
</Tooltip>
)}

{/* Recording/transcribing status indicator - positioned above the button row */}
Expand Down
75 changes: 44 additions & 31 deletions ui/desktop/src/components/InterruptionHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,28 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
bg: 'bg-red-50 dark:bg-red-950/20',
border: 'border-red-200 dark:border-red-800/50',
text: 'text-red-800 dark:text-red-200',
accent: 'text-red-600 dark:text-red-400'
accent: 'text-red-600 dark:text-red-400',
};
case 'pause':
return {
bg: 'bg-amber-50 dark:bg-amber-950/20',
border: 'border-amber-200 dark:border-amber-800/50',
text: 'text-amber-800 dark:text-amber-200',
accent: 'text-amber-600 dark:text-amber-400'
accent: 'text-amber-600 dark:text-amber-400',
};
case 'redirect':
return {
bg: 'bg-blue-50 dark:bg-blue-950/20',
border: 'border-blue-200 dark:border-blue-800/50',
text: 'text-blue-800 dark:text-blue-200',
accent: 'text-blue-600 dark:text-blue-400'
accent: 'text-blue-600 dark:text-blue-400',
};
default:
return {
bg: 'bg-orange-50 dark:bg-orange-950/20',
border: 'border-orange-200 dark:border-orange-800/50',
text: 'text-orange-800 dark:text-orange-200',
accent: 'text-orange-600 dark:text-orange-400'
accent: 'text-orange-600 dark:text-orange-400',
};
}
};
Expand All @@ -98,48 +98,56 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({

const getActionTitle = () => {
switch (match.keyword.action) {
case 'stop': return 'Stop Processing';
case 'pause': return 'Pause Processing';
case 'redirect': return 'Redirect Processing';
default: return 'Interrupt Processing';
case 'stop':
return 'Stop Processing';
case 'pause':
return 'Pause Processing';
case 'redirect':
return 'Redirect Processing';
default:
return 'Interrupt Processing';
}
};

const getActionDescription = () => {
switch (match.keyword.action) {
case 'stop':
case 'stop':
return 'This will immediately stop the current processing and clear any queued messages.';
case 'pause':
case 'pause':
return 'This will pause the current processing. Queued messages will be preserved.';
case 'redirect':
case 'redirect':
return 'This will stop current processing and redirect to a new task.';
default:
default:
return 'This will interrupt the current processing.';
}
};

return (
<div className={`fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4 ${className}`}>
<div className={`w-full max-w-md mx-auto transition-all duration-300 ease-out ${
isVisible ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}>
<div
className={`fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4 ${className}`}
>
<div
className={`w-full max-w-md mx-auto transition-all duration-300 ease-out ${
isVisible ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
}`}
>
{/* Main card */}
<div className={`rounded-xl border shadow-2xl backdrop-blur-xl ${colors.bg} ${colors.border}`}>
<div
className={`rounded-xl border shadow-2xl backdrop-blur-xl ${colors.bg} ${colors.border}`}
>
{/* Header */}
<div className="p-6 border-b border-current/10">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 p-2 rounded-full bg-white/50 dark:bg-black/20">
{getIcon()}
</div>
<div className="flex-1">
<h3 className={`text-lg font-semibold ${colors.text}`}>
{getActionTitle()}
</h3>
<p className={`text-sm mt-1 ${colors.accent}`}>
Detected: "{match.matchedText}"
</p>
<h3 className={`text-lg font-semibold ${colors.text}`}>{getActionTitle()}</h3>
<p className={`text-sm mt-1 ${colors.accent}`}>Detected: "{match.matchedText}"</p>
</div>
<div className={`text-xs px-2 py-1 rounded-full bg-white/30 dark:bg-black/20 ${colors.text}`}>
<div
className={`text-xs px-2 py-1 rounded-full bg-white/30 dark:bg-black/20 ${colors.text}`}
>
{Math.round(match.confidence * 100)}% confident
</div>
</div>
Expand All @@ -149,9 +157,7 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
<div className="p-6">
<div className="flex items-start gap-3 mb-4">
<AlertCircle className={`w-4 h-4 mt-0.5 flex-shrink-0 ${colors.accent}`} />
<p className={`text-sm leading-relaxed ${colors.text}`}>
{getActionDescription()}
</p>
<p className={`text-sm leading-relaxed ${colors.text}`}>{getActionDescription()}</p>
</div>

{/* Redirect input */}
Expand All @@ -178,10 +184,13 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
<span className={colors.text}>{Math.round(match.confidence * 100)}%</span>
</div>
<div className="w-full bg-white/30 dark:bg-black/20 rounded-full h-2">
<div
<div
className={`h-2 rounded-full transition-all duration-500 ${
match.confidence > 0.8 ? 'bg-green-500' :
match.confidence > 0.6 ? 'bg-amber-500' : 'bg-red-500'
match.confidence > 0.8
? 'bg-green-500'
: match.confidence > 0.6
? 'bg-amber-500'
: 'bg-red-500'
}`}
style={{ width: `${match.confidence * 100}%` }}
/>
Expand All @@ -204,7 +213,11 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
className={`flex-1 bg-white/80 hover:bg-white dark:bg-white/10 dark:hover:bg-white/20 ${colors.text} font-medium shadow-md hover:shadow-lg transition-all duration-200`}
>
<Zap className="w-4 h-4 mr-2" />
{showRedirectInput ? 'Redirect' : match.keyword.action === 'stop' ? 'Stop' : 'Confirm'}
{showRedirectInput
? 'Redirect'
: match.keyword.action === 'stop'
? 'Stop'
: 'Confirm'}
</Button>
</div>
</div>
Expand Down
39 changes: 22 additions & 17 deletions ui/desktop/src/components/ui/Pill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ export function Pill({
disabled = false,
animated = false,
}: PillProps) {
const baseStyles = 'inline-flex items-center justify-center rounded-full transition-all duration-300 ease-out font-medium';

const baseStyles =
'inline-flex items-center justify-center rounded-full transition-all duration-300 ease-out font-medium';

const variants = {
default: 'bg-background border border-border hover:bg-muted/50',
glass: 'bg-white/10 dark:bg-black/10 backdrop-blur-xl border border-white/20 dark:border-white/10 shadow-lg shadow-black/5 dark:shadow-black/20 hover:bg-white/15 dark:hover:bg-black/15 hover:shadow-xl',
glass:
'bg-white/10 dark:bg-black/10 backdrop-blur-xl border border-white/20 dark:border-white/10 shadow-lg shadow-black/5 dark:shadow-black/20 hover:bg-white/15 dark:hover:bg-black/15 hover:shadow-xl',
solid: 'bg-background border border-border shadow-md hover:shadow-lg hover:scale-105',
gradient: 'bg-gradient-to-r shadow-lg hover:shadow-xl hover:scale-105 border-0',
glow: 'shadow-lg hover:shadow-xl hover:scale-105 border-0',
Expand Down Expand Up @@ -54,9 +56,11 @@ export function Pill({
glass: 'text-red-700 dark:text-red-300 hover:text-red-800 dark:hover:text-red-200',
},
purple: {
gradient: 'from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white',
gradient:
'from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white',
glow: 'bg-purple-500 hover:bg-purple-600 text-white shadow-purple-500/25 hover:shadow-purple-500/40',
glass: 'text-purple-700 dark:text-purple-300 hover:text-purple-800 dark:hover:text-purple-200',
glass:
'text-purple-700 dark:text-purple-300 hover:text-purple-800 dark:hover:text-purple-200',
},
slate: {
gradient: 'from-slate-500 to-slate-600 hover:from-slate-600 hover:to-slate-700 text-white',
Expand All @@ -73,20 +77,21 @@ export function Pill({
};

const animatedStyles = animated ? 'animate-pulse' : '';
const disabledStyles = disabled
? 'opacity-50 cursor-not-allowed pointer-events-none'
: onClick
? 'cursor-pointer hover:scale-105 active:scale-95'

const disabledStyles = disabled
? 'opacity-50 cursor-not-allowed pointer-events-none'
: onClick
? 'cursor-pointer hover:scale-105 active:scale-95'
: '';

const colorStyles = variant === 'gradient'
? colors[color].gradient
: variant === 'glow'
? colors[color].glow
: variant === 'glass'
? colors[color].glass
: '';
const colorStyles =
variant === 'gradient'
? colors[color].gradient
: variant === 'glow'
? colors[color].glow
: variant === 'glass'
? colors[color].glass
: '';

return (
<div
Expand Down
Loading
Loading