From 4d1a76ebfc14373f5bc97e76d8b874e1010006b3 Mon Sep 17 00:00:00 2001 From: Parker Smith Date: Wed, 28 Feb 2024 20:31:50 -0700 Subject: [PATCH 1/3] [MLC-29] app: Add in font-awesome & rxjs --- app/package-lock.json | 114 ++++++++++++++++++++++++++++++++++++++++++ app/package.json | 6 +++ 2 files changed, 120 insertions(+) diff --git a/app/package-lock.json b/app/package-lock.json index b0fcc76..ade90fe 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,6 +8,11 @@ "name": "electron-app", "version": "0.1.0", "dependencies": { + "@fortawesome/fontawesome-free": "^6.5.1", + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-regular-svg-icons": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", @@ -32,6 +37,7 @@ "react-dom": "18.2.0", "react-redux": "^9.1.0", "react-resizable-panels": "^2.0.11", + "rxjs": "^7.8.1", "tailwind-merge": "^2.2.1", "tailwindcss": "^3.3.3", "tailwindcss-animate": "^1.0.7", @@ -524,6 +530,72 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz", + "integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -9024,6 +9096,48 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, + "@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==" + }, + "@fortawesome/fontawesome-free": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz", + "integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", diff --git a/app/package.json b/app/package.json index a452df7..4cb2c19 100644 --- a/app/package.json +++ b/app/package.json @@ -23,6 +23,11 @@ "lint": "npx eslint --max-warnings 0 --ext=.ts ." }, "dependencies": { + "@fortawesome/fontawesome-free": "^6.5.1", + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-regular-svg-icons": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", @@ -47,6 +52,7 @@ "react-dom": "18.2.0", "react-redux": "^9.1.0", "react-resizable-panels": "^2.0.11", + "rxjs": "^7.8.1", "tailwind-merge": "^2.2.1", "tailwindcss": "^3.3.3", "tailwindcss-animate": "^1.0.7", From 9334814a93eb8d2e0342f02eee64e26a35d4b8ea Mon Sep 17 00:00:00 2001 From: Parker Smith Date: Wed, 28 Feb 2024 20:32:06 -0700 Subject: [PATCH 2/3] [MLC-29] app: Center the window on resize --- app/main/main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/main/main.ts b/app/main/main.ts index 6313407..32cd985 100644 --- a/app/main/main.ts +++ b/app/main/main.ts @@ -250,6 +250,7 @@ app.whenReady().then(() => { win.setBounds({ height: arg.height, }); + win.center(); }); createSplashScreen(); From e0c2887b2f459543289977e9499687fb1fa33496 Mon Sep 17 00:00:00 2001 From: Parker Smith Date: Wed, 28 Feb 2024 20:32:49 -0700 Subject: [PATCH 3/3] [MLC-29] app: Introduce redux, add loading state for indexing, add auto focus --- app/src/app/layout.tsx | 5 +- app/src/app/page.tsx | 12 ++++ app/src/components/chat/Chat.tsx | 4 +- app/src/components/chat/ChatInput.tsx | 23 +++++++- .../components/options/SelectDirectory.tsx | 55 ++++++++++++++++--- app/src/components/ui/input.tsx | 2 +- app/src/lib/hooks.ts | 12 ++++ app/src/lib/store.ts | 25 ++++++++- 8 files changed, 125 insertions(+), 13 deletions(-) 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