@lobehub/chat 0.69.0 → 0.70.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/.eslintrc.js +1 -0
- package/.github/workflows/contributor-help.yml +2 -4
- package/CHANGELOG.md +50 -0
- package/package.json +3 -3
- package/src/const/url.ts +3 -0
- package/src/features/MobileTabBar/index.tsx +3 -1
- package/src/features/SideBar/TopActions.tsx +4 -3
- package/src/pages/api/openai.ts +33 -36
- package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/index.tsx +1 -1
- package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +1 -1
- package/src/pages/chat/features/Conversation/ChatList/Plugins/SystemJsRender/index.tsx +1 -1
- package/src/pages/chat/features/Header/Mobile.tsx +6 -8
- package/src/pages/chat/features/Header/PC.tsx +15 -16
- package/src/pages/chat/features/Header/index.tsx +3 -3
- package/src/pages/chat/{SessionList → features/SessionList}/Inbox.tsx +9 -2
- package/src/pages/chat/{SessionList → features/SessionList}/List/index.tsx +10 -1
- package/src/pages/chat/features/Sidebar/index.tsx +6 -2
- package/src/pages/chat/index.page.tsx +9 -11
- package/src/pages/chat/layout.tsx +6 -1
- package/src/pages/chat/{[id] → mobile}/index.page.tsx +7 -17
- package/src/pages/chat/mobile.tsx +2 -2
- package/src/pages/chat/{[id]/setting → setting}/Header.tsx +3 -4
- package/src/pages/index.page.tsx +10 -4
- package/src/pages/settings/Header.tsx +4 -2
- package/src/pages/welcome/Banner/index.tsx +11 -3
- package/src/services/chatModel.ts +1 -2
- package/src/store/middleware/createHyperStorage/index.ts +95 -0
- package/src/store/middleware/createHyperStorage/keyMapper.ts +53 -0
- package/src/store/middleware/createHyperStorage/type.ts +14 -0
- package/src/store/middleware/createHyperStorage/urlSearch.ts +17 -0
- package/src/store/plugin/selectors.ts +1 -1
- package/src/store/session/slices/session/action.ts +18 -9
- package/src/store/session/slices/session/initialState.ts +2 -3
- package/src/store/session/slices/session/selectors/index.ts +6 -0
- package/src/store/session/store.ts +16 -8
- package/src/types/openai.ts +23 -0
- package/src/pages/chat/[id]/layout.tsx +0 -34
- package/src/pages/chat/[id]/mobile.tsx +0 -31
- package/src/store/middleware/createHashStorage.ts +0 -49
- /package/src/pages/chat/{SessionList → features/SessionList}/CollapseGroup.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/Header.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/AddButton.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/Item/Actions.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/Item/index.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/SkeletonList.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/index.tsx +0 -0
- /package/src/pages/chat/{[id]/setting → setting}/index.page.tsx +0 -0
- /package/src/pages/chat/{[id]/setting → setting}/mobile.tsx +0 -0
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from '@lobehub/ui';
|
|
8
8
|
import { Dropdown, MenuProps } from 'antd';
|
|
9
9
|
import { HardDriveDownload, Share2 } from 'lucide-react';
|
|
10
|
-
import Router from 'next/router';
|
|
11
10
|
import { memo, useMemo } from 'react';
|
|
12
11
|
import { useTranslation } from 'react-i18next';
|
|
13
12
|
|
|
@@ -16,7 +15,7 @@ import { useSessionStore } from '@/store/session';
|
|
|
16
15
|
|
|
17
16
|
const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
18
17
|
const { t } = useTranslation('setting');
|
|
19
|
-
const id = useSessionStore((s) => s.activeId);
|
|
18
|
+
const [id, onBack] = useSessionStore((s) => [s.activeId, s.switchBackToChat]);
|
|
20
19
|
|
|
21
20
|
const items = useMemo<MenuProps['items']>(
|
|
22
21
|
() => [
|
|
@@ -46,7 +45,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
46
45
|
return (
|
|
47
46
|
<MobileNavBar
|
|
48
47
|
center={<MobileNavBarTitle title={t('header.session')} />}
|
|
49
|
-
onBackClick={
|
|
48
|
+
onBackClick={onBack}
|
|
50
49
|
right={
|
|
51
50
|
<>
|
|
52
51
|
<ActionIcon icon={Share2} title={t('share', { ns: 'common' })} />
|
|
@@ -62,7 +61,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
62
61
|
return (
|
|
63
62
|
<ChatHeader
|
|
64
63
|
left={<ChatHeaderTitle title={t('header.session')} />}
|
|
65
|
-
onBackClick={
|
|
64
|
+
onBackClick={onBack}
|
|
66
65
|
right={
|
|
67
66
|
<>
|
|
68
67
|
<ActionIcon icon={Share2} size={{ fontSize: 24 }} title={t('share', { ns: 'common' })} />
|
package/src/pages/index.page.tsx
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
1
|
+
import { memo, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useSessionHydrated, useSessionStore } from '@/store/session';
|
|
4
4
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
5
5
|
|
|
6
|
-
import Chat from './chat/index.page';
|
|
7
6
|
import Loading from './index/Loading';
|
|
8
7
|
import Welcome from './welcome/index.page';
|
|
9
8
|
|
|
10
9
|
const Home = memo(() => {
|
|
11
10
|
const hydrated = useSessionHydrated();
|
|
12
|
-
const hasSession = useSessionStore(
|
|
11
|
+
const [hasSession, switchSession] = useSessionStore((s) => [
|
|
12
|
+
sessionSelectors.hasSessionList(s),
|
|
13
|
+
s.switchSession,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (hasSession) switchSession();
|
|
18
|
+
}, [hasSession]);
|
|
13
19
|
|
|
14
20
|
if (!hydrated) return <Loading />;
|
|
15
21
|
|
|
16
|
-
return !hasSession ? <Welcome /> : <
|
|
22
|
+
return !hasSession ? <Welcome /> : <Loading />;
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
export default Home;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
|
|
2
|
-
import Router from 'next/router';
|
|
3
2
|
import { memo } from 'react';
|
|
4
3
|
import { useTranslation } from 'react-i18next';
|
|
5
4
|
|
|
5
|
+
import { useSessionStore } from '@/store/session';
|
|
6
|
+
|
|
6
7
|
const Header = memo(() => {
|
|
7
8
|
const { t } = useTranslation('setting');
|
|
9
|
+
const [switchBackToChat] = useSessionStore((s) => [s.switchBackToChat]);
|
|
8
10
|
|
|
9
11
|
return (
|
|
10
12
|
<ChatHeader
|
|
11
13
|
left={<ChatHeaderTitle title={t('header.global')} />}
|
|
12
|
-
onBackClick={
|
|
14
|
+
onBackClick={switchBackToChat}
|
|
13
15
|
showBackButton
|
|
14
16
|
/>
|
|
15
17
|
);
|
|
@@ -3,12 +3,13 @@ import { useSize } from 'ahooks';
|
|
|
3
3
|
import { Button, Upload } from 'antd';
|
|
4
4
|
import { SendHorizonal } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
|
-
import Router from 'next/router';
|
|
7
6
|
import { memo, useCallback, useRef } from 'react';
|
|
8
7
|
import { useTranslation } from 'react-i18next';
|
|
9
8
|
import { Flexbox } from 'react-layout-kit';
|
|
10
9
|
|
|
10
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
11
11
|
import { useImportConfig } from '@/hooks/useImportConfig';
|
|
12
|
+
import { useSessionStore } from '@/store/session';
|
|
12
13
|
|
|
13
14
|
import { useStyles } from '../style';
|
|
14
15
|
import Hero from './Hero';
|
|
@@ -22,9 +23,10 @@ const Banner = memo<{ mobile?: boolean }>(() => {
|
|
|
22
23
|
|
|
23
24
|
const { styles } = useStyles();
|
|
24
25
|
|
|
26
|
+
const switchSession = useSessionStore((s) => s.switchSession);
|
|
25
27
|
const handleImport = useCallback((e: any) => {
|
|
26
28
|
importConfig(e);
|
|
27
|
-
|
|
29
|
+
switchSession();
|
|
28
30
|
}, []);
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -37,7 +39,13 @@ const Banner = memo<{ mobile?: boolean }>(() => {
|
|
|
37
39
|
<Upload maxCount={1} onChange={handleImport} showUploadList={false}>
|
|
38
40
|
<Button size={'large'}>{t('button.import')}</Button>
|
|
39
41
|
</Upload>
|
|
40
|
-
<Link
|
|
42
|
+
<Link
|
|
43
|
+
href={SESSION_CHAT_URL()}
|
|
44
|
+
onClick={(e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
switchSession();
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
41
49
|
<Button size={'large'} type={'primary'}>
|
|
42
50
|
<Flexbox align={'center'} gap={4} horizontal>
|
|
43
51
|
{t('button.start')}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { merge } from 'lodash-es';
|
|
2
|
-
import { ChatCompletionFunctions } from 'openai-edge/types/api';
|
|
3
2
|
|
|
4
3
|
import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
|
|
5
4
|
import { useGlobalStore } from '@/store/global';
|
|
6
5
|
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
7
6
|
import { initialLobeAgentConfig } from '@/store/session/initialState';
|
|
8
|
-
import type { OpenAIStreamPayload } from '@/types/openai';
|
|
7
|
+
import type { ChatCompletionFunctions, OpenAIStreamPayload } from '@/types/openai';
|
|
9
8
|
|
|
10
9
|
import { URLS } from './url';
|
|
11
10
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isEmpty } from 'lodash-es';
|
|
2
|
+
import { PersistStorage } from 'zustand/middleware';
|
|
3
|
+
import { StorageValue } from 'zustand/middleware/persist';
|
|
4
|
+
|
|
5
|
+
import { createKeyMapper } from './keyMapper';
|
|
6
|
+
import { HyperStorageOptions } from './type';
|
|
7
|
+
import { createUrlSearch } from './urlSearch';
|
|
8
|
+
|
|
9
|
+
export const createHyperStorage = <T extends object>(
|
|
10
|
+
options: HyperStorageOptions,
|
|
11
|
+
): PersistStorage<T> => {
|
|
12
|
+
const hasUrl = !!options.url;
|
|
13
|
+
|
|
14
|
+
const { setUrlSearch, getUrlSearch } = createUrlSearch(options.url?.mode);
|
|
15
|
+
|
|
16
|
+
const { mapStateKeyToStorageKey, getStateKeyFromStorageKey } = createKeyMapper(options);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
getItem: (name): StorageValue<T> => {
|
|
20
|
+
const state: any = {};
|
|
21
|
+
let version: number | undefined;
|
|
22
|
+
// ============== 处理 Local Storage ============== //
|
|
23
|
+
const string = localStorage.getItem(name);
|
|
24
|
+
if (string) {
|
|
25
|
+
const localState: StorageValue<T> = JSON.parse(string);
|
|
26
|
+
version = localState.version;
|
|
27
|
+
for (const [k, v] of Object.entries(localState.state)) {
|
|
28
|
+
const key = getStateKeyFromStorageKey(k, 'localStorage');
|
|
29
|
+
if (key) state[key] = v;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============== 处理 URL Storage ============== //
|
|
34
|
+
// 不从 URL 中获取 version,由于 url 状态是临时态 不作为版本控制的依据
|
|
35
|
+
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;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { state, version };
|
|
48
|
+
},
|
|
49
|
+
removeItem: (key): void => {
|
|
50
|
+
// ============== 处理 URL Storage ============== //
|
|
51
|
+
if (hasUrl) {
|
|
52
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
53
|
+
|
|
54
|
+
const storageKey = getStateKeyFromStorageKey(key, 'url');
|
|
55
|
+
if (storageKey) searchParameters.delete(storageKey);
|
|
56
|
+
|
|
57
|
+
setUrlSearch(searchParameters);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
setItem: (name, newValue) => {
|
|
61
|
+
// ============== 处理 Local Storage ============== //
|
|
62
|
+
const localState: Record<string, any> = {};
|
|
63
|
+
for (const [k, v] of Object.entries(newValue.state)) {
|
|
64
|
+
const localKey = mapStateKeyToStorageKey(k, 'localStorage');
|
|
65
|
+
if (localKey) localState[localKey] = v;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
localStorage.setItem(name, JSON.stringify({ ...newValue, state: localState }));
|
|
69
|
+
|
|
70
|
+
// ============== 处理 URL Storage ============== //
|
|
71
|
+
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);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HyperStorageOptions } from './type';
|
|
2
|
+
|
|
3
|
+
export const createKeyMapper = (options: HyperStorageOptions) => {
|
|
4
|
+
const mapStateKeyToStorageKey = (
|
|
5
|
+
key: string,
|
|
6
|
+
mode: keyof HyperStorageOptions = 'localStorage',
|
|
7
|
+
) => {
|
|
8
|
+
const selectors = options[mode]?.selectors;
|
|
9
|
+
if (!selectors) return key;
|
|
10
|
+
|
|
11
|
+
let storageKey: string | undefined;
|
|
12
|
+
|
|
13
|
+
if (!selectors) return key;
|
|
14
|
+
|
|
15
|
+
for (const selector of selectors) {
|
|
16
|
+
if (typeof selector === 'string') {
|
|
17
|
+
if (selector === key) storageKey = key;
|
|
18
|
+
} else {
|
|
19
|
+
if (selector[key]) storageKey = selector[key];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return storageKey;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getStateKeyFromStorageKey = (
|
|
27
|
+
key: string,
|
|
28
|
+
mode: keyof HyperStorageOptions = 'localStorage',
|
|
29
|
+
) => {
|
|
30
|
+
const selectors = options[mode]?.selectors;
|
|
31
|
+
if (!selectors) return key;
|
|
32
|
+
|
|
33
|
+
let stateKey: string | undefined;
|
|
34
|
+
|
|
35
|
+
for (const item of selectors) {
|
|
36
|
+
// 对象如果是 字符串,直接返回该 item key
|
|
37
|
+
if (typeof item === 'string') {
|
|
38
|
+
if (item === key) stateKey = key;
|
|
39
|
+
} else {
|
|
40
|
+
for (const [k, v] of Object.entries(item)) {
|
|
41
|
+
if (v === key) stateKey = k;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return stateKey;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
getStateKeyFromStorageKey,
|
|
51
|
+
mapStateKeyToStorageKey,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type StorageSelector = string | Record<string, string>;
|
|
2
|
+
|
|
3
|
+
export interface HyperStorageOptions {
|
|
4
|
+
localStorage?: {
|
|
5
|
+
selectors: StorageSelector[];
|
|
6
|
+
};
|
|
7
|
+
url?: {
|
|
8
|
+
/**
|
|
9
|
+
* @default 'hash'
|
|
10
|
+
*/
|
|
11
|
+
mode?: 'hash' | 'search';
|
|
12
|
+
selectors: StorageSelector[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { uniqBy } from 'lodash-es';
|
|
2
|
-
import { ChatCompletionFunctions } from 'openai-edge/types/api';
|
|
3
2
|
|
|
4
3
|
import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
5
4
|
import { pluginHelpers } from '@/store/plugin/helpers';
|
|
5
|
+
import { ChatCompletionFunctions } from '@/types/openai';
|
|
6
6
|
|
|
7
7
|
import { PluginStoreState } from './initialState';
|
|
8
8
|
|
|
@@ -4,6 +4,7 @@ import Router from 'next/router';
|
|
|
4
4
|
import { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
7
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
7
8
|
import { useGlobalStore } from '@/store/global';
|
|
8
9
|
import { SessionStore } from '@/store/session';
|
|
9
10
|
import { LobeAgentSession, LobeSessions } from '@/types/session';
|
|
@@ -54,12 +55,14 @@ export interface SessionAction {
|
|
|
54
55
|
*/
|
|
55
56
|
removeSession: (sessionId: string) => void;
|
|
56
57
|
|
|
58
|
+
switchBackToChat: () => void;
|
|
59
|
+
|
|
57
60
|
/**
|
|
58
61
|
* @title 切换会话
|
|
59
62
|
* @param sessionId - 会话索引
|
|
60
63
|
* @returns void
|
|
61
64
|
*/
|
|
62
|
-
switchSession: (sessionId?: string) =>
|
|
65
|
+
switchSession: (sessionId?: string) => void;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export const createSessionSlice: StateCreator<
|
|
@@ -69,7 +72,7 @@ export const createSessionSlice: StateCreator<
|
|
|
69
72
|
SessionAction
|
|
70
73
|
> = (set, get) => ({
|
|
71
74
|
activeSession: (sessionId) => {
|
|
72
|
-
set({ activeId: sessionId }, false, t('activeSession'));
|
|
75
|
+
set({ activeId: sessionId, activeTopicId: undefined }, false, t('activeSession'));
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
clearSessions: () => {
|
|
@@ -144,18 +147,24 @@ export const createSessionSlice: StateCreator<
|
|
|
144
147
|
get().dispatchSession({ id: sessionId, type: 'removeSession' });
|
|
145
148
|
|
|
146
149
|
if (sessionId === get().activeId) {
|
|
147
|
-
|
|
150
|
+
get().switchSession();
|
|
148
151
|
}
|
|
149
152
|
},
|
|
150
153
|
|
|
151
|
-
|
|
154
|
+
switchBackToChat: () => {
|
|
155
|
+
const { activeId } = get();
|
|
156
|
+
|
|
157
|
+
const id = activeId || INBOX_SESSION_ID;
|
|
158
|
+
|
|
159
|
+
get().activeSession(id);
|
|
160
|
+
|
|
161
|
+
Router.push(SESSION_CHAT_URL(id));
|
|
162
|
+
},
|
|
163
|
+
switchSession: (sessionId = INBOX_SESSION_ID) => {
|
|
152
164
|
if (get().activeId === sessionId) return;
|
|
153
165
|
|
|
154
|
-
|
|
155
|
-
get().activeSession(sessionId);
|
|
156
|
-
}
|
|
166
|
+
get().activeSession(sessionId);
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
await Router.push(`/chat/${sessionId}`);
|
|
168
|
+
Router.push(SESSION_CHAT_URL(sessionId));
|
|
160
169
|
},
|
|
161
170
|
});
|
|
@@ -9,9 +9,8 @@ export interface SessionState {
|
|
|
9
9
|
/**
|
|
10
10
|
* @title 当前活动的会话
|
|
11
11
|
* @description 当前正在编辑或查看的会话
|
|
12
|
-
* @default null
|
|
13
12
|
*/
|
|
14
|
-
activeId: string |
|
|
13
|
+
activeId: string | undefined;
|
|
15
14
|
// 默认会话
|
|
16
15
|
inbox: LobeAgentSession;
|
|
17
16
|
searchKeywords: string;
|
|
@@ -39,7 +38,7 @@ export const initInbox = merge({}, initLobeSession, {
|
|
|
39
38
|
} as Partial<LobeAgentSession>);
|
|
40
39
|
|
|
41
40
|
export const initialSessionState: SessionState = {
|
|
42
|
-
activeId:
|
|
41
|
+
activeId: undefined,
|
|
43
42
|
inbox: initInbox,
|
|
44
43
|
searchKeywords: '',
|
|
45
44
|
sessions: {},
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
2
|
+
import { SessionStore } from '@/store/session';
|
|
3
|
+
|
|
1
4
|
import { exportAgents, exportSessions, getExportAgent } from './export';
|
|
2
5
|
import {
|
|
3
6
|
currentSession,
|
|
@@ -8,6 +11,8 @@ import {
|
|
|
8
11
|
sessionList,
|
|
9
12
|
} from './list';
|
|
10
13
|
|
|
14
|
+
const isInboxSession = (s: SessionStore) => s.activeId === INBOX_SESSION_ID;
|
|
15
|
+
|
|
11
16
|
export const sessionSelectors = {
|
|
12
17
|
currentSession,
|
|
13
18
|
currentSessionSafe,
|
|
@@ -17,5 +22,6 @@ export const sessionSelectors = {
|
|
|
17
22
|
getSessionById,
|
|
18
23
|
getSessionMetaById,
|
|
19
24
|
hasSessionList,
|
|
25
|
+
isInboxSession,
|
|
20
26
|
sessionList,
|
|
21
27
|
};
|
|
@@ -5,6 +5,7 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
5
5
|
|
|
6
6
|
import { isDev } from '@/utils/env';
|
|
7
7
|
|
|
8
|
+
import { createHyperStorage } from '../middleware/createHyperStorage';
|
|
8
9
|
import { SessionStoreState, initialState } from './initialState';
|
|
9
10
|
import { AgentAction, createAgentSlice } from './slices/agentConfig/action';
|
|
10
11
|
import { ChatAction, createChatSlice } from './slices/chat/actions';
|
|
@@ -22,20 +23,27 @@ const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (
|
|
|
22
23
|
|
|
23
24
|
// =============== persist 本地缓存中间件配置 ============ //
|
|
24
25
|
|
|
25
|
-
type SessionPersist = Pick<SessionStore, 'sessions' | 'inbox'>;
|
|
26
|
-
|
|
27
26
|
const LOBE_CHAT = 'LOBE_CHAT';
|
|
28
27
|
|
|
29
|
-
const persistOptions: PersistOptions<SessionStore
|
|
28
|
+
const persistOptions: PersistOptions<SessionStore> = {
|
|
30
29
|
name: LOBE_CHAT,
|
|
31
30
|
|
|
32
|
-
partialize: (s) => ({
|
|
33
|
-
inbox: s.inbox,
|
|
34
|
-
sessions: s.sessions,
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
31
|
// 手动控制 Hydration ,避免 ssr 报错
|
|
38
32
|
skipHydration: true,
|
|
33
|
+
|
|
34
|
+
storage: createHyperStorage({
|
|
35
|
+
localStorage: {
|
|
36
|
+
selectors: ['inbox', 'sessions'],
|
|
37
|
+
},
|
|
38
|
+
url: {
|
|
39
|
+
mode: 'hash',
|
|
40
|
+
selectors: [
|
|
41
|
+
// map state key to storage key
|
|
42
|
+
{ activeId: 'session' },
|
|
43
|
+
{ activeTopicId: 'topic' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
39
47
|
version: 0,
|
|
40
48
|
};
|
|
41
49
|
|
package/src/types/openai.ts
CHANGED
|
@@ -68,3 +68,26 @@ export interface OpenAIStreamPayload {
|
|
|
68
68
|
*/
|
|
69
69
|
top_p?: number;
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
export interface ChatCompletionFunctions {
|
|
73
|
+
/**
|
|
74
|
+
* The description of what the function does.
|
|
75
|
+
* @type {string}
|
|
76
|
+
* @memberof ChatCompletionFunctions
|
|
77
|
+
*/
|
|
78
|
+
description?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
|
|
81
|
+
* @type {string}
|
|
82
|
+
* @memberof ChatCompletionFunctions
|
|
83
|
+
*/
|
|
84
|
+
name: string;
|
|
85
|
+
/**
|
|
86
|
+
* The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.
|
|
87
|
+
* @type {{ [key: string]: any }}
|
|
88
|
+
* @memberof ChatCompletionFunctions
|
|
89
|
+
*/
|
|
90
|
+
parameters?: {
|
|
91
|
+
[key: string]: any;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/router';
|
|
2
|
-
import { PropsWithChildren, memo } from 'react';
|
|
3
|
-
import { shallow } from 'zustand/shallow';
|
|
4
|
-
|
|
5
|
-
import { usePluginStore } from '@/store/plugin';
|
|
6
|
-
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
|
|
7
|
-
|
|
8
|
-
import ChatLayout from '../layout';
|
|
9
|
-
|
|
10
|
-
const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
11
|
-
const useFetchPluginList = usePluginStore((s) => s.useFetchPluginList);
|
|
12
|
-
const [activeSession, toggleTopic] = useSessionStore((s) => {
|
|
13
|
-
return [s.activeSession, s.toggleTopic];
|
|
14
|
-
}, shallow);
|
|
15
|
-
|
|
16
|
-
const router = useRouter();
|
|
17
|
-
const { id } = router.query;
|
|
18
|
-
|
|
19
|
-
useOnFinishHydrationSession(() => {
|
|
20
|
-
// 1. 正常激活会话
|
|
21
|
-
if (typeof id === 'string') {
|
|
22
|
-
activeSession(id);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 将话题重置为默认值
|
|
26
|
-
toggleTopic();
|
|
27
|
-
}, [id]);
|
|
28
|
-
|
|
29
|
-
useFetchPluginList();
|
|
30
|
-
|
|
31
|
-
return <ChatLayout>{children}</ChatLayout>;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default Layout;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/router';
|
|
2
|
-
import { PropsWithChildren, memo } from 'react';
|
|
3
|
-
import { shallow } from 'zustand/shallow';
|
|
4
|
-
|
|
5
|
-
import AppMobileLayout from '@/layout/AppMobileLayout';
|
|
6
|
-
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
|
|
7
|
-
|
|
8
|
-
import Header from '../features/Header';
|
|
9
|
-
|
|
10
|
-
const MobileLayout = memo<PropsWithChildren>(({ children }) => {
|
|
11
|
-
const [activeSession, toggleTopic] = useSessionStore((s) => {
|
|
12
|
-
return [s.activeSession, s.toggleTopic];
|
|
13
|
-
}, shallow);
|
|
14
|
-
|
|
15
|
-
const router = useRouter();
|
|
16
|
-
const { id } = router.query;
|
|
17
|
-
|
|
18
|
-
useOnFinishHydrationSession(() => {
|
|
19
|
-
// 1. 正常激活会话
|
|
20
|
-
if (typeof id === 'string') {
|
|
21
|
-
activeSession(id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 将话题重置为默认值
|
|
25
|
-
toggleTopic();
|
|
26
|
-
}, [id]);
|
|
27
|
-
|
|
28
|
-
return <AppMobileLayout navBar={<Header />}>{children}</AppMobileLayout>;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export default MobileLayout;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { isEmpty } from 'lodash-es';
|
|
2
|
-
import { PersistStorage } from 'zustand/middleware';
|
|
3
|
-
|
|
4
|
-
import { Compressor } from '@/utils/compass';
|
|
5
|
-
|
|
6
|
-
export const createHashStorage = <T extends object>(): PersistStorage<T> => {
|
|
7
|
-
return {
|
|
8
|
-
getItem: async () => {
|
|
9
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
10
|
-
const state: any = {};
|
|
11
|
-
const pool = [...searchParameters.entries()].map(async ([k, v]) => {
|
|
12
|
-
const string_ = await Compressor.decompressAsync(v);
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
state[k] = JSON.parse(string_);
|
|
16
|
-
} catch {
|
|
17
|
-
state[k] = string_;
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await Promise.all(pool);
|
|
22
|
-
|
|
23
|
-
return { state } as any;
|
|
24
|
-
},
|
|
25
|
-
removeItem: (key): void => {
|
|
26
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
27
|
-
searchParameters.delete(key);
|
|
28
|
-
location.hash = searchParameters.toString();
|
|
29
|
-
},
|
|
30
|
-
setItem: async (_, newValue) => {
|
|
31
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
32
|
-
|
|
33
|
-
const pool = Object.entries(newValue.state).map(async ([k, v]) => {
|
|
34
|
-
if (isEmpty(v)) {
|
|
35
|
-
searchParameters.delete(k);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (typeof v === 'string') {
|
|
39
|
-
searchParameters.set(k, await Compressor.compressAsync(v));
|
|
40
|
-
} else {
|
|
41
|
-
searchParameters.set(k, await Compressor.compressAsync(JSON.stringify(v)));
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
await Promise.all(pool);
|
|
45
|
-
|
|
46
|
-
location.hash = searchParameters.toString();
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|