@lobehub/chat 0.70.3 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.71.0](https://github.com/lobehub/lobe-chat/compare/v0.70.4...v0.71.0)
6
+
7
+ <sup>Released on **2023-09-09**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Migrate localStorage to indexedDB.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Migrate localStorage to indexedDB, closes [#160](https://github.com/lobehub/lobe-chat/issues/160) ([7f96deb](https://github.com/lobehub/lobe-chat/commit/7f96deb))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 0.70.4](https://github.com/lobehub/lobe-chat/compare/v0.70.3...v0.70.4)
31
+
32
+ <sup>Released on **2023-09-09**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Fix route.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix route ([2d1e8d6](https://github.com/lobehub/lobe-chat/commit/2d1e8d6))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.70.3](https://github.com/lobehub/lobe-chat/compare/v0.70.2...v0.70.3)
6
56
 
7
57
  <sup>Released on **2023-09-09**</sup>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "about": "About",
3
3
  "advanceSettings": "Advanced Settings",
4
- "agentDefaultMessage": "Hello, I'm **{{name}}**. You can start chatting with me right away or go to [Assistant Settings](/chat/{{id}}/setting) to complete my information.",
4
+ "agentDefaultMessage": "Hello, I'm **{{name}}**. You can start chatting with me right away or go to [Assistant Settings](/chat/setting#session={{id}}) to complete my information.",
5
5
  "agentDefaultMessageWithSystemRole": "Hello, I'm **{{name}}**, {{systemRole}}. Let's start chatting!",
6
6
  "agentMaxToken": "Max Session Length",
7
7
  "agentModel": "Model",
@@ -62,7 +62,7 @@
62
62
  "stop": "Stop",
63
63
  "tab": {
64
64
  "chat": "Chat",
65
- "market": "Assistant Market",
65
+ "market": "Discover",
66
66
  "setting": "Settings"
67
67
  },
68
68
  "temp": "Temporary",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "about": "Наш Github",
3
3
  "advanceSettings": "Дополнительные настройки",
4
- "agentDefaultMessage": "Здравствуйте, я **{{name}}**. Вы можете начать общаться со мной прямо сейчас или перейти на [Assistant Settings](/chat/{{id}}/setting) для моей настройки.",
4
+ "agentDefaultMessage": "Здравствуйте, я **{{name}}**. Вы можете начать общаться со мной прямо сейчас или перейти на [Assistant Settings](/chat/setting#session={{id}}) для моей настройки.",
5
5
  "agentDefaultMessageWithSystemRole": "Здравствуйте, Я **{{name}}**, {{systemRole}}. Давайте начнем общаться!",
6
6
  "agentMaxToken": "Максимальная длина запроса",
7
7
  "agentModel": "Модель",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "about": "关于",
3
3
  "advanceSettings": "高级设置",
4
- "agentDefaultMessage": "你好,我是 **{{name}}**,你可以立即与我开始对话,也可以前往 [助手设置](/chat/{{id}}/setting) 完善我的信息。",
4
+ "agentDefaultMessage": "你好,我是 **{{name}}**,你可以立即与我开始对话,也可以前往 [助手设置](/chat/setting#session={{id}}) 完善我的信息。",
5
5
  "agentDefaultMessageWithSystemRole": "你好,我是 **{{name}}**,{{systemRole}},让我们开始对话吧!",
6
6
  "agentMaxToken": "会话最大长度",
7
7
  "agentModel": "模型",
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.70.3",
4
- "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
3
+ "version": "0.71.0",
4
+ "description": "Lobe Chat is an open-source chatbot client using Vercel AI SDK, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
7
7
  "ChatGPT",
8
- "LangChain",
8
+ "LobeHub",
9
9
  "typescript",
10
- "Next.js"
10
+ "Vercel AI SDK",
11
+ "Next.js",
12
+ "OpenAI"
11
13
  ],
12
14
  "homepage": "https://github.com/lobehub/lobe-chat",
13
15
  "bugs": {
@@ -80,12 +82,13 @@
80
82
  "fast-deep-equal": "^3",
81
83
  "gpt-tokenizer": "^2",
82
84
  "i18next": "^23",
85
+ "idb-keyval": "^6",
83
86
  "immer": "^10",
84
87
  "lodash-es": "^4",
85
88
  "lucide-react": "latest",
86
89
  "nanoid": "^4",
87
90
  "next": "13.4.7",
88
- "openai": "^4.4.0",
91
+ "openai": "^4.5.0",
89
92
  "polished": "^4",
90
93
  "react": "^18",
91
94
  "react-dom": "^18",
@@ -36,7 +36,7 @@ export default memo<{ className?: string }>(({ className }) => {
36
36
  icon: (active) => <Icon className={active ? styles.active : undefined} icon={Sticker} />,
37
37
  key: 'market',
38
38
  onClick: () => {
39
- Router.push('/market');
39
+ Router.push({ hash: '', pathname: `/market` });
40
40
  },
41
41
  title: t('tab.market'),
42
42
  },
@@ -2,7 +2,7 @@ export default {
2
2
  about: '关于',
3
3
  advanceSettings: '高级设置',
4
4
  agentDefaultMessage:
5
- '你好,我是 **{{name}}**,你可以立即与我开始对话,也可以前往 [助手设置](/chat/{{id}}/setting) 完善我的信息。',
5
+ '你好,我是 **{{name}}**,你可以立即与我开始对话,也可以前往 [助手设置](/chat/setting#session={{id}}) 完善我的信息。',
6
6
  agentDefaultMessageWithSystemRole: '你好,我是 **{{name}}**,{{systemRole}},让我们开始对话吧!',
7
7
  agentMaxToken: '会话最大长度',
8
8
  agentModel: '模型',
@@ -24,7 +24,7 @@ const MobileHeader = memo(() => {
24
24
  return (
25
25
  <MobileNavBar
26
26
  center={<MobileNavBarTitle desc={model} title={displayTitle} />}
27
- onBackClick={() => Router.push('/chat')}
27
+ onBackClick={() => Router.push({ hash: null, pathname: `/chat` })}
28
28
  right={
29
29
  <>
30
30
  <ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
@@ -1,17 +1,15 @@
1
1
  import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
2
+ import Router from 'next/router';
2
3
  import { ReactNode, memo } from 'react';
3
4
  import { useTranslation } from 'react-i18next';
4
5
 
5
- import { useSessionStore } from '@/store/session';
6
-
7
6
  const Header = memo<{ children: ReactNode }>(({ children }) => {
8
7
  const { t } = useTranslation('setting');
9
- const onBack = useSessionStore((s) => s.switchBackToChat);
10
8
 
11
9
  return (
12
10
  <ChatHeader
13
11
  left={<ChatHeaderTitle title={t('header.session')} />}
14
- onBackClick={onBack}
12
+ onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat` })}
15
13
  right={children}
16
14
  showBackButton
17
15
  />
@@ -9,7 +9,7 @@ const Header = memo<{ children: ReactNode }>(({ children }) => {
9
9
  return (
10
10
  <MobileNavBar
11
11
  center={<MobileNavBarTitle title={t('header.session')} />}
12
- onBackClick={() => Router.back()}
12
+ onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat/mobile` })}
13
13
  right={children}
14
14
  showBackButton
15
15
  />
@@ -1,53 +1,29 @@
1
- import { ActionIcon, Logo, SearchBar } from '@lobehub/ui';
1
+ import { ChatHeader, Logo, SearchBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
- import { MessageSquarePlus } from 'lucide-react';
4
3
  import Link from 'next/link';
5
4
  import { memo } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
- import { Flexbox } from 'react-layout-kit';
8
-
9
- import { useSessionStore } from '@/store/session';
10
6
 
11
7
  export const useStyles = createStyles(({ css, token }) => ({
12
8
  logo: css`
9
+ color: ${token.colorText};
13
10
  fill: ${token.colorText};
14
11
  `,
15
- top: css`
16
- position: sticky;
17
- top: 0;
18
- `,
19
12
  }));
20
13
 
21
14
  const Header = memo(() => {
22
15
  const { styles } = useStyles();
23
16
  const { t } = useTranslation('common');
24
- const [keywords, createSession] = useSessionStore((s) => [s.searchKeywords, s.createSession]);
25
17
 
26
18
  return (
27
- <Flexbox className={styles.top} gap={16} padding={16}>
28
- <Flexbox distribution={'space-between'} horizontal>
19
+ <ChatHeader
20
+ left={
29
21
  <Link href={'/'}>
30
- <Logo className={styles.logo} size={36} type={'text'} />
22
+ <Logo className={styles.logo} extra={t('tab.market')} size={36} type={'text'} />
31
23
  </Link>
32
- <ActionIcon
33
- icon={MessageSquarePlus}
34
- onClick={createSession}
35
- size={{ fontSize: 24 }}
36
- style={{ flex: 'none' }}
37
- title={t('newAgent')}
38
- />
39
- </Flexbox>
40
- <SearchBar
41
- allowClear
42
- enableShortKey
43
- onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
44
- placeholder={t('searchAgentPlaceholder')}
45
- shortKey={'k'}
46
- spotlight
47
- type={'ghost'}
48
- value={keywords}
49
- />
50
- </Flexbox>
24
+ }
25
+ right={<SearchBar allowClear spotlight type={'ghost'} />}
26
+ />
51
27
  );
52
28
  });
53
29
 
@@ -9,11 +9,9 @@ export const useGlobalHydrated = () => {
9
9
  const [isInit, setInit] = useState(hasInited);
10
10
 
11
11
  useEffect(() => {
12
- const hasRehydrated = useGlobalStore.persist.hasHydrated();
13
-
14
- if (hasRehydrated && !isInit) {
15
- setInit(true);
16
- }
12
+ useGlobalStore.persist.onFinishHydration(() => {
13
+ if (!isInit) setInit(true);
14
+ });
17
15
  }, []);
18
16
 
19
17
  return isInit;
@@ -9,10 +9,8 @@ import { GlobalStore, useGlobalStore } from '../store';
9
9
  */
10
10
  export const useOnFinishHydrationGlobal = (fn: (state: GlobalStore) => void, deps: any[] = []) => {
11
11
  useEffect(() => {
12
- const hasRehydrated = useGlobalStore.persist.hasHydrated();
13
- // 只有当水合完毕后再开始做操作
14
- if (!hasRehydrated) return;
15
-
16
- fn(useGlobalStore.getState());
12
+ useGlobalStore.persist.onFinishHydration(() => {
13
+ fn(useGlobalStore.getState());
14
+ });
17
15
  }, deps);
18
16
  };
@@ -1,28 +1,40 @@
1
- import { isEmpty } from 'lodash-es';
2
1
  import { PersistStorage } from 'zustand/middleware';
3
2
  import { StorageValue } from 'zustand/middleware/persist';
4
3
 
4
+ import { createIndexedDB } from './indexedDB';
5
5
  import { createKeyMapper } from './keyMapper';
6
+ import { createLocalStorage } from './localStorage';
6
7
  import { HyperStorageOptions } from './type';
7
- import { createUrlSearch } from './urlSearch';
8
+ import { creatUrlStorage } from './urlStorage';
8
9
 
9
10
  export const createHyperStorage = <T extends object>(
10
11
  options: HyperStorageOptions,
11
12
  ): PersistStorage<T> => {
12
13
  const hasUrl = !!options.url;
13
-
14
- const { setUrlSearch, getUrlSearch } = createUrlSearch(options.url?.mode);
14
+ const useIndexedDB = options.localStorage?.mode === 'indexedDB';
15
+ const dbName = options.localStorage?.dbName || 'indexedDB';
15
16
 
16
17
  const { mapStateKeyToStorageKey, getStateKeyFromStorageKey } = createKeyMapper(options);
17
18
 
19
+ const indexedDB = createIndexedDB(dbName);
20
+ const localStorage = createLocalStorage();
21
+ const urlStorage = creatUrlStorage(options.url?.mode);
18
22
  return {
19
- getItem: (name): StorageValue<T> => {
23
+ getItem: async (name): Promise<StorageValue<T>> => {
20
24
  const state: any = {};
21
25
  let version: number | undefined;
26
+
22
27
  // ============== 处理 Local Storage ============== //
23
- const string = localStorage.getItem(name);
24
- if (string) {
25
- const localState: StorageValue<T> = JSON.parse(string);
28
+ let localState: StorageValue<T> | undefined;
29
+
30
+ // 如果使用 indexedDB,优先从 indexedDB 中获取
31
+ if (useIndexedDB) {
32
+ localState = await indexedDB.getItem(name);
33
+ }
34
+ // 如果 indexedDB 中不存在,则再试试 localStorage
35
+ if (!localState) localState = localStorage.getItem(name);
36
+
37
+ if (localState) {
26
38
  version = localState.version;
27
39
  for (const [k, v] of Object.entries(localState.state)) {
28
40
  const key = getStateKeyFromStorageKey(k, 'localStorage');
@@ -33,31 +45,39 @@ export const createHyperStorage = <T extends object>(
33
45
  // ============== 处理 URL Storage ============== //
34
46
  // 不从 URL 中获取 version,由于 url 状态是临时态 不作为版本控制的依据
35
47
  if (hasUrl) {
36
- const searchParameters = new URLSearchParams(getUrlSearch());
37
-
38
- for (const [k, v] of searchParameters.entries()) {
39
- const key = getStateKeyFromStorageKey(k, 'url');
40
- // 当存在 UrlSelector 逻辑,且 key 有效时,才将状态加入终态 state
41
- if (hasUrl && key) {
42
- state[key] = v;
48
+ const urlState = urlStorage.getItem();
49
+
50
+ if (urlState) {
51
+ for (const [k, v] of Object.entries(urlState.state)) {
52
+ const key = getStateKeyFromStorageKey(k, 'url');
53
+ // 当存在 UrlSelector 逻辑,且 key 有效时,才将状态加入终态 state
54
+ if (hasUrl && key) {
55
+ state[key] = v;
56
+ }
43
57
  }
44
58
  }
45
59
  }
46
60
 
47
61
  return { state, version };
48
62
  },
49
- removeItem: (key): void => {
63
+ removeItem: async (key) => {
64
+ // ============== 处理 Local Storage ============== //
65
+
66
+ if (useIndexedDB) {
67
+ await indexedDB.removeItem(key);
68
+ } else {
69
+ localStorage.removeItem(key);
70
+ }
71
+
50
72
  // ============== 处理 URL Storage ============== //
51
73
  if (hasUrl) {
52
- const searchParameters = new URLSearchParams(getUrlSearch());
53
-
54
74
  const storageKey = getStateKeyFromStorageKey(key, 'url');
55
- if (storageKey) searchParameters.delete(storageKey);
56
75
 
57
- setUrlSearch(searchParameters);
76
+ urlStorage.removeItem(storageKey);
58
77
  }
59
78
  },
60
- setItem: (name, newValue) => {
79
+
80
+ setItem: async (name, newValue) => {
61
81
  // ============== 处理 Local Storage ============== //
62
82
  const localState: Record<string, any> = {};
63
83
  for (const [k, v] of Object.entries(newValue.state)) {
@@ -65,30 +85,24 @@ export const createHyperStorage = <T extends object>(
65
85
  if (localKey) localState[localKey] = v;
66
86
  }
67
87
 
68
- localStorage.setItem(name, JSON.stringify({ ...newValue, state: localState }));
88
+ if (useIndexedDB) {
89
+ await indexedDB.setItem(name, localState, newValue.version);
90
+ } else {
91
+ localStorage.setItem(name, localState, newValue.version);
92
+ }
69
93
 
70
94
  // ============== 处理 URL Storage ============== //
71
95
  if (hasUrl) {
72
- const searchParameters = new URLSearchParams(getUrlSearch());
73
-
74
- for (const [k, v] of Object.entries(newValue.state)) {
75
- const urlKey = mapStateKeyToStorageKey(k, 'url');
76
-
77
- if (!urlKey) continue;
78
-
79
- if (isEmpty(v)) {
80
- searchParameters.delete(urlKey);
81
- continue;
82
- }
83
-
84
- if (typeof v === 'string') {
85
- searchParameters.set(urlKey, v);
86
- } else {
87
- searchParameters.set(urlKey, JSON.stringify(v));
88
- }
89
- }
90
-
91
- setUrlSearch(searchParameters);
96
+ // 转换 key 为目标 key
97
+ const finalEntries = Object.entries(newValue.state)
98
+ .map(([k, v]) => {
99
+ const urlKey = mapStateKeyToStorageKey(k, 'url');
100
+ if (!urlKey) return undefined;
101
+ return [urlKey, v];
102
+ })
103
+ .filter(Boolean) as [string, any][];
104
+
105
+ urlStorage.setItem(name, Object.fromEntries(finalEntries));
92
106
  }
93
107
  },
94
108
  };
@@ -0,0 +1,26 @@
1
+ import { createStore, delMany, getMany, setMany } from 'idb-keyval';
2
+ import { StorageValue } from 'zustand/middleware/persist';
3
+
4
+ export const createIndexedDB = <State extends any>(dbName: string = 'indexedDB') => ({
5
+ getItem: async <T extends State>(name: string): Promise<StorageValue<T> | undefined> => {
6
+ const [version, state] = await getMany(['version', 'state'], createStore(dbName, name));
7
+
8
+ if (!state) return undefined;
9
+
10
+ return { state, version };
11
+ },
12
+ removeItem: async (name: string) => {
13
+ await delMany(['version', 'state'], createStore(dbName, name));
14
+ },
15
+ setItem: async (name: string, state: any, version: number | undefined) => {
16
+ const store = createStore(dbName, name);
17
+
18
+ await setMany(
19
+ [
20
+ ['version', version],
21
+ ['state', state],
22
+ ],
23
+ store,
24
+ );
25
+ },
26
+ });
@@ -0,0 +1,17 @@
1
+ import { StorageValue } from 'zustand/middleware/persist';
2
+
3
+ export const createLocalStorage = <State extends any>() => ({
4
+ getItem: <T extends State>(name: string): StorageValue<T> | undefined => {
5
+ const string = localStorage.getItem(name);
6
+
7
+ if (string) return JSON.parse(string) as StorageValue<T>;
8
+
9
+ return undefined;
10
+ },
11
+ removeItem: (name: string) => {
12
+ localStorage.removeItem(name);
13
+ },
14
+ setItem: <T extends State>(name: string, state: T, version: number | undefined) => {
15
+ localStorage.setItem(name, JSON.stringify({ state, version }));
16
+ },
17
+ });
@@ -2,6 +2,11 @@ export type StorageSelector = string | Record<string, string>;
2
2
 
3
3
  export interface HyperStorageOptions {
4
4
  localStorage?: {
5
+ dbName?: string;
6
+ /**
7
+ * @default 'localStorage'
8
+ */
9
+ mode?: 'indexedDB' | 'localStorage';
5
10
  selectors: StorageSelector[];
6
11
  };
7
12
  url?: {
@@ -0,0 +1,60 @@
1
+ import { isEmpty } from 'lodash-es';
2
+ import { StorageValue } from 'zustand/middleware/persist';
3
+
4
+ interface UrlSearchHelper {
5
+ getUrlSearch: () => string;
6
+ setUrlSearch: (params: URLSearchParams) => void;
7
+ }
8
+
9
+ const createUrlSearch = (mode: 'search' | 'hash' = 'hash'): UrlSearchHelper => {
10
+ if (mode === 'hash')
11
+ return {
12
+ getUrlSearch: () => location.hash.slice(1),
13
+ setUrlSearch: (params: URLSearchParams) => (location.hash = params.toString()),
14
+ };
15
+
16
+ return {
17
+ getUrlSearch: () => location.search.slice(1),
18
+ setUrlSearch: (params: URLSearchParams) => (location.search = params.toString()),
19
+ };
20
+ };
21
+
22
+ export const creatUrlStorage = <State extends object>(mode: 'hash' | 'search' = 'hash') => {
23
+ const { setUrlSearch, getUrlSearch } = createUrlSearch(mode);
24
+
25
+ return {
26
+ getItem: <T extends State>(): StorageValue<T> | undefined => {
27
+ const searchParameters = new URLSearchParams(getUrlSearch());
28
+
29
+ if (searchParameters.size === 0) return undefined;
30
+
31
+ const state = Object.fromEntries(searchParameters.entries()) as T;
32
+
33
+ return { state };
34
+ },
35
+ removeItem: (key?: string) => {
36
+ const searchParameters = new URLSearchParams(getUrlSearch());
37
+ if (key) searchParameters.delete(key);
38
+
39
+ setUrlSearch(searchParameters);
40
+ },
41
+ setItem: <T extends State>(name: string, state: T) => {
42
+ const searchParameters = new URLSearchParams(getUrlSearch());
43
+
44
+ for (const [urlKey, v] of Object.entries(state)) {
45
+ if (isEmpty(v)) {
46
+ searchParameters.delete(urlKey);
47
+ continue;
48
+ }
49
+
50
+ if (typeof v === 'string') {
51
+ searchParameters.set(urlKey, v);
52
+ } else {
53
+ searchParameters.set(urlKey, JSON.stringify(v));
54
+ }
55
+ }
56
+
57
+ setUrlSearch(searchParameters);
58
+ },
59
+ };
60
+ };
@@ -12,10 +12,9 @@ export const useOnFinishHydrationSession = (
12
12
  deps: any[] = [],
13
13
  ) => {
14
14
  useEffect(() => {
15
- const hasRehydrated = useSessionStore.persist.hasHydrated();
16
15
  // 只有当水合完毕后再开始做操作
17
- if (!hasRehydrated) return;
18
-
19
- fn(useSessionStore.getState());
16
+ useSessionStore.persist.onFinishHydration(() => {
17
+ fn(useSessionStore.getState());
18
+ });
20
19
  }, deps);
21
20
  };
@@ -5,14 +5,13 @@ import { useSessionStore } from '../store';
5
5
  export const useSessionHydrated = () => {
6
6
  // 根据 sessions 是否有值来判断是否已经初始化
7
7
  const hasInited = !!Object.values(useSessionStore.getState().sessions).length;
8
+
8
9
  const [isInit, setInit] = useState(hasInited);
9
10
 
10
11
  useEffect(() => {
11
- const hasRehydrated = useSessionStore.persist.hasHydrated();
12
-
13
- if (hasRehydrated && !isInit) {
14
- setInit(true);
15
- }
12
+ useSessionStore.persist.onFinishHydration(() => {
13
+ if (!isInit) setInit(true);
14
+ });
16
15
  }, []);
17
16
 
18
17
  return isInit;
@@ -33,6 +33,8 @@ const persistOptions: PersistOptions<SessionStore> = {
33
33
 
34
34
  storage: createHyperStorage({
35
35
  localStorage: {
36
+ dbName: 'LobeHub',
37
+ mode: 'indexedDB',
36
38
  selectors: ['inbox', 'sessions'],
37
39
  },
38
40
  url: {
@@ -1,17 +0,0 @@
1
- interface UrlSearchHelper {
2
- getUrlSearch: () => string;
3
- setUrlSearch: (params: URLSearchParams) => void;
4
- }
5
-
6
- export const createUrlSearch = (mode: 'search' | 'hash' = 'hash'): UrlSearchHelper => {
7
- if (mode === 'hash')
8
- return {
9
- getUrlSearch: () => location.hash.slice(1),
10
- setUrlSearch: (params: URLSearchParams) => (location.hash = params.toString()),
11
- };
12
-
13
- return {
14
- getUrlSearch: () => location.search.slice(1),
15
- setUrlSearch: (params: URLSearchParams) => (location.search = params.toString()),
16
- };
17
- };