From 47b51c50a30e0aa997a397dcc1a6aa9b5ac584ec Mon Sep 17 00:00:00 2001 From: anastasiia Date: Thu, 18 Jan 2024 08:19:08 -0500 Subject: [PATCH] studio conversation design fixes --- .../CurrentTabContent/ChatTab/Input/index.tsx | 4 +- .../StudioTab/Conversation.tsx | 143 --------- .../{ => Conversation}/GeneratedDiff.tsx | 6 +- .../Input/TemplatesDropdown.tsx | 14 +- .../{ => Conversation}/Input/index.tsx | 115 +------- .../{ => Conversation}/StarterMessage.tsx | 2 +- .../StudioTab/Conversation/index.tsx | 271 ++++++++++++++++++ .../StudioTab/StudioPersistentState.tsx | 82 +++--- .../src/components/Button/KeyHintButton.tsx | 44 +++ client/src/context/studiosContext.tsx | 4 + 10 files changed, 381 insertions(+), 304 deletions(-) delete mode 100644 client/src/Project/CurrentTabContent/StudioTab/Conversation.tsx rename client/src/Project/CurrentTabContent/StudioTab/{ => Conversation}/GeneratedDiff.tsx (94%) rename client/src/Project/CurrentTabContent/StudioTab/{ => Conversation}/Input/TemplatesDropdown.tsx (88%) rename client/src/Project/CurrentTabContent/StudioTab/{ => Conversation}/Input/index.tsx (65%) rename client/src/Project/CurrentTabContent/StudioTab/{ => Conversation}/StarterMessage.tsx (95%) create mode 100644 client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx create mode 100644 client/src/components/Button/KeyHintButton.tsx diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx index e4bc5f13fa..95088f5da1 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx @@ -269,7 +269,9 @@ const ConversationInput = ({ return (
diff --git a/client/src/Project/CurrentTabContent/StudioTab/Conversation.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation.tsx deleted file mode 100644 index 1dd303307e..0000000000 --- a/client/src/Project/CurrentTabContent/StudioTab/Conversation.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { Trans } from 'react-i18next'; -import ScrollToBottom from '../../../components/ScrollToBottom'; -import { StudioContext } from '../../../context/studiosContext'; -import { TOKEN_LIMIT } from '../../../consts/codeStudio'; -import { BranchIcon, WarningSignIcon } from '../../../icons'; -import SpinLoaderContainer from '../../../components/Loaders/SpinnerLoader'; -import { StudioConversationMessageAuthor } from '../../../types/general'; -import { getTemplates } from '../../../services/api'; -import { StudioTemplateType } from '../../../types/api'; -import StarterMessage from './StarterMessage'; -import ConversationInput from './Input'; -import GeneratedDiff from './GeneratedDiff'; - -type Props = { - side: 'left' | 'right'; - tabKey: string; - studioData: StudioContext; - isActiveTab: boolean; - requestsLeft: number; -}; - -const Conversation = ({ - side, - studioData, - isActiveTab, - requestsLeft, -}: Props) => { - // const { project } = useContext(ProjectContext.Current); - const scrollableRef = useRef(null); - const inputRef = useRef(null); - const [templates, setTemplates] = useState([]); - - const refetchTemplates = useCallback(() => { - getTemplates().then(setTemplates); - }, []); - - useEffect(() => { - refetchTemplates(); - }, []); - // const [isScrollable, setIsScrollable] = useState(false); - - // useEffect(() => { - // setTimeout(() => { - // if (scrollableRef.current) { - // setIsScrollable( - // scrollableRef.current.scrollHeight > - // scrollableRef.current.clientHeight, - // ); - // } - // }, 100); - // }, [studioData?.conversation, studioData?.hideMessagesFrom]); - - return !studioData ? null : ( -
- - - {studioData.conversation.map((m, i) => ( - TOKEN_LIMIT} - isLast={i === studioData.conversation.length - 1} - side={side} - isActiveTab={isActiveTab} - requestsLeft={requestsLeft} - isLoading={studioData.isLoading} - handleCancel={studioData.handleCancel} - templates={templates} - /> - ))} - {!!studioData.diff && ( - - )} - {(studioData.isDiffApplied || - studioData.waitingForDiff || - studioData.isDiffGenFailed) && ( -
- {studioData.isDiffGenFailed ? ( - - ) : studioData.waitingForDiff ? ( - - ) : ( - - )} - - {studioData.isDiffGenFailed - ? 'Diff generation failed' - : studioData.waitingForDiff - ? 'Generating diff...' - : 'The diff has been applied locally.'} - -
- )} - {!studioData.isLoading && - // !studioData.isPreviewing && - !studioData.waitingForDiff && - !studioData.diff && - !( - studioData.conversation[studioData.conversation.length - 1] - ?.author === StudioConversationMessageAuthor.USER - ) && ( - TOKEN_LIMIT} - isLast - side={side} - onSubmit={studioData.onSubmit} - isActiveTab={isActiveTab} - requestsLeft={requestsLeft} - isLoading={studioData.isLoading} - handleCancel={studioData.handleCancel} - templates={templates} - /> - )} -
-
- ); -}; - -export default memo(Conversation); diff --git a/client/src/Project/CurrentTabContent/StudioTab/GeneratedDiff.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/GeneratedDiff.tsx similarity index 94% rename from client/src/Project/CurrentTabContent/StudioTab/GeneratedDiff.tsx rename to client/src/Project/CurrentTabContent/StudioTab/Conversation/GeneratedDiff.tsx index 81f0e679f3..49e4646198 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/GeneratedDiff.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/GeneratedDiff.tsx @@ -1,8 +1,8 @@ import { Dispatch, memo, SetStateAction, useCallback, useContext } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { GeneratedCodeDiff } from '../../../types/api'; -import { BranchIcon, WarningSignIcon } from '../../../icons'; -import CodeDiff from '../../../components/Code/CodeDiff'; +import { GeneratedCodeDiff } from '../../../../types/api'; +import { BranchIcon, WarningSignIcon } from '../../../../icons'; +import CodeDiff from '../../../../components/Code/CodeDiff'; type Props = { diff: GeneratedCodeDiff; diff --git a/client/src/Project/CurrentTabContent/StudioTab/Input/TemplatesDropdown.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/TemplatesDropdown.tsx similarity index 88% rename from client/src/Project/CurrentTabContent/StudioTab/Input/TemplatesDropdown.tsx rename to client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/TemplatesDropdown.tsx index 8f62246d30..2dfaeb4fb7 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/Input/TemplatesDropdown.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/TemplatesDropdown.tsx @@ -9,13 +9,13 @@ import { useRef, } from 'react'; import { useTranslation } from 'react-i18next'; -import DropdownSection from '../../../../components/Dropdown/Section'; -import { StudioTemplateType } from '../../../../types/api'; -import SectionItem from '../../../../components/Dropdown/Section/SectionItem'; -import { CogIcon, TemplatesIcon } from '../../../../icons'; -import { UIContext } from '../../../../context/uiContext'; -import { ProjectSettingSections } from '../../../../types/general'; -import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; +import DropdownSection from '../../../../../components/Dropdown/Section'; +import { StudioTemplateType } from '../../../../../types/api'; +import SectionItem from '../../../../../components/Dropdown/Section/SectionItem'; +import { CogIcon, TemplatesIcon } from '../../../../../icons'; +import { UIContext } from '../../../../../context/uiContext'; +import { ProjectSettingSections } from '../../../../../types/general'; +import useKeyboardNavigation from '../../../../../hooks/useKeyboardNavigation'; type Props = { templates: StudioTemplateType[]; diff --git a/client/src/Project/CurrentTabContent/StudioTab/Input/index.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/index.tsx similarity index 65% rename from client/src/Project/CurrentTabContent/StudioTab/Input/index.tsx rename to client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/index.tsx index 25b8488aa4..e223051836 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/Input/index.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/Input/index.tsx @@ -10,25 +10,21 @@ import React, { useState, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { StudioConversationMessageAuthor } from '../../../../types/general'; -import MarkdownWithCode from '../../../../components/MarkdownWithCode'; -import { EnvContext } from '../../../../context/envContext'; -import KeyboardHint from '../../../../components/KeyboardHint'; +import { StudioConversationMessageAuthor } from '../../../../../types/general'; +import MarkdownWithCode from '../../../../../components/MarkdownWithCode'; +import { EnvContext } from '../../../../../context/envContext'; import { PencilIcon, RefreshIcon, TemplatesIcon, TrashCanIcon, WarningSignIcon, -} from '../../../../icons'; -import Button from '../../../../components/Button'; -import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; -import { checkEventKeys } from '../../../../utils/keyboardUtils'; -import { UIContext } from '../../../../context/uiContext'; -import CopyButton from '../../../../components/MarkdownWithCode/CopyButton'; -import { StudioTemplateType } from '../../../../types/api'; -import Dropdown from '../../../../components/Dropdown'; -import { useTemplateShortcut } from '../../../../consts/shortcuts'; +} from '../../../../../icons'; +import Button from '../../../../../components/Button'; +import CopyButton from '../../../../../components/MarkdownWithCode/CopyButton'; +import { StudioTemplateType } from '../../../../../types/api'; +import Dropdown from '../../../../../components/Dropdown'; +import { useTemplateShortcut } from '../../../../../consts/shortcuts'; import TemplatesDropdown from './TemplatesDropdown'; type Props = { @@ -38,15 +34,12 @@ type Props = { onMessageRemoved?: (i: number, andSubsequent?: boolean) => void; i?: number; inputRef?: React.MutableRefObject; + templatesRef?: React.MutableRefObject; isTokenLimitExceeded: boolean; isLast: boolean; side: 'left' | 'right'; - onSubmit?: () => void; - isActiveTab: boolean; - isLoading: boolean; - requestsLeft: number; - handleCancel: () => void; templates?: StudioTemplateType[]; + setIsDropdownShown: (b: boolean) => void; }; const ConversationInput = ({ @@ -59,31 +52,20 @@ const ConversationInput = ({ isLast, isTokenLimitExceeded, side, - onSubmit, - isActiveTab, - requestsLeft, - isLoading, - handleCancel, templates, + setIsDropdownShown, + templatesRef, }: Props) => { const { t } = useTranslation(); const { envConfig } = useContext(EnvContext); - const { setIsUpgradeRequiredPopupOpen } = useContext( - UIContext.UpgradeRequiredPopup, - ); - // const { refetchTemplates, setTemplates } = useContext(StudioContext.Setters); const [isFocused, setFocused] = useState(false); - const [isDropdownShown, setIsDropdownShown] = useState(false); const ref = useRef(null); const cloneRef = useRef(null); - const templatesRef = useRef(null); - const [isSaved, setSaved] = useState(false); useImperativeHandle(inputRef, () => ref.current!); const handleChange = useCallback( (e: ChangeEvent) => { onMessageChange(e.target.value, i); - setSaved(false); }, [i, onMessageChange], ); @@ -100,67 +82,10 @@ const ConversationInput = ({ } }, [message, isFocused]); - const saveAsTemplate = useCallback(() => { - setSaved(true); - // setTemplates((prev) => [ - // { - // name: '', - // content: message, - // id: 'new', - // modified_at: '', - // is_default: false, - // }, - // ...prev, - // ]); - // setLeftPanel({ type: StudioLeftPanelType.TEMPLATES }); - }, [message]); - - const useTemplates = useCallback(() => { - // setLeftPanel({ type: StudioLeftPanelType.TEMPLATES }); - }, []); - - const handleKeyEvent = useCallback( - (e: KeyboardEvent) => { - if (checkEventKeys(e, ['entr'])) { - e.preventDefault(); - e.stopPropagation(); - if ( - message && - !isTokenLimitExceeded && - // !hasContextError && - requestsLeft - // && !isChangeUnsaved - ) { - onSubmit?.(); - } else if (!requestsLeft) { - setIsUpgradeRequiredPopupOpen(true); - } - // } else if ((e.metaKey || e.ctrlKey) && e.key === 't') { - // setLeftPanel({ type: StudioLeftPanelType.TEMPLATES }); - } - if (checkEventKeys(e, ['Esc']) && isLoading) { - e.preventDefault(); - e.stopPropagation(); - handleCancel(); - } - if (checkEventKeys(e, useTemplateShortcut) && i === undefined) { - templatesRef.current?.parentElement?.click(); - } - }, - [onSubmit, isLoading, requestsLeft, i], - ); - useKeyboardNavigation( - handleKeyEvent, - !isActiveTab || !onSubmit || isDropdownShown, - ); - const dropdownProps = useMemo(() => { return { templates, - onTemplateSelected: (t: string) => { - onMessageChange(t, i); - setSaved(false); - }, + onTemplateSelected: onMessageChange, }; }, [templates, onMessageChange, i]); @@ -230,18 +155,6 @@ const ConversationInput = ({ rows={1} ref={cloneRef} /> - {!!onSubmit && ( -
- -
- )} ) : ( <> diff --git a/client/src/Project/CurrentTabContent/StudioTab/StarterMessage.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/StarterMessage.tsx similarity index 95% rename from client/src/Project/CurrentTabContent/StudioTab/StarterMessage.tsx rename to client/src/Project/CurrentTabContent/StudioTab/Conversation/StarterMessage.tsx index be308b43a9..ff839a3cd7 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/StarterMessage.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/StarterMessage.tsx @@ -1,6 +1,6 @@ import { memo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { CodeStudioIcon } from '../../../icons'; +import { CodeStudioIcon } from '../../../../icons'; type Props = {}; diff --git a/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx b/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx new file mode 100644 index 0000000000..c35ee9d1e0 --- /dev/null +++ b/client/src/Project/CurrentTabContent/StudioTab/Conversation/index.tsx @@ -0,0 +1,271 @@ +import React, { + memo, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import ScrollToBottom from '../../../../components/ScrollToBottom'; +import { StudioContext } from '../../../../context/studiosContext'; +import { TOKEN_LIMIT } from '../../../../consts/codeStudio'; +import { BranchIcon, WarningSignIcon } from '../../../../icons'; +import SpinLoaderContainer from '../../../../components/Loaders/SpinnerLoader'; +import { StudioConversationMessageAuthor } from '../../../../types/general'; +import { getTemplates } from '../../../../services/api'; +import { StudioTemplateType } from '../../../../types/api'; +import { checkEventKeys } from '../../../../utils/keyboardUtils'; +import { useTemplateShortcut } from '../../../../consts/shortcuts'; +import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; +import { UIContext } from '../../../../context/uiContext'; +import KeyHintButton from '../../../../components/Button/KeyHintButton'; +import GeneratedDiff from './GeneratedDiff'; +import ConversationInput from './Input'; +import StarterMessage from './StarterMessage'; + +type Props = { + side: 'left' | 'right'; + tabKey: string; + studioData: StudioContext; + isActiveTab: boolean; + requestsLeft: number; +}; + +const generateShortcut = ['cmd', 'entr']; +const stopShortcut = ['Esc']; +const noShortcut: string[] = []; + +const Conversation = ({ + side, + studioData, + isActiveTab, + requestsLeft, +}: Props) => { + const { t } = useTranslation(); + const scrollableRef = useRef(null); + const inputRef = useRef(null); + const [templates, setTemplates] = useState([]); + const [isDropdownShown, setIsDropdownShown] = useState(false); + const { setIsUpgradeRequiredPopupOpen } = useContext( + UIContext.UpgradeRequiredPopup, + ); + const templatesRef = useRef(null); + + const refetchTemplates = useCallback(() => { + getTemplates().then(setTemplates); + }, []); + + useEffect(() => { + refetchTemplates(); + }, []); + const [isScrollable, setIsScrollable] = useState(false); + + useEffect(() => { + setTimeout(() => { + if (scrollableRef.current) { + setIsScrollable( + scrollableRef.current.scrollHeight > + scrollableRef.current.clientHeight, + ); + } + }, 100); + }, [studioData?.conversation]); + + const handleKeyEvent = useCallback( + (e: KeyboardEvent) => { + if (checkEventKeys(e, generateShortcut)) { + e.preventDefault(); + e.stopPropagation(); + if ( + studioData.inputValue && + studioData.tokenCount < TOKEN_LIMIT && + // !hasContextError && + requestsLeft + // && !isChangeUnsaved + ) { + studioData.onSubmit(); + } else if (!requestsLeft) { + setIsUpgradeRequiredPopupOpen(true); + } + } + if (checkEventKeys(e, stopShortcut) && studioData.isLoading) { + e.preventDefault(); + e.stopPropagation(); + studioData.handleCancel(); + } + if (checkEventKeys(e, useTemplateShortcut)) { + templatesRef.current?.parentElement?.click(); + } + }, + [ + studioData.inputValue, + studioData.tokenCount, + studioData.onSubmit, + studioData.isLoading, + studioData.handleCancel, + requestsLeft, + ], + ); + useKeyboardNavigation(handleKeyEvent, !isActiveTab || isDropdownShown); + + const hasCodeBlock = useMemo(() => { + return studioData.conversation.some( + (m) => + m.author === StudioConversationMessageAuthor.ASSISTANT && + m.message.includes('```'), + ); + }, [studioData.conversation]); + + const isDiffForLocalRepo = useMemo(() => { + return studioData.diff?.chunks.find((c) => c.repo.startsWith('local//')); + }, [studioData.diff]); + + return !studioData ? null : ( +
+ + + {studioData.conversation.map((m, i) => ( + TOKEN_LIMIT} + isLast={i === studioData.conversation.length - 1} + side={side} + templates={templates} + setIsDropdownShown={setIsDropdownShown} + /> + ))} + {!!studioData.diff && ( + + )} + {(studioData.isDiffApplied || + studioData.waitingForDiff || + studioData.isDiffGenFailed) && ( +
+ {studioData.isDiffGenFailed ? ( + + ) : studioData.waitingForDiff ? ( + + ) : ( + + )} + + {studioData.isDiffGenFailed + ? 'Diff generation failed' + : studioData.waitingForDiff + ? 'Generating diff...' + : 'The diff has been applied locally.'} + +
+ )} + {!studioData.isLoading && + // !studioData.isPreviewing && + !studioData.waitingForDiff && + !studioData.diff && + !( + studioData.conversation[studioData.conversation.length - 1] + ?.author === StudioConversationMessageAuthor.USER + ) && ( + TOKEN_LIMIT} + isLast + side={side} + templates={templates} + setIsDropdownShown={setIsDropdownShown} + templatesRef={templatesRef} + /> + )} +
+
+
+ +
+
+ {studioData.isLoading ? ( + + ) : ( + <> + {(hasCodeBlock || studioData.diff) && + (studioData.isDiffApplied ? null : !studioData.diff ? ( + + ) : ( + <> + studioData.setDiff(null)} + /> + {isDiffForLocalRepo && ( + + )} + + ))} + {!studioData.diff && ( + + )} + + )} +
+
+
+ ); +}; + +export default memo(Conversation); diff --git a/client/src/Project/CurrentTabContent/StudioTab/StudioPersistentState.tsx b/client/src/Project/CurrentTabContent/StudioTab/StudioPersistentState.tsx index 6c68de1ac0..8aedd40aae 100644 --- a/client/src/Project/CurrentTabContent/StudioTab/StudioPersistentState.tsx +++ b/client/src/Project/CurrentTabContent/StudioTab/StudioPersistentState.tsx @@ -1,9 +1,8 @@ -import React, { +import { memo, useCallback, useContext, useEffect, - useMemo, useRef, useState, } from 'react'; @@ -12,7 +11,6 @@ import throttle from 'lodash.throttle'; import { DeviceContext } from '../../../context/deviceContext'; import { ProjectContext } from '../../../context/projectContext'; import { - DiffHunkType, StudioConversationMessage, StudioConversationMessageAuthor, StudioTabType, @@ -155,6 +153,7 @@ const StudioPersistentState = ({ tabKey, side }: Props) => { [tabKey]: { ...prev[tabKey], setConversation, + setDiff, }, }; }); @@ -493,14 +492,6 @@ const StudioPersistentState = ({ tabKey, side }: Props) => { }); }, [clearConversation]); - const hasCodeBlock = useMemo(() => { - return conversation.some( - (m) => - m.author === StudioConversationMessageAuthor.ASSISTANT && - m.message.includes('```'), - ); - }, [conversation]); - const handleApplyChanges = useCallback(async () => { if (!project?.id) { return; @@ -522,39 +513,50 @@ const StudioPersistentState = ({ tabKey, side }: Props) => { setWaitingForDiff(false); } }, [tabKey, project?.id]); + useEffect(() => { + setStudios((prev) => { + return { ...prev, [tabKey]: { ...prev[tabKey], handleApplyChanges } }; + }); + }, [handleApplyChanges]); const handleCancelDiff = useCallback(() => { abortController.current?.abort(); setWaitingForDiff(false); }, []); + useEffect(() => { + setStudios((prev) => { + return { ...prev, [tabKey]: { ...prev[tabKey], handleCancelDiff } }; + }); + }, [handleCancelDiff]); - const handleConfirmDiff = useCallback( - async (e: React.MouseEvent) => { - e.preventDefault(); - if (!diff || !project?.id) { - return; - } - const result = diff.chunks.map((c) => c.raw_patch).join(''); - try { - await confirmStudioDiff(project.id, tabKey, result); - setDiff(null); - setDiffApplied(true); - } catch (err: unknown) { - console.log(err); - // @ts-ignore - if (err.code !== 'ERR_CANCELED') { - setDiffApplyError(true); - } + const handleConfirmDiff = useCallback(async () => { + if (!diff || !project?.id) { + return; + } + const result = diff.chunks.map((c) => c.raw_patch).join(''); + try { + await confirmStudioDiff(project.id, tabKey, result); + setDiff(null); + setDiffApplied(true); + } catch (err: unknown) { + console.log(err); + // @ts-ignore + if (err.code !== 'ERR_CANCELED') { + setDiffApplyError(true); } - }, - [tabKey, diff, project?.id], - ); + } + }, [tabKey, diff, project?.id]); + useEffect(() => { + setStudios((prev) => { + return { ...prev, [tabKey]: { ...prev[tabKey], handleConfirmDiff } }; + }); + }, [handleConfirmDiff]); const onDiffChanged = useCallback((i: number, v: string) => { setDiff((prev) => { const newValue: GeneratedCodeDiff = JSON.parse(JSON.stringify(prev)); newValue.chunks[i].raw_patch = v; - const newHunks: DiffHunkType[] = v + newValue.chunks[i].hunks = v .split(/\n(?=@@ -)/) .slice(1) .map((h) => { @@ -564,7 +566,6 @@ const StudioPersistentState = ({ tabKey, side }: Props) => { patch: h.split('\n').slice(1).join('\n'), }; }); - newValue.chunks[i].hunks = newHunks; return newValue; }); }, []); @@ -590,21 +591,6 @@ const StudioPersistentState = ({ tabKey, side }: Props) => { }); }, [onDiffRemoved]); - const isDiffForLocalRepo = useMemo(() => { - return diff?.chunks.find((c) => c.repo.startsWith('local//')); - }, [diff]); - - // useEffect(() => { - // if (conversation.length && conversation.length < 3 && !tabTitle) { - // updateTabProperty( - // tabKey, - // 'title', - // conversation[0].text, - // side, - // ); - // } - // }, [conversation, tabKey, side, tabTitle]); - return null; }; diff --git a/client/src/components/Button/KeyHintButton.tsx b/client/src/components/Button/KeyHintButton.tsx new file mode 100644 index 0000000000..9482af9454 --- /dev/null +++ b/client/src/components/Button/KeyHintButton.tsx @@ -0,0 +1,44 @@ +import { ButtonHTMLAttributes, DetailedHTMLProps, memo } from 'react'; +import useShortcuts from '../../hooks/useShortcuts'; + +type Props = { + text: string; + shortcut: string[]; +}; + +const KeyHintButton = ({ + text, + shortcut, + ...btnProps +}: DetailedHTMLProps< + ButtonHTMLAttributes, + HTMLButtonElement +> & + Props) => { + const keys = useShortcuts(shortcut); + return ( + + ); +}; + +export default memo(KeyHintButton); diff --git a/client/src/context/studiosContext.tsx b/client/src/context/studiosContext.tsx index 5275ce6b6b..76694fb933 100644 --- a/client/src/context/studiosContext.tsx +++ b/client/src/context/studiosContext.tsx @@ -15,6 +15,7 @@ export type StudioContext = { onMessageChange: (message: string, i?: number) => void; onMessageRemoved: (i: number, andSubsequent?: boolean) => void; diff: GeneratedCodeDiff | null; + setDiff: Dispatch>; onDiffRemoved: (i: number) => void; onDiffChanged: (i: number, v: string) => void; isDiffApplyError: boolean; @@ -26,6 +27,9 @@ export type StudioContext = { isLoading: boolean; handleCancel: () => void; clearConversation: () => void; + handleCancelDiff: () => void; + handleApplyChanges: () => void; + handleConfirmDiff: () => void; }; type ContextType = {