From 5ecf1ab8dfb4be34f4ed86ff5ef99b07d4238f1c Mon Sep 17 00:00:00 2001 From: anastasiia Date: Thu, 1 Feb 2024 12:39:10 -0500 Subject: [PATCH 1/2] support alternative input type for chinese users --- .../ChatTab/ChatPersistentState.tsx | 9 + .../{InputCore.tsx => ProseMirror/index.tsx} | 11 +- .../Input/{ => ProseMirror}/mentionPlugin.ts | 2 +- .../ChatTab/Input/{ => ProseMirror}/nodes.ts | 2 +- .../{ => ProseMirror}/placeholderPlugin.ts | 0 .../ChatTab/Input/{ => ProseMirror}/utils.ts | 2 +- .../ChatTab/Input/ReactMentions/index.tsx | 294 ++++++++++++++++++ .../CurrentTabContent/ChatTab/Input/index.tsx | 62 ++-- client/src/index.css | 6 + client/src/utils/domUtils.ts | 2 + client/src/utils/index.ts | 12 +- 11 files changed, 374 insertions(+), 28 deletions(-) rename client/src/Project/CurrentTabContent/ChatTab/Input/{InputCore.tsx => ProseMirror/index.tsx} (96%) rename client/src/Project/CurrentTabContent/ChatTab/Input/{ => ProseMirror}/mentionPlugin.ts (99%) rename client/src/Project/CurrentTabContent/ChatTab/Input/{ => ProseMirror}/nodes.ts (99%) rename client/src/Project/CurrentTabContent/ChatTab/Input/{ => ProseMirror}/placeholderPlugin.ts (100%) rename client/src/Project/CurrentTabContent/ChatTab/Input/{ => ProseMirror}/utils.ts (96%) create mode 100644 client/src/Project/CurrentTabContent/ChatTab/Input/ReactMentions/index.tsx diff --git a/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx b/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx index 85dbc642ff..2472b870b0 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/ChatPersistentState.tsx @@ -26,6 +26,10 @@ import { focusInput } from '../../../utils/domUtils'; import { ChatsContext } from '../../../context/chatsContext'; import { TabsContext } from '../../../context/tabsContext'; import { getConversation } from '../../../services/api'; +import { + concatenateParsedQuery, + splitUserInputAfterAutocomplete, +} from '../../../utils'; type Options = { path: string; @@ -190,6 +194,11 @@ const ChatPersistentState = ({ const setInputValueImperatively = useCallback( (value: ParsedQueryType[] | string) => { + setInputValue( + typeof value === 'string' + ? { plain: value, parsed: splitUserInputAfterAutocomplete(value) } + : { parsed: value, plain: concatenateParsedQuery(value) }, + ); setInputImperativeValue({ type: 'paragraph', content: diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/index.tsx similarity index 96% rename from client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx rename to client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/index.tsx index 7f680be5b7..e8863efb0c 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/InputCore.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/index.tsx @@ -14,10 +14,13 @@ import { schema as basicSchema } from 'prosemirror-schema-basic'; // @ts-ignore import * as icons from 'file-icons-js'; import { useTranslation } from 'react-i18next'; -import { InputEditorContent, ParsedQueryType } from '../../../../types/general'; -import { getFileExtensionForLang } from '../../../../utils'; -import { blurInput } from '../../../../utils/domUtils'; -import { MentionOptionType } from '../../../../types/results'; +import { + InputEditorContent, + ParsedQueryType, +} from '../../../../../types/general'; +import { getFileExtensionForLang } from '../../../../../utils'; +import { blurInput } from '../../../../../utils/domUtils'; +import { MentionOptionType } from '../../../../../types/results'; import { getMentionsPlugin } from './mentionPlugin'; import { addMentionNodes, mapEditorContentToInputValue } from './utils'; import { placeholderPlugin } from './placeholderPlugin'; diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/mentionPlugin.ts b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/mentionPlugin.ts similarity index 99% rename from client/src/Project/CurrentTabContent/ChatTab/Input/mentionPlugin.ts rename to client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/mentionPlugin.ts index 3809854f28..d32fe8bb4d 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/mentionPlugin.ts +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/mentionPlugin.ts @@ -1,7 +1,7 @@ import { Plugin, PluginKey } from 'prosemirror-state'; import { Decoration, DecorationSet, EditorView } from 'prosemirror-view'; import { ResolvedPos } from 'prosemirror-model'; -import { MentionOptionType } from '../../../../types/results'; +import { MentionOptionType } from '../../../../../types/results'; export function getRegexp(mentionTrigger: string, allowSpace?: boolean) { return allowSpace diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/nodes.ts b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/nodes.ts similarity index 99% rename from client/src/Project/CurrentTabContent/ChatTab/Input/nodes.ts rename to client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/nodes.ts index 0921c27694..e582fae90f 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/nodes.ts +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/nodes.ts @@ -1,7 +1,7 @@ // @ts-ignore import * as icons from 'file-icons-js'; import { type AttributeSpec, type NodeSpec } from 'prosemirror-model'; -import { getFileExtensionForLang, splitPath } from '../../../../utils'; +import { getFileExtensionForLang, splitPath } from '../../../../../utils'; export const mentionNode: NodeSpec = { group: 'inline', diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/placeholderPlugin.ts b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/placeholderPlugin.ts similarity index 100% rename from client/src/Project/CurrentTabContent/ChatTab/Input/placeholderPlugin.ts rename to client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/placeholderPlugin.ts diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/utils.ts b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/utils.ts similarity index 96% rename from client/src/Project/CurrentTabContent/ChatTab/Input/utils.ts rename to client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/utils.ts index 1d3667045a..d70bdfa5f1 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/utils.ts +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/ProseMirror/utils.ts @@ -3,7 +3,7 @@ import { type NodeSpec } from 'prosemirror-model'; import { InputEditorContent, ParsedQueryTypeEnum, -} from '../../../../types/general'; +} from '../../../../../types/general'; import { mentionNode } from './nodes'; export function addMentionNodes(nodes: OrderedMap) { diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/ReactMentions/index.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/ReactMentions/index.tsx new file mode 100644 index 0000000000..f27e8f3a3e --- /dev/null +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/ReactMentions/index.tsx @@ -0,0 +1,294 @@ +import React, { + memo, + ReactNode, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { + Mention, + MentionsInput, + OnChangeHandlerFunc, + SuggestionDataItem, +} from 'react-mentions'; +import { Trans, useTranslation } from 'react-i18next'; +import { getFileExtensionForLang, splitPath } from '../../../../../utils'; +import FileIcon from '../../../../../components/FileIcon'; +import { FolderIcon, RepositoryIcon } from '../../../../../icons'; +import { ParsedQueryType } from '../../../../../types/general'; +import { blurInput } from '../../../../../utils/domUtils'; +import { MentionOptionType } from '../../../../../types/results'; + +type Props = { + placeholder: string; + getDataLang: (s: string) => Promise; + getDataPath: (s: string) => Promise; + getDataRepo: (s: string) => Promise; + value?: { parsed: ParsedQueryType[]; plain: string }; + onChange: (v: string) => void; + onSubmit: (v: { parsed: ParsedQueryType[]; plain: string }) => void; + isDisabled?: boolean; +}; + +const inputStyle = { + '&multiLine': { + highlighter: { + maxHeight: 300, + overflow: 'auto', + }, + input: { + maxHeight: 300, + overflow: 'auto', + outline: 'none', + }, + }, + suggestions: { + list: { + maxHeight: '40vh', + overflowY: 'auto', + backgroundColor: 'rgb(var(--bg-shade))', + border: '1px solid rgb(var(--bg-border))', + boxShadow: 'var(--shadow-high)', + padding: 4, + zIndex: 100, + borderRadius: 6, + marginTop: 6, + }, + }, +}; + +const ReactMentionsInput = ({ + placeholder, + onSubmit, + onChange, + getDataPath, + getDataRepo, + getDataLang, + value, + isDisabled, +}: Props) => { + const { t } = useTranslation(); + const inputRef = useRef(null); + const [isComposing, setComposition] = useState(false); + + useEffect(() => { + if (inputRef.current) { + // We need to reset the height momentarily to get the correct scrollHeight for the textarea + inputRef.current.style.height = '56px'; + const scrollHeight = inputRef.current.scrollHeight; + + // We then set the height directly, outside of the render loop + // Trying to set this with state or a ref will product an incorrect value. + inputRef.current.style.height = + Math.max(Math.min(scrollHeight, 300), 56) + 'px'; + } + }, [inputRef.current, value]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (isComposing) { + return true; + } + if (e.key === 'Enter' && !e.shiftKey && onSubmit && value) { + e.preventDefault(); + blurInput(); + onSubmit({ + plain: value.plain + .replace(/\|(repo:.*?)\|/, '$1') + .replace(/\|(path:.*?)\|/, '$1') + .replace(/\|(lang:.*?)\|/, '$1'), + parsed: value.parsed, + }); + } + }, + [isComposing, onSubmit, value], + ); + + const repoTransform = useCallback((id: string, trans: string) => { + const split = splitPath(trans); + return trans.startsWith('local//') + ? split.slice(-1)[0] + : split.slice(-2).join('/'); + }, []); + + const pathTransform = useCallback((id: string, trans: string) => { + const split = splitPath(trans); + return `${split[split.length - 1] || split[split.length - 2]}`; + }, []); + + const onCompositionStart = useCallback(() => { + setComposition(true); + }, []); + + const onCompositionEnd = useCallback(() => { + // this event comes before keydown and sets state faster causing unintentional submit + setTimeout(() => setComposition(false), 10); + }, []); + + const handleChange = useCallback( + (e) => { + onChange(e.target.value); + }, + [onChange], + ); + + const renderRepoSuggestion = useCallback( + ( + entry: SuggestionDataItem, + search: string, + highlightedDisplay: ReactNode, + index: number, + focused: boolean, + ) => { + const d = entry as MentionOptionType; + return ( +
+ {d.isFirst ? ( +
+ Repositories +
+ ) : null} +
+ + + {d.display} + + + {d.hint} + +
+
+ ); + }, + [], + ); + + const renderPathSuggestion = useCallback( + ( + entry: SuggestionDataItem, + search: string, + highlightedDisplay: ReactNode, + index: number, + focused: boolean, + ) => { + const d = entry as MentionOptionType; + return ( +
+ {d.isFirst ? ( +
+ {d.type === 'dir' ? 'Directories' : 'Files'} +
+ ) : null} +
+ + {d.type === 'dir' ? ( + + ) : ( + + )} + {d.display} + + + {d.hint} + +
+
+ ); + }, + [], + ); + + const renderLangSuggestion = useCallback( + ( + entry: SuggestionDataItem, + search: string, + highlightedDisplay: ReactNode, + index: number, + focused: boolean, + ) => { + const d = entry as MentionOptionType; + return ( +
+ {d.isFirst ? ( +
+ Languages +
+ ) : null} +
+ + + {d.display} + + + {d.hint} + +
+
+ ); + }, + [], + ); + + return ( +
+ + + + + +
+ ); +}; + +export default memo(ReactMentionsInput); diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx index 30b38f3efb..3efc231860 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx @@ -24,12 +24,14 @@ import useKeyboardNavigation from '../../../../hooks/useKeyboardNavigation'; import KeyboardHint from '../../../../components/KeyboardHint'; import { focusInput } from '../../../../utils/domUtils'; import { MentionOptionType } from '../../../../types/results'; -import { splitPath } from '../../../../utils'; +import { splitPath, splitUserInputAfterAutocomplete } from '../../../../utils'; import { openTabsCache } from '../../../../services/cache'; import { CommandBarContext } from '../../../../context/commandBarContext'; import { UIContext } from '../../../../context/uiContext'; -import InputCore from './InputCore'; -import { mapEditorContentToInputValue } from './utils'; +import { LocaleContext } from '../../../../context/localeContext'; +import InputCore from './ProseMirror'; +import { mapEditorContentToInputValue } from './ProseMirror/utils'; +import ReactMentionsInput from './ReactMentions'; type Props = { value?: { parsed: ParsedQueryType[]; plain: string }; @@ -77,6 +79,7 @@ const ConversationInput = ({ const { t } = useTranslation(); const { envConfig } = useContext(EnvContext); const { isVisible } = useContext(CommandBarContext.General); + const { locale } = useContext(LocaleContext); const { setIsLeftSidebarFocused } = useContext(UIContext.Focus); const [initialValue, setInitialValue] = useState< Record | null | undefined @@ -135,8 +138,19 @@ const ConversationInput = ({ [conversation, submittedQuery, hideMessagesFrom], ); - const onChangeInput = useCallback((inputState: InputEditorContent[]) => { - setInputValue(mapEditorContentToInputValue(inputState)); + const onChangeProseMirrorInput = useCallback( + (inputState: InputEditorContent[]) => { + setInputValue(mapEditorContentToInputValue(inputState)); + setIsLeftSidebarFocused(false); + }, + [], + ); + + const onChangeReactMentionsInput = useCallback((newVal: string) => { + setInputValue({ + plain: newVal, + parsed: splitUserInputAfterAutocomplete(newVal), + }); setIsLeftSidebarFocused(false); }, []); @@ -147,7 +161,7 @@ const ConversationInput = ({ }, [value, onSubmit]); const getDataPath = useCallback( - async (search: string) => { + async (search: string, callback?: (v: MentionOptionType[]) => void) => { const respPath = await getAutocomplete( projectId, `path:${search}&content=false&page_size=8`, @@ -187,7 +201,7 @@ const ConversationInput = ({ const results: MentionOptionType[] = []; filesResults.forEach((fr, i) => { results.push({ - id: fr.path, + id: `${fr.repo}-${fr.path}`, display: fr.path, type: 'file', isFirst: i === 0, @@ -196,23 +210,21 @@ const ConversationInput = ({ }); dirResults.forEach((fr, i) => { results.push({ - id: fr.path, + id: `${fr.repo}-${fr.path}`, display: fr.path, type: 'dir', isFirst: i === 0, hint: splitPath(fr.repo).pop(), }); }); + callback?.(results); return results; }, [projectId], ); const getDataLang = useCallback( - async ( - search: string, - // callback: (a: { id: string; display: string }[]) => void, - ) => { + async (search: string, callback?: (v: MentionOptionType[]) => void) => { const respLang = await getAutocomplete( projectId, `lang:${search}&content=false&page_size=8`, @@ -224,16 +236,14 @@ const ConversationInput = ({ langResults.forEach((fr, i) => { results.push({ id: fr, display: fr, type: 'lang', isFirst: i === 0 }); }); + callback?.(results); return results; }, [projectId], ); const getDataRepo = useCallback( - async ( - search: string, - // callback: (a: { id: string; display: string }[]) => void, - ) => { + async (search: string, callback?: (v: MentionOptionType[]) => void) => { const respRepo = await getAutocomplete( projectId, `repo:${search}&content=false&path=false&file=false&page_size=8`, @@ -250,6 +260,7 @@ const ConversationInput = ({ isFirst: i === 0, }); }); + callback?.(results); return results; }, [projectId], @@ -290,7 +301,22 @@ const ConversationInput = ({

You

- {generationInProgress ? ( + {locale === 'zhCN' ? ( + + ) : generationInProgress ? (
Generating answer...
@@ -300,7 +326,7 @@ const ConversationInput = ({ getDataPath={getDataPath} getDataRepo={getDataRepo} initialValue={initialValue} - onChange={onChangeInput} + onChange={onChangeProseMirrorInput} onSubmit={onSubmit} placeholder={t( 'Write a message, @ to mention files, folders or docs...', diff --git a/client/src/index.css b/client/src/index.css index bcc832f1e7..92b859980b 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1270,3 +1270,9 @@ img.ProseMirror-separator { stroke-linecap: round; stroke-linejoin: round; } + +/* react-mentions editor start */ +.w-full__suggestions { + background-color: transparent !important; +} +/* react-mentions editor end */ \ No newline at end of file diff --git a/client/src/utils/domUtils.ts b/client/src/utils/domUtils.ts index eb0603838a..a131b01d81 100644 --- a/client/src/utils/domUtils.ts +++ b/client/src/utils/domUtils.ts @@ -23,8 +23,10 @@ export const isFocusInInput = (ignoreCommandInput?: boolean) => { export const focusInput = () => { findElementInCurrentTab('.ProseMirror')?.focus(); + findElementInCurrentTab('.ReactMention textarea')?.focus(); }; export const blurInput = () => { findElementInCurrentTab('.ProseMirror')?.blur(); + findElementInCurrentTab('.ReactMention textarea')?.blur(); }; diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index e3d87b9045..7bcbb09080 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -423,7 +423,7 @@ export function splitUserInputAfterAutocomplete( ): ParsedQueryType[] { const pathRegex = /\|path:(.*?)\|/g; const langRegex = /\|lang:(.*?)\|/g; - const combinedRegex = /\|(path|lang):(.*?)\|/g; + const combinedRegex = /\|(path|lang|repo):(.*?)\|/g; const result: ParsedQueryType[] = []; let lastIndex = 0; @@ -438,7 +438,11 @@ export function splitUserInputAfterAutocomplete( addTextContent(input.substring(lastIndex, index)); result.push({ type: - type === 'lang' ? ParsedQueryTypeEnum.LANG : ParsedQueryTypeEnum.PATH, + type === 'lang' + ? ParsedQueryTypeEnum.LANG + : type === 'repo' + ? ParsedQueryTypeEnum.REPO + : ParsedQueryTypeEnum.PATH, text, }); lastIndex = index + text.length + type.length + 3; // 3 is the length of "(type:" @@ -459,6 +463,8 @@ export function concatenateParsedQuery(query: ParsedQueryType[]) { result += `|path:${q.text}|`; } else if (q.type === ParsedQueryTypeEnum.LANG) { result += `|lang:${q.text}|`; + } else if (q.type === ParsedQueryTypeEnum.REPO) { + result += `|repo:${q.text}|`; } }); return result; @@ -472,7 +478,7 @@ type InputEditorTextContent = { type InputEditorMentionContent = { type: 'mention'; attrs: { - type: 'lang' | 'path'; + type: 'lang' | 'path' | 'repo'; id: string; display: string; }; From 9cc5110fdb4c0d3ce5872006a5446e322f5a53eb Mon Sep 17 00:00:00 2001 From: anastasiia Date: Mon, 5 Feb 2024 07:35:24 -0600 Subject: [PATCH 2/2] add input type to settings to allow users to choose input type --- .../CurrentTabContent/ChatTab/Input/index.tsx | 5 +-- .../Preferences/ChatInputTypeDropdown.tsx | 40 +++++++++++++++++ .../Settings/Preferences/LanguageDropdown.tsx | 9 +++- client/src/Settings/Preferences/index.tsx | 27 ++++++++++++ .../context/providers/UIContextProvider.tsx | 43 ++++++++++++++++--- client/src/context/uiContext.ts | 5 +++ client/src/locales/en.json | 8 +++- client/src/locales/es.json | 8 +++- client/src/locales/it.json | 8 +++- client/src/locales/ja.json | 8 +++- client/src/locales/zh-CN.json | 8 +++- client/src/services/storage.ts | 1 + client/src/types/general.ts | 2 + 13 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 client/src/Settings/Preferences/ChatInputTypeDropdown.tsx diff --git a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx index 3efc231860..fb880a4400 100644 --- a/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx +++ b/client/src/Project/CurrentTabContent/ChatTab/Input/index.tsx @@ -28,7 +28,6 @@ import { splitPath, splitUserInputAfterAutocomplete } from '../../../../utils'; import { openTabsCache } from '../../../../services/cache'; import { CommandBarContext } from '../../../../context/commandBarContext'; import { UIContext } from '../../../../context/uiContext'; -import { LocaleContext } from '../../../../context/localeContext'; import InputCore from './ProseMirror'; import { mapEditorContentToInputValue } from './ProseMirror/utils'; import ReactMentionsInput from './ReactMentions'; @@ -79,7 +78,7 @@ const ConversationInput = ({ const { t } = useTranslation(); const { envConfig } = useContext(EnvContext); const { isVisible } = useContext(CommandBarContext.General); - const { locale } = useContext(LocaleContext); + const { chatInputType } = useContext(UIContext.ChatInputType); const { setIsLeftSidebarFocused } = useContext(UIContext.Focus); const [initialValue, setInitialValue] = useState< Record | null | undefined @@ -301,7 +300,7 @@ const ConversationInput = ({

You

- {locale === 'zhCN' ? ( + {chatInputType === 'simplified' ? ( { + const { t } = useTranslation(); + const { chatInputType, setChatInputType } = useContext( + UIContext.ChatInputType, + ); + return ( +
+ + setChatInputType('default')} + description={t('Recommended: The classic input')} + /> + + + setChatInputType('simplified')} + description={t( + 'Fallback: Use if experiencing problems with the default one', + )} + /> + +
+ ); +}; + +export default memo(ChatInputTypeDropdown); diff --git a/client/src/Settings/Preferences/LanguageDropdown.tsx b/client/src/Settings/Preferences/LanguageDropdown.tsx index a3c6077b9d..47f327c173 100644 --- a/client/src/Settings/Preferences/LanguageDropdown.tsx +++ b/client/src/Settings/Preferences/LanguageDropdown.tsx @@ -4,12 +4,14 @@ import SectionItem from '../../components/Dropdown/Section/SectionItem'; import { LocaleContext } from '../../context/localeContext'; import { localesMap } from '../../consts/general'; import { LocaleType } from '../../types/general'; +import { UIContext } from '../../context/uiContext'; type Props = {}; const LanguageDropdown = ({}: Props) => { useTranslation(); const { locale, setLocale } = useContext(LocaleContext); + const { setChatInputType } = useContext(UIContext.ChatInputType); return (
@@ -19,7 +21,12 @@ const LanguageDropdown = ({}: Props) => { key={k} index={`lang-${k}`} isSelected={locale === k} - onClick={() => setLocale(k)} + onClick={() => { + if (k === 'zhCN') { + setChatInputType('simplified'); + } + setLocale(k); + }} label={localesMap[k].name} icon={{localesMap[k].icon}} /> diff --git a/client/src/Settings/Preferences/index.tsx b/client/src/Settings/Preferences/index.tsx index 2d2f221efd..51ce10bbea 100644 --- a/client/src/Settings/Preferences/index.tsx +++ b/client/src/Settings/Preferences/index.tsx @@ -14,6 +14,7 @@ import { localesMap, themesMap } from '../../consts/general'; import { LocaleContext } from '../../context/localeContext'; import ThemeDropdown from './ThemeDropdown'; import LanguageDropdown from './LanguageDropdown'; +import ChatInputTypeDropdown from './ChatInputTypeDropdown'; type Props = {}; @@ -27,6 +28,7 @@ const Preferences = ({}: Props) => { useTranslation(); const { theme } = useContext(UIContext.Theme); const { locale } = useContext(LocaleContext); + const { chatInputType } = useContext(UIContext.ChatInputType); return (
@@ -82,6 +84,31 @@ const Preferences = ({}: Props) => {
+
+
+
+

+ Conversation input +

+

+ Select the input type to use in conversations +

+
+ + + +
); }; diff --git a/client/src/context/providers/UIContextProvider.tsx b/client/src/context/providers/UIContextProvider.tsx index c008e22939..e610bf01f3 100644 --- a/client/src/context/providers/UIContextProvider.tsx +++ b/client/src/context/providers/UIContextProvider.tsx @@ -13,6 +13,7 @@ import { DeviceContext } from '../deviceContext'; import { getConfig, refreshToken as refreshTokenApi } from '../../services/api'; import { ACCESS_TOKEN_KEY, + CHAT_INPUT_TYPE_KEY, getPlainFromStorage, ONBOARDING_DONE_KEY, REFRESH_TOKEN_KEY, @@ -21,7 +22,12 @@ import { } from '../../services/storage'; import { Theme } from '../../types'; import { EnvContext } from '../envContext'; -import { ProjectSettingSections, SettingSections } from '../../types/general'; +import { + ChatInputType, + ProjectSettingSections, + SettingSections, +} from '../../types/general'; +import { LocaleContext } from '../localeContext'; type Props = {}; @@ -41,6 +47,7 @@ export const UIContextProvider = memo( 'onBoardingState', ); const { isSelfServe } = useContext(DeviceContext); + const { locale } = useContext(LocaleContext); const { setEnvConfig, envConfig } = useContext(EnvContext); const [isGithubConnected, setGithubConnected] = useState( isSelfServe ? !!getPlainFromStorage(REFRESH_TOKEN_KEY) : false, @@ -53,6 +60,12 @@ export const UIContextProvider = memo( const [theme, setTheme] = useState( (getPlainFromStorage(THEME) as 'system' | null) || 'system', ); + const [chatInputType, setChatInputType] = useState( + (getPlainFromStorage(CHAT_INPUT_TYPE_KEY) as 'default' | null) || + locale === 'zhCN' + ? 'simplified' + : 'default', + ); const [isLeftSidebarFocused, setIsLeftSidebarFocused] = useState(false); const [isUpgradeRequiredPopupOpen, setIsUpgradeRequiredPopupOpen] = useState(false); @@ -110,6 +123,10 @@ export const UIContextProvider = memo( } }, [theme]); + useEffect(() => { + savePlainToStorage(CHAT_INPUT_TYPE_KEY, chatInputType); + }, [chatInputType]); + const settingsContextValue = useMemo( () => ({ isSettingsOpen, @@ -182,6 +199,14 @@ export const UIContextProvider = memo( [isUpgradeRequiredPopupOpen], ); + const chatInputTypeContextValue = useMemo( + () => ({ + chatInputType, + setChatInputType, + }), + [chatInputType], + ); + return ( @@ -189,13 +214,17 @@ export const UIContextProvider = memo( - - - {children} - - + + + {children} + + + diff --git a/client/src/context/uiContext.ts b/client/src/context/uiContext.ts index db863cdb61..9806fd3b60 100644 --- a/client/src/context/uiContext.ts +++ b/client/src/context/uiContext.ts @@ -1,6 +1,7 @@ import { createContext, Dispatch, SetStateAction } from 'react'; import { Theme } from '../types'; import { + ChatInputType, OnboardingStateType, ProjectSettingSections, SettingSections, @@ -52,4 +53,8 @@ export const UIContext = { isUpgradeRequiredPopupOpen: false, setIsUpgradeRequiredPopupOpen: (b: boolean) => {}, }), + ChatInputType: createContext({ + chatInputType: 'default' as ChatInputType, + setChatInputType: (t: ChatInputType) => {}, + }), }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 76c1dff60d..be01f008a6 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -530,5 +530,11 @@ "Add documentation": "Add documentation", "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.": "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.", "Start by selecting again and pressing Enter (↵) on your keyboard.": "Start by selecting again and pressing Enter (↵) on your keyboard.", - "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project." + "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.", + "Select the input type to use in conversations": "Select the input type to use in conversations", + "Conversation input": "Conversation input", + "Default": "Default", + "Simplified": "Simplified", + "Recommended: The classic input": "Recommended: The classic input", + "Fallback: Use if experiencing problems with the default one": "Fallback: Use if experiencing problems with the default one" } \ No newline at end of file diff --git a/client/src/locales/es.json b/client/src/locales/es.json index a9bfed2068..275a6dd2f1 100644 --- a/client/src/locales/es.json +++ b/client/src/locales/es.json @@ -529,5 +529,11 @@ "Restore session": "Restaurar sesion", "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.": "<0>{{repoName}} ha terminado de indexación y se puede agregar a sus proyectos. Haga clic en el botón a continuación para agregarlo al proyecto actual.", "Start by selecting again and pressing Enter (↵) on your keyboard.": "Comience seleccionando nuevamente y presionando Enter (↵) en su teclado.", - "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} actualmente está indexando tan pronto como esté terminado, podrá agregarlo a su proyecto." + "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} actualmente está indexando tan pronto como esté terminado, podrá agregarlo a su proyecto.", + "Conversation input": "Aportación de conversación", + "Simplified: Choose if the default one fails": "Simplificado: elija si el predeterminado falla", + "Fallback: Use if experiencing problems with the default one": "Fallback: use si experimenta problemas con el predeterminado", + "Select the input type to use in conversations": "Seleccione el tipo de entrada a usar en conversaciones", + "Default": "Por defecto", + "Recommended: The classic input": "Recomendado: la entrada clásica" } \ No newline at end of file diff --git a/client/src/locales/it.json b/client/src/locales/it.json index 8b4824916e..b3906c3de1 100644 --- a/client/src/locales/it.json +++ b/client/src/locales/it.json @@ -511,5 +511,11 @@ "Let’s get you started with bloop!": "Ti cominciamo con Bloop!", "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.": "<0>{{repoName}} ha terminato l'indicizzazione e può essere aggiunto ai tuoi progetti. Fai clic sul pulsante sotto per aggiungerlo al progetto corrente.", "Start by selecting again and pressing Enter (↵) on your keyboard.": "Inizia selezionando di nuovo e premendo Invio (↵) sulla tastiera.", - "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} sta attualmente indicizzando non appena sarà finito, sarai in grado di aggiungerlo al tuo progetto." + "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}} sta attualmente indicizzando non appena sarà finito, sarai in grado di aggiungerlo al tuo progetto.", + "Default": "Predefinita", + "Fallback: Use if experiencing problems with the default one": "Fallback: usa se si riscontra problemi con quello predefinito", + "Simplified": "Semplificata", + "Select the input type to use in conversations": "Seleziona il tipo di input da utilizzare nelle conversazioni", + "Recommended: The classic input": "Consigliato: l'ingresso classico", + "Conversation input": "Input di conversazione" } \ No newline at end of file diff --git a/client/src/locales/ja.json b/client/src/locales/ja.json index ea65c5d8d8..e1839c51bf 100644 --- a/client/src/locales/ja.json +++ b/client/src/locales/ja.json @@ -516,5 +516,11 @@ "In this project": "このプロジェクトで", "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.": "<0>{{repoName}}はインデックス作成が終了し、プロジェクトに追加できます。 以下のボタンをクリックして、現在のプロジェクトに追加します。", "Start by selecting again and pressing Enter (↵) on your keyboard.": "もう一度選択して、キーボードのEnter(‡)を押すことから始めます。", - "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}}は現在、終了したらすぐにインデックスを作成しています。プロジェクトに追加できるようになります。" + "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}}は現在、終了したらすぐにインデックスを作成しています。プロジェクトに追加できるようになります。", + "Select the input type to use in conversations": "会話で使用する入力タイプを選択します", + "Fallback: Use if experiencing problems with the default one": "フォールバック:デフォルトの問題で問題が発生している場合に使用します", + "Simplified": "簡素化", + "Default": "デフォルト", + "Recommended: The classic input": "推奨:クラシック入力", + "Conversation input": "会話の入力" } \ No newline at end of file diff --git a/client/src/locales/zh-CN.json b/client/src/locales/zh-CN.json index 8750ceec45..320e8e365e 100644 --- a/client/src/locales/zh-CN.json +++ b/client/src/locales/zh-CN.json @@ -526,5 +526,11 @@ "Existing studio conversations": "现有的工作室对话", "<0>{{repoName}} has finished indexing and can be added to your projects. Click the button to below to add it to the current project.": "<0>{{repoName}}已完成索引,可以添加到您的项目中。 单击下面的按钮将其添加到当前项目中。", "Start by selecting again and pressing Enter (↵) on your keyboard.": "首先选择再次选择键盘上的Enter(↵)。", - "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}}当前,一旦完成后,您就可以将其添加到项目中。" + "{{repoName}} is currently indexing as soon as it is finished you will be able to add it to your project.": "{{repoName}}当前,一旦完成后,您就可以将其添加到项目中。", + "Conversation input": "对话输入", + "Fallback: Use if experiencing problems with the default one": "后备:如果遇到默认一个问题,请使用", + "Simplified": "简化", + "Select the input type to use in conversations": "选择要在对话中使用的输入类型", + "Default": "默认", + "Recommended: The classic input": "推荐:经典输入" } \ No newline at end of file diff --git a/client/src/services/storage.ts b/client/src/services/storage.ts index de2d7ff69d..b001321539 100644 --- a/client/src/services/storage.ts +++ b/client/src/services/storage.ts @@ -43,3 +43,4 @@ export const USER_FONT_SIZE_KEY = 'user_font_size'; export const PROJECT_KEY = 'project'; export const RECENT_COMMANDS_KEY = 'recent_commands'; export const RECENT_FILES_KEY = 'recent_files'; +export const CHAT_INPUT_TYPE_KEY = 'chat_input_type'; diff --git a/client/src/types/general.ts b/client/src/types/general.ts index 49fa052ada..b052d25fd6 100644 --- a/client/src/types/general.ts +++ b/client/src/types/general.ts @@ -626,3 +626,5 @@ export type OnboardingStateType = { isCodeExplained?: boolean; isCodeNavigated?: boolean; }; + +export type ChatInputType = 'default' | 'simplified';