Disable chat input while extensions load (#4417)
This commit is contained in:
+16
-10
@@ -45,11 +45,13 @@ const HubRouteWrapper = ({
|
||||
setChat,
|
||||
setPairChat,
|
||||
setIsGoosehintsModalOpen,
|
||||
isExtensionsLoading,
|
||||
}: {
|
||||
chat: ChatType;
|
||||
setChat: (chat: ChatType) => void;
|
||||
setPairChat: (chat: ChatType) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
isExtensionsLoading: boolean;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const setView = createNavigationHandler(navigate);
|
||||
@@ -62,6 +64,7 @@ const HubRouteWrapper = ({
|
||||
setPairChat={setPairChat}
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -400,6 +403,7 @@ export default function App() {
|
||||
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);
|
||||
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
||||
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
||||
const [isExtensionsLoading, setIsExtensionsLoading] = useState(false);
|
||||
|
||||
// Add separate state for pair chat to maintain its own conversation
|
||||
const [pairChat, setPairChat] = useState<ChatType>({
|
||||
@@ -482,6 +486,7 @@ export default function App() {
|
||||
addExtension,
|
||||
setPairChat,
|
||||
setMessage: setAgentWaitingMessage,
|
||||
setIsExtensionsLoading,
|
||||
provider: provider as string,
|
||||
model: model as string,
|
||||
});
|
||||
@@ -802,12 +807,13 @@ export default function App() {
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<HubRouteWrapper
|
||||
chat={chat}
|
||||
setChat={setChat}
|
||||
setPairChat={setPairChat}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
/>
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -815,7 +821,7 @@ export default function App() {
|
||||
<Route
|
||||
path="pair"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<ChatProvider
|
||||
chat={pairChat}
|
||||
setChat={setPairChat}
|
||||
@@ -836,7 +842,7 @@ export default function App() {
|
||||
<Route
|
||||
path="settings"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<SettingsRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -844,7 +850,7 @@ export default function App() {
|
||||
<Route
|
||||
path="extensions"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<ExtensionsRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -852,7 +858,7 @@ export default function App() {
|
||||
<Route
|
||||
path="sessions"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<SessionsRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -860,7 +866,7 @@ export default function App() {
|
||||
<Route
|
||||
path="schedules"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<SchedulesRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -868,7 +874,7 @@ export default function App() {
|
||||
<Route
|
||||
path="recipes"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<RecipesRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -876,7 +882,7 @@ export default function App() {
|
||||
<Route
|
||||
path="recipe-editor"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<RecipeEditorRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
@@ -884,7 +890,7 @@ export default function App() {
|
||||
<Route
|
||||
path="shared-session"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<SharedSessionRouteWrapper
|
||||
isLoadingSharedSession={isLoadingSharedSession}
|
||||
setIsLoadingSharedSession={setIsLoadingSharedSession}
|
||||
@@ -896,7 +902,7 @@ export default function App() {
|
||||
<Route
|
||||
path="permission"
|
||||
element={
|
||||
<ProviderGuard>
|
||||
<ProviderGuard setIsExtensionsLoading={setIsExtensionsLoading}>
|
||||
<PermissionRoute />
|
||||
</ProviderGuard>
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ interface ChatInputProps {
|
||||
autoSubmit: boolean;
|
||||
setAncestorMessages?: (messages: Message[]) => void;
|
||||
append?: (message: Message) => void;
|
||||
isExtensionsLoading?: boolean;
|
||||
}
|
||||
|
||||
export default function ChatInput({
|
||||
@@ -111,6 +112,7 @@ export default function ChatInput({
|
||||
autoSubmit = false,
|
||||
append,
|
||||
setAncestorMessages,
|
||||
isExtensionsLoading = false,
|
||||
}: ChatInputProps) {
|
||||
const [_value, setValue] = useState(initialValue);
|
||||
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
|
||||
@@ -1125,6 +1127,25 @@ export default function ChatInput({
|
||||
const isAnyImageLoading = pastedImages.some((img) => img.isLoading);
|
||||
const isAnyDroppedFileLoading = allDroppedFiles.some((file) => file.isLoading);
|
||||
|
||||
const isSubmitButtonDisabled =
|
||||
!hasSubmittableContent ||
|
||||
isAnyImageLoading ||
|
||||
isAnyDroppedFileLoading ||
|
||||
isRecording ||
|
||||
isTranscribing ||
|
||||
isCompacting ||
|
||||
!agentIsReady ||
|
||||
isExtensionsLoading;
|
||||
|
||||
const isUserInputDisabled =
|
||||
isAnyImageLoading ||
|
||||
isAnyDroppedFileLoading ||
|
||||
isRecording ||
|
||||
isTranscribing ||
|
||||
isCompacting ||
|
||||
!agentIsReady ||
|
||||
isExtensionsLoading;
|
||||
|
||||
// Queue management functions - no storage persistence, only in-memory
|
||||
const handleRemoveQueuedMessage = (messageId: string) => {
|
||||
setQueuedMessages((prev) => prev.filter((msg) => msg.id !== messageId));
|
||||
@@ -1239,6 +1260,7 @@ export default function ChatInput({
|
||||
onBlur={() => setIsFocused(false)}
|
||||
ref={textAreaRef}
|
||||
rows={1}
|
||||
disabled={isUserInputDisabled}
|
||||
style={{
|
||||
maxHeight: `${maxHeight}px`,
|
||||
overflowY: 'auto',
|
||||
@@ -1349,23 +1371,9 @@ export default function ChatInput({
|
||||
size="sm"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
disabled={
|
||||
!hasSubmittableContent ||
|
||||
isAnyImageLoading ||
|
||||
isAnyDroppedFileLoading ||
|
||||
isRecording ||
|
||||
isTranscribing ||
|
||||
isCompacting ||
|
||||
!agentIsReady
|
||||
}
|
||||
disabled={isSubmitButtonDisabled}
|
||||
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
|
||||
!hasSubmittableContent ||
|
||||
isAnyImageLoading ||
|
||||
isAnyDroppedFileLoading ||
|
||||
isRecording ||
|
||||
isTranscribing ||
|
||||
isCompacting ||
|
||||
!agentIsReady
|
||||
isSubmitButtonDisabled
|
||||
? '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'
|
||||
}`}
|
||||
@@ -1377,17 +1385,19 @@ export default function ChatInput({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
{isCompacting
|
||||
? 'Compacting conversation...'
|
||||
: isAnyImageLoading
|
||||
? 'Waiting for images to save...'
|
||||
: isAnyDroppedFileLoading
|
||||
? 'Processing dropped files...'
|
||||
: isRecording
|
||||
? 'Recording...'
|
||||
: isTranscribing
|
||||
? 'Transcribing...'
|
||||
: (chatContext?.agentWaitingMessage ?? 'Send')}
|
||||
{isExtensionsLoading
|
||||
? 'Loading extensions...'
|
||||
: isCompacting
|
||||
? 'Compacting conversation...'
|
||||
: isAnyImageLoading
|
||||
? 'Waiting for images to save...'
|
||||
: isAnyDroppedFileLoading
|
||||
? 'Processing dropped files...'
|
||||
: isRecording
|
||||
? 'Recording...'
|
||||
: isTranscribing
|
||||
? 'Transcribing...'
|
||||
: (chatContext?.agentWaitingMessage ?? 'Send')}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -16,9 +16,10 @@ import { Ollama } from './icons';
|
||||
interface OllamaSetupProps {
|
||||
onSuccess: () => void;
|
||||
onCancel: () => void;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) {
|
||||
export function OllamaSetup({ onSuccess, onCancel, setIsExtensionsLoading }: OllamaSetupProps) {
|
||||
const { addExtension, getExtensions, upsert } = useConfig();
|
||||
const [isChecking, setIsChecking] = useState(true);
|
||||
const [ollamaDetected, setOllamaDetected] = useState(false);
|
||||
@@ -113,6 +114,7 @@ export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) {
|
||||
await initializeSystem('ollama', getPreferredModel(), {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
|
||||
toastService.success({
|
||||
@@ -157,7 +159,9 @@ export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) {
|
||||
{ollamaDetected ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start mb-16">
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-green-600 text-white rounded-full">Ollama is detected and running</span>
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-green-600 text-white rounded-full">
|
||||
Ollama is detected and running
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{modelStatus === 'checking' ? (
|
||||
@@ -185,14 +189,10 @@ export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) {
|
||||
) : modelStatus === 'downloading' ? (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-background-info/10 border border-border-info rounded-lg p-4">
|
||||
<p className="text-text-info text-sm">
|
||||
Downloading {getPreferredModel()}...
|
||||
</p>
|
||||
<p className="text-text-info text-sm">Downloading {getPreferredModel()}...</p>
|
||||
{downloadProgress && (
|
||||
<>
|
||||
<p className="text-text-muted text-xs mt-2">
|
||||
{downloadProgress.status}
|
||||
</p>
|
||||
<p className="text-text-muted text-xs mt-2">{downloadProgress.status}</p>
|
||||
{downloadProgress.total && downloadProgress.completed && (
|
||||
<div className="mt-3">
|
||||
<div className="bg-background-muted rounded-full h-2 overflow-hidden">
|
||||
@@ -225,7 +225,9 @@ export function OllamaSetup({ onSuccess, onCancel }: OllamaSetupProps) {
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start mb-16">
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-orange-600 text-white rounded-full">Ollama is not detected on your system</span>
|
||||
<span className="inline-block px-2 py-1 text-xs font-medium bg-orange-600 text-white rounded-full">
|
||||
Ollama is not detected on your system
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isPolling ? (
|
||||
|
||||
@@ -14,9 +14,10 @@ import { OpenRouter } from './icons';
|
||||
|
||||
interface ProviderGuardProps {
|
||||
children: React.ReactNode;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ProviderGuard({ children }: ProviderGuardProps) {
|
||||
export default function ProviderGuard({ children, setIsExtensionsLoading }: ProviderGuardProps) {
|
||||
const { read, getExtensions, addExtension } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
const [isChecking, setIsChecking] = useState(true);
|
||||
@@ -72,6 +73,7 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
|
||||
await initializeSystem(provider as string, model as string, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
|
||||
toastService.configure({ silent: false });
|
||||
@@ -138,6 +140,7 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
|
||||
await initializeSystem(provider as string, model as string, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
|
||||
toastService.configure({ silent: false });
|
||||
@@ -267,6 +270,7 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
|
||||
setShowOllamaSetup(false);
|
||||
setShowFirstTimeSetup(true);
|
||||
}}
|
||||
setIsExtensionsLoading={setIsExtensionsLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +36,7 @@ export default function Hub({
|
||||
setPairChat,
|
||||
setView,
|
||||
setIsGoosehintsModalOpen,
|
||||
isExtensionsLoading,
|
||||
}: {
|
||||
readyForAutoUserPrompt: boolean;
|
||||
chat: ChatType;
|
||||
@@ -43,6 +44,7 @@ export default function Hub({
|
||||
setPairChat: (chat: ChatType) => void;
|
||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
isExtensionsLoading: boolean;
|
||||
}) {
|
||||
// Handle chat input submission - create new chat and navigate to pair
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
@@ -101,6 +103,7 @@ export default function Hub({
|
||||
disableAnimation={false}
|
||||
sessionCosts={undefined}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
isExtensionsLoading={isExtensionsLoading}
|
||||
/>
|
||||
</div>
|
||||
</ContextManagerProvider>
|
||||
|
||||
@@ -10,9 +10,14 @@ import { toastService } from '../../../toasts';
|
||||
interface ProviderSettingsProps {
|
||||
onClose: () => void;
|
||||
isOnboarding: boolean;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ProviderSettings({ onClose, isOnboarding }: ProviderSettingsProps) {
|
||||
export default function ProviderSettings({
|
||||
onClose,
|
||||
isOnboarding,
|
||||
setIsExtensionsLoading,
|
||||
}: ProviderSettingsProps) {
|
||||
const { getProviders, upsert, getExtensions, addExtension } = useConfig();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [providers, setProviders] = useState<ProviderDetails[]>([]);
|
||||
@@ -71,6 +76,7 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
|
||||
await initializeSystem(provider.name, model, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
|
||||
toastService.configure({ silent: false });
|
||||
@@ -92,7 +98,7 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
|
||||
});
|
||||
}
|
||||
},
|
||||
[onClose, upsert, getExtensions, addExtension]
|
||||
[onClose, upsert, getExtensions, addExtension, setIsExtensionsLoading]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,7 @@ interface InitializationDependencies {
|
||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
setPairChat: (chat: ChatType | ((prev: ChatType) => ChatType)) => void;
|
||||
setMessage: (message: string | null) => void;
|
||||
setIsExtensionsLoading: (loading: boolean) => void;
|
||||
provider: string;
|
||||
model: string;
|
||||
}
|
||||
@@ -24,6 +25,7 @@ export const initializeApp = async ({
|
||||
addExtension,
|
||||
setPairChat,
|
||||
setMessage,
|
||||
setIsExtensionsLoading,
|
||||
provider,
|
||||
model,
|
||||
}: InitializationDependencies) => {
|
||||
@@ -36,7 +38,13 @@ export const initializeApp = async ({
|
||||
|
||||
if (resumeSessionId) {
|
||||
console.log('Session resume detected, letting useChat hook handle navigation');
|
||||
await initializeForSessionResume({ getExtensions, addExtension, provider, model });
|
||||
await initializeForSessionResume({
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
provider,
|
||||
model,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,6 +55,7 @@ export const initializeApp = async ({
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setPairChat,
|
||||
setIsExtensionsLoading,
|
||||
provider,
|
||||
model,
|
||||
});
|
||||
@@ -82,6 +91,7 @@ export const initializeApp = async ({
|
||||
initializeSystem(provider, model, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -115,15 +125,20 @@ export const initializeApp = async ({
|
||||
const initializeForSessionResume = async ({
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
provider,
|
||||
model,
|
||||
}: Pick<InitializationDependencies, 'getExtensions' | 'addExtension' | 'provider' | 'model'>) => {
|
||||
}: Pick<
|
||||
InitializationDependencies,
|
||||
'getExtensions' | 'addExtension' | 'setIsExtensionsLoading' | 'provider' | 'model'
|
||||
>) => {
|
||||
await initConfig();
|
||||
await readAllConfig({ throwOnError: true });
|
||||
|
||||
await initializeSystem(provider, model, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -132,11 +147,12 @@ const initializeForRecipe = async ({
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setPairChat,
|
||||
setIsExtensionsLoading,
|
||||
provider,
|
||||
model,
|
||||
}: Pick<
|
||||
InitializationDependencies,
|
||||
'getExtensions' | 'addExtension' | 'setPairChat' | 'provider' | 'model'
|
||||
'getExtensions' | 'addExtension' | 'setPairChat' | 'setIsExtensionsLoading' | 'provider' | 'model'
|
||||
> & {
|
||||
recipeConfig: Recipe;
|
||||
}) => {
|
||||
@@ -146,6 +162,7 @@ const initializeForRecipe = async ({
|
||||
await initializeSystem(provider, model, {
|
||||
getExtensions,
|
||||
addExtension,
|
||||
setIsExtensionsLoading,
|
||||
});
|
||||
|
||||
setPairChat((prevChat) => ({
|
||||
|
||||
@@ -115,6 +115,7 @@ export const initializeSystem = async (
|
||||
options?: {
|
||||
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
|
||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
@@ -182,6 +183,8 @@ export const initializeSystem = async (
|
||||
// Add enabled extensions to agent in parallel
|
||||
const enabledExtensions = refreshedExtensions.filter((ext) => ext.enabled);
|
||||
|
||||
options?.setIsExtensionsLoading?.(true);
|
||||
|
||||
const extensionLoadingPromises = enabledExtensions.map(async (extensionEntry) => {
|
||||
const extensionConfig = extractExtensionConfig(extensionEntry);
|
||||
const extensionName = extensionConfig.name;
|
||||
@@ -198,8 +201,10 @@ export const initializeSystem = async (
|
||||
});
|
||||
|
||||
await Promise.allSettled(extensionLoadingPromises);
|
||||
options?.setIsExtensionsLoading?.(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize agent:', error);
|
||||
options?.setIsExtensionsLoading?.(false);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user