From 4e27eb8129bfe43a44af040152e183a3055c743f Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Thu, 4 Jan 2024 00:07:17 -0500 Subject: [PATCH 01/17] back/forward as hook + ctrl arrow keybinds --- src/App.tsx | 14 ++++++++++++++ src/components/MobileBar/MobileBar.tsx | 14 +++++++------- src/hooks/useGoBack.ts | 17 +++++++++++++++++ src/hooks/useGoForward.ts | 16 ++++++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useGoBack.ts create mode 100644 src/hooks/useGoForward.ts diff --git a/src/App.tsx b/src/App.tsx index 900b4a88..def5b062 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,8 @@ import Chat from '@components/Chat'; import Menu from '@components/Menu'; import useAddChat from '@hooks/useAddChat'; +import useGoBack from '@hooks/useGoBack'; +import useGoForward from '@hooks/useGoForward'; import useInitialiseNewChat from '@hooks/useInitialiseNewChat'; import { ChatInterface } from '@type/chat'; import { Theme } from '@type/theme'; @@ -22,6 +24,8 @@ function App() { const setHideSideMenu = useStore((state) => state.setHideSideMenu); const hideSideMenu = useStore((state) => state.hideSideMenu); const addChat = useAddChat(); + const goBack = useGoBack(); + const goForward = useGoForward(); const handleKeyDown = (e: React.KeyboardEvent) => { // put any general app-wide keybinds here @@ -34,6 +38,16 @@ function App() { e.preventDefault(); addChat(); } + + if (e.ctrlKey && e.key === 'ArrowLeft') { + e.preventDefault(); + goBack(); + } + + if (e.ctrlKey && e.key === 'ArrowRight') { + e.preventDefault(); + goForward(); + } }; if (isElectron()) { diff --git a/src/components/MobileBar/MobileBar.tsx b/src/components/MobileBar/MobileBar.tsx index 89056fa8..b2fb155e 100644 --- a/src/components/MobileBar/MobileBar.tsx +++ b/src/components/MobileBar/MobileBar.tsx @@ -10,6 +10,9 @@ import CloneChat from '@components/Chat/ChatContent/CloneChat'; import BackIcon from '@icon/BackIcon'; import ForwardIcon from '@icon/ForwardIcon'; +import useGoBack from '@hooks/useGoBack'; +import useGoForward from '@hooks/useGoForward'; + const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID || undefined; const MobileBar = () => { @@ -33,6 +36,8 @@ const MobileBar = () => { ); const addChat = useAddChat(); + const goBack = useGoBack(); + const goForward = useGoForward(); return (
@@ -55,10 +60,7 @@ const MobileBar = () => { - {isModalOpen && ( - -
- - -
-
- )} - - ); - } -); - -export default DownloadChat; diff --git a/src/components/Chat/ChatContent/Message/CodeBlock/CodeBlock.tsx b/src/components/Chat/ChatContent/Message/CodeBlock/CodeBlock.tsx index 51cb0aaa..c5db1275 100644 --- a/src/components/Chat/ChatContent/Message/CodeBlock/CodeBlock.tsx +++ b/src/components/Chat/ChatContent/Message/CodeBlock/CodeBlock.tsx @@ -1,7 +1,5 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; -import CopyIcon from '@icon/CopyIcon'; -import TickIcon from '@icon/TickIcon'; import CodeBar from './CodeBar'; const CodeBlock = ({ diff --git a/src/components/Chat/ChatContent/Message/RoleSelector.tsx b/src/components/Chat/ChatContent/Message/RoleSelector.tsx index 23b710b8..5ad13006 100644 --- a/src/components/Chat/ChatContent/Message/RoleSelector.tsx +++ b/src/components/Chat/ChatContent/Message/RoleSelector.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; diff --git a/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx b/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx index 60d6cbbb..a49f90d6 100644 --- a/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx +++ b/src/components/Chat/ChatContent/Message/View/Button/MarkdownModeButton.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import useStore from '@store/store'; diff --git a/src/components/Chat/ChatContent/Message/WhisperRecord.tsx b/src/components/Chat/ChatContent/Message/WhisperRecord.tsx index 5b77e953..17cf70a3 100644 --- a/src/components/Chat/ChatContent/Message/WhisperRecord.tsx +++ b/src/components/Chat/ChatContent/Message/WhisperRecord.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useWhisper } from '@chengsokdara/use-whisper'; import useStore from '@store/store'; import StopIcon from '@icon/StopIcon'; diff --git a/src/components/Chat/ChatContent/ModelConfigBar.tsx b/src/components/Chat/ChatContent/ModelConfigBar.tsx index 3de69c2c..ea1747c1 100644 --- a/src/components/Chat/ChatContent/ModelConfigBar.tsx +++ b/src/components/Chat/ChatContent/ModelConfigBar.tsx @@ -23,10 +23,6 @@ const ModelConfigBar = React.memo(() => { const setChats = useStore((state) => state.setChats); const currentChatIndex = useStore((state) => state.currentChatIndex); const [isModalOpen, setIsModalOpen] = useState(false); - const [_model, _setModel] = useState( - config?.model || 'gpt-3.5-turbo' - ); - const setConfig = (config: ConfigInterface) => { const updatedChats: ChatInterface[] = JSON.parse( JSON.stringify(useStore.getState().chats) diff --git a/src/components/Chat/ChatContent/ScrollToBottomButton.tsx b/src/components/Chat/ChatContent/ScrollToBottomButton.tsx deleted file mode 100644 index e82694aa..00000000 --- a/src/components/Chat/ChatContent/ScrollToBottomButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { useAtBottom, useScrollToBottom } from 'react-scroll-to-bottom'; - -import DownArrow from '@icon/DownArrow'; - -const ScrollToBottomButton = React.memo(() => { - const scrollToBottom = useScrollToBottom(); - const [atBottom] = useAtBottom(); - - return ( - - ); -}); - -export default ScrollToBottomButton; diff --git a/src/components/ChatConfigMenu/ChatConfigMenu.tsx b/src/components/ChatConfigMenu/ChatConfigMenu.tsx index e6638f60..cbe844d1 100644 --- a/src/components/ChatConfigMenu/ChatConfigMenu.tsx +++ b/src/components/ChatConfigMenu/ChatConfigMenu.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import useStore from '@store/store'; import { useTranslation } from 'react-i18next'; -import ChatIcon from '@icon/ChatIcon'; import PopupModal from '@components/PopupModal'; import { diff --git a/src/components/ConfigMenu/ConfigMenu.tsx b/src/components/ConfigMenu/ConfigMenu.tsx index 2dbb27cb..39f11429 100644 --- a/src/components/ConfigMenu/ConfigMenu.tsx +++ b/src/components/ConfigMenu/ConfigMenu.tsx @@ -1,9 +1,7 @@ -import React, { useEffect, useRef, useState } from 'react'; -import useStore from '@store/store'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import PopupModal from '@components/PopupModal'; import { ConfigInterface, ModelChoice } from '@type/chat'; -import { modelMaxToken } from '@constants/chat'; import { ModelSelect } from './ModelSelect'; import { FrequencyPenaltySlider, diff --git a/src/components/ConfigMenu/SettingsSliders.tsx b/src/components/ConfigMenu/SettingsSliders.tsx index 29a8e4be..723ff7b3 100644 --- a/src/components/ConfigMenu/SettingsSliders.tsx +++ b/src/components/ConfigMenu/SettingsSliders.tsx @@ -1,10 +1,7 @@ -import React, { useEffect, useRef, useState } from 'react'; -import useStore from '@store/store'; +import React, { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import PopupModal from '@components/PopupModal'; -import { ConfigInterface, ModelChoice } from '@type/chat'; +import { ModelChoice } from '@type/chat'; import { modelMaxToken } from '@constants/chat'; -import { ModelSelect } from './ModelSelect'; export const MaxTokenSlider = ({ _maxToken, diff --git a/src/components/GoogleSync/GoogleSyncButton.tsx b/src/components/GoogleSync/GoogleSyncButton.tsx index 3a1c43fe..4450fe71 100644 --- a/src/components/GoogleSync/GoogleSyncButton.tsx +++ b/src/components/GoogleSync/GoogleSyncButton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { useGoogleLogin, googleLogout } from '@react-oauth/google'; diff --git a/src/components/LanguageSelector/LanguageSelector.tsx b/src/components/LanguageSelector/LanguageSelector.tsx index 90169776..6a55b4bf 100644 --- a/src/components/LanguageSelector/LanguageSelector.tsx +++ b/src/components/LanguageSelector/LanguageSelector.tsx @@ -1,9 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import DownChevronArrow from '@icon/DownChevronArrow'; import { languageCodeToName, selectableLanguages } from '@constants/language'; -import FileTextIcon from '@icon/FileTextIcon'; import LanguageIcon from '@icon/LanguageIcon'; import useHideOnOutsideClick from '@hooks/useHideOnOutsideClick'; diff --git a/src/components/Menu/ChatHistory.tsx b/src/components/Menu/ChatHistory.tsx index e92a3688..90971a91 100644 --- a/src/components/Menu/ChatHistory.tsx +++ b/src/components/Menu/ChatHistory.tsx @@ -108,7 +108,7 @@ const ChatHistory = React.memo( onChange={(e) => { _setTitle(e.target.value); }} - onBlur={(e) => { + onBlur={() => { setIsEdit(false); }} onKeyDown={handleKeyDown} diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index f945e1e9..bf162c76 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -8,8 +8,6 @@ import ChatHistoryList from './ChatHistoryList'; import MenuOptions from './MenuOptions'; import CrossIcon2 from '@icon/CrossIcon2'; -import DownArrow from '@icon/DownArrow'; -import MenuIcon from '@icon/MenuIcon'; const Menu = () => { const hideSideMenu = useStore((state) => state.hideSideMenu); diff --git a/src/components/Menu/MenuOptions/MenuOptions.tsx b/src/components/Menu/MenuOptions/MenuOptions.tsx index 6150c625..78d8b657 100644 --- a/src/components/Menu/MenuOptions/MenuOptions.tsx +++ b/src/components/Menu/MenuOptions/MenuOptions.tsx @@ -1,19 +1,12 @@ import React from 'react'; -import useStore from '@store/store'; - import SettingsMenu from '@components/SettingsMenu'; -import CollapseOptions from './CollapseOptions'; import { GoogleSync } from '@components/GoogleSync/GoogleSync'; -import { TotalTokenCostDisplay } from '@components/SettingsMenu/TotalTokenCost'; import isElectron from '@utils/electron'; -import GithubLink from './GithubLink'; import DesktopLink from './DesktopLink'; const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID || undefined; const MenuOptions = () => { - const hideMenuOptions = useStore((state) => state.hideMenuOptions); - const countTotalTokens = useStore((state) => state.countTotalTokens); return ( <>
diff --git a/src/components/Menu/NewFolder.tsx b/src/components/Menu/NewFolder.tsx index 94448a64..cd8bdc00 100644 --- a/src/components/Menu/NewFolder.tsx +++ b/src/components/Menu/NewFolder.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; import { v4 as uuidv4 } from 'uuid'; import useStore from '@store/store'; @@ -7,7 +6,6 @@ import NewFolderIcon from '@icon/NewFolderIcon'; import { Folder, FolderCollection } from '@type/chat'; const NewFolder = () => { - const { t } = useTranslation(); const generating = useStore((state) => state.generating); const setFolders = useStore((state) => state.setFolders); @@ -43,7 +41,7 @@ const NewFolder = () => { return ( { const setHideSideMenu = useStore((state) => state.setHideSideMenu); const hideSideMenu = useStore((state) => state.hideSideMenu); const currentChatIndex = useStore((state) => state.currentChatIndex); - const setCurrentChatIndex = useStore((state) => state.setCurrentChatIndex); const chats = useStore((state) => state.chats); const cloudSync = useGStore((state) => state.cloudSync); diff --git a/src/components/PopupModal/PopupModal.tsx b/src/components/PopupModal/PopupModal.tsx index 2ce3e594..d79f8190 100644 --- a/src/components/PopupModal/PopupModal.tsx +++ b/src/components/PopupModal/PopupModal.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { useTranslation } from 'react-i18next'; -import HeartIcon from '@icon/HeartIcon'; import CrossIcon2 from '@icon/CrossIcon2'; diff --git a/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx b/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx index 32d250f1..d83ce6f0 100644 --- a/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx +++ b/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx @@ -9,7 +9,6 @@ import CrossIcon from '@icon/CrossIcon'; import { v4 as uuidv4 } from 'uuid'; import ImportPrompt from './ImportPrompt'; import ExportPrompt from './ExportPrompt'; -import EditIcon from '@icon/EditIcon'; const PromptLibraryMenu = () => { const { t } = useTranslation(); diff --git a/src/components/SettingsMenu/CloseToTrayToggle.tsx b/src/components/SettingsMenu/CloseToTrayToggle.tsx index 14d319e7..3bb84e33 100644 --- a/src/components/SettingsMenu/CloseToTrayToggle.tsx +++ b/src/components/SettingsMenu/CloseToTrayToggle.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; import Toggle from '@components/Toggle'; -import { stringify } from 'uuid'; const CloseToTrayToggle = () => { const { t } = useTranslation(); diff --git a/src/components/SettingsMenu/SettingsMenu.tsx b/src/components/SettingsMenu/SettingsMenu.tsx index 79232fd4..931c1353 100644 --- a/src/components/SettingsMenu/SettingsMenu.tsx +++ b/src/components/SettingsMenu/SettingsMenu.tsx @@ -1,15 +1,12 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; -import useCloudAuthStore from '@store/cloud-auth-store'; import isElectron from '@utils/electron'; import PopupModal from '@components/PopupModal'; import SettingIcon from '@icon/SettingIcon'; -import ThemeSwitcher from '@components/Menu/MenuOptions/ThemeSwitcher'; import LanguageSelector from '@components/LanguageSelector'; import AutoTitleToggle from './AutoTitleToggle'; import CloseToTrayToggle from './CloseToTrayToggle'; -//import AdvancedModeToggle from './AdvencedModeToggle'; import InlineLatexToggle from './InlineLatexToggle'; import HeartIcon from '@icon/HeartIcon'; import PromptLibraryMenu from '@components/PromptLibraryMenu'; diff --git a/src/components/SettingsMenu/TotalTokenCost.tsx b/src/components/SettingsMenu/TotalTokenCost.tsx index 21486f86..0a4c5ad1 100644 --- a/src/components/SettingsMenu/TotalTokenCost.tsx +++ b/src/components/SettingsMenu/TotalTokenCost.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; @@ -25,8 +24,6 @@ const tokenCostToCost = ( }; const TotalTokenCost = () => { - const { t } = useTranslation(['main', 'model']); - const totalTokenUsed = useStore((state) => state.totalTokenUsed); const setTotalTokenUsed = useStore((state) => state.setTotalTokenUsed); const countTotalTokens = useStore((state) => state.countTotalTokens); @@ -110,7 +107,6 @@ export const TotalTokenCostToggle = () => { }; export const TotalTokenCostDisplay = () => { - const { t } = useTranslation(); const totalTokenUsed = useStore((state) => state.totalTokenUsed); const [totalCost, setTotalCost] = useState(0); diff --git a/src/constants/chat.ts b/src/constants/chat.ts index 7026c0d1..18203ad4 100644 --- a/src/constants/chat.ts +++ b/src/constants/chat.ts @@ -2,14 +2,6 @@ import { v4 as uuidv4 } from 'uuid'; import { ChatInterface, ConfigInterface, ModelChoice } from '@type/chat'; import useStore from '@store/store'; -const date = new Date(); -const dateString = - date.getFullYear() + - '-' + - ('0' + (date.getMonth() + 1)).slice(-2) + - '-' + - ('0' + date.getDate()).slice(-2); - // default system message obtained using the following method: https://twitter.com/DeminDimin/status/1619935545144279040 export const _defaultSystemMessage = import.meta.env.VITE_DEFAULT_SYSTEM_MESSAGE ?? diff --git a/src/hooks/useAddChat.ts b/src/hooks/useAddChat.ts index 70a43fb4..154ae809 100644 --- a/src/hooks/useAddChat.ts +++ b/src/hooks/useAddChat.ts @@ -1,4 +1,3 @@ -import React from 'react'; import useStore from '@store/store'; import { generateDefaultChat } from '@constants/chat'; import { ChatInterface } from '@type/chat'; diff --git a/src/hooks/useInitialiseNewChat.ts b/src/hooks/useInitialiseNewChat.ts index 99975440..10111f0a 100644 --- a/src/hooks/useInitialiseNewChat.ts +++ b/src/hooks/useInitialiseNewChat.ts @@ -1,6 +1,4 @@ -import React from 'react'; import useStore from '@store/store'; -import { MessageInterface } from '@type/chat'; import { generateDefaultChat } from '@constants/chat'; const useInitialiseNewChat = () => { diff --git a/src/hooks/useSaveToLocalStorage.ts b/src/hooks/useSaveToLocalStorage.ts index 8c0da3ff..033d7442 100644 --- a/src/hooks/useSaveToLocalStorage.ts +++ b/src/hooks/useSaveToLocalStorage.ts @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import useStore from '@store/store'; const useSaveToLocalStorage = () => { diff --git a/src/hooks/useSubmit.ts b/src/hooks/useSubmit.ts index c0059668..c15eb47d 100644 --- a/src/hooks/useSubmit.ts +++ b/src/hooks/useSubmit.ts @@ -1,4 +1,3 @@ -import React from 'react'; import useStore from '@store/store'; import { useTranslation } from 'react-i18next'; import { ChatInterface, MessageInterface } from '@type/chat'; diff --git a/src/store/auth-slice.ts b/src/store/auth-slice.ts index 52088906..f09368b4 100644 --- a/src/store/auth-slice.ts +++ b/src/store/auth-slice.ts @@ -10,7 +10,7 @@ export interface AuthSlice { setFirstVisit: (firstVisit: boolean) => void; } -export const createAuthSlice: StoreSlice = (set, get) => ({ +export const createAuthSlice: StoreSlice = (set) => ({ apiKey: import.meta.env.VITE_OPENAI_API_KEY || undefined, apiEndpoint: defaultAPIEndpoint, firstVisit: true, diff --git a/src/store/chat-slice.ts b/src/store/chat-slice.ts index 37939428..8f53194e 100644 --- a/src/store/chat-slice.ts +++ b/src/store/chat-slice.ts @@ -17,7 +17,7 @@ export interface ChatSlice { setConfirmEditSubmission: (confirmEditSubmission: boolean) => void; } -export const createChatSlice: StoreSlice = (set, get) => ({ +export const createChatSlice: StoreSlice = (set) => ({ messages: [], currentChatIndex: -1, generating: false, diff --git a/src/store/cloud-auth-slice.ts b/src/store/cloud-auth-slice.ts index 08f452e8..aac75c65 100644 --- a/src/store/cloud-auth-slice.ts +++ b/src/store/cloud-auth-slice.ts @@ -14,7 +14,7 @@ export interface CloudAuthSlice { setSyncStatus: (syncStatus: SyncStatus) => void; } -export const createCloudAuthSlice: StoreSlice = (set, get) => ({ +export const createCloudAuthSlice: StoreSlice = (set) => ({ cloudSync: false, syncStatus: 'unauthenticated', setGoogleAccessToken: (googleAccessToken?: string) => { diff --git a/src/store/config-slice.ts b/src/store/config-slice.ts index 451b222e..ab297da7 100644 --- a/src/store/config-slice.ts +++ b/src/store/config-slice.ts @@ -36,7 +36,7 @@ export interface ConfigSlice { setTotalTokenUsed: (totalTokenUsed: TotalTokenUsed) => void; } -export const createConfigSlice: StoreSlice = (set, get) => ({ +export const createConfigSlice: StoreSlice = (set) => ({ openConfig: false, theme: 'dark', hideMenuOptions: false, diff --git a/src/store/input-slice.ts b/src/store/input-slice.ts index 6741910e..80c340b3 100644 --- a/src/store/input-slice.ts +++ b/src/store/input-slice.ts @@ -6,7 +6,7 @@ export interface InputSlice { setInputRole: (inputRole: Role) => void; } -export const createInputSlice: StoreSlice = (set, get) => ({ +export const createInputSlice: StoreSlice = (set) => ({ inputRole: 'user', setInputRole: (inputRole: Role) => { set((prev: InputSlice) => ({ diff --git a/src/store/prompt-slice.ts b/src/store/prompt-slice.ts index 50731561..6e5c291b 100644 --- a/src/store/prompt-slice.ts +++ b/src/store/prompt-slice.ts @@ -7,7 +7,7 @@ export interface PromptSlice { setPrompts: (commandPrompt: Prompt[]) => void; } -export const createPromptSlice: StoreSlice = (set, get) => ({ +export const createPromptSlice: StoreSlice = (set) => ({ prompts: defaultPrompts, setPrompts: (prompts: Prompt[]) => { set((prev: PromptSlice) => ({ diff --git a/src/store/storage/GoogleCloudStorage.ts b/src/store/storage/GoogleCloudStorage.ts index 48c1c0f0..6714f0dc 100644 --- a/src/store/storage/GoogleCloudStorage.ts +++ b/src/store/storage/GoogleCloudStorage.ts @@ -1,4 +1,4 @@ -import { PersistStorage, StorageValue, StateStorage } from 'zustand/middleware'; +import { PersistStorage, StorageValue } from 'zustand/middleware'; import useCloudAuthStore from '@store/cloud-auth-store'; import useStore from '@store/store'; import { @@ -21,7 +21,7 @@ const createGoogleCloudStorage = (): PersistStorage | undefined => { return; } const persistStorage: PersistStorage = { - getItem: async (name) => { + getItem: async () => { useCloudAuthStore.getState().setSyncStatus('syncing'); try { const accessToken = useCloudAuthStore.getState().googleAccessToken; @@ -39,7 +39,7 @@ const createGoogleCloudStorage = (): PersistStorage | undefined => { return null; } }, - setItem: async (name, newValue): Promise => { + setItem: async (newValue): Promise => { const accessToken = useCloudAuthStore.getState().googleAccessToken; const fileId = useCloudAuthStore.getState().fileId; if (!accessToken || !fileId) return; @@ -58,7 +58,7 @@ const createGoogleCloudStorage = (): PersistStorage | undefined => { } }, - removeItem: async (name): Promise => { + removeItem: async (): Promise => { const accessToken = useCloudAuthStore.getState().googleAccessToken; const fileId = useCloudAuthStore.getState().fileId; if (!accessToken || !fileId) return; diff --git a/src/store/toast-slice.ts b/src/store/toast-slice.ts index e3a9d807..7cb34b3f 100644 --- a/src/store/toast-slice.ts +++ b/src/store/toast-slice.ts @@ -10,7 +10,7 @@ export interface ToastSlice { setToastStatus: (toastStatus: ToastStatus) => void; } -export const createToastSlice: StoreSlice = (set, get) => ({ +export const createToastSlice: StoreSlice = (set) => ({ toastShow: false, toastMessage: '', toastStatus: 'success', diff --git a/src/types/chat.ts b/src/types/chat.ts index ca426e88..04c92d30 100644 --- a/src/types/chat.ts +++ b/src/types/chat.ts @@ -21,7 +21,7 @@ export interface ChatInterface { export interface ConfigInterface { model: ModelChoice; max_tokens: number; - max_context: number; + max_context?: number; temperature: number; presence_penalty: number; top_p: number; From 477a6728bdaee4a51829672d8a23af7433c59ff1 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 17:40:46 -0500 Subject: [PATCH 07/17] env.version gh action fix --- .github/workflows/publish.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 10b2f7af..527346f3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -97,8 +97,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-Linux/koala-client-${{VERSION}}-linux-x86_64.AppImage - asset_name: KoalaClient-${{VERSION}}-linux-x86_64.AppImage + asset_path: ./release-Linux/koala-client-${{env.VERSION}}-linux-x86_64.AppImage + asset_name: KoalaClient-${{env.VERSION}}-linux-x86_64.AppImage asset_content_type: application - name: Upload Release Asset - MacOS @@ -107,8 +107,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-macOS/koala-client-${{VERSION}}-mac-x64.dmg - asset_name: KoalaClient-${{VERSION}}-mac-x64.dmg + asset_path: ./release-macOS/koala-client-${{env.VERSION}}-mac-x64.dmg + asset_name: KoalaClient-${{env.VERSION}}-mac-x64.dmg asset_content_type: application - name: Upload Release Asset - Windows @@ -117,8 +117,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-Windows/koala-client-${{VERSION}}-win-x64.exe - asset_name: KoalaClient-${{VERSION}}-win-x64.exe + asset_path: ./release-Windows/koala-client-${{env.VERSION}}-win-x64.exe + asset_name: KoalaClient-${{env.VERSION}}-win-x64.exe asset_content_type: application - name: Zip Unpacked Release - Windows @@ -134,7 +134,7 @@ jobs: with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./release-Windows/win-unpacked.zip - asset_name: KoalaClient-${{VERSION}}-win-x64-portable.zip + asset_name: KoalaClient-${{env.VERSION}}-win-x64-portable.zip asset_content_type: zip - name: Zip Hash Info From 9098f5b18f2d1b98af29784cd44d6e6327e64a9a Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 17:47:17 -0500 Subject: [PATCH 08/17] eslint check action --- .github/workflows/eslint.yml | 38 ++++++++++++++++++++++++++++++++++ .github/workflows/prettier.yml | 3 +++ src/App.tsx | 2 ++ 3 files changed, 43 insertions(+) create mode 100644 .github/workflows/eslint.yml diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 00000000..a86e10e8 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,38 @@ +name: ESLint + +on: + push: + branches: ['main'] + pull_request: + # The branches below must be a subset of the branches above + branches: ['main'] + +jobs: + eslint: + name: Run eslint scanning + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install ESLint + run: | + npm install eslint@8.10.0 + npm install @microsoft/eslint-formatter-sarif@2.1.7 + + - name: Run ESLint + run: npx eslint . + --config .eslintrc.cjs + --format @microsoft/eslint-formatter-sarif + --output-file eslint-results.sarif + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: eslint-results.sarif + wait-for-processing: true diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 60e3573f..010ea96a 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -8,6 +8,9 @@ on: jobs: prettier: + permissions: + contents: write + actions: write runs-on: ubuntu-latest steps: diff --git a/src/App.tsx b/src/App.tsx index def5b062..f0c70dbf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,8 @@ function App() { const goBack = useGoBack(); const goForward = useGoForward(); + let test = 'eslint find this'; + const handleKeyDown = (e: React.KeyboardEvent) => { // put any general app-wide keybinds here if (e.ctrlKey && e.key === 'e') { From 72711fb7b9e04647cdfe0f78812a7e582853294f Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 20:18:39 -0500 Subject: [PATCH 09/17] eslint cleanup aftermath --- .eslintrc.cjs | 6 +++--- src/App.tsx | 2 -- src/components/ChatConfigMenu/ChatConfigMenu.tsx | 6 ++++-- src/components/ConfigMenu/ConfigMenu.tsx | 4 +++- src/components/SettingsMenu/TotalTokenCost.tsx | 3 +++ src/utils/import.ts | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e7e809ad..20d225aa 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -31,9 +31,9 @@ module.exports = { }, plugins: ['@typescript-eslint', 'react'], rules: { - '@typescript-eslint/no-this-alias': 'warn', - '@typescript-eslint/no-unused-vars': 'warn', - 'no-useless-escape': 'warn', + // '@typescript-eslint/no-this-alias': 'warn', + // '@typescript-eslint/no-unused-vars': 'warn', + // 'no-useless-escape': 'warn', 'react/display-name': 'off', }, }; diff --git a/src/App.tsx b/src/App.tsx index f0c70dbf..def5b062 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,8 +27,6 @@ function App() { const goBack = useGoBack(); const goForward = useGoForward(); - let test = 'eslint find this'; - const handleKeyDown = (e: React.KeyboardEvent) => { // put any general app-wide keybinds here if (e.ctrlKey && e.key === 'e') { diff --git a/src/components/ChatConfigMenu/ChatConfigMenu.tsx b/src/components/ChatConfigMenu/ChatConfigMenu.tsx index cbe844d1..ceab6547 100644 --- a/src/components/ChatConfigMenu/ChatConfigMenu.tsx +++ b/src/components/ChatConfigMenu/ChatConfigMenu.tsx @@ -50,7 +50,9 @@ const ChatConfigPopup = ({ ); const [_model, _setModel] = useState(config.model); const [_maxToken, _setMaxToken] = useState(config.max_tokens); - const [_maxContext, _setMaxContext] = useState(config.max_context); + const [_maxContext, _setMaxContext] = useState( + config.max_context ?? 0 + ); const [_temperature, _setTemperature] = useState(config.temperature); const [_topP, _setTopP] = useState(config.top_p); const [_presencePenalty, _setPresencePenalty] = useState( @@ -79,7 +81,7 @@ const ChatConfigPopup = ({ const handleReset = () => { _setModel(_defaultChatConfig.model); _setMaxToken(_defaultChatConfig.max_tokens); - _setMaxContext(_defaultChatConfig.max_context); + _setMaxContext(_defaultChatConfig.max_context ?? 0); _setTemperature(_defaultChatConfig.temperature); _setTopP(_defaultChatConfig.top_p); _setPresencePenalty(_defaultChatConfig.presence_penalty); diff --git a/src/components/ConfigMenu/ConfigMenu.tsx b/src/components/ConfigMenu/ConfigMenu.tsx index 39f11429..f71fe6fe 100644 --- a/src/components/ConfigMenu/ConfigMenu.tsx +++ b/src/components/ConfigMenu/ConfigMenu.tsx @@ -22,7 +22,9 @@ const ConfigMenu = ({ setConfig: (config: ConfigInterface) => void; }) => { const [_maxToken, _setMaxToken] = useState(config.max_tokens); - const [_maxContext, _setMaxContext] = useState(config.max_context); + const [_maxContext, _setMaxContext] = useState( + config.max_context ?? 0 + ); const [_model, _setModel] = useState(config.model); const [_temperature, _setTemperature] = useState(config.temperature); const [_presencePenalty, _setPresencePenalty] = useState( diff --git a/src/components/SettingsMenu/TotalTokenCost.tsx b/src/components/SettingsMenu/TotalTokenCost.tsx index 0a4c5ad1..77eb8bbc 100644 --- a/src/components/SettingsMenu/TotalTokenCost.tsx +++ b/src/components/SettingsMenu/TotalTokenCost.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; @@ -24,6 +25,8 @@ const tokenCostToCost = ( }; const TotalTokenCost = () => { + const { t } = useTranslation(['main', 'model']); + const totalTokenUsed = useStore((state) => state.totalTokenUsed); const setTotalTokenUsed = useStore((state) => state.setTotalTokenUsed); const countTotalTokens = useStore((state) => state.countTotalTokens); diff --git a/src/utils/import.ts b/src/utils/import.ts index 70037878..d0e2bd35 100644 --- a/src/utils/import.ts +++ b/src/utils/import.ts @@ -90,7 +90,7 @@ export const validateFolders = ( }; export const validateExportV1 = (data: ExportV1): data is ExportV1 => { - return validateAndFixChats(data.chats) && validateFolders(data.folders); + return validateAndFixChats(data.chats ?? []) && validateFolders(data.folders); }; // Convert OpenAI chat format to KoalaClient format From e344a0308eb221b18f310064465d1f0ef2af1053 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 20:56:20 -0500 Subject: [PATCH 10/17] ctrl-o keybinding to copy most recent code block --- src/App.tsx | 15 ++++++++++++++- src/hooks/useCopyCodeBlock.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useCopyCodeBlock.ts diff --git a/src/App.tsx b/src/App.tsx index def5b062..e6fb69dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import Menu from '@components/Menu'; import useAddChat from '@hooks/useAddChat'; import useGoBack from '@hooks/useGoBack'; import useGoForward from '@hooks/useGoForward'; +import useCopyCodeBlock from '@hooks/useCopyCodeBlock'; import useInitialiseNewChat from '@hooks/useInitialiseNewChat'; import { ChatInterface } from '@type/chat'; import { Theme } from '@type/theme'; @@ -26,24 +27,36 @@ function App() { const addChat = useAddChat(); const goBack = useGoBack(); const goForward = useGoForward(); + const copyCodeBlock = useCopyCodeBlock(); const handleKeyDown = (e: React.KeyboardEvent) => { - // put any general app-wide keybinds here + // Put any general app-wide keybinds here: + + // ctrl+e - Toggle side menu if (e.ctrlKey && e.key === 'e') { e.preventDefault(); setHideSideMenu(!hideSideMenu); } + // ctrl+n - New chat if (e.ctrlKey && e.key === 'n') { e.preventDefault(); addChat(); } + // ctrl+o - Copy code block + if (e.ctrlKey && e.key === 'o') { + e.preventDefault(); + copyCodeBlock(); + } + + // ctrl+left - Previous chat if (e.ctrlKey && e.key === 'ArrowLeft') { e.preventDefault(); goBack(); } + // ctrl+left - Next chat if (e.ctrlKey && e.key === 'ArrowRight') { e.preventDefault(); goForward(); diff --git a/src/hooks/useCopyCodeBlock.ts b/src/hooks/useCopyCodeBlock.ts new file mode 100644 index 00000000..89a13fe2 --- /dev/null +++ b/src/hooks/useCopyCodeBlock.ts @@ -0,0 +1,33 @@ +import useStore from '@store/store'; + +const useCopyCodeBlock = () => { + const currentChatIndex = useStore((state) => state.currentChatIndex); + const chats = useStore((state) => state.chats) ?? []; + + const copyCodeBlock = () => { + if (chats.length == 0) return; + + const currentChat = chats[currentChatIndex].messages; + + if (currentChat.length == 0) return; + + const latestMessage = currentChat[currentChat.length - 1].content; + + const codeBlockRegex = /```(\w+)?\s*([^`]+)```/s; + + const match = codeBlockRegex.exec(latestMessage); + + let toCopy; + if (match && match[2]) { + toCopy = match[2].trim(); + } else { + toCopy = latestMessage; + } + + navigator.clipboard.writeText(toCopy); + }; + + return copyCodeBlock; +}; + +export default useCopyCodeBlock; From e6c6badb2b2525ff677ef9fc36fa9c6b84a78f85 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 21:54:05 -0500 Subject: [PATCH 11/17] ctrl-g keybinding to focus the bottom textarea --- src/App.tsx | 39 +++++++++++++------ .../ChatContent/Message/View/EditView.tsx | 13 +++++-- src/hooks/GlobalContext.tsx | 13 +++++++ src/main.tsx | 2 +- 4 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 src/hooks/GlobalContext.tsx diff --git a/src/App.tsx b/src/App.tsx index e6fb69dc..0be9e69a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import useStore from '@store/store'; import i18n from './i18n'; @@ -15,6 +15,7 @@ import { Theme } from '@type/theme'; import ApiPopup from '@components/ApiPopup'; import Toast from '@components/Toast'; import isElectron from '@utils/electron'; +import GlobalContext from '@hooks/GlobalContext'; function App() { const initialiseNewChat = useInitialiseNewChat(); @@ -29,6 +30,13 @@ function App() { const goForward = useGoForward(); const copyCodeBlock = useCopyCodeBlock(); + const [sharedTextareaRef, setSharedTextareaRef] = + useState | null>(null); + + const setRef = (newRef: React.RefObject | null) => { + setSharedTextareaRef(newRef); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { // Put any general app-wide keybinds here: @@ -50,6 +58,13 @@ function App() { copyCodeBlock(); } + // ctrl+g - Focus textarea + if (e.ctrlKey && e.key === 'g') { + e.preventDefault(); + console.log(sharedTextareaRef); + sharedTextareaRef?.current?.focus(); + } + // ctrl+left - Previous chat if (e.ctrlKey && e.key === 'ArrowLeft') { e.preventDefault(); @@ -124,16 +139,18 @@ function App() { }, []); return ( -
- - - - -
+ +
+ + + + +
+
); } diff --git a/src/components/Chat/ChatContent/Message/View/EditView.tsx b/src/components/Chat/ChatContent/Message/View/EditView.tsx index dd87cd49..1346f4d9 100644 --- a/src/components/Chat/ChatContent/Message/View/EditView.tsx +++ b/src/components/Chat/ChatContent/Message/View/EditView.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useContext, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; import isElectron from '@utils/electron'; @@ -11,6 +11,7 @@ import TokenCount from '@components/TokenCount'; import CommandPrompt from '../CommandPrompt'; import WhisperRecord from '../WhisperRecord'; +import GlobalContext from '@hooks/GlobalContext'; const EditView = ({ content, @@ -33,10 +34,16 @@ const EditView = ({ const [cursorPosition, setCursorPosition] = useState(0); const [_content, _setContent] = useState(content); const [isModalOpen, setIsModalOpen] = useState(false); - const textareaRef = React.createRef(); - + const textareaRef = useRef(null); + const { setRef } = useContext(GlobalContext); const { t } = useTranslation(); + useEffect(() => { + if (sticky) { + setRef(textareaRef); + } + }, [textareaRef]); + const resetTextAreaHeight = () => { if (textareaRef.current) textareaRef.current.style.height = 'auto'; }; diff --git a/src/hooks/GlobalContext.tsx b/src/hooks/GlobalContext.tsx new file mode 100644 index 00000000..4951869a --- /dev/null +++ b/src/hooks/GlobalContext.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface GlobalContextProps { + ref: React.RefObject | null; + setRef: (newRef: React.RefObject | null) => void; +} + +const GlobalContext = React.createContext({ + ref: null, + setRef: () => {}, +}); + +export default GlobalContext; diff --git a/src/main.tsx b/src/main.tsx index 2de2b8a3..e8583372 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './main.css'; From 56c6f4dde12c8ccc1610af69735532ed488d0136 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 22:29:52 -0500 Subject: [PATCH 12/17] mouse button forward/back (untested bc no mouse rn lol) --- src/App.tsx | 31 ++++++++++++++++++++++++++++++- src/main.tsx | 2 +- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0be9e69a..fb85f121 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import useStore from '@store/store'; import i18n from './i18n'; @@ -78,6 +78,20 @@ function App() { } }; + const handleMouseUp = (e: React.MouseEvent) => { + // Mouse button 3 (back) + if (e.button === 3) { + e.preventDefault(); + goBack(); + } + + // Mouse button 4 (forward) + if (e.button === 4) { + e.preventDefault(); + goForward(); + } + }; + if (isElectron()) { window.electronAPI.setCloseToTray(useStore((state) => state.closeToTray)); } @@ -138,12 +152,27 @@ function App() { } }, []); + useEffect(() => { + const handleGlobalMouseUp = (e: MouseEvent) => { + if (e.button === 3 || e.button === 4) { + handleMouseUp(e as unknown as React.MouseEvent); + } + }; + + document.addEventListener('mouseup', handleGlobalMouseUp); + + return () => { + document.removeEventListener('mouseup', handleGlobalMouseUp); + }; + }, []); + return (
diff --git a/src/main.tsx b/src/main.tsx index e8583372..2de2b8a3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './main.css'; From c1dab4c3cc6e112ce894d0b15f85fe9bfd47a197 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 22:48:49 -0500 Subject: [PATCH 13/17] ctrl-s keybinding generate from bottom + cancel button and re-edit bugfix --- src/App.tsx | 29 +++++++++++++++++++ .../ChatContent/Message/View/EditView.tsx | 10 ++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index fb85f121..afbca489 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import useGoBack from '@hooks/useGoBack'; import useGoForward from '@hooks/useGoForward'; import useCopyCodeBlock from '@hooks/useCopyCodeBlock'; import useInitialiseNewChat from '@hooks/useInitialiseNewChat'; +import useSubmit from '@hooks/useSubmit'; import { ChatInterface } from '@type/chat'; import { Theme } from '@type/theme'; import ApiPopup from '@components/ApiPopup'; @@ -22,6 +23,7 @@ function App() { const setChats = useStore((state) => state.setChats); const setTheme = useStore((state) => state.setTheme); const setApiKey = useStore((state) => state.setApiKey); + const currentChatIndex = useStore((state) => state.currentChatIndex); const setCurrentChatIndex = useStore((state) => state.setCurrentChatIndex); const setHideSideMenu = useStore((state) => state.setHideSideMenu); const hideSideMenu = useStore((state) => state.hideSideMenu); @@ -29,6 +31,7 @@ function App() { const goBack = useGoBack(); const goForward = useGoForward(); const copyCodeBlock = useCopyCodeBlock(); + const { handleSubmit } = useSubmit(); const [sharedTextareaRef, setSharedTextareaRef] = useState | null>(null); @@ -37,6 +40,26 @@ function App() { setSharedTextareaRef(newRef); }; + const handleGenerate = () => { + if (useStore.getState().generating) return; + const updatedChats: ChatInterface[] = JSON.parse( + JSON.stringify(useStore.getState().chats) + ); + const content = sharedTextareaRef?.current?.value; + const updatedMessages = updatedChats[currentChatIndex].messages; + if (!content) { + return; + } + + updatedMessages.push({ role: 'user', content: content }); + if (sharedTextareaRef && sharedTextareaRef.current) { + sharedTextareaRef.current.value = ''; + } + + setChats(updatedChats); + handleSubmit(); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { // Put any general app-wide keybinds here: @@ -65,6 +88,12 @@ function App() { sharedTextareaRef?.current?.focus(); } + // ctrl+s - Save bottom message + generate + if (e.ctrlKey && e.key === 's') { + e.preventDefault(); + handleGenerate(); + } + // ctrl+left - Previous chat if (e.ctrlKey && e.key === 'ArrowLeft') { e.preventDefault(); diff --git a/src/components/Chat/ChatContent/Message/View/EditView.tsx b/src/components/Chat/ChatContent/Message/View/EditView.tsx index 1346f4d9..136fd697 100644 --- a/src/components/Chat/ChatContent/Message/View/EditView.tsx +++ b/src/components/Chat/ChatContent/Message/View/EditView.tsx @@ -160,6 +160,7 @@ const EditView = ({ messageIndex={messageIndex} role={role} content={content} + setEditingMessageIndex={setEditingMessageIndex} /> {isModalOpen && ( void; @@ -196,6 +198,7 @@ const EditViewButtons = memo( messageIndex: number; role: string; content: string; + setEditingMessageIndex: (index: number | null) => void; }) => { const { t } = useTranslation(); const generating = useStore.getState().generating; @@ -203,6 +206,11 @@ const EditViewButtons = memo( (state) => state.confirmEditSubmission ); + const handleCancel = () => { + setIsEdit(false); + setEditingMessageIndex(null); + }; + const handleEditGenerate = () => { if (generating) { return; @@ -267,7 +275,7 @@ const EditViewButtons = memo( className={`btn relative ${ messageIndex % 2 ? 'btn-neutral' : 'btn-neutral-dark' }`} - onClick={() => setIsEdit(false)} + onClick={() => handleCancel()} aria-label={t('cancel') as string} >
From 0b86a6dad798a41ba1176874197aeb28ad291184 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 23:19:46 -0500 Subject: [PATCH 14/17] 2.0.7 versioning + workflow autopopulation --- .github/workflows/publish.yml | 23 +++++++++++++---------- package.json | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 527346f3..ddba047c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,9 +5,6 @@ on: branches: [main] workflow_dispatch: -env: - VERSION: 2.0.6 - permissions: contents: write id-token: write @@ -26,6 +23,12 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Get version from package.json + id: packageJson + uses: RadovanPelka/github-action-json@main + with: + path: 'package.json' + - name: Use Node.js 18 uses: actions/setup-node@v3 with: @@ -97,8 +100,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-Linux/koala-client-${{env.VERSION}}-linux-x86_64.AppImage - asset_name: KoalaClient-${{env.VERSION}}-linux-x86_64.AppImage + asset_path: ./release-Linux/koala-client-${{steps.packageJson.outputs.version}}-linux-x86_64.AppImage + asset_name: KoalaClient-${{steps.packageJson.outputs.version}}-linux-x86_64.AppImage asset_content_type: application - name: Upload Release Asset - MacOS @@ -107,8 +110,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-macOS/koala-client-${{env.VERSION}}-mac-x64.dmg - asset_name: KoalaClient-${{env.VERSION}}-mac-x64.dmg + asset_path: ./release-macOS/koala-client-${{steps.packageJson.outputs.version}}-mac-x64.dmg + asset_name: KoalaClient-${{steps.packageJson.outputs.version}}-mac-x64.dmg asset_content_type: application - name: Upload Release Asset - Windows @@ -117,8 +120,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./release-Windows/koala-client-${{env.VERSION}}-win-x64.exe - asset_name: KoalaClient-${{env.VERSION}}-win-x64.exe + asset_path: ./release-Windows/koala-client-${{steps.packageJson.outputs.version}}-win-x64.exe + asset_name: KoalaClient-${{steps.packageJson.outputs.version}}-win-x64.exe asset_content_type: application - name: Zip Unpacked Release - Windows @@ -134,7 +137,7 @@ jobs: with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./release-Windows/win-unpacked.zip - asset_name: KoalaClient-${{env.VERSION}}-win-x64-portable.zip + asset_name: KoalaClient-${{steps.packageJson.outputs.version}}-win-x64-portable.zip asset_content_type: zip - name: Zip Hash Info diff --git a/package.json b/package.json index cbb49c83..9930e444 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "koala-client", "private": true, - "version": "2.0.6", + "version": "2.0.7", "type": "module", "homepage": "./", "main": "electron/index.cjs", From 3852fe27896574bc76f2a05c81f3f897dcfde225 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 23:37:09 -0500 Subject: [PATCH 15/17] ctrl-p keybinding newchat-paste-submit --- src/App.tsx | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index afbca489..f9d4425b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,6 +60,31 @@ function App() { handleSubmit(); }; + const pasteSubmit = async () => { + try { + if (useStore.getState().generating) return; + const updatedChats: ChatInterface[] = JSON.parse( + JSON.stringify(useStore.getState().chats) + ); + const updatedMessages = updatedChats[currentChatIndex].messages; + + const text = await navigator.clipboard.readText(); + if (!text) { + return; + } + + updatedMessages.push({ role: 'user', content: text }); + if (sharedTextareaRef && sharedTextareaRef.current) { + sharedTextareaRef.current.value = ''; + } + + setChats(updatedChats); + handleSubmit(); + } catch (err) { + console.error('Failed to read clipboard contents:', err); + } + }; + const handleKeyDown = (e: React.KeyboardEvent) => { // Put any general app-wide keybinds here: @@ -84,7 +109,6 @@ function App() { // ctrl+g - Focus textarea if (e.ctrlKey && e.key === 'g') { e.preventDefault(); - console.log(sharedTextareaRef); sharedTextareaRef?.current?.focus(); } @@ -94,6 +118,14 @@ function App() { handleGenerate(); } + // ctrl+p - New chat from clipboard (insta-generate) + if (e.ctrlKey && e.key === 'p') { + e.preventDefault(); + console.log('test'); + addChat(); + pasteSubmit(); + } + // ctrl+left - Previous chat if (e.ctrlKey && e.key === 'ArrowLeft') { e.preventDefault(); From a8504caf80a9c65c54b89d64722b609cf2280ae9 Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 23:45:34 -0500 Subject: [PATCH 16/17] autofocus text area on new chat fixes #91 --- src/App.tsx | 1 + src/components/Menu/NewChat.tsx | 9 +++++++-- src/components/MobileBar/MobileBar.tsx | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f9d4425b..c8dd572c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -98,6 +98,7 @@ function App() { if (e.ctrlKey && e.key === 'n') { e.preventDefault(); addChat(); + sharedTextareaRef?.current?.focus(); } // ctrl+o - Copy code block diff --git a/src/components/Menu/NewChat.tsx b/src/components/Menu/NewChat.tsx index d598ea9a..a4e5db85 100644 --- a/src/components/Menu/NewChat.tsx +++ b/src/components/Menu/NewChat.tsx @@ -1,14 +1,16 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import useStore from '@store/store'; import PlusIcon from '@icon/PlusIcon'; import useAddChat from '@hooks/useAddChat'; +import GlobalContext from '@hooks/GlobalContext'; const NewChat = ({ folder }: { folder?: string }) => { const { t } = useTranslation(); const addChat = useAddChat(); + const { ref } = useContext(GlobalContext); const generating = useStore((state) => state.generating); return ( @@ -23,7 +25,10 @@ const NewChat = ({ folder }: { folder?: string }) => { : 'text-custom-white gap-2 mb-2 border border-custom-white/20' }`} onClick={() => { - if (!generating) addChat(folder); + if (!generating) { + addChat(folder); + ref?.current?.focus(); + } }} title={String(t('newChat'))} > diff --git a/src/components/MobileBar/MobileBar.tsx b/src/components/MobileBar/MobileBar.tsx index 2d80bef7..b1a71489 100644 --- a/src/components/MobileBar/MobileBar.tsx +++ b/src/components/MobileBar/MobileBar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import useGStore from '@store/cloud-auth-store'; import useStore from '@store/store'; import PlusIcon from '@icon/PlusIcon'; @@ -12,6 +12,7 @@ import ForwardIcon from '@icon/ForwardIcon'; import useGoBack from '@hooks/useGoBack'; import useGoForward from '@hooks/useGoForward'; +import GlobalContext from '@hooks/GlobalContext'; const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID || undefined; @@ -21,7 +22,7 @@ const MobileBar = () => { const hideSideMenu = useStore((state) => state.hideSideMenu); const currentChatIndex = useStore((state) => state.currentChatIndex); const chats = useStore((state) => state.chats); - + const { ref } = useContext(GlobalContext); const cloudSync = useGStore((state) => state.cloudSync); const syncStatus = useGStore((state) => state.syncStatus); @@ -88,6 +89,7 @@ const MobileBar = () => { onClick={() => { if (!generating) { addChat(chats?.[currentChatIndex]?.folder); + ref?.current?.focus(); } }} > From ca21a2dd6da1b566e9b4125a7de3a45978bbc37e Mon Sep 17 00:00:00 2001 From: Jack Schedel Date: Fri, 5 Jan 2024 23:55:31 -0500 Subject: [PATCH 17/17] whisper uses generating lock + stop generating button has onClick rather than whole div fixes #87 --- src/components/Chat/ChatContent/Message/WhisperRecord.tsx | 6 ++++-- .../StopGeneratingButton/StopGeneratingButton.tsx | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Chat/ChatContent/Message/WhisperRecord.tsx b/src/components/Chat/ChatContent/Message/WhisperRecord.tsx index 17cf70a3..e60a79fa 100644 --- a/src/components/Chat/ChatContent/Message/WhisperRecord.tsx +++ b/src/components/Chat/ChatContent/Message/WhisperRecord.tsx @@ -14,16 +14,17 @@ const WhisperRecord = ({ messageIndex: number; }) => { let apiKey = useStore((state) => state.apiKey); - + const setGenerating = useStore((state) => state.setGenerating); apiKey = apiKey || '0'; const { transcript, startRecording, stopRecording } = useWhisper({ apiKey }); useEffect(() => { - if (transcript.text) { + if (transcript.text != null) { _setContent((prev) => { return prev.replace('◯', transcript.text || ''); }); + setGenerating(false); } }, [transcript.text]); @@ -54,6 +55,7 @@ const WhisperRecord = ({ return startContent + paddedStart + '◉' + paddedEnd + endContent; }); startRecording(); + setGenerating(true); } setIsRecording(!isRecording); }; diff --git a/src/components/StopGeneratingButton/StopGeneratingButton.tsx b/src/components/StopGeneratingButton/StopGeneratingButton.tsx index 197af571..4625fcd0 100644 --- a/src/components/StopGeneratingButton/StopGeneratingButton.tsx +++ b/src/components/StopGeneratingButton/StopGeneratingButton.tsx @@ -6,13 +6,11 @@ const StopGeneratingButton = () => { const generating = useStore((state) => state.generating); return generating ? ( -
setGenerating(false)} - > +