Skip to content

Commit

Permalink
[MLC-29] app: Introduce redux, add loading state for indexing, add au…
Browse files Browse the repository at this point in the history
…to focus
  • Loading branch information
ParkerSm1th committed Feb 29, 2024
1 parent 9334814 commit e0c2887
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 13 deletions.
5 changes: 4 additions & 1 deletion app/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import StoreProvider from '../AppProvider';
import './globals.css';

export default function RootLayout({
Expand All @@ -15,7 +16,9 @@ export default function RootLayout({
userSelect: 'none',
}}
>
{children}
<StoreProvider>
{children}
</StoreProvider>
</body>
</html>
);
Expand Down
12 changes: 12 additions & 0 deletions app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@ import React, {
import Chat from '../components/chat/Chat';
import SelectDirectory from '../components/options/SelectDirectory';
import SelectModel from '../components/options/SelectModel';
import {
useAppDispatch,
} from '../lib/hooks';
import {
startDirectoryIndexing,
stopDirectoryIndexing,
} from '../lib/store';

export default function Home() {
const [selectedDirectory, setSelectedDirectory] = useState<string | null>(null);
const [selectedModel, setSelectedModel] = useState<string | null>(null);

const dispatch = useAppDispatch();

function handleOpen() {
if (typeof window !== 'undefined') {
window.electronAPI.selectDirectory();
Expand All @@ -22,6 +31,7 @@ export default function Home() {
window.electronAPI.onSelectDirectory(async (customData) => {
setSelectedDirectory(customData[0]);
try {
dispatch(startDirectoryIndexing());
await fetch('http://localhost:8080/api/index', {
method: 'POST',
headers: {
Expand All @@ -31,10 +41,12 @@ export default function Home() {
directory: customData[0],
}),
});
dispatch(stopDirectoryIndexing());
// TODO: spinner while indexing
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error sending message: ', error);
dispatch(stopDirectoryIndexing());
}
});
}, []);
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ const Chat = () => {
</div>
<div
className={cn(
'flex-grow min-w-full rounded-sm border-0 flex h-0',
'flex-grow min-w-full border-0 flex h-0',
{
'border h-[400px]': chatHistory.length > 0,
'h-[400px]': chatHistory.length > 0,
},
)}
>
Expand Down
23 changes: 22 additions & 1 deletion app/src/components/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, {
useEffect,
useRef,
useState,
} from 'react';
import {
useAppSelector,
} from '../../lib/hooks';
import {
Input,
} from '../ui/input';
Expand All @@ -23,6 +27,22 @@ const ChatInput = ({
sendMessage(message);
};

// detect website focus and focus the input
const handleFocus = () => {
if (document.activeElement !== inputRef.current) {
inputRef.current?.focus();
}
};

useEffect(() => {
window.addEventListener('focus', handleFocus);
return () => {
window.removeEventListener('focus', handleFocus);
};
}, []);

const isDirectoryIndexing = useAppSelector((state) => state.isDirectoryIndexing);

return (
<div
className='w-full py-2'
Expand All @@ -40,9 +60,10 @@ const ChatInput = ({
<Input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder='Enter prompt here'
placeholder={isDirectoryIndexing ? 'Indexing your files..' : 'Enter prompt here'}
onKeyDown={handleSend}
ref={inputRef}
disabled={isDirectoryIndexing}
className={'text-xl border-0 focus-visible:outline-transparent focus-visible:ring-0 focus-visible:shadow-0 w-full shadow-0'}
style={{
// @ts-expect-error -- WebkitAppRegion is a valid property
Expand Down
55 changes: 48 additions & 7 deletions app/src/components/options/SelectDirectory.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
import React from 'react';
import {
faCheckCircle,
faCircleNotch,
} from '@fortawesome/free-solid-svg-icons';
import {
FontAwesomeIcon,
} from '@fortawesome/react-fontawesome';
import React, {
useEffect,
} from 'react';
import {
useAppSelector,
usePrevious,
} from '../../lib/hooks';
import {
cn,
} from '../../lib/utils';
import {
Button,
} from '../ui/button';
Expand All @@ -13,14 +29,39 @@ const SelectDirectory = ({
const shortenedDirectory = selectedDirectory
? `/${selectedDirectory.split('/')[1]}/../${selectedDirectory.split('/').pop()}`
: 'Select Directory';
const [isCheckShowing, setIsCheckShowing] = React.useState(false);

const isDirectoryIndexing = useAppSelector((state) => state.isDirectoryIndexing);

const oldLoadingState = usePrevious(isDirectoryIndexing);

useEffect(() => {
if (oldLoadingState && !isDirectoryIndexing) {
setIsCheckShowing(true);
setTimeout(() => {
setIsCheckShowing(false);
}, 3000);
}
}, [isDirectoryIndexing]);

return (
<Button
className='bg-transparent text-neutral-800 dark:text-white text-sm font-normal shadow-none hover:bg-neutral-200 dark:hover:bg-neutral-700 transition-all border-0 border-zinc-600 w-fit rounded-md py-1 px-3 flex items-center cursor-pointer'
onClick={handleOpen}
>
{shortenedDirectory}
</Button>
<div className='flex'>
<Button
className={cn(
'bg-transparent text-neutral-800 dark:text-white text-sm font-normal shadow-none hover:bg-neutral-200 dark:hover:bg-neutral-700 transition-all border-0 border-zinc-600 w-fit rounded-md py-1 px-3 flex items-center cursor-pointer',
{
'hover:bg-transparent dark:hover:bg-transparent cursor-default': isDirectoryIndexing,
},
)}
onClick={isDirectoryIndexing ? undefined : handleOpen}
>
<div className='pr-1'>
{isDirectoryIndexing && <FontAwesomeIcon className='animate-spin' icon={faCircleNotch} />}
{isCheckShowing && <FontAwesomeIcon className='text-green-500' icon={faCheckCircle} />}
</div>
{shortenedDirectory}
</Button>
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion app/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-0 transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
Expand Down
12 changes: 12 additions & 0 deletions app/src/lib/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
useEffect,
useRef,
} from 'react';
import {
useDispatch,
useSelector,
Expand All @@ -16,3 +20,11 @@ import type {
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppStore: () => AppStore = useStore;

export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
25 changes: 24 additions & 1 deletion app/src/lib/store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
import {
configureStore,
createSlice,
} from '@reduxjs/toolkit';

const globalSlice = createSlice({
name: 'global',
initialState: {
isDirectoryIndexing: false,
},
reducers: {
startDirectoryIndexing: (state) => {
// eslint-disable-next-line no-param-reassign
state.isDirectoryIndexing = true;
},
stopDirectoryIndexing: (state) => {
// eslint-disable-next-line no-param-reassign
state.isDirectoryIndexing = false;
},
},
});

export const {
startDirectoryIndexing,
stopDirectoryIndexing,
} = globalSlice.actions;

export const makeStore = () =>
configureStore({
reducer: {},
reducer: globalSlice.reducer,
});

// Infer the type of makeStore
Expand Down

0 comments on commit e0c2887

Please sign in to comment.