diff --git a/app/api/openai.ts b/app/api/openai.ts index 0059ff8b4..7dfd84e17 100644 --- a/app/api/openai.ts +++ b/app/api/openai.ts @@ -13,7 +13,9 @@ function getModels(remoteModelRes: OpenAIListModelResponse) { if (config.disableGPT4) { remoteModelRes.data = remoteModelRes.data.filter( - (m) => !m.id.startsWith("gpt-4") || m.id.startsWith("gpt-4o-mini"), + (m) => + !(m.id.startsWith("gpt-4") || m.id.startsWith("chatgpt-4o")) || + m.id.startsWith("gpt-4o-mini"), ); } diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 780ef0676..664ff872b 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -160,6 +160,7 @@ export class ChatGPTApi implements LLMApi { let requestPayload: RequestPayload | DalleRequestPayload; const isDalle3 = _isDalle3(options.config.model); + const isO1 = options.config.model.startsWith("o1"); if (isDalle3) { const prompt = getMessageTextContent( options.messages.slice(-1)?.pop() as any, @@ -181,30 +182,32 @@ export class ChatGPTApi implements LLMApi { const content = visionModel ? await preProcessImageContent(v.content) : getMessageTextContent(v); - messages.push({ role: v.role, content }); + if (!(isO1 && v.role === "system")) + messages.push({ role: v.role, content }); } + // O1 not support image, tools (plugin in ChatGPTNextWeb) and system, stream, logprobs, temperature, top_p, n, presence_penalty, frequency_penalty yet. requestPayload = { messages, - stream: options.config.stream, + stream: !isO1 ? options.config.stream : false, model: modelConfig.model, - temperature: modelConfig.temperature, - presence_penalty: modelConfig.presence_penalty, - frequency_penalty: modelConfig.frequency_penalty, - top_p: modelConfig.top_p, + temperature: !isO1 ? modelConfig.temperature : 1, + presence_penalty: !isO1 ? modelConfig.presence_penalty : 0, + frequency_penalty: !isO1 ? modelConfig.frequency_penalty : 0, + top_p: !isO1 ? modelConfig.top_p : 1, // max_tokens: Math.max(modelConfig.max_tokens, 1024), // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore. }; // add max_tokens to vision model - if (visionModel && modelConfig.model.includes("preview")) { + if (visionModel) { requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000); } } console.log("[Request] openai payload: ", requestPayload); - const shouldStream = !isDalle3 && !!options.config.stream; + const shouldStream = !isDalle3 && !!options.config.stream && !isO1; const controller = new AbortController(); options.onController?.(controller); @@ -313,7 +316,7 @@ export class ChatGPTApi implements LLMApi { // make a fetch request const requestTimeoutId = setTimeout( () => controller.abort(), - isDalle3 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow. + isDalle3 || isO1 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow. ); const res = await fetch(chatPath, chatPayload); @@ -407,7 +410,9 @@ export class ChatGPTApi implements LLMApi { }); const resJson = (await res.json()) as OpenAIListModelResponse; - const chatModels = resJson.data?.filter((m) => m.id.startsWith("gpt-")); + const chatModels = resJson.data?.filter( + (m) => m.id.startsWith("gpt-") || m.id.startsWith("chatgpt-"), + ); console.log("[Models]", chatModels); if (!chatModels) { diff --git a/app/components/artifacts.tsx b/app/components/artifacts.tsx index ac0d713b3..d725ee659 100644 --- a/app/components/artifacts.tsx +++ b/app/components/artifacts.tsx @@ -80,7 +80,7 @@ export const HTMLPreview = forwardRef( }, [props.autoHeight, props.height, iframeHeight]); const srcDoc = useMemo(() => { - const script = ``; + const script = ``; if (props.code.includes("")) { props.code.replace("", "" + script); } diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 7176399cc..73542fc67 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -646,3 +646,51 @@ bottom: 30px; } } + +.shortcut-key-container { + padding: 10px; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.shortcut-key-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 16px; +} + +.shortcut-key-item { + display: flex; + justify-content: space-between; + align-items: center; + overflow: hidden; + padding: 10px; + background-color: var(--white); +} + +.shortcut-key-title { + font-size: 14px; + color: var(--black); +} + +.shortcut-key-keys { + display: flex; + gap: 8px; +} + +.shortcut-key { + display: flex; + align-items: center; + justify-content: center; + border: var(--border-in-light); + border-radius: 8px; + padding: 4px; + background-color: var(--gray); + min-width: 32px; +} + +.shortcut-key span { + font-size: 12px; + color: var(--black); +} \ No newline at end of file diff --git a/app/components/chat.tsx b/app/components/chat.tsx index dad1933ac..dafb98464 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -42,6 +42,7 @@ import SizeIcon from "../icons/size.svg"; import QualityIcon from "../icons/hd.svg"; import StyleIcon from "../icons/palette.svg"; import PluginIcon from "../icons/plugin.svg"; +import ShortcutkeyIcon from "../icons/shortcutkey.svg"; import { ChatMessage, @@ -67,6 +68,7 @@ import { isVisionModel, isDalle3, showPlugins, + safeLocalStorage, } from "../utils"; import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; @@ -109,6 +111,8 @@ import { getClientConfig } from "../config/client"; import { useAllModels } from "../utils/hooks"; import { MultimodalContent } from "../client/api"; +const localStorage = safeLocalStorage(); + const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { loading: () => , }); @@ -188,7 +192,7 @@ function PromptToast(props: { return (
- {props.showToast && ( + {props.showToast && context.length > 0 && (
void; hitBottom: boolean; uploading: boolean; + setShowShortcutKeyModal: React.Dispatch>; }) { const config = useAppConfig(); const navigate = useNavigate(); @@ -502,6 +507,8 @@ export function ChatActions(props: { const currentStyle = chatStore.currentSession().mask.modelConfig?.style ?? "vivid"; + const isMobileScreen = useMobileScreen(); + useEffect(() => { const show = isVisionModel(currentModel); setShowUploadImage(show); @@ -755,6 +762,14 @@ export function ChatActions(props: { }} /> )} + + {!isMobileScreen && ( + props.setShowShortcutKeyModal(true)} + text={Locale.Chat.ShortcutKey.Title} + icon={} + /> + )}
); } @@ -829,6 +844,67 @@ export function DeleteImageButton(props: { deleteImage: () => void }) { ); } +export function ShortcutKeyModal(props: { onClose: () => void }) { + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const shortcuts = [ + { + title: Locale.Chat.ShortcutKey.newChat, + keys: isMac ? ["⌘", "Shift", "O"] : ["Ctrl", "Shift", "O"], + }, + { title: Locale.Chat.ShortcutKey.focusInput, keys: ["Shift", "Esc"] }, + { + title: Locale.Chat.ShortcutKey.copyLastCode, + keys: isMac ? ["⌘", "Shift", ";"] : ["Ctrl", "Shift", ";"], + }, + { + title: Locale.Chat.ShortcutKey.copyLastMessage, + keys: isMac ? ["⌘", "Shift", "C"] : ["Ctrl", "Shift", "C"], + }, + { + title: Locale.Chat.ShortcutKey.showShortcutKey, + keys: isMac ? ["⌘", "/"] : ["Ctrl", "/"], + }, + ]; + return ( +
+ } + key="ok" + onClick={() => { + props.onClose(); + }} + />, + ]} + > +
+
+ {shortcuts.map((shortcut, index) => ( +
+
+ {shortcut.title} +
+
+ {shortcut.keys.map((key, i) => ( +
+ {key} +
+ ))} +
+
+ ))} +
+
+
+
+ ); +} + function _Chat() { type RenderMessage = ChatMessage & { preview?: boolean }; @@ -941,7 +1017,7 @@ function _Chat() { .onUserInput(userInput, attachImages) .then(() => setIsLoading(false)); setAttachImages([]); - localStorage.setItem(LAST_INPUT_KEY, userInput); + chatStore.setLastInput(userInput); setUserInput(""); setPromptHints([]); if (!isMobileScreen) inputRef.current?.focus(); @@ -1007,7 +1083,7 @@ function _Chat() { userInput.length <= 0 && !(e.metaKey || e.altKey || e.ctrlKey) ) { - setUserInput(localStorage.getItem(LAST_INPUT_KEY) ?? ""); + setUserInput(chatStore.lastInput ?? ""); e.preventDefault(); return; } @@ -1373,6 +1449,70 @@ function _Chat() { setAttachImages(images); } + // 快捷键 shortcut keys + const [showShortcutKeyModal, setShowShortcutKeyModal] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: any) => { + // 打开新聊天 command + shift + o + if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.key.toLowerCase() === "o" + ) { + event.preventDefault(); + setTimeout(() => { + chatStore.newSession(); + navigate(Path.Chat); + }, 10); + } + // 聚焦聊天输入 shift + esc + else if (event.shiftKey && event.key.toLowerCase() === "escape") { + event.preventDefault(); + inputRef.current?.focus(); + } + // 复制最后一个代码块 command + shift + ; + else if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.code === "Semicolon" + ) { + event.preventDefault(); + const copyCodeButton = + document.querySelectorAll(".copy-code-button"); + if (copyCodeButton.length > 0) { + copyCodeButton[copyCodeButton.length - 1].click(); + } + } + // 复制最后一个回复 command + shift + c + else if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.key.toLowerCase() === "c" + ) { + event.preventDefault(); + const lastNonUserMessage = messages + .filter((message) => message.role !== "user") + .pop(); + if (lastNonUserMessage) { + const lastMessageContent = getMessageTextContent(lastNonUserMessage); + copyToClipboard(lastMessageContent); + } + } + // 展示快捷键 command + / + else if ((event.metaKey || event.ctrlKey) && event.key === "/") { + event.preventDefault(); + setShowShortcutKeyModal(true); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [messages, chatStore, navigate]); + return (
@@ -1689,6 +1829,7 @@ function _Chat() { setUserInput("/"); onSearch(""); }} + setShowShortcutKeyModal={setShowShortcutKeyModal} />
); } diff --git a/app/components/emoji.tsx b/app/components/emoji.tsx index 3b1f5e751..6db746c46 100644 --- a/app/components/emoji.tsx +++ b/app/components/emoji.tsx @@ -36,7 +36,8 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { if (props.model) { return (
- {props.model?.startsWith("gpt-4") ? ( + {props.model?.startsWith("gpt-4") || + props.model?.startsWith("chatgpt-4o") ? ( ) : ( diff --git a/app/components/error.tsx b/app/components/error.tsx index c90997d11..4fcf759c1 100644 --- a/app/components/error.tsx +++ b/app/components/error.tsx @@ -8,6 +8,7 @@ import { ISSUE_URL } from "../constant"; import Locale from "../locales"; import { showConfirm } from "./ui-lib"; import { useSyncStore } from "../store/sync"; +import { useChatStore } from "../store/chat"; interface IErrorBoundaryState { hasError: boolean; @@ -30,8 +31,7 @@ export class ErrorBoundary extends React.Component { try { useSyncStore.getState().export(); } finally { - localStorage.clear(); - location.reload(); + useChatStore.getState().clearAllData(); } } diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index dc11c572d..b57fd7490 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -237,9 +237,26 @@ function escapeBrackets(text: string) { ); } +function tryWrapHtmlCode(text: string) { + // try add wrap html code (fixed: html codeblock include 2 newline) + return text + .replace( + /([`]*?)(\w*?)([\n\r]*?)()/g, + (match, quoteStart, lang, newLine, doctype) => { + return !quoteStart ? "\n```html\n" + doctype : match; + }, + ) + .replace( + /(<\/body>)([\r\n\s]*?)(<\/html>)([\n\r]*?)([`]*?)([\n\r]*?)/g, + (match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => { + return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match; + }, + ); +} + function _MarkDownContent(props: { content: string }) { const escapedContent = useMemo(() => { - return escapeBrackets(escapeDollarNumber(props.content)); + return tryWrapHtmlCode(escapeBrackets(escapeDollarNumber(props.content))); }, [props.content]); return ( diff --git a/app/components/mask.tsx b/app/components/mask.tsx index 62503c37a..ee6c7da97 100644 --- a/app/components/mask.tsx +++ b/app/components/mask.tsx @@ -426,16 +426,7 @@ export function MaskPage() { const maskStore = useMaskStore(); const chatStore = useChatStore(); - const [filterLang, setFilterLang] = useState( - () => localStorage.getItem("Mask-language") as Lang | undefined, - ); - useEffect(() => { - if (filterLang) { - localStorage.setItem("Mask-language", filterLang); - } else { - localStorage.removeItem("Mask-language"); - } - }, [filterLang]); + const filterLang = maskStore.language; const allMasks = maskStore .getAll() @@ -542,9 +533,9 @@ export function MaskPage() { onChange={(e) => { const value = e.currentTarget.value; if (value === Locale.Settings.Lang.All) { - setFilterLang(undefined); + maskStore.setLanguage(undefined); } else { - setFilterLang(value as Lang); + maskStore.setLanguage(value as Lang); } }} > diff --git a/app/config/server.ts b/app/config/server.ts index e953af369..676b0174f 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -120,12 +120,15 @@ export const getServerSideConfig = () => { if (disableGPT4) { if (customModels) customModels += ","; customModels += DEFAULT_MODELS.filter( - (m) => m.name.startsWith("gpt-4") && !m.name.startsWith("gpt-4o-mini"), + (m) => + (m.name.startsWith("gpt-4") || m.name.startsWith("chatgpt-4o")) && + !m.name.startsWith("gpt-4o-mini"), ) .map((m) => "-" + m.name) .join(","); if ( - defaultModel.startsWith("gpt-4") && + (defaultModel.startsWith("gpt-4") || + defaultModel.startsWith("chatgpt-4o")) && !defaultModel.startsWith("gpt-4o-mini") ) defaultModel = ""; diff --git a/app/constant.ts b/app/constant.ts index 90557c16c..3d33a047e 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -246,9 +246,12 @@ export const KnowledgeCutOffDate: Record = { "gpt-4o": "2023-10", "gpt-4o-2024-05-13": "2023-10", "gpt-4o-2024-08-06": "2023-10", + "chatgpt-4o-latest": "2023-10", "gpt-4o-mini": "2023-10", "gpt-4o-mini-2024-07-18": "2023-10", "gpt-4-vision-preview": "2023-04", + "o1-mini": "2023-10", + "o1-preview": "2023-10", // After improvements, // it's now easier to add "KnowledgeCutOffDate" instead of stupid hardcoding it, as was done previously. "gemini-pro": "2023-12", @@ -268,12 +271,15 @@ const openaiModels = [ "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", + "chatgpt-4o-latest", "gpt-4o-mini", "gpt-4o-mini-2024-07-18", "gpt-4-vision-preview", "gpt-4-turbo-2024-04-09", "gpt-4-1106-preview", "dall-e-3", + "o1-mini", + "o1-preview" ]; const googleModels = [ diff --git a/app/icons/shortcutkey.svg b/app/icons/shortcutkey.svg new file mode 100644 index 000000000..32a4e7d3e --- /dev/null +++ b/app/icons/shortcutkey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 33e368f69..92e81bcb1 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -1,3 +1,4 @@ +import { ShortcutKeyModal } from "../components/chat"; import { getClientConfig } from "../config/client"; import { SubmitKey } from "../store/config"; @@ -81,6 +82,14 @@ const cn = { SaveAs: "存为面具", }, IsContext: "预设提示词", + ShortcutKey: { + Title: "键盘快捷方式", + newChat: "打开新聊天", + focusInput: "聚焦输入框", + copyLastMessage: "复制最后一个回复", + copyLastCode: "复制最后一个代码块", + showShortcutKey: "显示快捷方式", + }, }, Export: { Title: "分享聊天记录", @@ -495,8 +504,8 @@ const cn = { }, }, Copy: { - Success: "已写入剪切板", - Failed: "复制失败,请赋予剪切板权限", + Success: "已写入剪贴板", + Failed: "复制失败,请赋予剪贴板权限", }, Download: { Success: "内容已下载到您的目录。", diff --git a/app/locales/en.ts b/app/locales/en.ts index 403b9b687..09b76f1fa 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -83,6 +83,14 @@ const en: LocaleType = { SaveAs: "Save as Mask", }, IsContext: "Contextual Prompt", + ShortcutKey: { + Title: "Keyboard Shortcuts", + newChat: "Open New Chat", + focusInput: "Focus Input Field", + copyLastMessage: "Copy Last Reply", + copyLastCode: "Copy Last Code Block", + showShortcutKey: "Show Shortcuts", + }, }, Export: { Title: "Export Messages", diff --git a/app/locales/index.ts b/app/locales/index.ts index acdb3e878..ff7e3a262 100644 --- a/app/locales/index.ts +++ b/app/locales/index.ts @@ -18,10 +18,13 @@ import ar from "./ar"; import bn from "./bn"; import sk from "./sk"; import { merge } from "../utils/merge"; +import { safeLocalStorage } from "@/app/utils"; import type { LocaleType } from "./cn"; export type { LocaleType, PartialLocaleType } from "./cn"; +const localStorage = safeLocalStorage(); + const ALL_LANGS = { cn, en, @@ -82,17 +85,11 @@ merge(fallbackLang, targetLang); export default fallbackLang as LocaleType; function getItem(key: string) { - try { - return localStorage.getItem(key); - } catch { - return null; - } + return localStorage.getItem(key); } function setItem(key: string, value: string) { - try { - localStorage.setItem(key, value); - } catch {} + localStorage.setItem(key, value); } function getLanguage() { diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 6b2c0fd65..c54a7b8c5 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -81,6 +81,14 @@ const tw = { SaveAs: "另存新檔", }, IsContext: "預設提示詞", + ShortcutKey: { + Title: "鍵盤快捷方式", + newChat: "打開新聊天", + focusInput: "聚焦輸入框", + copyLastMessage: "複製最後一個回覆", + copyLastCode: "複製最後一個代碼塊", + showShortcutKey: "顯示快捷方式", + }, }, Export: { Title: "將聊天記錄匯出為 Markdown", diff --git a/app/store/chat.ts b/app/store/chat.ts index 8b0cc39eb..58c105e7e 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -26,9 +26,11 @@ import { nanoid } from "nanoid"; import { createPersistStore } from "../utils/store"; import { collectModelsWithDefaultModel } from "../utils/model"; import { useAccessStore } from "./access"; -import { isDalle3 } from "../utils"; +import { isDalle3, safeLocalStorage } from "../utils"; import { indexedDBStorage } from "@/app/utils/indexedDB-storage"; +const localStorage = safeLocalStorage(); + export type ChatMessageTool = { id: string; index?: number; @@ -106,7 +108,7 @@ function createEmptySession(): ChatSession { function getSummarizeModel(currentModel: string) { // if it is using gpt-* models, force to use 4o-mini to summarize - if (currentModel.startsWith("gpt")) { + if (currentModel.startsWith("gpt") || currentModel.startsWith("chatgpt")) { const configStore = useAppConfig.getState(); const accessStore = useAccessStore.getState(); const allModel = collectModelsWithDefaultModel( @@ -179,6 +181,7 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) { const DEFAULT_CHAT_STATE = { sessions: [createEmptySession()], currentSessionIndex: 0, + lastInput: "", }; export const useChatStore = createPersistStore( @@ -476,7 +479,8 @@ export const useChatStore = createPersistStore( // system prompts, to get close to OpenAI Web ChatGPT const shouldInjectSystemPrompts = modelConfig.enableInjectSystemPrompts && - session.mask.modelConfig.model.startsWith("gpt-"); + (session.mask.modelConfig.model.startsWith("gpt-") || + session.mask.modelConfig.model.startsWith("chatgpt-")); var systemPrompts: ChatMessage[] = []; systemPrompts = shouldInjectSystemPrompts @@ -700,6 +704,11 @@ export const useChatStore = createPersistStore( localStorage.clear(); location.reload(); }, + setLastInput(lastInput: string) { + set({ + lastInput, + }); + }, }; return methods; diff --git a/app/store/mask.ts b/app/store/mask.ts index 083121b65..0c74a892e 100644 --- a/app/store/mask.ts +++ b/app/store/mask.ts @@ -23,9 +23,12 @@ export type Mask = { export const DEFAULT_MASK_STATE = { masks: {} as Record, + language: undefined as Lang | undefined, }; -export type MaskState = typeof DEFAULT_MASK_STATE; +export type MaskState = typeof DEFAULT_MASK_STATE & { + language?: Lang | undefined; +}; export const DEFAULT_MASK_AVATAR = "gpt-bot"; export const createEmptyMask = () => @@ -102,6 +105,11 @@ export const useMaskStore = createPersistStore( search(text: string) { return Object.values(get().masks); }, + setLanguage(language: Lang | undefined) { + set({ + language, + }); + }, }), { name: StoreKey.Mask, diff --git a/app/utils.ts b/app/utils.ts index 60041ba06..bf7450929 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -318,3 +318,63 @@ export function adapter(config: Record) { : path; return fetch(fetchUrl as string, { ...rest, responseType: "text" }); } + +export function safeLocalStorage(): { + getItem: (key: string) => string | null; + setItem: (key: string, value: string) => void; + removeItem: (key: string) => void; + clear: () => void; +} { + let storage: Storage | null; + + try { + if (typeof window !== "undefined" && window.localStorage) { + storage = window.localStorage; + } else { + storage = null; + } + } catch (e) { + console.error("localStorage is not available:", e); + storage = null; + } + + return { + getItem(key: string): string | null { + if (storage) { + return storage.getItem(key); + } else { + console.warn( + `Attempted to get item "${key}" from localStorage, but localStorage is not available.`, + ); + return null; + } + }, + setItem(key: string, value: string): void { + if (storage) { + storage.setItem(key, value); + } else { + console.warn( + `Attempted to set item "${key}" in localStorage, but localStorage is not available.`, + ); + } + }, + removeItem(key: string): void { + if (storage) { + storage.removeItem(key); + } else { + console.warn( + `Attempted to remove item "${key}" from localStorage, but localStorage is not available.`, + ); + } + }, + clear(): void { + if (storage) { + storage.clear(); + } else { + console.warn( + "Attempted to clear localStorage, but localStorage is not available.", + ); + } + }, + }; +} diff --git a/app/utils/indexedDB-storage.ts b/app/utils/indexedDB-storage.ts index da3094550..51417e9f3 100644 --- a/app/utils/indexedDB-storage.ts +++ b/app/utils/indexedDB-storage.ts @@ -1,5 +1,8 @@ import { StateStorage } from "zustand/middleware"; import { get, set, del, clear } from "idb-keyval"; +import { safeLocalStorage } from "@/app/utils"; + +const localStorage = safeLocalStorage(); class IndexedDBStorage implements StateStorage { public async getItem(name: string): Promise { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 78835d24d..2a19c9332 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -9,7 +9,7 @@ }, "package": { "productName": "NextChat", - "version": "2.15.1" + "version": "2.15.2" }, "tauri": { "allowlist": {