diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx index 15d0086..6a52445 100644 --- a/app/src/app/layout.tsx +++ b/app/src/app/layout.tsx @@ -1,5 +1,6 @@ 'use client'; +import StoreProvider from '../AppProvider'; import './globals.css'; export default function RootLayout({ @@ -15,7 +16,9 @@ export default function RootLayout({ userSelect: 'none', }} > - {children} + + {children} + ); diff --git a/app/src/app/page.tsx b/app/src/app/page.tsx index 7554a84..b3a87b3 100644 --- a/app/src/app/page.tsx +++ b/app/src/app/page.tsx @@ -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(null); const [selectedModel, setSelectedModel] = useState(null); + const dispatch = useAppDispatch(); + function handleOpen() { if (typeof window !== 'undefined') { window.electronAPI.selectDirectory(); @@ -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: { @@ -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()); } }); }, []); diff --git a/app/src/components/chat/Chat.tsx b/app/src/components/chat/Chat.tsx index b1062c0..fac59fa 100644 --- a/app/src/components/chat/Chat.tsx +++ b/app/src/components/chat/Chat.tsx @@ -56,9 +56,9 @@ const Chat = () => {
0, + 'h-[400px]': chatHistory.length > 0, }, )} > diff --git a/app/src/components/chat/ChatInput.tsx b/app/src/components/chat/ChatInput.tsx index 7525864..0d86dc1 100644 --- a/app/src/components/chat/ChatInput.tsx +++ b/app/src/components/chat/ChatInput.tsx @@ -1,7 +1,11 @@ import React, { + useEffect, useRef, useState, } from 'react'; +import { + useAppSelector, +} from '../../lib/hooks'; import { Input, } from '../ui/input'; @@ -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 (
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 diff --git a/app/src/components/options/SelectDirectory.tsx b/app/src/components/options/SelectDirectory.tsx index 69219d4..42b9231 100644 --- a/app/src/components/options/SelectDirectory.tsx +++ b/app/src/components/options/SelectDirectory.tsx @@ -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'; @@ -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 ( - +
+ +
); }; diff --git a/app/src/components/ui/input.tsx b/app/src/components/ui/input.tsx index 8becd60..9b29750 100644 --- a/app/src/components/ui/input.tsx +++ b/app/src/components/ui/input.tsx @@ -10,7 +10,7 @@ const Input = React.forwardRef( AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; export const useAppStore: () => AppStore = useStore; + +export function usePrevious(value: T): T | undefined { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +} diff --git a/app/src/lib/store.ts b/app/src/lib/store.ts index 4346d87..5d2ab56 100644 --- a/app/src/lib/store.ts +++ b/app/src/lib/store.ts @@ -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