diff --git a/app/command.ts b/app/command.ts index 40bad92b3..ba3bb6538 100644 --- a/app/command.ts +++ b/app/command.ts @@ -1,4 +1,5 @@ import { useSearchParams } from "react-router-dom"; +import Locale from "./locales"; type Command = (param: string) => void; interface Commands { @@ -26,3 +27,45 @@ export function useCommand(commands: Commands = {}) { setSearchParams(searchParams); } } + +interface ChatCommands { + new?: Command; + newm?: Command; + next?: Command; + prev?: Command; + clear?: Command; + del?: Command; +} + +export const ChatCommandPrefix = ":"; + +export function useChatCommand(commands: ChatCommands = {}) { + function extract(userInput: string) { + return ( + userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput + ) as keyof ChatCommands; + } + + function search(userInput: string) { + const input = extract(userInput); + const desc = Locale.Chat.Commands; + return Object.keys(commands) + .filter((c) => c.startsWith(input)) + .map((c) => ({ + title: desc[c as keyof ChatCommands], + content: ChatCommandPrefix + c, + })); + } + + function match(userInput: string) { + const command = extract(userInput); + const matched = typeof commands[command] === "function"; + + return { + matched, + invoke: () => matched && commands[command]!(userInput), + }; + } + + return { match, search }; +} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 047607d4e..e1011e422 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -3,8 +3,8 @@ import React, { useState, useRef, useEffect, - useLayoutEffect, useMemo, + useCallback, } from "react"; import SendWhiteIcon from "../icons/send-white.svg"; @@ -27,6 +27,7 @@ import DarkIcon from "../icons/dark.svg"; import AutoIcon from "../icons/auto.svg"; import BottomIcon from "../icons/bottom.svg"; import StopIcon from "../icons/pause.svg"; +import RobotIcon from "../icons/robot.svg"; import { ChatMessage, @@ -38,6 +39,7 @@ import { Theme, useAppConfig, DEFAULT_TOPIC, + ALL_MODELS, } from "../store"; import { @@ -64,7 +66,7 @@ import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant"; import { Avatar } from "./emoji"; import { MaskAvatar, MaskConfig } from "./mask"; import { useMaskStore } from "../store/mask"; -import { useCommand } from "../command"; +import { ChatCommandPrefix, useChatCommand, useCommand } from "../command"; import { prettyObject } from "../utils/format"; import { ExportMessageModal } from "./exporter"; import { getClientConfig } from "../config/client"; @@ -206,8 +208,7 @@ export function PromptHints(props: { useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { - if (noPrompts) return; - if (e.metaKey || e.altKey || e.ctrlKey) { + if (noPrompts || e.metaKey || e.altKey || e.ctrlKey) { return; } // arrow up / down to select prompt @@ -341,15 +342,15 @@ function useScrollToBottom() { // for auto-scroll const scrollRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); - const scrollToBottom = () => { + const scrollToBottom = useCallback(() => { const dom = scrollRef.current; if (dom) { - setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1); + requestAnimationFrame(() => dom.scrollTo(0, dom.scrollHeight)); } - }; + }, []); // auto scroll - useLayoutEffect(() => { + useEffect(() => { autoScroll && scrollToBottom(); }); @@ -385,6 +386,19 @@ export function ChatActions(props: { const couldStop = ChatControllerPool.hasPending(); const stopAll = () => ChatControllerPool.stopAll(); + // switch model + const currentModel = chatStore.currentSession().mask.modelConfig.model; + function nextModel() { + const models = ALL_MODELS.filter((m) => m.available).map((m) => m.name); + const modelIndex = models.indexOf(currentModel); + const nextIndex = (modelIndex + 1) % models.length; + const nextModel = models[nextIndex]; + chatStore.updateCurrentSession((session) => { + session.mask.modelConfig.model = nextModel; + session.mask.syncGlobalConfig = false; + }); + } + return (
{couldStop && ( @@ -453,6 +467,12 @@ export function ChatActions(props: { }); }} /> + + } + />
); } @@ -489,16 +509,19 @@ export function Chat() { const [promptHints, setPromptHints] = useState([]); const onSearch = useDebouncedCallback( (text: string) => { - setPromptHints(promptStore.search(text)); + const matchedPrompts = promptStore.search(text); + setPromptHints(matchedPrompts); }, 100, { leading: true, trailing: true }, ); const onPromptSelect = (prompt: Prompt) => { - setPromptHints([]); - inputRef.current?.focus(); - setTimeout(() => setUserInput(prompt.content), 60); + setTimeout(() => { + setPromptHints([]); + setUserInput(prompt.content); + inputRef.current?.focus(); + }, 30); }; // auto grow input @@ -522,6 +545,19 @@ export function Chat() { // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(measure, [userInput]); + // chat commands shortcuts + const chatCommands = useChatCommand({ + new: () => chatStore.newSession(), + newm: () => navigate(Path.NewChat), + prev: () => chatStore.nextSession(-1), + next: () => chatStore.nextSession(1), + clear: () => + chatStore.updateCurrentSession( + (session) => (session.clearContextIndex = session.messages.length), + ), + del: () => chatStore.deleteSession(chatStore.currentSessionIndex), + }); + // only search prompts when user input is short const SEARCH_TEXT_LIMIT = 30; const onInput = (text: string) => { @@ -531,6 +567,8 @@ export function Chat() { // clear search results if (n === 0) { setPromptHints([]); + } else if (text.startsWith(ChatCommandPrefix)) { + setPromptHints(chatCommands.search(text)); } else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { // check if need to trigger auto completion if (text.startsWith("/")) { @@ -542,6 +580,13 @@ export function Chat() { const doSubmit = (userInput: string) => { if (userInput.trim() === "") return; + const matchCommand = chatCommands.match(userInput); + if (matchCommand.matched) { + setUserInput(""); + setPromptHints([]); + matchCommand.invoke(); + return; + } setIsLoading(true); chatStore.onUserInput(userInput).then(() => setIsLoading(false)); localStorage.setItem(LAST_INPUT_KEY, userInput); @@ -605,6 +650,10 @@ export function Chat() { const onRightClick = (e: any, message: ChatMessage) => { // copy to clipboard if (selectOrCopy(e.currentTarget, message.content)) { + if (userInput.length === 0) { + setUserInput(message.content); + } + e.preventDefault(); } }; diff --git a/app/components/home.module.scss b/app/components/home.module.scss index c6fc3674b..81916d71b 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -185,7 +185,7 @@ .chat-item-delete { position: absolute; - top: 10px; + top: 0; right: 0; transition: all ease 0.3s; opacity: 0; @@ -194,7 +194,7 @@ .chat-item:hover > .chat-item-delete { opacity: 0.5; - transform: translateX(-10px); + transform: translateX(-4px); } .chat-item:hover > .chat-item-delete:hover { @@ -283,15 +283,6 @@ } } - .chat-item-delete { - top: 15px; - } - - .chat-item:hover > .chat-item-delete { - opacity: 0.5; - right: 5px; - } - .sidebar-tail { flex-direction: column-reverse; align-items: center; @@ -567,3 +558,7 @@ height: 100%; width: 100%; } + +.rtl-screen { + direction: rtl; +} diff --git a/app/components/home.tsx b/app/components/home.tsx index 46fd78e81..b4b190289 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -15,6 +15,8 @@ import dynamic from "next/dynamic"; import { Path, SlotID } from "../constant"; import { ErrorBoundary } from "./error"; +import { getLang } from "../locales"; + import { HashRouter as Router, Routes, @@ -124,7 +126,7 @@ function Screen() { config.tightBorder && !isMobileScreen ? styles["tight-container"] : styles.container - }` + } ${getLang() === "ar" ? styles["rtl-screen"] : ""}` } > {isAuth ? ( diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index fbde64530..4db5f573b 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -195,6 +195,7 @@ export function Markdown( fontSize: `${props.fontSize ?? 14}px`, height: getSize(renderedHeight.current), width: getSize(renderedWidth.current), + direction: /[\u0600-\u06FF]/.test(props.content) ? "rtl" : "ltr", }} ref={mdRef} onContextMenu={props.onContextMenu} diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 33e7e3c1c..12820ef6f 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -38,13 +38,10 @@ function useHotKey() { useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.metaKey || e.altKey || e.ctrlKey) { - const n = chatStore.sessions.length; - const limit = (x: number) => (x + n) % n; - const i = chatStore.currentSessionIndex; if (e.key === "ArrowUp") { - chatStore.selectSession(limit(i - 1)); + chatStore.nextSession(-1); } else if (e.key === "ArrowDown") { - chatStore.selectSession(limit(i + 1)); + chatStore.nextSession(1); } } }; diff --git a/app/constant.ts b/app/constant.ts index 6630b7043..59a8c1b50 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -53,9 +53,4 @@ export const OpenaiPath = { SubsPath: "dashboard/billing/subscription", }; -export const DEFAULT_INPUT_TEMPLATE = ` -Act as a virtual assistant powered by model: '{{model}}', my input is: -''' -{{input}} -''' -`; +export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang diff --git a/app/icons/robot.svg b/app/icons/robot.svg new file mode 100644 index 000000000..62dd9dc8d --- /dev/null +++ b/app/icons/robot.svg @@ -0,0 +1 @@ + diff --git a/app/locales/ar.ts b/app/locales/ar.ts new file mode 100644 index 000000000..7a3eaa2b2 --- /dev/null +++ b/app/locales/ar.ts @@ -0,0 +1,296 @@ +import { SubmitKey } from "../store/config"; +import type { PartialLocaleType } from "./index"; + +const ar: PartialLocaleType = { + WIP: "قريبًا...", + Error: { + Unauthorized: + "غير مصرح بالوصول، يرجى إدخال رمز الوصول [auth](/#/auth) في صفحة المصادقة.", + }, + Auth: { + Title: "تحتاج إلى رمز الوصول", + Tips: "يرجى إدخال رمز الوصول أدناه", + Input: "رمز الوصول", + Confirm: "تأكيد", + Later: "لاحقًا", + }, + ChatItem: { + ChatItemCount: (count: number) => `${count} رسائل`, + }, + Chat: { + SubTitle: (count: number) => ` ${count} رسائل مع ChatGPT`, + Actions: { + ChatList: "الانتقال إلى قائمة الدردشة", + CompressedHistory: "ملخص ضغط ذاكرة التاريخ", + Export: "تصدير جميع الرسائل كـ Markdown", + Copy: "نسخ", + Stop: "توقف", + Retry: "إعادة المحاولة", + Delete: "حذف", + }, + InputActions: { + Stop: "توقف", + ToBottom: "إلى آخر", + Theme: { + auto: "تلقائي", + light: "نمط فاتح", + dark: "نمط داكن", + }, + Prompt: "الاقتراحات", + Masks: "الأقنعة", + Clear: "مسح السياق", + Settings: "الإعدادات", + }, + Rename: "إعادة تسمية الدردشة", + Typing: "كتابة...", + Input: (submitKey: string) => { + var inputHints = ` اضغط على ${submitKey} للإرسال`; + if (submitKey === String(SubmitKey.Enter)) { + inputHints += "، Shift + Enter للإنشاء"; + } + return inputHints + "، / للبحث في الاقتراحات"; + }, + Send: "إرسال", + Config: { + Reset: "إعادة التعيين إلى الإعدادات الافتراضية", + SaveAs: "حفظ كأقنعة", + }, + }, + Export: { + Title: "تصدير الرسائل", + Copy: "نسخ الكل", + Download: "تنزيل", + MessageFromYou: "رسالة منك", + MessageFromChatGPT: "رسالة من ChatGPT", + Share: "مشاركة على ShareGPT", + Format: { + Title: "صيغة التصدير", + SubTitle: "Markdown أو صورة PNG", + }, + IncludeContext: { + Title: "تضمين السياق", + SubTitle: "تصدير اقتراحات السياق في الأقنعة أم لا", + }, + Steps: { + Select: "تحديد", + Preview: "معاينة", + }, + }, + Select: { + Search: "بحث", + All: "تحديد الكل", + Latest: "تحديد أحدث", + Clear: "مسح", + }, + Memory: { + Title: "اقتراحات الذاكرة", + EmptyContent: "لا شيء حتى الآن.", + Send: "إرسال الذاكرة", + Copy: "نسخ الذاكرة", + Reset: "إعادة التعيين", + ResetConfirm: + "سيؤدي إعادة التعيين إلى مسح سجل المحادثة الحالي والذاكرة التاريخية. هل أنت متأكد أنك تريد الاستمرار؟", + }, + Home: { + NewChat: "دردشة جديدة", + DeleteChat: "هل تريد تأكيد حذف المحادثة المحددة؟", + DeleteToast: "تم حذف الدردشة", + Revert: "التراجع", + }, + Settings: { + Title: "الإعدادات", + SubTitle: "جميع الإعدادات", + Actions: { + ClearAll: "مسح جميع البيانات", + ResetAll: "إعادة تعيين جميع الإعدادات", + Close: "إغلاق", + ConfirmResetAll: "هل أنت متأكد من رغبتك في إعادة تعيين جميع الإعدادات؟", + ConfirmClearAll: "هل أنت متأكد من رغبتك في مسح جميع البيانات؟", + }, + Lang: { + Name: "Language", // تنبيه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة وتركها "Language" + All: "كل اللغات", + }, + Avatar: "الصورة الرمزية", + FontSize: { + Title: "حجم الخط", + SubTitle: "ضبط حجم الخط لمحتوى الدردشة", + }, + InputTemplate: { + Title: "نموذج الإدخال", + SubTitle: "سيتم ملء أحدث رسالة في هذا النموذج", + }, + Update: { + Version: (x: string) => ` الإصدار: ${x}`, + IsLatest: "أحدث إصدار", + CheckUpdate: "التحقق من التحديث", + IsChecking: "جارٍ التحقق من التحديث...", + FoundUpdate: (x: string) => ` تم العثور على إصدار جديد: ${x}`, + GoToUpdate: "التحديث", + }, + SendKey: "مفتاح الإرسال", + Theme: "السمة", + TightBorder: "حدود ضيقة", + SendPreviewBubble: { + Title: "عرض معاينة الـ Send", + SubTitle: "معاينة Markdown في فقاعة", + }, + Mask: { + Title: "شاشة تظهر الأقنعة", + SubTitle: "عرض شاشة تظهر الأقنعة قبل بدء الدردشة الجديدة", + }, + Prompt: { + Disable: { + Title: "تعطيل الاكتمال التلقائي", + SubTitle: "اكتب / لتشغيل الاكتمال التلقائي", + }, + List: "قائمة الاقتراحات", + ListCount: (builtin: number, custom: number) => ` +${builtin} مدمجة، ${custom} تم تعريفها من قبل المستخدم`, + Edit: "تعديل", + Modal: { + Title: "قائمة الاقتراحات", + Add: "إضافة واحدة", + Search: "البحث في الاقتراحات", + }, + EditModal: { + Title: "تحرير الاقتراح", + }, + }, + HistoryCount: { + Title: "عدد الرسائل المرفقة", + SubTitle: "عدد الرسائل المرسلة المرفقة في كل طلب", + }, + CompressThreshold: { + Title: "حد الضغط للتاريخ", + SubTitle: "سيتم الضغط إذا تجاوزت طول الرسائل غير المضغوطة الحد المحدد", + }, + Token: { + Title: "مفتاح API", + SubTitle: "استخدم مفتاحك لتجاوز حد رمز الوصول", + Placeholder: "مفتاح OpenAI API", + }, + Usage: { + Title: "رصيد الحساب", + SubTitle(used: any, total: any) { + return `تم استخدام $${used} من هذا الشهر، الاشتراك ${total}`; + }, + IsChecking: "جارٍ التحقق...", + Check: "التحقق", + NoAccess: "أدخل مفتاح API للتحقق من الرصيد", + }, + AccessCode: { + Title: "رمز الوصول", + SubTitle: "تم تمكين التحكم في الوصول", + Placeholder: "رمز الوصول المطلوب", + }, + Endpoint: { + Title: "نقطة النهاية", + SubTitle: "يجب أن تبدأ نقطة النهاية المخصصة بـ http(s)://", + }, + Model: "النموذج", + Temperature: { + Title: "الحرارة", + SubTitle: "قيمة أكبر تجعل الإخراج أكثر عشوائية", + }, + MaxTokens: { + Title: "الحد الأقصى للرموز", + SubTitle: "الحد الأقصى لعدد الرموز المدخلة والرموز المُنشأة", + }, + PresencePenalty: { + Title: "تأثير الوجود", + SubTitle: "قيمة أكبر تزيد من احتمالية التحدث عن مواضيع جديدة", + }, + FrequencyPenalty: { + Title: "تأثير التكرار", + SubTitle: "قيمة أكبر تقلل من احتمالية تكرار نفس السطر", + }, + }, + Store: { + DefaultTopic: "محادثة جديدة", + BotHello: "مرحبًا! كيف يمكنني مساعدتك اليوم؟", + Error: "حدث خطأ ما، يرجى المحاولة مرة أخرى في وقت لاحق.", + Prompt: { + History: (content: string) => "هذا ملخص لسجل الدردشة كمراجعة: " + content, + Topic: + "يرجى إنشاء عنوان يتكون من أربع إلى خمس كلمات يلخص محادثتنا دون أي مقدمة أو ترقيم أو علامات ترقيم أو نقاط أو رموز إضافية. قم بإزالة علامات التنصيص المحيطة.", + Summarize: + "قم بتلخيص النقاش بشكل موجز في 200 كلمة أو أقل لاستخدامه كاقتراح للسياق في المستقبل.", + }, + }, + Copy: { + Success: "تم النسخ إلى الحافظة", + Failed: "فشلت عملية النسخ، يرجى منح الإذن للوصول إلى الحافظة", + }, + Context: { + Toast: (x: any) => `مع ${x} اقتراحًا ذا سياق`, + Edit: "الاقتراحات السياقية والذاكرة", + Add: "إضافة اقتراح", + Clear: "مسح السياق", + Revert: "التراجع", + }, + Plugin: { + Name: "المكوّن الإضافي", + }, + Mask: { + Name: "الأقنعة", + Page: { + Title: "قالب الاقتراح", + SubTitle: (count: number) => `${count} قوالب الاقتراح`, + Search: "البحث في القوالب", + Create: "إنشاء", + }, + Item: { + Info: (count: number) => `${count} اقتراحات`, + Chat: "الدردشة", + View: "عرض", + Edit: "تعديل", + Delete: "حذف", + DeleteConfirm: "تأكيد الحذف؟", + }, + EditModal: { + Title: (readonly: boolean) => ` +تعديل قالب الاقتراح ${readonly ? "(للقراءة فقط)" : ""}`, + Download: "تنزيل", + Clone: "استنساخ", + }, + Config: { + Avatar: "صورة الروبوت", + Name: "اسم الروبوت", + Sync: { + Title: "استخدام الإعدادات العامة", + SubTitle: "استخدام الإعدادات العامة في هذه الدردشة", + Confirm: "تأكيد الاستبدال بالإعدادات المخصصة بالإعدادات العامة؟", + }, + HideContext: { + Title: "إخفاء اقتراحات السياق", + SubTitle: "عدم عرض اقتراحات السياق في الدردشة", + }, + }, + }, + NewChat: { + Return: "العودة", + Skip: "ابدأ فقط", + Title: "اختيار قناع", + SubTitle: "دردشة مع الروح وراء القناع", + More: "المزيد", + NotShow: "عدم العرض مرة أخرى", + ConfirmNoShow: "تأكيد تعطيله؟ يمكنك تمكينه في الإعدادات لاحقًا.", + }, + + UI: { + Confirm: "تأكيد", + Cancel: "إلغاء", + Close: "إغلاق", + Create: "إنشاء", + Edit: "تعديل", + }, + Exporter: { + Model: "النموذج", + Messages: "الرسائل", + Topic: "الموضوع", + Time: "الوقت", + }, +}; + +export default ar; diff --git a/app/locales/cn.ts b/app/locales/cn.ts index beaef6d85..581a5a3af 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -27,6 +27,14 @@ const cn = { Retry: "重试", Delete: "删除", }, + Commands: { + new: "新建聊天", + newm: "从面具新建聊天", + next: "下一个聊天", + prev: "上一个聊天", + clear: "清除上下文", + del: "删除聊天", + }, InputActions: { Stop: "停止响应", ToBottom: "滚到最新", @@ -47,7 +55,7 @@ const cn = { if (submitKey === String(SubmitKey.Enter)) { inputHints += ",Shift + Enter 换行"; } - return inputHints + ",/ 触发补全"; + return inputHints + ",/ 触发补全,: 触发命令"; }, Send: "发送", Config: { diff --git a/app/locales/en.ts b/app/locales/en.ts index a7cf32bad..bc71bf92f 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -28,6 +28,14 @@ const en: LocaleType = { Retry: "Retry", Delete: "Delete", }, + Commands: { + new: "Start a new chat", + newm: "Start a new chat with mask", + next: "Next Chat", + prev: "Previous Chat", + clear: "Clear Context", + del: "Delete Chat", + }, InputActions: { Stop: "Stop", ToBottom: "To Latest", @@ -48,7 +56,7 @@ const en: LocaleType = { if (submitKey === String(SubmitKey.Enter)) { inputHints += ", Shift + Enter to wrap"; } - return inputHints + ", / to search prompts"; + return inputHints + ", / to search prompts, : to use commands"; }, Send: "Send", Config: { diff --git a/app/locales/index.ts b/app/locales/index.ts index 38d54770e..abdb4eaab 100644 --- a/app/locales/index.ts +++ b/app/locales/index.ts @@ -12,6 +12,7 @@ import ru from "./ru"; import no from "./no"; import cs from "./cs"; import ko from "./ko"; +import ar from "./ar"; import { merge } from "../utils/merge"; import type { LocaleType } from "./cn"; @@ -32,6 +33,7 @@ const ALL_LANGS = { ru, cs, no, + ar, }; export type Lang = keyof typeof ALL_LANGS; @@ -53,6 +55,7 @@ export const ALL_LANG_OPTIONS: Record = { ru: "Русский", cs: "Čeština", no: "Nynorsk", + ar: "العربية", }; const LANG_KEY = "lang"; diff --git a/app/store/chat.ts b/app/store/chat.ts index d311c88ff..fa6296811 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -85,6 +85,7 @@ interface ChatStore { newSession: (mask?: Mask) => void; deleteSession: (index: number) => void; currentSession: () => ChatSession; + nextSession: (delta: number) => void; onNewMessage: (message: ChatMessage) => void; onUserInput: (content: string) => Promise; summarizeSession: () => void; @@ -200,6 +201,13 @@ export const useChatStore = create()( })); }, + nextSession(delta) { + const n = get().sessions.length; + const limit = (x: number) => (x + n) % n; + const i = get().currentSessionIndex; + get().selectSession(limit(i + delta)); + }, + deleteSession(index) { const deletingLastSession = get().sessions.length === 1; const deletedSession = get().sessions.at(index); @@ -405,10 +413,9 @@ export const useChatStore = create()( // 2. pre-defined in-context prompts // 3. short term memory: latest n messages // 4. newest input message - const memoryStartIndex = Math.min( - longTermMemoryStartIndex, - shortTermMemoryStartIndex, - ); + const memoryStartIndex = shouldSendLongTermMemory + ? Math.min(longTermMemoryStartIndex, shortTermMemoryStartIndex) + : shortTermMemoryStartIndex; // and if user has cleared history messages, we should exclude the memory too. const contextStartIndex = Math.max(clearContextIndex, memoryStartIndex); const maxTokenThreshold = modelConfig.max_tokens; diff --git a/app/styles/markdown.scss b/app/styles/markdown.scss index 31d10c4c4..672167d4d 100644 --- a/app/styles/markdown.scss +++ b/app/styles/markdown.scss @@ -844,6 +844,7 @@ font-size: 85%; line-height: 1.45; border-radius: 6px; + direction: ltr; } .markdown-body pre code,