feat: UI tweaks including dark mode fixes, FOUC flash fixes, suspenseful loading states, and 7 more (#2079)
This commit is contained in:
@@ -19,6 +19,7 @@ import { SharedSessionDetails } from './sharedSessions';
|
|||||||
|
|
||||||
import WelcomeView from './components/WelcomeView';
|
import WelcomeView from './components/WelcomeView';
|
||||||
import ChatView from './components/ChatView';
|
import ChatView from './components/ChatView';
|
||||||
|
import SuspenseLoader from './suspense-loader';
|
||||||
import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView';
|
import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView';
|
||||||
import SettingsViewV2 from './components/settings_v2/SettingsView';
|
import SettingsViewV2 from './components/settings_v2/SettingsView';
|
||||||
import MoreModelsView from './components/settings/models/MoreModelsView';
|
import MoreModelsView from './components/settings/models/MoreModelsView';
|
||||||
@@ -44,7 +45,8 @@ export type View =
|
|||||||
| 'ConfigureProviders'
|
| 'ConfigureProviders'
|
||||||
| 'settingsV2'
|
| 'settingsV2'
|
||||||
| 'sessions'
|
| 'sessions'
|
||||||
| 'sharedSession';
|
| 'sharedSession'
|
||||||
|
| 'loading';
|
||||||
|
|
||||||
export type ViewConfig = {
|
export type ViewConfig = {
|
||||||
view: View;
|
view: View;
|
||||||
@@ -62,7 +64,7 @@ export default function App() {
|
|||||||
const [pendingLink, setPendingLink] = useState<string | null>(null);
|
const [pendingLink, setPendingLink] = useState<string | null>(null);
|
||||||
const [modalMessage, setModalMessage] = useState<string>('');
|
const [modalMessage, setModalMessage] = useState<string>('');
|
||||||
const [{ view, viewOptions }, setInternalView] = useState<ViewConfig>({
|
const [{ view, viewOptions }, setInternalView] = useState<ViewConfig>({
|
||||||
view: 'welcome',
|
view: 'loading',
|
||||||
viewOptions: {},
|
viewOptions: {},
|
||||||
});
|
});
|
||||||
const { getExtensions, addExtension, read } = useConfig();
|
const { getExtensions, addExtension, read } = useConfig();
|
||||||
@@ -412,6 +414,7 @@ export default function App() {
|
|||||||
<div className="relative w-screen h-screen overflow-hidden bg-bgApp flex flex-col">
|
<div className="relative w-screen h-screen overflow-hidden bg-bgApp flex flex-col">
|
||||||
<div className="titlebar-drag-region" />
|
<div className="titlebar-drag-region" />
|
||||||
<div>
|
<div>
|
||||||
|
{view === 'loading' && <SuspenseLoader />}
|
||||||
{view === 'welcome' &&
|
{view === 'welcome' &&
|
||||||
(settingsV2Enabled ? (
|
(settingsV2Enabled ? (
|
||||||
<ProviderSettings onClose={() => setView('chat')} isOnboarding={true} />
|
<ProviderSettings onClose={() => setView('chat')} isOnboarding={true} />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function GooseLogo({ className = '', size = 'default', hover = tr
|
|||||||
className={`${className} ${sizes[size].frame} ${hover ? 'group/with-hover' : ''} relative overflow-hidden`}
|
className={`${className} ${sizes[size].frame} ${hover ? 'group/with-hover' : ''} relative overflow-hidden`}
|
||||||
>
|
>
|
||||||
<Rain
|
<Rain
|
||||||
className={`${sizes[size].rain} absolute left-0 bottom-0 ${hover ? 'opacity-0 opacity-0 group-hover/with-hover:opacity-100' : ''} transition-all duration-300 z-1`}
|
className={`${sizes[size].rain} absolute left-0 bottom-0 ${hover ? 'opacity-0 group-hover/with-hover:opacity-100' : ''} transition-all duration-300 z-1`}
|
||||||
/>
|
/>
|
||||||
<Goose className={`${sizes[size].goose} absolute left-0 bottom-0 z-2`} />
|
<Goose className={`${sizes[size].goose} absolute left-0 bottom-0 z-2`} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
|
||||||
import { getActiveProviders } from './utils';
|
import { getActiveProviders } from './utils';
|
||||||
|
import SuspenseLoader from '../../../suspense-loader';
|
||||||
|
|
||||||
// Create a context for active keys
|
// Create a context for active keys
|
||||||
const ActiveKeysContext = createContext<
|
const ActiveKeysContext = createContext<
|
||||||
@@ -33,7 +34,7 @@ export const ActiveKeysProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
// Provide active keys and ability to update them
|
// Provide active keys and ability to update them
|
||||||
return (
|
return (
|
||||||
<ActiveKeysContext.Provider value={{ activeKeys, setActiveKeys }}>
|
<ActiveKeysContext.Provider value={{ activeKeys, setActiveKeys }}>
|
||||||
{!isLoading ? children : <div>Loading...</div>} {/* Conditional rendering */}
|
{!isLoading ? children : <SuspenseLoader />}
|
||||||
</ActiveKeysContext.Provider>
|
</ActiveKeysContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,58 +49,60 @@ export function ConfigureApproveMode({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm">
|
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm z-10">
|
||||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[440px] bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden p-[16px] pt-[24px] pb-0">
|
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[440px] bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden p-0">
|
||||||
<div className="px-4 pb-0 space-y-6">
|
<div className="px-4 pb-0 space-y-6">
|
||||||
{/* Header */}
|
<div className="p-[16px] pt-[24px] pb-0">
|
||||||
<div className="flex">
|
{/* Header */}
|
||||||
<h2 className="text-2xl font-regular dark:text-white text-gray-900">
|
<div className="flex">
|
||||||
Configure Approve Mode
|
<h2 className="text-2xl font-regular dark:text-white text-gray-900">
|
||||||
</h2>
|
Configure Approve Mode
|
||||||
</div>
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-[24px]">
|
<div className="mt-[24px]">
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
Approve requests can either be given to all tool requests or determine which actions
|
Approve requests can either be given to all tool requests or determine which actions
|
||||||
may need integration
|
may need integration
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{approveModes.map((mode) => (
|
{approveModes.map((mode) => (
|
||||||
<ModeSelectionItem
|
<ModeSelectionItem
|
||||||
key={mode.key}
|
key={mode.key}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
showDescription={true}
|
showDescription={true}
|
||||||
currentMode={approveMode}
|
currentMode={approveMode}
|
||||||
isApproveModeConfigure={true}
|
isApproveModeConfigure={true}
|
||||||
handleModeChange={(newMode) => {
|
handleModeChange={(newMode) => {
|
||||||
setApproveMode(newMode);
|
setApproveMode(newMode);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="mt-[8px] ml-[-24px] mr-[-24px] pt-[16px]">
|
<div className="mt-[8px] ml-[-24px] mr-[-24px] pt-[16px]">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onClick={handleModeSubmit}
|
onClick={handleModeSubmit}
|
||||||
className="w-full h-[60px] rounded-none border-t dark:border-gray-600 text-lg hover:bg-gray-50 hover:dark:text-black dark:text-white dark:border-gray-600 font-regular"
|
className="w-full h-[60px] rounded-none border-t text-lg hover:bg-gray-50 dark:hover:text-black dark:text-white dark:border-gray-600 font-regular"
|
||||||
>
|
>
|
||||||
{isSubmitting ? 'Saving...' : 'Save Mode'}
|
{isSubmitting ? 'Saving...' : 'Save Mode'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="w-full h-[60px] rounded-none border-t dark:border-gray-600 text-gray-400 hover:bg-gray-50 dark:border-gray-600 text-lg font-regular"
|
className="w-full h-[60px] rounded-none border-t text-gray-400 dark:hover:text-black hover:bg-gray-50 dark:border-gray-600 text-lg font-regular"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function AddModelInline() {
|
|||||||
<Select
|
<Select
|
||||||
options={providerOptions}
|
options={providerOptions}
|
||||||
value={providerOptions.find((option) => option.value === selectedProvider) || null}
|
value={providerOptions.find((option) => option.value === selectedProvider) || null}
|
||||||
onChange={(option) => {
|
onChange={(option: { value: string | null }) => {
|
||||||
setSelectedProvider(option?.value || null);
|
setSelectedProvider(option?.value || null);
|
||||||
setModelName(''); // Clear model name when provider changes
|
setModelName(''); // Clear model name when provider changes
|
||||||
setFilteredModels([]);
|
setFilteredModels([]);
|
||||||
@@ -110,10 +110,10 @@ export function AddModelInline() {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-black text-white hover:bg-black/90"
|
className="bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
<Plus className="mr-2 h-4 w-4" /> Add Model
|
<Plus className="h-4 w-4" /> Add Model
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function SearchBar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative" ref={searchBarRef}>
|
<div className="relative" ref={searchBarRef}>
|
||||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-[0.8rem] h-4 w-4 text-textSubtle" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search models..."
|
placeholder="Search models..."
|
||||||
@@ -85,17 +85,17 @@ export function SearchBar() {
|
|||||||
}}
|
}}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={() => setShowResults(true)}
|
onFocus={() => setShowResults(true)}
|
||||||
className="w-full pl-12 py-2 bg-background border border-muted-foreground/20 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full pl-9 py-2 text-black dark:text-white bg-bgApp border border-borderSubtle rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
{showResults && search && (
|
{showResults && search && (
|
||||||
<div className="absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-muted-foreground/20 rounded-md shadow-lg">
|
<div className="absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-borderSubtle rounded-md shadow-lg">
|
||||||
{filteredModels.length > 0 ? (
|
{filteredModels.length > 0 ? (
|
||||||
filteredModels.map((model, index) => (
|
filteredModels.map((model, index) => (
|
||||||
<div
|
<div
|
||||||
key={model.id}
|
key={model.id}
|
||||||
ref={(el) => (resultsRef.current[index] = el)}
|
ref={(el) => (resultsRef.current[index] = el)}
|
||||||
className={`p-2 flex justify-between items-center hover:bg-muted/50 dark:hover:bg-gray-700 cursor-pointer ${
|
className={`p-2 flex justify-between items-center hover:bg-bgSubtle/50 dark:hover:bg-gray-700 cursor-pointer ${
|
||||||
model.id === currentModel?.id ? 'bg-muted/50 dark:bg-gray-700' : ''
|
model.id === currentModel?.id ? 'bg-bgSubtle/50 dark:bg-gray-700' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -112,7 +112,7 @@ export function SearchBar() {
|
|||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2 text-muted-foreground dark:text-gray-400">No models found</div>
|
<div className="p-2 text-textSubtle dark:text-gray-400">No models found</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function BaseProviderCard({
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-textSubtle mt-1.5 mb-3 leading-normal overflow-y-auto max-h-[54px] ">
|
<p className="text-xs text-textSubtle mt-1.5 mb-3 leading-normal scrollbar-thin overflow-y-auto max-h-[54px] ">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+15
-11
@@ -1,24 +1,28 @@
|
|||||||
import React from 'react';
|
import React, { Suspense, lazy } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import App from './App';
|
|
||||||
import { ModelProvider } from './components/settings/models/ModelContext';
|
import { ModelProvider } from './components/settings/models/ModelContext';
|
||||||
import { ConfigProvider } from './components/ConfigContext';
|
import { ConfigProvider } from './components/ConfigContext';
|
||||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||||
import { ActiveKeysProvider } from './components/settings/api_keys/ActiveKeysContext';
|
import { ActiveKeysProvider } from './components/settings/api_keys/ActiveKeysContext';
|
||||||
import { patchConsoleLogging } from './utils';
|
import { patchConsoleLogging } from './utils';
|
||||||
|
import SuspenseLoader from './suspense-loader';
|
||||||
|
|
||||||
patchConsoleLogging();
|
patchConsoleLogging();
|
||||||
|
|
||||||
|
const App = lazy(() => import('./App'));
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ConfigProvider>
|
<Suspense fallback={SuspenseLoader()}>
|
||||||
<ModelProvider>
|
<ConfigProvider>
|
||||||
<ActiveKeysProvider>
|
<ModelProvider>
|
||||||
<ErrorBoundary>
|
<ActiveKeysProvider>
|
||||||
<App />
|
<ErrorBoundary>
|
||||||
</ErrorBoundary>
|
<App />
|
||||||
</ActiveKeysProvider>
|
</ErrorBoundary>
|
||||||
</ModelProvider>
|
</ActiveKeysProvider>
|
||||||
</ConfigProvider>
|
</ModelProvider>
|
||||||
|
</ConfigProvider>
|
||||||
|
</Suspense>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -240,6 +240,10 @@
|
|||||||
background: var(--dark-grey-60);
|
background: var(--dark-grey-60);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background-app);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
@@ -296,4 +300,8 @@
|
|||||||
.user-message p {
|
.user-message p {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrollbar-thin {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import GooseLogo from './components/GooseLogo';
|
||||||
|
|
||||||
|
export default function SuspenseLoader() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 items-center justify-center w-screen h-screen overflow-hidden bg-bgApp text-textProminent">
|
||||||
|
<GooseLogo />
|
||||||
|
<span className="text-lg">Loading...</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user