Block send until extensions are ready (#4271)
This commit is contained in:
+16
-6
@@ -359,6 +359,7 @@ export default function App() {
|
|||||||
const [extensionConfirmTitle, setExtensionConfirmTitle] = useState<string>('');
|
const [extensionConfirmTitle, setExtensionConfirmTitle] = useState<string>('');
|
||||||
const [isLoadingSession, setIsLoadingSession] = useState(false);
|
const [isLoadingSession, setIsLoadingSession] = useState(false);
|
||||||
const [isGoosehintsModalOpen, setIsGoosehintsModalOpen] = 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
|
// Add separate state for pair chat to maintain its own conversation
|
||||||
const [pairChat, setPairChat] = useState<ChatType>({
|
const [pairChat, setPairChat] = useState<ChatType>({
|
||||||
@@ -449,16 +450,19 @@ export default function App() {
|
|||||||
getExtensions,
|
getExtensions,
|
||||||
addExtension,
|
addExtension,
|
||||||
setPairChat,
|
setPairChat,
|
||||||
|
setMessage: setAgentWaitingMessage,
|
||||||
provider: provider as string,
|
provider: provider as string,
|
||||||
model: model as string,
|
model: model as string,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
initialize().catch((error) => {
|
initialize()
|
||||||
console.error('Fatal error during initialization:', error);
|
.then(() => setAgentWaitingMessage(null))
|
||||||
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
|
.catch((error) => {
|
||||||
});
|
console.error('Fatal error during initialization:', error);
|
||||||
}, [getExtensions, addExtension, read, setPairChat]);
|
setFatalError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||||
|
});
|
||||||
|
}, [getExtensions, addExtension, read, setPairChat, setAgentWaitingMessage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Sending reactReady signal to Electron');
|
console.log('Sending reactReady signal to Electron');
|
||||||
@@ -838,7 +842,12 @@ export default function App() {
|
|||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<ChatProvider chat={chat} setChat={setChat} contextKey="hub">
|
<ChatProvider
|
||||||
|
chat={chat}
|
||||||
|
setChat={setChat}
|
||||||
|
contextKey="hub"
|
||||||
|
agentWaitingMessage={agentWaitingMessage}
|
||||||
|
>
|
||||||
<AppLayout setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
<AppLayout setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
}
|
}
|
||||||
@@ -864,6 +873,7 @@ export default function App() {
|
|||||||
chat={pairChat}
|
chat={pairChat}
|
||||||
setChat={setPairChat}
|
setChat={setPairChat}
|
||||||
contextKey={`pair-${pairChat.id}`}
|
contextKey={`pair-${pairChat.id}`}
|
||||||
|
agentWaitingMessage={agentWaitingMessage}
|
||||||
key={pairChat.id}
|
key={pairChat.id}
|
||||||
>
|
>
|
||||||
<PairRouteWrapper
|
<PairRouteWrapper
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export default function ChatInput({
|
|||||||
// Draft functionality - get chat context and global draft context
|
// 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)
|
// 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 chatContext = useChatContext(); // This should always be available now
|
||||||
|
const agentIsReady = chatContext === null || chatContext.agentWaitingMessage === null;
|
||||||
const draftLoadedRef = useRef(false);
|
const draftLoadedRef = useRef(false);
|
||||||
|
|
||||||
// Debug logging for draft context
|
// Debug logging for draft context
|
||||||
@@ -1060,6 +1061,7 @@ export default function ChatInput({
|
|||||||
const canSubmit =
|
const canSubmit =
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
!isLoadingCompaction &&
|
!isLoadingCompaction &&
|
||||||
|
agentIsReady &&
|
||||||
(displayValue.trim() ||
|
(displayValue.trim() ||
|
||||||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
||||||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
||||||
@@ -1074,6 +1076,7 @@ export default function ChatInput({
|
|||||||
const canSubmit =
|
const canSubmit =
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
!isLoadingCompaction &&
|
!isLoadingCompaction &&
|
||||||
|
agentIsReady &&
|
||||||
(displayValue.trim() ||
|
(displayValue.trim() ||
|
||||||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
|
||||||
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
allDroppedFiles.some((file) => !file.error && !file.isLoading));
|
||||||
@@ -1337,46 +1340,56 @@ export default function ChatInput({
|
|||||||
<Stop />
|
<Stop />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Tooltip>
|
||||||
type="submit"
|
<TooltipTrigger asChild>
|
||||||
size="sm"
|
<span>
|
||||||
shape="round"
|
<Button
|
||||||
variant="outline"
|
type="submit"
|
||||||
disabled={
|
size="sm"
|
||||||
!hasSubmittableContent ||
|
shape="round"
|
||||||
isAnyImageLoading ||
|
variant="outline"
|
||||||
isAnyDroppedFileLoading ||
|
disabled={
|
||||||
isRecording ||
|
!hasSubmittableContent ||
|
||||||
isTranscribing ||
|
isAnyImageLoading ||
|
||||||
isLoadingCompaction
|
isAnyDroppedFileLoading ||
|
||||||
}
|
isRecording ||
|
||||||
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
|
isTranscribing ||
|
||||||
!hasSubmittableContent ||
|
isLoadingCompaction ||
|
||||||
isAnyImageLoading ||
|
!agentIsReady
|
||||||
isAnyDroppedFileLoading ||
|
}
|
||||||
isRecording ||
|
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
|
||||||
isTranscribing ||
|
!hasSubmittableContent ||
|
||||||
isLoadingCompaction
|
isAnyImageLoading ||
|
||||||
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
|
isAnyDroppedFileLoading ||
|
||||||
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
|
isRecording ||
|
||||||
}`}
|
isTranscribing ||
|
||||||
title={
|
isLoadingCompaction ||
|
||||||
isLoadingCompaction
|
!agentIsReady
|
||||||
? 'Summarizing conversation...'
|
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
|
||||||
: isAnyImageLoading
|
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
|
||||||
? 'Waiting for images to save...'
|
}`}
|
||||||
: isAnyDroppedFileLoading
|
>
|
||||||
? 'Processing dropped files...'
|
<Send className="w-4 h-4" />
|
||||||
: isRecording
|
<span className="text-sm">Send</span>
|
||||||
? 'Recording...'
|
</Button>
|
||||||
: isTranscribing
|
</span>
|
||||||
? 'Transcribing...'
|
</TooltipTrigger>
|
||||||
: 'Send'
|
<TooltipContent>
|
||||||
}
|
<p>
|
||||||
>
|
{isLoadingCompaction
|
||||||
<Send className="w-4 h-4" />
|
? 'Summarizing conversation...'
|
||||||
<span className="text-sm">Send</span>
|
: isAnyImageLoading
|
||||||
</Button>
|
? '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 */}
|
{/* Recording/transcribing status indicator - positioned above the button row */}
|
||||||
|
|||||||
@@ -60,28 +60,28 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
|||||||
bg: 'bg-red-50 dark:bg-red-950/20',
|
bg: 'bg-red-50 dark:bg-red-950/20',
|
||||||
border: 'border-red-200 dark:border-red-800/50',
|
border: 'border-red-200 dark:border-red-800/50',
|
||||||
text: 'text-red-800 dark:text-red-200',
|
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':
|
case 'pause':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-amber-50 dark:bg-amber-950/20',
|
bg: 'bg-amber-50 dark:bg-amber-950/20',
|
||||||
border: 'border-amber-200 dark:border-amber-800/50',
|
border: 'border-amber-200 dark:border-amber-800/50',
|
||||||
text: 'text-amber-800 dark:text-amber-200',
|
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':
|
case 'redirect':
|
||||||
return {
|
return {
|
||||||
bg: 'bg-blue-50 dark:bg-blue-950/20',
|
bg: 'bg-blue-50 dark:bg-blue-950/20',
|
||||||
border: 'border-blue-200 dark:border-blue-800/50',
|
border: 'border-blue-200 dark:border-blue-800/50',
|
||||||
text: 'text-blue-800 dark:text-blue-200',
|
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:
|
default:
|
||||||
return {
|
return {
|
||||||
bg: 'bg-orange-50 dark:bg-orange-950/20',
|
bg: 'bg-orange-50 dark:bg-orange-950/20',
|
||||||
border: 'border-orange-200 dark:border-orange-800/50',
|
border: 'border-orange-200 dark:border-orange-800/50',
|
||||||
text: 'text-orange-800 dark:text-orange-200',
|
text: 'text-orange-800 dark:text-orange-200',
|
||||||
accent: 'text-orange-600 dark:text-orange-400'
|
accent: 'text-orange-600 dark:text-orange-400',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -98,33 +98,43 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
|||||||
|
|
||||||
const getActionTitle = () => {
|
const getActionTitle = () => {
|
||||||
switch (match.keyword.action) {
|
switch (match.keyword.action) {
|
||||||
case 'stop': return 'Stop Processing';
|
case 'stop':
|
||||||
case 'pause': return 'Pause Processing';
|
return 'Stop Processing';
|
||||||
case 'redirect': return 'Redirect Processing';
|
case 'pause':
|
||||||
default: return 'Interrupt Processing';
|
return 'Pause Processing';
|
||||||
|
case 'redirect':
|
||||||
|
return 'Redirect Processing';
|
||||||
|
default:
|
||||||
|
return 'Interrupt Processing';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActionDescription = () => {
|
const getActionDescription = () => {
|
||||||
switch (match.keyword.action) {
|
switch (match.keyword.action) {
|
||||||
case 'stop':
|
case 'stop':
|
||||||
return 'This will immediately stop the current processing and clear any queued messages.';
|
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.';
|
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.';
|
return 'This will stop current processing and redirect to a new task.';
|
||||||
default:
|
default:
|
||||||
return 'This will interrupt the current processing.';
|
return 'This will interrupt the current processing.';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4 ${className}`}>
|
<div
|
||||||
<div className={`w-full max-w-md mx-auto transition-all duration-300 ease-out ${
|
className={`fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4 ${className}`}
|
||||||
isVisible ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
|
>
|
||||||
}`}>
|
<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 */}
|
{/* 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 */}
|
{/* Header */}
|
||||||
<div className="p-6 border-b border-current/10">
|
<div className="p-6 border-b border-current/10">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
@@ -132,14 +142,12 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
|||||||
{getIcon()}
|
{getIcon()}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className={`text-lg font-semibold ${colors.text}`}>
|
<h3 className={`text-lg font-semibold ${colors.text}`}>{getActionTitle()}</h3>
|
||||||
{getActionTitle()}
|
<p className={`text-sm mt-1 ${colors.accent}`}>Detected: "{match.matchedText}"</p>
|
||||||
</h3>
|
|
||||||
<p className={`text-sm mt-1 ${colors.accent}`}>
|
|
||||||
Detected: "{match.matchedText}"
|
|
||||||
</p>
|
|
||||||
</div>
|
</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
|
{Math.round(match.confidence * 100)}% confident
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,9 +157,7 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-start gap-3 mb-4">
|
<div className="flex items-start gap-3 mb-4">
|
||||||
<AlertCircle className={`w-4 h-4 mt-0.5 flex-shrink-0 ${colors.accent}`} />
|
<AlertCircle className={`w-4 h-4 mt-0.5 flex-shrink-0 ${colors.accent}`} />
|
||||||
<p className={`text-sm leading-relaxed ${colors.text}`}>
|
<p className={`text-sm leading-relaxed ${colors.text}`}>{getActionDescription()}</p>
|
||||||
{getActionDescription()}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Redirect input */}
|
{/* Redirect input */}
|
||||||
@@ -178,10 +184,13 @@ export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
|||||||
<span className={colors.text}>{Math.round(match.confidence * 100)}%</span>
|
<span className={colors.text}>{Math.round(match.confidence * 100)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-white/30 dark:bg-black/20 rounded-full h-2">
|
<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 ${
|
className={`h-2 rounded-full transition-all duration-500 ${
|
||||||
match.confidence > 0.8 ? 'bg-green-500' :
|
match.confidence > 0.8
|
||||||
match.confidence > 0.6 ? 'bg-amber-500' : 'bg-red-500'
|
? 'bg-green-500'
|
||||||
|
: match.confidence > 0.6
|
||||||
|
? 'bg-amber-500'
|
||||||
|
: 'bg-red-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${match.confidence * 100}%` }}
|
style={{ width: `${match.confidence * 100}%` }}
|
||||||
/>
|
/>
|
||||||
@@ -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`}
|
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" />
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ export function Pill({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
animated = false,
|
animated = false,
|
||||||
}: PillProps) {
|
}: 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 = {
|
const variants = {
|
||||||
default: 'bg-background border border-border hover:bg-muted/50',
|
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',
|
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',
|
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',
|
glow: 'shadow-lg hover:shadow-xl hover:scale-105 border-0',
|
||||||
@@ -54,9 +56,11 @@ export function Pill({
|
|||||||
glass: 'text-red-700 dark:text-red-300 hover:text-red-800 dark:hover:text-red-200',
|
glass: 'text-red-700 dark:text-red-300 hover:text-red-800 dark:hover:text-red-200',
|
||||||
},
|
},
|
||||||
purple: {
|
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',
|
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: {
|
slate: {
|
||||||
gradient: 'from-slate-500 to-slate-600 hover:from-slate-600 hover:to-slate-700 text-white',
|
gradient: 'from-slate-500 to-slate-600 hover:from-slate-600 hover:to-slate-700 text-white',
|
||||||
@@ -73,20 +77,21 @@ export function Pill({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const animatedStyles = animated ? 'animate-pulse' : '';
|
const animatedStyles = animated ? 'animate-pulse' : '';
|
||||||
|
|
||||||
const disabledStyles = disabled
|
const disabledStyles = disabled
|
||||||
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
||||||
: onClick
|
: onClick
|
||||||
? 'cursor-pointer hover:scale-105 active:scale-95'
|
? 'cursor-pointer hover:scale-105 active:scale-95'
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const colorStyles = variant === 'gradient'
|
const colorStyles =
|
||||||
? colors[color].gradient
|
variant === 'gradient'
|
||||||
: variant === 'glow'
|
? colors[color].gradient
|
||||||
? colors[color].glow
|
: variant === 'glow'
|
||||||
: variant === 'glass'
|
? colors[color].glow
|
||||||
? colors[color].glass
|
: variant === 'glass'
|
||||||
: '';
|
? colors[color].glass
|
||||||
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface ChatContextType {
|
|||||||
clearDraft: () => void;
|
clearDraft: () => void;
|
||||||
// Context identification
|
// Context identification
|
||||||
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
||||||
|
agentWaitingMessage: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
||||||
@@ -30,12 +31,14 @@ interface ChatProviderProps {
|
|||||||
chat: ChatType;
|
chat: ChatType;
|
||||||
setChat: (chat: ChatType) => void;
|
setChat: (chat: ChatType) => void;
|
||||||
contextKey?: string; // Optional context key, defaults to 'hub'
|
contextKey?: string; // Optional context key, defaults to 'hub'
|
||||||
|
agentWaitingMessage: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
chat,
|
chat,
|
||||||
setChat,
|
setChat,
|
||||||
|
agentWaitingMessage,
|
||||||
contextKey = 'hub',
|
contextKey = 'hub',
|
||||||
}) => {
|
}) => {
|
||||||
const draftContext = useDraftContext();
|
const draftContext = useDraftContext();
|
||||||
@@ -108,6 +111,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|||||||
setDraft,
|
setDraft,
|
||||||
clearDraft,
|
clearDraft,
|
||||||
contextKey,
|
contextKey,
|
||||||
|
agentWaitingMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface InitializationDependencies {
|
|||||||
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
|
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
|
||||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||||
setPairChat: (chat: ChatType | ((prev: ChatType) => ChatType)) => void;
|
setPairChat: (chat: ChatType | ((prev: ChatType) => ChatType)) => void;
|
||||||
|
setMessage: (message: string | null) => void;
|
||||||
provider: string;
|
provider: string;
|
||||||
model: string;
|
model: string;
|
||||||
}
|
}
|
||||||
@@ -22,6 +23,7 @@ export const initializeApp = async ({
|
|||||||
getExtensions,
|
getExtensions,
|
||||||
addExtension,
|
addExtension,
|
||||||
setPairChat,
|
setPairChat,
|
||||||
|
setMessage,
|
||||||
provider,
|
provider,
|
||||||
model,
|
model,
|
||||||
}: InitializationDependencies) => {
|
}: InitializationDependencies) => {
|
||||||
@@ -87,6 +89,7 @@ export const initializeApp = async ({
|
|||||||
initPromises.push(costDbPromise);
|
initPromises.push(costDbPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMessage('starting extensions...');
|
||||||
await Promise.all(initPromises);
|
await Promise.all(initPromises);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in system initialization:', error);
|
console.error('Error in system initialization:', error);
|
||||||
|
|||||||
@@ -15,32 +15,32 @@ export const INTERRUPTION_KEYWORDS: InterruptionKeyword[] = [
|
|||||||
keyword: 'stop',
|
keyword: 'stop',
|
||||||
variations: ['stop', 'halt', 'cease', 'quit', 'end', 'abort', 'cancel'],
|
variations: ['stop', 'halt', 'cease', 'quit', 'end', 'abort', 'cancel'],
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
action: 'stop'
|
action: 'stop',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyword: 'wait',
|
keyword: 'wait',
|
||||||
variations: ['wait', 'hold', 'pause', 'hold on', 'wait up', 'hold up'],
|
variations: ['wait', 'hold', 'pause', 'hold on', 'wait up', 'hold up'],
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
action: 'pause'
|
action: 'pause',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyword: 'no',
|
keyword: 'no',
|
||||||
variations: ['no', 'nope', 'nah', 'wrong', 'incorrect', 'not right'],
|
variations: ['no', 'nope', 'nah', 'wrong', 'incorrect', 'not right'],
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
action: 'stop'
|
action: 'stop',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyword: 'actually',
|
keyword: 'actually',
|
||||||
variations: ['actually', 'instead', 'rather', 'better idea', 'change of plans'],
|
variations: ['actually', 'instead', 'rather', 'better idea', 'change of plans'],
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
action: 'redirect'
|
action: 'redirect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
keyword: 'nevermind',
|
keyword: 'nevermind',
|
||||||
variations: ['nevermind', 'never mind', 'forget it', 'ignore that', 'disregard'],
|
variations: ['nevermind', 'never mind', 'forget it', 'ignore that', 'disregard'],
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
action: 'stop'
|
action: 'stop',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface InterruptionMatch {
|
export interface InterruptionMatch {
|
||||||
@@ -59,7 +59,7 @@ export function detectInterruption(input: string): InterruptionMatch | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedInput = input.toLowerCase().trim();
|
const normalizedInput = input.toLowerCase().trim();
|
||||||
|
|
||||||
// Check for exact matches first (highest confidence)
|
// Check for exact matches first (highest confidence)
|
||||||
for (const keyword of INTERRUPTION_KEYWORDS) {
|
for (const keyword of INTERRUPTION_KEYWORDS) {
|
||||||
for (const variation of keyword.variations) {
|
for (const variation of keyword.variations) {
|
||||||
@@ -68,7 +68,7 @@ export function detectInterruption(input: string): InterruptionMatch | null {
|
|||||||
keyword,
|
keyword,
|
||||||
matchedText: variation,
|
matchedText: variation,
|
||||||
confidence: 1.0,
|
confidence: 1.0,
|
||||||
shouldInterrupt: true
|
shouldInterrupt: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,12 +77,15 @@ export function detectInterruption(input: string): InterruptionMatch | null {
|
|||||||
// Check for matches at the beginning of input (high confidence)
|
// Check for matches at the beginning of input (high confidence)
|
||||||
for (const keyword of INTERRUPTION_KEYWORDS) {
|
for (const keyword of INTERRUPTION_KEYWORDS) {
|
||||||
for (const variation of keyword.variations) {
|
for (const variation of keyword.variations) {
|
||||||
if (normalizedInput.startsWith(variation + ' ') || normalizedInput.startsWith(variation + ',')) {
|
if (
|
||||||
|
normalizedInput.startsWith(variation + ' ') ||
|
||||||
|
normalizedInput.startsWith(variation + ',')
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
keyword,
|
keyword,
|
||||||
matchedText: variation,
|
matchedText: variation,
|
||||||
confidence: 0.9,
|
confidence: 0.9,
|
||||||
shouldInterrupt: true
|
shouldInterrupt: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +100,7 @@ export function detectInterruption(input: string): InterruptionMatch | null {
|
|||||||
keyword,
|
keyword,
|
||||||
matchedText: variation,
|
matchedText: variation,
|
||||||
confidence: 0.7,
|
confidence: 0.7,
|
||||||
shouldInterrupt: keyword.priority === 'high'
|
shouldInterrupt: keyword.priority === 'high',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user