Skip to content

Commit

Permalink
Merge pull request #99 from jackschedel/2.0.8
Browse files Browse the repository at this point in the history
2.0.8a
  • Loading branch information
jackschedel authored Jan 11, 2024
2 parents ded4562 + a5feaa7 commit 828928b
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 86 deletions.
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,28 @@

## 🔥 Features

- OpenAI Whisper Speech Transcription (desktop-only)
- Searchable prompt pallete to instantly insert frequently used prompts (from
user-defined prompt library)
- Directly tweak model settings, including Max Tokens and Max Context (change
default settings or change it per chat)

## 🛠️ UI Tweaks

- Massive color scheme and styling changes
- Reduced empty whitespace to increase text screen real-estate
- Model select dropdown menu within individual chats
- **Many** minor style and QoL changes
- Speech transcription using OpenAI's Whisper
- Searchable prompt pallete to instantly insert frequently used prompts from
user-defined prompt library
- Directly tweak model settings, including Max Tokens and Max Context (change default settings or change it per chat)
- Minimal whitespace, designed to be used maximized or in a small window
- Keyboard shortcuts intended to allow for workflow-optimization hotkeys (using AHK or Karabinerk)
- Beautiful UI with consistent styling

<p align="center">
<img src="https://cdn.discordapp.com/attachments/446426925209092098/1192293382920351744/Screenshot_2024-01-03_at_9.27.06_PM.png?ex=65a88cbe&is=659617be&hm=5d60622b900e4c834ef11a62423045edca40075655cc5597ee1bbda7b2eb2bb4&" alt="landing" width=800 />
</p>

## 🖥️ Electron-focused development philosophy
## 🌐 Website version

- Access KoalaClient from the web, or via the desktop app.
- https://client.koaladev.io/ is updated automatically with every commit
- Enable Google Sync to sync your chats across devices

## 🖥️ Desktop-focused development

- Automatically generated desktop builds for every commit
- Minimize to tray on close setting
- Speech transcription with OpenAI Whisper
- Electron-only tweaks
- Desktop-only tweaks
- Open links in browser
- Right click context menu
- Spellcheck
Expand Down
10 changes: 9 additions & 1 deletion src/components/Chat/ChatContent/CloneChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import CloneIcon from '@icon/CloneIcon';
const CloneChat = React.memo(() => {
const setChats = useStore((state) => state.setChats);
const setCurrentChatIndex = useStore((state) => state.setCurrentChatIndex);
const generating = useStore((state) => state.generating);

const [cloned, setCloned] = useState<boolean>(false);

const cloneChat = () => {
if (generating) {
return;
}
const chats = useStore.getState().chats;

if (chats) {
Expand Down Expand Up @@ -44,7 +48,11 @@ const CloneChat = React.memo(() => {
return (
<button
type='button'
className={`text-custom-white transition-opacity cursor-pointer opacity-100`}
className={`text-custom-white transition-opacity ${
generating
? 'cursor-not-allowed opacity-40'
: 'cursor-pointer opacity-100'
}`}
onClick={cloneChat}
>
<div className='-ml-0.5 -mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-neutral-light'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const CommandPrompt = ({
}) => {
const { t } = useTranslation();
const prompts = useStore((state) => state.prompts);
const generating = useStore((state) => state.generating);
const [_prompts, _setPrompts] = useState<Prompt[]>(prompts);
const [input, setInput] = useState<string>('');
const inputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -54,9 +55,17 @@ const CommandPrompt = ({
<button
className={`btn ${
messageIndex % 2 ? 'btn-neutral' : 'btn-neutral-dark'
} btn-small inline-flex h-8 w-8 items-center justify-center`}
} btn-small inline-flex h-8 w-8 items-center justify-center ${
generating
? 'cursor-not-allowed opacity-40'
: 'cursor-pointer opacity-100'
}`}
aria-label='prompt library'
onClick={() => setDropDown(!dropDown)}
onClick={() => {
if (!generating) {
setDropDown(!dropDown);
}
}}
>
/
</button>
Expand Down
45 changes: 23 additions & 22 deletions src/components/Chat/ChatContent/Message/View/ContentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ContentView = memo(
);
const inlineLatex = useStore((state) => state.inlineLatex);
const markdownMode = useStore((state) => state.markdownMode);
const generating = useStore((state) => state.generating);

const handleDelete = () => {
const updatedChats: ChatInterface[] = JSON.parse(
Expand Down Expand Up @@ -141,28 +142,11 @@ const ContentView = memo(
)}
</div>
<div className='flex justify-end gap-2 w-full mt-2'>
{isDelete || (
<>
{!useStore.getState().generating &&
role === 'assistant' &&
messageIndex === lastMessageIndex && (
<RefreshButton onClick={handleRefresh} />
)}
{messageIndex !== 0 && <UpButton onClick={handleMoveUp} />}
{messageIndex !== lastMessageIndex && (
<DownButton onClick={handleMoveDown} />
)}

<MarkdownModeButton />
<CopyButton onClick={handleCopy} />
<EditButton
setEditingMessageIndex={setEditingMessageIndex}
messageIndex={messageIndex}
/>
<DeleteButton setIsDelete={setIsDelete} />
</>
)}
{isDelete && (
{generating ? (
<div className='p-1 invisible'>
<TickIcon />
</div>
) : isDelete ? (
<>
<button
className='p-1 text-custom-white hover:text-neutral-dark hover:bg-custom-white/70 hover:rounded'
Expand All @@ -179,6 +163,23 @@ const ContentView = memo(
<TickIcon />
</button>
</>
) : (
<>
{role === 'assistant' && messageIndex === lastMessageIndex && (
<RefreshButton onClick={handleRefresh} />
)}
{messageIndex !== 0 && <UpButton onClick={handleMoveUp} />}
{messageIndex !== lastMessageIndex && (
<DownButton onClick={handleMoveDown} />
)}
<MarkdownModeButton />
<CopyButton onClick={handleCopy} />
<EditButton
setEditingMessageIndex={setEditingMessageIndex}
messageIndex={messageIndex}
/>
<DeleteButton setIsDelete={setIsDelete} />
</>
)}
</div>
</>
Expand Down
102 changes: 69 additions & 33 deletions src/components/Chat/ChatContent/Message/WhisperRecord.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useWhisper } from '@chengsokdara/use-whisper';
import useStore from '@store/store';
import StopIcon from '@icon/StopIcon';
import MicrophoneIcon from '@icon/MicrophoneIcon';
import { useTranslation } from 'react-i18next';

const WhisperRecord = ({
cursorPosition,
Expand All @@ -13,51 +14,76 @@ const WhisperRecord = ({
_setContent: React.Dispatch<React.SetStateAction<string>>;
messageIndex: number;
}) => {
const { t } = useTranslation('api');
let apiKey = useStore((state) => state.apiKey);
const setGenerating = useStore((state) => state.setGenerating);
const generating = useStore((state) => state.generating);
const setError = useStore((state) => state.setError);
const setIsRecording = useStore((state) => state.setIsRecording);
const isRecording = useStore((state) => state.isRecording);
apiKey = apiKey || '0';

const { transcript, startRecording, stopRecording } = useWhisper({ apiKey });
const { transcript, startRecording, stopRecording } = useWhisper({
apiKey,
});

useEffect(() => {
if (transcript.text != null) {
_setContent((prev) => {
return prev.replace('◯', transcript.text || '');
});
setGenerating(false);
if (generating) {
if (transcript.text != null) {
_setContent((prev) => {
return prev.replace('◯', transcript.text || '');
});
setGenerating(false);
}
}
}, [transcript.text]);

const [isRecording, setIsRecording] = useState(false);

const handleRecording = () => {
if (isRecording) {
useEffect(() => {
if (!generating) {
_setContent((prev) => {
return prev.replace('◉', '◯' || '');
return prev.replace('◯', '');
});
stopRecording();
} else {
_setContent((prev) => {
const startContent = prev.slice(0, cursorPosition);
const endContent = prev.slice(cursorPosition);
}
}, [generating]);

const handleRecording = () => {
if (apiKey != '0') {
if (isRecording) {
_setContent((prev) => {
return prev.replace('◉', '◯' || '');
});
stopRecording();
} else {
_setContent((prev) => {
const startContent = prev.slice(0, cursorPosition);
const endContent = prev.slice(cursorPosition);

const paddedStart =
!startContent.endsWith(' ') &&
!startContent.endsWith('\n') &&
startContent.length > 0
? ' '
: '';
const paddedEnd =
!endContent.startsWith(' ') && !endContent.startsWith('\n')
? ' '
: '';
const paddedStart =
startContent &&
!startContent.endsWith(' ') &&
!startContent.endsWith('\n') &&
startContent.length > 0
? ' '
: '';
const paddedEnd =
endContent &&
!endContent.startsWith(' ') &&
!endContent.startsWith('\n')
? ' '
: '';

return startContent + paddedStart + '◉' + paddedEnd + endContent;
return startContent + paddedStart + '◉' + paddedEnd + endContent;
});
startRecording();
setGenerating(true);
}
setIsRecording(!isRecording);
} else {
setError(t('noApiKeyWarning') as string);
_setContent((prev) => {
return prev.replace('◯', transcript.text || '');
});
startRecording();
setGenerating(true);
}
setIsRecording(!isRecording);
};

return (
Expand All @@ -69,9 +95,19 @@ const WhisperRecord = ({
? 'btn-primary'
: 'btn-neutral-dark'
: 'btn-primary'
} btn-small inline-flex p-0 h-8 w-8 items-center justify-center mr-3`}
} btn-small inline-flex p-0 h-8 w-8 items-center justify-center mr-3 ${
generating && !isRecording
? 'cursor-not-allowed opacity-40'
: 'cursor-pointer opacity-100'
}
`}
aria-label='whisper'
onClick={handleRecording}
onClick={() => {
if (!generating || isRecording) {
handleRecording();
}
}}
>
{isRecording ? <StopIcon /> : <MicrophoneIcon />}
</button>
Expand Down
24 changes: 20 additions & 4 deletions src/components/MobileBar/MobileBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,32 @@ const MobileBar = () => {
</div>
<button
type='button'
className='-mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-neutral-light'
onClick={goBack}
className={`-mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-neutral-light ${
generating
? 'cursor-not-allowed opacity-40'
: 'cursor-pointer opacity-100'
}`}
onClick={() => {
if (!generating) {
goBack();
}
}}
>
<span className='sr-only'>Open sidebar</span>
<BackIcon height='1em' />
</button>
<button
type='button'
className='-mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-neutral-light mr-4'
onClick={goForward}
className={`-mt-0.5 inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-neutral-light ${
generating
? 'cursor-not-allowed opacity-40'
: 'cursor-pointer opacity-100'
}`}
onClick={() => {
if (!generating) {
goForward();
}
}}
>
<span className='sr-only'>Open sidebar</span>
<ForwardIcon height='1em' />
Expand Down
15 changes: 9 additions & 6 deletions src/components/StopGeneratingButton/StopGeneratingButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import useStore from '@store/store';
const StopGeneratingButton = () => {
const setGenerating = useStore((state) => state.setGenerating);
const generating = useStore((state) => state.generating);
const isRecording = useStore((state) => state.isRecording);

return generating ? (
<div className='absolute bottom-6 left-0 right-0 m-auto flex md:w-full md:m-auto gap-0 md:gap-2 justify-center'>
return generating && !isRecording ? (
<div
className='absolute bottom-6 left-0 right-0 m-auto flex md:w-full md:m-auto gap-0 md:gap-2 justify-center'
style={{ pointerEvents: 'none' }}
>
<button
className='btn relative btn-neutral border-0 md:border hover:bg-neutral-dark'
className='btn relative btn-neutral border-0 md:border hover:bg-neutral-dark px-6 py-3'
aria-label='stop generating'
onClick={() => setGenerating(false)}
style={{ pointerEvents: 'auto' }}
>
<div className='flex w-full items-center justify-center gap-2'>
<svg
Expand All @@ -31,9 +36,7 @@ const StopGeneratingButton = () => {
</div>
</button>
</div>
) : (
<></>
);
) : null;
};

export default StopGeneratingButton;
Loading

0 comments on commit 828928b

Please sign in to comment.