Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
539 changes: 44 additions & 495 deletions ui/desktop/src/App.tsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ui/desktop/src/components/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
ChatContextManagerProvider,
useChatContextManager,
} from './context_management/ChatContextManager';
import { type View, ViewOptions } from '../App';
import { View, ViewOptions } from '../utils/navigationUtils';
import { MainPanelLayout } from './Layout/MainPanelLayout';
import ChatInput from './ChatInput';
import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect, useMemo } from 'react';
import { FolderKey, ScrollText } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import { Button } from './ui/button';
import type { View } from '../App';
import type { View } from '../utils/navigationUtils';
import Stop from './ui/Stop';
import { Attach, Send, Close, Microphone } from './icons';
import { ChatState } from '../types/chatState';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/GooseSidebar/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
SidebarSeparator,
} from '../ui/sidebar';
import { ChatSmart, Gear } from '../icons';
import { ViewOptions, View } from '../../App';
import { ViewOptions, View } from '../../utils/navigationUtils';
import { useChatContext } from '../../contexts/ChatContext';
import { DEFAULT_CHAT_TITLE } from '../../contexts/ChatContext';

Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/Layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import AppSidebar from '../GooseSidebar/AppSidebar';
import { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../utils/navigationUtils';
import { AppWindowMac, AppWindow } from 'lucide-react';
import { Button } from '../ui/button';
import { Sidebar, SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../ui/sidebar';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/conversation/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface SearchBarProps {
currentIndex: number;
};
/** Optional ref for the search input element */
inputRef?: React.RefObject<HTMLInputElement | null>;
inputRef?: React.RefObject<HTMLInputElement>;
/** Initial search term */
initialSearchTerm?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/extensions/ExtensionsView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../utils/navigationUtils';
import ExtensionsSection from '../settings/extensions/ExtensionsSection';
import { ExtensionConfig } from '../../api';
import { MainPanelLayout } from '../Layout/MainPanelLayout';
Expand Down
3 changes: 2 additions & 1 deletion ui/desktop/src/components/hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import { useState } from 'react';
import FlappyGoose from './FlappyGoose';
import { type View, ViewOptions } from '../App';

import { SessionInsights } from './sessions/SessionsInsights';
import ChatInput from './ChatInput';
import { generateSessionId } from '../sessions';
Expand All @@ -31,6 +31,7 @@ import 'react-toastify/dist/ReactToastify.css';

import { ChatType } from '../types/chat';
import { DEFAULT_CHAT_TITLE } from '../contexts/ChatContext';
import { View, ViewOptions } from '../utils/navigationUtils';

export default function Hub({
chat: _chat,
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/pair.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { type View, ViewOptions } from '../App';
import { View, ViewOptions } from '../utils/navigationUtils';
import BaseChat from './BaseChat';
import { useRecipeManager } from '../hooks/useRecipeManager';
import { useIsMobile } from '../hooks/use-mobile';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/sessions/SessionListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fetchSessions, updateSessionMetadata, type Session } from '../../sessio
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { ScrollArea } from '../ui/scroll-area';
import { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../utils/navigationUtils';
import { formatMessageTimestamp } from '../../utils/timeUtils';
import { SearchView } from '../conversation/SearchView';
import { SearchHighlighter } from '../../utils/searchHighlighter';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/sessions/SessionsView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
import { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../utils/navigationUtils';
import { fetchSessionDetails, type SessionDetails } from '../../sessions';
import SessionListView from './SessionListView';
import SessionHistoryView from './SessionHistoryView';
Expand Down
2 changes: 1 addition & 1 deletion ui/desktop/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ScrollArea } from '../ui/scroll-area';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
import type { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../utils/navigationUtils';
import ModelsSection from './models/ModelsSection';
import SessionSharingSection from './sessions/SessionSharingSection';
import AppSettingsSection from './app/AppSettingsSection';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback, useRef } from 'react';
import type { View } from '../../../App';
import { View } from '../../../utils/navigationUtils';
import ModelSettingsButtons from './subcomponents/ModelSettingsButtons';
import { useConfig } from '../../ConfigContext';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { useModelAndProvider } from '../../../ModelAndProviderContext';
import { AddModelModal } from '../subcomponents/AddModelModal';
import { LeadWorkerSettings } from '../subcomponents/LeadWorkerSettings';
import { View } from '../../../../App';
import { View } from '../../../../utils/navigationUtils';
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -22,12 +22,13 @@ import { toastSuccess, toastError } from '../../../../toasts';
import ViewRecipeModal from '../../../ViewRecipeModal';

interface ModelsBottomBarProps {
dropdownRef: React.RefObject<HTMLDivElement | null>;
dropdownRef: React.RefObject<HTMLDivElement>;
setView: (view: View) => void;
alerts: Alert[];
recipeConfig?: Recipe | null;
hasMessages?: boolean; // Add prop to know if there are messages to create a recipe from
}

export default function ModelsBottomBar({
dropdownRef,
setView,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Input } from '../../../ui/input';
import { Select } from '../../../ui/Select';
import { useConfig } from '../../../ConfigContext';
import { useModelAndProvider } from '../../../ModelAndProviderContext';
import type { View } from '../../../../App';
import type { View } from '../../../../utils/navigationUtils';
import Model, { getProviderMetadata } from '../modelInterface';
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Button } from '../../../ui/button';
import { AddModelModal } from './AddModelModal';
import type { View } from '../../../../App';
import type { View } from '../../../../utils/navigationUtils';
import { shouldShowPredefinedModels } from '../predefinedModelsUtils';

interface ConfigureModelButtonsProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '../../ui/button';
import { RefreshCw } from 'lucide-react';
import { useConfig } from '../../ConfigContext';
import { View, ViewOptions } from '../../../App';
import { View, ViewOptions } from '../../../utils/navigationUtils';

interface ResetProviderSectionProps {
setView: (view: View, viewOptions?: ViewOptions) => void;
Expand Down
3 changes: 2 additions & 1 deletion ui/desktop/src/hooks/useChat.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import { ChatType } from '../types/chat';
import { fetchSessionDetails, generateSessionId } from '../sessions';
import { View, ViewOptions } from '../App';

import { DEFAULT_CHAT_TITLE } from '../contexts/ChatContext';
import { View, ViewOptions } from '../utils/navigationUtils';

type UseChatArgs = {
setIsLoadingSession: (isLoading: boolean) => void;
Expand Down
3 changes: 2 additions & 1 deletion ui/desktop/src/sessionLinks.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { fetchSharedSessionDetails, SharedSessionDetails } from './sharedSessions';
import { type View } from './App';
import { View } from './utils/navigationUtils';

export interface SessionLinksViewOptions {
sessionDetails?: SharedSessionDetails | null;
error?: string;
shareToken?: string;
baseUrl?: string;

[key: string]: unknown;
}

Expand Down
215 changes: 215 additions & 0 deletions ui/desktop/src/utils/appInitialization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { ChatType } from '../types/chat';
import { Recipe } from '../recipe';
import { initializeSystem } from './providerUtils';
import { initializeCostDatabase } from './costDatabase';
import {
type ExtensionConfig,
type FixedExtensionEntry,
MalformedConfigError,
} from '../components/ConfigContext';
import { backupConfig, initConfig, readAllConfig, recoverConfig, validateConfig } from '../api';
import { COST_TRACKING_ENABLED } from '../updates';

interface InitializationDependencies {
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
setPairChat: (chat: ChatType | ((prev: ChatType) => ChatType)) => void;
provider: string;
model: string;
}

export const initializeApp = async ({
getExtensions,
addExtension,
setPairChat,
provider,
model,
}: InitializationDependencies) => {
console.log(`Initializing app`);

const urlParams = new URLSearchParams(window.location.search);
const viewType = urlParams.get('view');
const resumeSessionId = urlParams.get('resumeSessionId');
const recipeConfig = window.appConfig.get('recipe');

if (resumeSessionId) {
console.log('Session resume detected, letting useChat hook handle navigation');
await initializeForSessionResume({ getExtensions, addExtension, provider, model });
return;
}

if (recipeConfig && typeof recipeConfig === 'object') {
console.log('Recipe deeplink detected, initializing system for recipe');
await initializeForRecipe({
recipeConfig: recipeConfig as Recipe,
getExtensions,
addExtension,
setPairChat,
provider,
model,
});
return;
}

if (viewType) {
handleViewTypeDeepLink(viewType, recipeConfig);
return;
}

const costDbPromise = COST_TRACKING_ENABLED
? initializeCostDatabase().catch((error) => {
console.error('Failed to initialize cost database:', error);
})
: (() => {
console.log('Cost tracking disabled, skipping cost database initialization');
return Promise.resolve();
})();

await initConfig();

try {
await readAllConfig({ throwOnError: true });
} catch (error) {
console.warn('Initial config read failed, attempting recovery:', error);
await handleConfigRecovery();
}

if (provider && model) {
try {
const initPromises = [
initializeSystem(provider, model, {
getExtensions,
addExtension,
}),
];

if (COST_TRACKING_ENABLED) {
initPromises.push(costDbPromise);
}

await Promise.all(initPromises);
} catch (error) {
console.error('Error in system initialization:', error);
if (error instanceof MalformedConfigError) {
throw error;
}
window.location.hash = '#/';
window.history.replaceState({}, '', '#/');
}
} else {
window.location.hash = '#/';
window.history.replaceState({}, '', '#/');
}
};

const initializeForSessionResume = async ({
getExtensions,
addExtension,
provider,
model,
}: Pick<InitializationDependencies, 'getExtensions' | 'addExtension' | 'provider' | 'model'>) => {
await initConfig();
await readAllConfig({ throwOnError: true });

await initializeSystem(provider, model, {
getExtensions,
addExtension,
});
};

const initializeForRecipe = async ({
recipeConfig,
getExtensions,
addExtension,
setPairChat,
provider,
model,
}: Pick<
InitializationDependencies,
'getExtensions' | 'addExtension' | 'setPairChat' | 'provider' | 'model'
> & {
recipeConfig: Recipe;
}) => {
await initConfig();
await readAllConfig({ throwOnError: true });

await initializeSystem(provider, model, {
getExtensions,
addExtension,
});

setPairChat((prevChat) => ({
...prevChat,
recipeConfig: recipeConfig,
title: recipeConfig?.title || 'Recipe Chat',
messages: [],
messageHistoryIndex: 0,
}));

window.location.hash = '#/pair';
window.history.replaceState(
{
recipeConfig: recipeConfig,
resetChat: true,
},
'',
'#/pair'
);
};

const handleViewTypeDeepLink = (viewType: string, recipeConfig: unknown) => {
if (viewType === 'recipeEditor' && recipeConfig) {
window.location.hash = '#/recipe-editor';
window.history.replaceState({ config: recipeConfig }, '', '#/recipe-editor');
} else {
const routeMap: Record<string, string> = {
chat: '#/',
pair: '#/pair',
settings: '#/settings',
sessions: '#/sessions',
schedules: '#/schedules',
recipes: '#/recipes',
permission: '#/permission',
ConfigureProviders: '#/configure-providers',
sharedSession: '#/shared-session',
recipeEditor: '#/recipe-editor',
welcome: '#/welcome',
};

const route = routeMap[viewType];
if (route) {
window.location.hash = route;
window.history.replaceState({}, '', route);
}
}
};

const handleConfigRecovery = async () => {
const configVersion = localStorage.getItem('configVersion');
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;

if (shouldMigrateExtensions) {
console.log('Performing extension migration...');
try {
await backupConfig({ throwOnError: true });
await initConfig();
} catch (migrationError) {
console.error('Migration failed:', migrationError);
}
}

console.log('Attempting config recovery...');
try {
await validateConfig({ throwOnError: true });
await readAllConfig({ throwOnError: true });
} catch {
console.log('Config validation failed, attempting recovery...');
try {
await recoverConfig({ throwOnError: true });
await readAllConfig({ throwOnError: true });
} catch {
console.warn('Config recovery failed, reinitializing...');
await initConfig();
}
}
};
Loading
Loading