Move token limits to backend (#2484)

This commit is contained in:
Zane
2025-05-12 13:52:46 -07:00
committed by GitHub
parent b4aadeb5d4
commit 7027de6238
14 changed files with 260 additions and 1121 deletions
+17 -309
View File
@@ -1,26 +1,18 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { IpcRendererEvent } from 'electron';
import { addExtensionFromDeepLink } from './extensions';
import { openSharedSessionFromDeepLink } from './sessionLinks';
import { getStoredModel } from './utils/providerUtils';
import { getStoredProvider, initializeSystem } from './utils/providerUtils';
import { useModel } from './components/settings/models/ModelContext';
import { useRecentModels } from './components/settings/models/RecentModels';
import { createSelectedModel } from './components/settings/models/utils';
import { getDefaultModel } from './components/settings/models/hardcoded_stuff';
import { initializeSystem } from './utils/providerUtils';
import { ErrorUI } from './components/ErrorBoundary';
import { ConfirmationModal } from './components/ui/ConfirmationModal';
import { ToastContainer } from 'react-toastify';
import { toastService } from './toasts';
import { settingsV2Enabled } from './flags';
import { extractExtensionName } from './components/settings/extensions/utils';
import { GoosehintsModal } from './components/GoosehintsModal';
import { SessionDetails } from './sessions';
import WelcomeView from './components/WelcomeView';
import ChatView from './components/ChatView';
import SuspenseLoader from './suspense-loader';
import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView';
import { type SettingsViewOptions } from './components/settings/SettingsView';
import SettingsViewV2 from './components/settings_v2/SettingsView';
import MoreModelsView from './components/settings/models/MoreModelsView';
import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView';
@@ -29,7 +21,6 @@ import SharedSessionView from './components/sessions/SharedSessionView';
import ProviderSettings from './components/settings_v2/providers/ProviderSettingsPage';
import RecipeEditor from './components/RecipeEditor';
import { useChat } from './hooks/useChat';
import { addExtension as addExtensionDirect, FullExtensionConfig } from './extensions';
import 'react-toastify/dist/ReactToastify.css';
import { useConfig, MalformedConfigError } from './components/ConfigContext';
@@ -101,7 +92,7 @@ export default function App() {
const [extensionConfirmLabel, setExtensionConfirmLabel] = useState<string>('');
const [extensionConfirmTitle, setExtensionConfirmTitle] = useState<string>('');
const [{ view, viewOptions }, setInternalView] = useState<ViewConfig>(getInitialView());
const { getExtensions, addExtension, disableAllExtensions, read } = useConfig();
const { getExtensions, addExtension, read } = useConfig();
const initAttemptedRef = useRef(false);
// Utility function to extract the command from the link
@@ -123,158 +114,7 @@ export default function App() {
setInternalView({ view, viewOptions });
};
const disableAllStoredExtensions = () => {
const userSettingsStr = localStorage.getItem('user_settings');
if (!userSettingsStr) return;
try {
const userSettings = JSON.parse(userSettingsStr);
// Store original state before modifying
localStorage.setItem('user_settings_backup', userSettingsStr);
console.log('Backing up user_settings');
// Disable all extensions
userSettings.extensions = userSettings.extensions.map((ext) => ({
...ext,
enabled: false,
}));
localStorage.setItem('user_settings', JSON.stringify(userSettings));
console.log('Disabled all stored extensions');
window.electron.emit('settings-updated');
} catch (error) {
console.error('Error disabling stored extensions:', error);
}
};
// Function to restore original extension states for new non-recipe windows
const restoreOriginalExtensionStates = () => {
const backupStr = localStorage.getItem('user_settings_backup');
if (backupStr) {
localStorage.setItem('user_settings', backupStr);
console.log('Restored original extension states');
}
};
const updateUserSettingsWithConfig = (extensions: FullExtensionConfig[]) => {
try {
const userSettingsStr = localStorage.getItem('user_settings');
const userSettings = userSettingsStr ? JSON.parse(userSettingsStr) : { extensions: [] };
// For each extension in the passed in config
extensions.forEach((newExtension) => {
// Find if this extension already exists
const existingIndex = userSettings.extensions.findIndex(
(ext) => ext.id === newExtension.id
);
if (existingIndex !== -1) {
// Extension exists - just set its enabled to true
userSettings.extensions[existingIndex].enabled = true;
} else {
// Extension is new - add it to the array
userSettings.extensions.push({
...newExtension,
enabled: true,
});
}
});
localStorage.setItem('user_settings', JSON.stringify(userSettings));
console.log('Updated user settings with new/enabled extensions:', userSettings.extensions);
// Notify any listeners (like the settings page) that settings have changed
window.electron.emit('settings-updated');
} catch (error) {
console.error('Error updating user settings:', error);
}
};
const enableRecipeConfigExtensions = async (extensions: FullExtensionConfig[]) => {
if (!extensions?.length) {
console.log('No extensions to enable from bot config');
return;
}
console.log(`Enabling ${extensions.length} extensions from bot config:`, extensions);
disableAllStoredExtensions();
// Wait for initial server readiness
await new Promise((resolve) => setTimeout(resolve, 2000));
for (const extension of extensions) {
try {
console.log(`Enabling extension: ${extension.name}`);
const extensionConfig = {
...extension,
enabled: true,
};
// Try to add the extension
const response = await addExtensionDirect(extensionConfig, false);
if (!response.ok) {
console.error(
`Failed to enable extension ${extension.name}: Server returned ${response.status}`
);
// If it's a 428, retry once
if (response.status === 428) {
console.log('Server not ready, waiting and will retry...');
await new Promise((resolve) => setTimeout(resolve, 2000));
try {
await addExtensionDirect(extensionConfig, true);
console.log(`Successfully enabled extension ${extension.name} on retry`);
} catch (retryError) {
console.error(`Failed to enable extension ${extension.name} on retry:`, retryError);
}
}
continue;
}
updateUserSettingsWithConfig(extensions);
console.log(`Successfully enabled extension: ${extension.name}`);
} catch (error) {
console.error(`Failed to enable extension ${extension.name}:`, error);
}
}
console.log('Finished enabling bot config extensions');
};
const _enableRecipeConfigExtensionsV2 = useCallback(
async (extensions: FullExtensionConfig[]) => {
if (!extensions?.length) {
console.log('No extensions to enable from bot config');
return;
}
try {
await disableAllExtensions();
console.log('Disabled all existing extensions');
for (const extension of extensions) {
try {
console.log(`Enabling extension: ${extension.name}`);
await addExtension(extension.name, extension, true);
} catch (error) {
console.error(`Failed to enable extension ${extension.name}:`, error);
}
}
} catch (error) {
console.error('Failed to enable bot extensions');
}
console.log('Finished enabling bot config extensions');
},
[disableAllExtensions, addExtension]
);
// settings v2 initialization
useEffect(() => {
if (!settingsV2Enabled) {
return;
}
// Guard against multiple initialization attempts
if (initAttemptedRef.current) {
console.log('Initialization already attempted, skipping...');
@@ -326,12 +166,6 @@ export default function App() {
return;
}
// Handle bot config extensions first
// if (recipeConfig?.extensions?.length > 0 && viewType != 'recipeEditor') {
// console.log('Found extensions in bot config:', recipeConfig.extensions);
// await enableRecipeConfigExtensionsV2(recipeConfig.extensions);
// }
const config = window.electron.getConfig();
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
@@ -622,11 +456,7 @@ export default function App() {
console.log(`Confirming installation of extension from: ${pendingLink}`);
setModalVisible(false); // Dismiss modal immediately
try {
if (settingsV2Enabled) {
await addExtensionFromDeepLinkV2(pendingLink, addExtension, setView);
} else {
await addExtensionFromDeepLink(pendingLink, setView);
}
await addExtensionFromDeepLinkV2(pendingLink, addExtension, setView);
console.log('Extension installation successful');
} catch (error) {
console.error('Failed to add extension:', error);
@@ -648,112 +478,6 @@ export default function App() {
setPendingLink(null);
};
// TODO: remove -- careful removal of these and the useEffect below breaks
// reloading to chat view using stored provider
const { switchModel } = useModel(); // TODO: remove
const { addRecentModel } = useRecentModels(); // TODO: remove
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const viewType = urlParams.get('view');
const recipeConfig = window.appConfig.get('recipeConfig');
if (settingsV2Enabled) {
return;
}
console.log(`Initializing app with settings v1`);
// Handle bot config extensions first
if (recipeConfig?.extensions?.length > 0 && viewType != 'recipeEditor') {
console.log('Found extensions in bot config:', recipeConfig.extensions);
enableRecipeConfigExtensions(recipeConfig.extensions);
}
// If we have a specific view type in the URL, use that and skip provider detection
if (viewType) {
if (viewType === 'recipeEditor' && recipeConfig) {
console.log('Setting view to recipeEditor with config:', recipeConfig);
setView('recipeEditor', { config: recipeConfig });
} else {
setView(viewType as View);
}
return;
}
// if not in any of the states above (in a regular chat)
if (!recipeConfig) {
restoreOriginalExtensionStates();
}
console.log(`Initializing app with settings v1`);
// Attempt to detect config for a stored provider
const detectStoredProvider = () => {
try {
const config = window.electron.getConfig();
console.log('Loaded config:', JSON.stringify(config));
const storedProvider = getStoredProvider(config);
console.log('Stored provider:', storedProvider);
if (storedProvider) {
setView('chat');
} else {
setView('welcome');
}
} catch (error) {
console.error('DETECTION ERROR:', error);
setFatalError(`Config detection error: ${error.message || 'Unknown error'}`);
}
};
// Initialize system if we have a stored provider
const setupStoredProvider = async () => {
try {
const config = window.electron.getConfig();
if (config.GOOSE_PROVIDER && config.GOOSE_MODEL) {
console.log('using GOOSE_PROVIDER and GOOSE_MODEL from config');
await initializeSystem(config.GOOSE_PROVIDER, config.GOOSE_MODEL);
return;
}
const storedProvider = getStoredProvider(config);
const storedModel = getStoredModel();
if (storedProvider) {
try {
await initializeSystem(storedProvider, storedModel);
console.log('Setup using locally stored provider:', storedProvider);
console.log('Setup using locally stored model:', storedModel);
if (!storedModel) {
const modelName = getDefaultModel(storedProvider.toLowerCase());
const model = createSelectedModel(storedProvider.toLowerCase(), modelName);
switchModel(model);
addRecentModel(model);
}
} catch (error) {
console.error('Failed to initialize with stored provider:', error);
setFatalError(`Initialization failed: ${error.message || 'Unknown error'}`);
}
}
} catch (error) {
console.error('SETUP ERROR:', error);
setFatalError(`Setup error: ${error.message || 'Unknown error'}`);
}
};
// Execute the functions with better error handling
detectStoredProvider();
setupStoredProvider().catch((error) => {
console.error('ASYNC SETUP ERROR:', error);
setFatalError(`Async setup error: ${error.message || 'Unknown error'}`);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (fatalError) {
return <ErrorUI error={new Error(fatalError)} />;
}
@@ -796,34 +520,18 @@ export default function App() {
<div className="titlebar-drag-region" />
<div>
{view === 'loading' && <SuspenseLoader />}
{view === 'welcome' &&
(settingsV2Enabled ? (
<ProviderSettings onClose={() => setView('chat')} isOnboarding={true} />
) : (
<WelcomeView
onSubmit={() => {
setView('chat');
}}
/>
))}
{view === 'settings' &&
(settingsV2Enabled ? (
<SettingsViewV2
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
) : (
<SettingsView
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
))}
{view === 'welcome' && (
<ProviderSettings onClose={() => setView('chat')} isOnboarding={true} />
)}
{view === 'settings' && (
<SettingsViewV2
onClose={() => {
setView('chat');
}}
setView={setView}
viewOptions={viewOptions as SettingsViewOptions}
/>
)}
{view === 'moreModels' && (
<MoreModelsView
onClose={() => {