@lobehub/chat 0.140.0 → 0.141.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/locales/ar/common.json +34 -6
  3. package/locales/ar/setting.json +36 -0
  4. package/locales/de-DE/common.json +34 -6
  5. package/locales/de-DE/setting.json +36 -0
  6. package/locales/en-US/common.json +34 -6
  7. package/locales/en-US/setting.json +36 -0
  8. package/locales/es-ES/common.json +34 -6
  9. package/locales/es-ES/setting.json +36 -0
  10. package/locales/fr-FR/common.json +34 -6
  11. package/locales/fr-FR/setting.json +36 -0
  12. package/locales/it-IT/common.json +34 -6
  13. package/locales/it-IT/setting.json +38 -0
  14. package/locales/ja-JP/common.json +34 -6
  15. package/locales/ja-JP/setting.json +38 -0
  16. package/locales/ko-KR/common.json +34 -6
  17. package/locales/ko-KR/setting.json +36 -0
  18. package/locales/nl-NL/common.json +34 -6
  19. package/locales/nl-NL/setting.json +38 -0
  20. package/locales/pl-PL/common.json +34 -6
  21. package/locales/pl-PL/setting.json +36 -0
  22. package/locales/pt-BR/common.json +34 -6
  23. package/locales/pt-BR/setting.json +36 -0
  24. package/locales/ru-RU/common.json +34 -6
  25. package/locales/ru-RU/setting.json +36 -0
  26. package/locales/tr-TR/common.json +34 -6
  27. package/locales/tr-TR/setting.json +36 -0
  28. package/locales/vi-VN/common.json +34 -6
  29. package/locales/vi-VN/setting.json +36 -0
  30. package/locales/zh-CN/common.json +34 -6
  31. package/locales/zh-CN/setting.json +36 -0
  32. package/locales/zh-TW/common.json +34 -6
  33. package/locales/zh-TW/setting.json +36 -0
  34. package/package.json +10 -5
  35. package/src/app/chat/(desktop)/features/SessionHeader.tsx +5 -1
  36. package/src/app/chat/(mobile)/features/SessionHeader.tsx +9 -4
  37. package/src/app/chat/features/SessionListContent/List/SkeletonList.tsx +0 -1
  38. package/src/app/settings/(desktop)/features/Header.tsx +11 -1
  39. package/src/app/settings/(mobile)/features/Header/index.tsx +12 -1
  40. package/src/app/settings/features/SettingList/index.tsx +2 -1
  41. package/src/app/settings/sync/Alert.tsx +39 -0
  42. package/src/app/settings/sync/DeviceInfo/Card.tsx +41 -0
  43. package/src/app/settings/sync/DeviceInfo/DeviceName.tsx +66 -0
  44. package/src/app/settings/sync/DeviceInfo/index.tsx +117 -0
  45. package/src/app/settings/sync/PageTitle.tsx +11 -0
  46. package/src/app/settings/sync/WebRTC/ChannelNameInput.tsx +46 -0
  47. package/src/app/settings/sync/WebRTC/index.tsx +97 -0
  48. package/src/app/settings/sync/components/SyncSwitch/index.css +237 -0
  49. package/src/app/settings/sync/components/SyncSwitch/index.tsx +79 -0
  50. package/src/app/settings/sync/components/SystemIcon.tsx +16 -0
  51. package/src/app/settings/sync/layout.tsx +9 -0
  52. package/src/app/settings/sync/page.tsx +23 -0
  53. package/src/app/settings/sync/util.ts +4 -0
  54. package/src/components/BrowserIcon/components/Brave.tsx +56 -0
  55. package/src/components/BrowserIcon/components/Chrome.tsx +14 -0
  56. package/src/components/BrowserIcon/components/Chromium.tsx +14 -0
  57. package/src/components/BrowserIcon/components/Edge.tsx +36 -0
  58. package/src/components/BrowserIcon/components/Firefox.tsx +38 -0
  59. package/src/components/BrowserIcon/components/Opera.tsx +19 -0
  60. package/src/components/BrowserIcon/components/Safari.tsx +23 -0
  61. package/src/components/BrowserIcon/components/Samsung.tsx +21 -0
  62. package/src/components/BrowserIcon/index.tsx +50 -0
  63. package/src/components/BrowserIcon/types.ts +8 -0
  64. package/src/config/modelProviders/moonshot.ts +8 -0
  65. package/src/const/settings.ts +6 -0
  66. package/src/database/core/__tests__/model.test.ts +2 -2
  67. package/src/database/core/db.ts +1 -1
  68. package/src/database/core/index.ts +1 -0
  69. package/src/database/core/model.ts +83 -5
  70. package/src/database/core/sync.ts +328 -0
  71. package/src/database/models/__tests__/message.test.ts +0 -1
  72. package/src/database/models/__tests__/plugin.test.ts +5 -2
  73. package/src/database/models/file.ts +1 -1
  74. package/src/database/models/message.ts +49 -30
  75. package/src/database/models/plugin.ts +6 -5
  76. package/src/database/models/session.ts +15 -16
  77. package/src/database/models/sessionGroup.ts +14 -8
  78. package/src/database/models/topic.ts +14 -21
  79. package/src/features/SyncStatusInspector/DisableSync.tsx +79 -0
  80. package/src/features/SyncStatusInspector/EnableSync.tsx +136 -0
  81. package/src/features/SyncStatusInspector/EnableTag.tsx +66 -0
  82. package/src/features/SyncStatusInspector/index.tsx +27 -0
  83. package/src/hooks/useSyncData.ts +48 -0
  84. package/src/layout/GlobalLayout/StoreHydration.tsx +5 -0
  85. package/src/locales/default/common.ts +27 -5
  86. package/src/locales/default/setting.ts +37 -1
  87. package/src/services/chat.ts +6 -2
  88. package/src/services/config.ts +1 -1
  89. package/src/services/global.ts +15 -0
  90. package/src/store/chat/slices/topic/action.test.ts +1 -1
  91. package/src/store/chat/slices/topic/action.ts +21 -10
  92. package/src/store/global/slices/common/action.ts +71 -1
  93. package/src/store/global/slices/common/initialState.ts +9 -0
  94. package/src/store/global/slices/common/selectors.ts +1 -0
  95. package/src/store/global/slices/preference/initialState.ts +2 -1
  96. package/src/store/global/slices/preference/selectors.ts +3 -0
  97. package/src/store/global/slices/settings/selectors/index.ts +1 -0
  98. package/src/store/global/slices/settings/selectors/sync.ts +14 -0
  99. package/src/types/settings/index.ts +3 -0
  100. package/src/types/settings/sync.ts +10 -0
  101. package/src/types/sync.ts +41 -0
  102. package/src/utils/platform.ts +9 -3
  103. package/src/utils/responsive.ts +21 -0
@@ -10,17 +10,14 @@ export default {
10
10
  },
11
11
  about: '关于',
12
12
  advanceSettings: '高级设置',
13
- agentMaxToken: '会话最大长度',
14
- agentModel: '模型',
15
- agentProfile: '助手信息',
13
+
16
14
  appInitializing: 'LobeChat 启动中,请耐心等待...',
17
- archive: '归档',
15
+
18
16
  autoGenerate: '自动补全',
19
17
  autoGenerateTooltip: '基于提示词自动补全助手描述',
20
18
  cancel: '取消',
21
19
  changelog: '更新日志',
22
20
  close: '关闭',
23
- confirmRemoveSessionItemAlert: '即将删除该助手,删除后该将无法找回,请确认你的操作',
24
21
  copy: '复制',
25
22
  copyFail: '复制失败',
26
23
  copySuccess: '复制成功',
@@ -128,6 +125,31 @@ export default {
128
125
  setting: '设置',
129
126
  share: '分享',
130
127
  stop: '停止',
128
+ sync: {
129
+ actions: { settings: '同步设置', sync: '立即同步' },
130
+ awareness: {
131
+ current: '当前设备',
132
+ },
133
+ channel: '频道',
134
+ disabled: {
135
+ actions: { enable: '开启云端同步', settings: '配置同步参数' },
136
+ desc: '当前会话数据仅存储于此浏览器中。如果你需要在多个设备间同步数据,请配置并开启云端同步。',
137
+ title: '数据同步未开启',
138
+ },
139
+ enabled: {
140
+ title: '数据同步',
141
+ },
142
+ status: {
143
+ connecting: '连接中',
144
+ disabled: '同步未开启',
145
+ ready: '已连接',
146
+ synced: '已同步',
147
+ syncing: '同步中',
148
+ unconnected: '连接失败',
149
+ },
150
+ title: '同步状态',
151
+ unconnected: { tip: '信令服务器连接失败,将无法建立点对点通信频道,请检查网络后重试' },
152
+ },
131
153
  tab: {
132
154
  chat: '会话',
133
155
  market: '发现',
@@ -442,12 +442,48 @@ export default {
442
442
  placeholder: '请输入助手的标识符,需要是唯一的,比如 web-development',
443
443
  tooltips: '分享到助手市场',
444
444
  },
445
-
445
+ sync: {
446
+ device: {
447
+ deviceName: {
448
+ hint: '添加名称以便于识别',
449
+ placeholder: '请输入设备名称',
450
+ title: '设备名称',
451
+ },
452
+ title: '设备信息',
453
+ unknownBrowser: '未知浏览器',
454
+ unknownOS: '未知系统',
455
+ },
456
+ warning: {
457
+ message: '本功能目前仍为实验性功能,可能存在预期外或不稳定的情况,如遇到问题请及时提交反馈。',
458
+ },
459
+ webrtc: {
460
+ channelName: {
461
+ desc: 'WebRTC 将使用此名创建同步频道,确保频道名称唯一',
462
+ placeholder: '请输入同步频道名称',
463
+ shuffle: '随机生成',
464
+ title: '同步频道名称',
465
+ },
466
+ channelPassword: {
467
+ desc: '添加密码确保频道私密性,只有密码正确时,设备才可加入频道',
468
+ placeholder: '请输入同步频道密码',
469
+ title: '同步频道密码',
470
+ },
471
+ desc: '实时、点对点的数据通信,需设备同时在线才可同步',
472
+ enabled: {
473
+ invalid: '请填写同步频道名称后再开启',
474
+ // desc: 'WebRTC 将使用此名创建同步频道,确保频道名称唯一',
475
+ title: '开启同步',
476
+ },
477
+ title: 'WebRTC 同步',
478
+ },
479
+ },
446
480
  tab: {
447
481
  about: '关于',
448
482
  agent: '默认助手',
449
483
  common: '通用设置',
484
+ experiment: '实验',
450
485
  llm: '语言模型',
486
+ sync: '云端同步',
451
487
  tts: '语音服务',
452
488
  },
453
489
  tools: {
@@ -7,7 +7,11 @@ import { TracePayload, TraceTagMap } from '@/const/trace';
7
7
  import { ModelProvider } from '@/libs/agent-runtime';
8
8
  import { filesSelectors, useFileStore } from '@/store/file';
9
9
  import { useGlobalStore } from '@/store/global';
10
- import { modelProviderSelectors, preferenceSelectors } from '@/store/global/selectors';
10
+ import {
11
+ commonSelectors,
12
+ modelProviderSelectors,
13
+ preferenceSelectors,
14
+ } from '@/store/global/selectors';
11
15
  import { useSessionStore } from '@/store/session';
12
16
  import { agentSelectors } from '@/store/session/selectors';
13
17
  import { useToolStore } from '@/store/tool';
@@ -310,7 +314,7 @@ class ChatService {
310
314
  ...trace,
311
315
  enabled: true,
312
316
  tags: [tag, ...(trace?.tags || []), ...tags].filter(Boolean) as string[],
313
- userId: useGlobalStore.getState().userId,
317
+ userId: commonSelectors.userId(useGlobalStore.getState()),
314
318
  };
315
319
  }
316
320
  }
@@ -42,7 +42,7 @@ class ConfigService {
42
42
  return topicService.batchCreateTopics(topics);
43
43
  };
44
44
  importSessionGroups = async (sessionGroups: SessionGroupItem[]) => {
45
- return sessionService.batchCreateSessionGroups(sessionGroups);
45
+ return sessionService.batchCreateSessionGroups(sessionGroups || []);
46
46
  };
47
47
 
48
48
  importConfigState = async (config: ConfigFile): Promise<ImportResults | undefined> => {
@@ -1,4 +1,6 @@
1
+ import { dataSync } from '@/database/core';
1
2
  import { GlobalServerConfig } from '@/types/settings';
3
+ import { StartDataSyncParams } from '@/types/sync';
2
4
 
3
5
  import { API_ENDPOINTS } from './_url';
4
6
 
@@ -20,6 +22,19 @@ class GlobalService {
20
22
 
21
23
  return res.json();
22
24
  };
25
+
26
+ enabledSync = async (params: StartDataSyncParams) => {
27
+ if (typeof window === 'undefined') return false;
28
+
29
+ await dataSync.startDataSync(params);
30
+ return true;
31
+ };
32
+
33
+ disableSync = async () => {
34
+ await dataSync.disconnect();
35
+
36
+ return false;
37
+ };
23
38
  }
24
39
 
25
40
  export const globalService = new GlobalService();
@@ -148,7 +148,7 @@ describe('topic action', () => {
148
148
  });
149
149
 
150
150
  // Check if mutate has been called with the active session ID
151
- expect(mutate).toHaveBeenCalledWith(activeId);
151
+ expect(mutate).toHaveBeenCalledWith(['SWR_USE_FETCH_TOPIC', activeId]);
152
152
  });
153
153
 
154
154
  it('should handle errors during refreshing topics', async () => {
@@ -9,6 +9,7 @@ import { StateCreator } from 'zustand/vanilla';
9
9
  import { chainSummaryTitle } from '@/chains/summaryTitle';
10
10
  import { LOADING_FLAT } from '@/const/message';
11
11
  import { TraceNameMap } from '@/const/trace';
12
+ import { useClientDataSWR } from '@/libs/swr';
12
13
  import { chatService } from '@/services/chat';
13
14
  import { messageService } from '@/services/message';
14
15
  import { topicService } from '@/services/topic';
@@ -22,6 +23,9 @@ import { topicSelectors } from './selectors';
22
23
 
23
24
  const n = setNamespace('topic');
24
25
 
26
+ const SWR_USE_FETCH_TOPIC = 'SWR_USE_FETCH_TOPIC';
27
+ const SWR_USE_SEARCH_TOPIC = 'SWR_USE_SEARCH_TOPIC';
28
+
25
29
  export interface ChatTopicAction {
26
30
  favoriteTopic: (id: string, favState: boolean) => Promise<void>;
27
31
  openNewTopicOrSaveTopic: () => Promise<void>;
@@ -141,18 +145,25 @@ export const chatTopic: StateCreator<
141
145
  },
142
146
  // query
143
147
  useFetchTopics: (sessionId) =>
144
- useSWR<ChatTopic[]>(sessionId, async (sessionId) => topicService.getTopics({ sessionId }), {
145
- onSuccess: (topics) => {
146
- set({ topics, topicsInit: true }, false, n('useFetchTopics(success)', { sessionId }));
148
+ useClientDataSWR<ChatTopic[]>(
149
+ [SWR_USE_FETCH_TOPIC, sessionId],
150
+ async ([, sessionId]: [string, string]) => topicService.getTopics({ sessionId }),
151
+ {
152
+ onSuccess: (topics) => {
153
+ set({ topics, topicsInit: true }, false, n('useFetchTopics(success)', { sessionId }));
154
+ },
147
155
  },
148
- dedupingInterval: 0,
149
- }),
156
+ ),
150
157
  useSearchTopics: (keywords) =>
151
- useSWR<ChatTopic[]>(keywords, topicService.searchTopics, {
152
- onSuccess: (data) => {
153
- set({ searchTopics: data }, false, n('useSearchTopics(success)', { keywords }));
158
+ useSWR<ChatTopic[]>(
159
+ [SWR_USE_SEARCH_TOPIC, keywords],
160
+ ([, keywords]: [string, string]) => topicService.searchTopics(keywords),
161
+ {
162
+ onSuccess: (data) => {
163
+ set({ searchTopics: data }, false, n('useSearchTopics(success)', { keywords }));
164
+ },
154
165
  },
155
- }),
166
+ ),
156
167
  switchTopic: async (id) => {
157
168
  set({ activeTopicId: id }, false, n('toggleTopic'));
158
169
 
@@ -213,6 +224,6 @@ export const chatTopic: StateCreator<
213
224
  set({ topicLoadingId: id }, false, n('updateTopicLoading'));
214
225
  },
215
226
  refreshTopic: async () => {
216
- await mutate(get().activeId);
227
+ await mutate([SWR_USE_FETCH_TOPIC, get().activeId]);
217
228
  },
218
229
  });
@@ -11,12 +11,15 @@ import { messageService } from '@/services/message';
11
11
  import { UserConfig, userService } from '@/services/user';
12
12
  import type { GlobalStore } from '@/store/global';
13
13
  import type { GlobalServerConfig, GlobalSettings } from '@/types/settings';
14
+ import { OnSyncEvent, PeerSyncStatus } from '@/types/sync';
14
15
  import { merge } from '@/utils/merge';
16
+ import { browserInfo } from '@/utils/platform';
15
17
  import { setNamespace } from '@/utils/storeDebug';
16
18
  import { switchLang } from '@/utils/switchLang';
17
19
 
18
20
  import { preferenceSelectors } from '../preference/selectors';
19
- import { settingsSelectors } from '../settings/selectors';
21
+ import { settingsSelectors, syncSettingsSelectors } from '../settings/selectors';
22
+ import { commonSelectors } from './selectors';
20
23
 
21
24
  const n = setNamespace('common');
22
25
 
@@ -24,11 +27,18 @@ const n = setNamespace('common');
24
27
  * 设置操作
25
28
  */
26
29
  export interface CommonAction {
30
+ refreshConnection: (onEvent: OnSyncEvent) => Promise<void>;
27
31
  refreshUserConfig: () => Promise<void>;
28
32
  switchBackToChat: (sessionId?: string) => void;
33
+ triggerEnableSync: (userId: string, onEvent: OnSyncEvent) => Promise<boolean>;
29
34
  updateAvatar: (avatar: string) => Promise<void>;
30
35
  useCheckLatestVersion: () => SWRResponse<string>;
31
36
  useCheckTrace: (shouldFetch: boolean) => SWRResponse;
37
+ useEnabledSync: (
38
+ userEnableSync: boolean,
39
+ userId: string | undefined,
40
+ onEvent: OnSyncEvent,
41
+ ) => SWRResponse;
32
42
  useFetchServerConfig: () => SWRResponse;
33
43
  useFetchUserConfig: (initServer: boolean) => SWRResponse<UserConfig | undefined>;
34
44
  }
@@ -41,13 +51,53 @@ export const createCommonSlice: StateCreator<
41
51
  [],
42
52
  CommonAction
43
53
  > = (set, get) => ({
54
+ refreshConnection: async (onEvent) => {
55
+ const userId = commonSelectors.userId(get());
56
+
57
+ if (!userId) return;
58
+
59
+ await get().triggerEnableSync(userId, onEvent);
60
+ },
61
+
44
62
  refreshUserConfig: async () => {
45
63
  await mutate([USER_CONFIG_FETCH_KEY, true]);
46
64
  },
65
+
47
66
  switchBackToChat: (sessionId) => {
48
67
  get().router?.push(SESSION_CHAT_URL(sessionId || INBOX_SESSION_ID, get().isMobile));
49
68
  },
69
+ triggerEnableSync: async (userId: string, onEvent: OnSyncEvent) => {
70
+ // double-check the sync ability
71
+ // if there is no channelName, don't start sync
72
+ const sync = syncSettingsSelectors.webrtcConfig(get());
73
+ if (!sync.channelName) return false;
74
+
75
+ const name = syncSettingsSelectors.deviceName(get());
50
76
 
77
+ const defaultUserName = `My ${browserInfo.browser} (${browserInfo.os})`;
78
+
79
+ set({ syncStatus: PeerSyncStatus.Connecting });
80
+ return globalService.enabledSync({
81
+ channel: {
82
+ name: sync.channelName,
83
+ password: sync.channelPassword,
84
+ },
85
+ onAwarenessChange(state) {
86
+ set({ syncAwareness: state });
87
+ },
88
+ onSyncEvent: onEvent,
89
+ onSyncStatusChange: (status) => {
90
+ set({ syncStatus: status });
91
+ },
92
+ signaling: sync.signaling,
93
+ user: {
94
+ id: userId,
95
+ // if user don't set the name, use default name
96
+ name: name || defaultUserName,
97
+ ...browserInfo,
98
+ },
99
+ });
100
+ },
51
101
  updateAvatar: async (avatar) => {
52
102
  await userService.updateAvatar(avatar);
53
103
  await get().refreshUserConfig();
@@ -78,6 +128,26 @@ export const createCommonSlice: StateCreator<
78
128
  revalidateOnFocus: false,
79
129
  },
80
130
  ),
131
+
132
+ useEnabledSync: (userEnableSync, userId, onEvent) =>
133
+ useSWR<boolean>(
134
+ ['enableSync', userEnableSync, userId],
135
+ async () => {
136
+ // if user don't enable sync or no userId ,don't start sync
137
+ if (!userId) return false;
138
+
139
+ // if user don't enable sync, stop sync
140
+ if (!userEnableSync) return globalService.disableSync();
141
+
142
+ return get().triggerEnableSync(userId, onEvent);
143
+ },
144
+ {
145
+ onSuccess: (syncEnabled) => {
146
+ set({ syncEnabled });
147
+ },
148
+ revalidateOnFocus: false,
149
+ },
150
+ ),
81
151
  useFetchServerConfig: () =>
82
152
  useSWR<GlobalServerConfig>('fetchGlobalConfig', globalService.getGlobalConfig, {
83
153
  onSuccess: (data) => {
@@ -1,5 +1,7 @@
1
1
  import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
2
2
 
3
+ import { PeerSyncStatus, SyncAwarenessState } from '@/types/sync';
4
+
3
5
  export enum SidebarTabKey {
4
6
  Chat = 'chat',
5
7
  Market = 'market',
@@ -11,6 +13,7 @@ export enum SettingsTabs {
11
13
  Agent = 'agent',
12
14
  Common = 'common',
13
15
  LLM = 'llm',
16
+ Sync = 'sync',
14
17
  TTS = 'tts',
15
18
  }
16
19
 
@@ -25,9 +28,15 @@ export interface GlobalCommonState {
25
28
  latestVersion?: string;
26
29
  router?: AppRouterInstance;
27
30
  sidebarKey: SidebarTabKey;
31
+ syncAwareness: SyncAwarenessState[];
32
+ syncEnabled: boolean;
33
+ syncStatus: PeerSyncStatus;
28
34
  }
29
35
 
30
36
  export const initialCommonState: GlobalCommonState = {
31
37
  isMobile: false,
32
38
  sidebarKey: SidebarTabKey.Chat,
39
+ syncAwareness: [],
40
+ syncEnabled: false,
41
+ syncStatus: PeerSyncStatus.Disabled,
33
42
  };
@@ -4,4 +4,5 @@ export const commonSelectors = {
4
4
  enabledOAuthSSO: (s: GlobalStore) => s.serverConfig.enabledOAuthSSO,
5
5
  enabledTelemetryChat: (s: GlobalStore) => s.serverConfig.telemetry.langfuse || false,
6
6
  userAvatar: (s: GlobalStore) => s.avatar || '',
7
+ userId: (s: GlobalStore) => s.userId,
7
8
  };
@@ -9,10 +9,11 @@ export interface GlobalPreference {
9
9
  // which sessionGroup should expand
10
10
  expandSessionGroupKeys: SessionGroupId[];
11
11
  guide?: Guide;
12
+ hideSyncAlert?: boolean;
12
13
  inputHeight: number;
13
14
  mobileShowTopic?: boolean;
14
- sessionsWidth: number;
15
15
 
16
+ sessionsWidth: number;
16
17
  showChatSideBar?: boolean;
17
18
  showSessionPanel?: boolean;
18
19
  showSystemRole?: boolean;
@@ -6,7 +6,10 @@ const useCmdEnterToSend = (s: GlobalStore): boolean => s.preference.useCmdEnterT
6
6
 
7
7
  const userAllowTrace = (s: GlobalStore) => s.preference.telemetry;
8
8
 
9
+ const hideSyncAlert = (s: GlobalStore) => s.preference.hideSyncAlert;
10
+
9
11
  export const preferenceSelectors = {
12
+ hideSyncAlert,
10
13
  sessionGroupKeys,
11
14
  useCmdEnterToSend,
12
15
  userAllowTrace,
@@ -1,2 +1,3 @@
1
1
  export { modelProviderSelectors } from './modelProvider';
2
2
  export { settingsSelectors } from './settings';
3
+ export { syncSettingsSelectors } from './sync';
@@ -0,0 +1,14 @@
1
+ import { GlobalStore } from '../../../store';
2
+ import { currentSettings } from './settings';
3
+
4
+ const webrtcConfig = (s: GlobalStore) => currentSettings(s).sync.webrtc;
5
+ const webrtcChannelName = (s: GlobalStore) => webrtcConfig(s).channelName;
6
+ const enableWebRTC = (s: GlobalStore) => webrtcConfig(s).enabled;
7
+ const deviceName = (s: GlobalStore) => currentSettings(s).sync.deviceName;
8
+
9
+ export const syncSettingsSelectors = {
10
+ deviceName,
11
+ enableWebRTC,
12
+ webrtcChannelName,
13
+ webrtcConfig,
14
+ };
@@ -4,12 +4,14 @@ import type { LobeAgentSession } from '@/types/session';
4
4
 
5
5
  import { GlobalBaseSettings } from './base';
6
6
  import { GlobalLLMConfig } from './modelProvider';
7
+ import { GlobalSyncSettings } from './sync';
7
8
  import { GlobalTTSConfig } from './tts';
8
9
 
9
10
  export type GlobalDefaultAgent = Pick<LobeAgentSession, 'config' | 'meta'>;
10
11
 
11
12
  export * from './base';
12
13
  export * from './modelProvider';
14
+ export * from './sync';
13
15
  export * from './tts';
14
16
 
15
17
  export interface GlobalTool {
@@ -34,6 +36,7 @@ export interface GlobalServerConfig {
34
36
  export interface GlobalSettings extends GlobalBaseSettings {
35
37
  defaultAgent: GlobalDefaultAgent;
36
38
  languageModel: GlobalLLMConfig;
39
+ sync: GlobalSyncSettings;
37
40
  tool: GlobalTool;
38
41
  tts: GlobalTTSConfig;
39
42
  }
@@ -0,0 +1,10 @@
1
+ export interface WebRTCSyncConfig {
2
+ channelName?: string;
3
+ channelPassword?: string;
4
+ enabled: boolean;
5
+ signaling?: string;
6
+ }
7
+ export interface GlobalSyncSettings {
8
+ deviceName?: string;
9
+ webrtc: WebRTCSyncConfig;
10
+ }
@@ -0,0 +1,41 @@
1
+ import { LobeDBSchemaMap } from '@/database/core/db';
2
+
3
+ export type OnSyncEvent = (tableKey: keyof LobeDBSchemaMap) => void;
4
+ export type OnSyncStatusChange = (status: PeerSyncStatus) => void;
5
+ export type OnAwarenessChange = (state: SyncAwarenessState[]) => void;
6
+
7
+ // export type PeerSyncStatus = 'syncing' | 'synced' | 'ready' | 'unconnected';
8
+
9
+ export enum PeerSyncStatus {
10
+ Connecting = 'connecting',
11
+ Disabled = 'disabled',
12
+ Ready = 'ready',
13
+ Synced = 'synced',
14
+ Syncing = 'syncing',
15
+ Unconnected = 'unconnected',
16
+ }
17
+
18
+ export interface StartDataSyncParams {
19
+ channel: {
20
+ name: string;
21
+ password?: string;
22
+ };
23
+ onAwarenessChange: OnAwarenessChange;
24
+ onSyncEvent: OnSyncEvent;
25
+ onSyncStatusChange: OnSyncStatusChange;
26
+ signaling?: string;
27
+ user: SyncUserInfo;
28
+ }
29
+
30
+ export interface SyncUserInfo {
31
+ browser?: string;
32
+ id: string;
33
+ isMobile: boolean;
34
+ name?: string;
35
+ os?: string;
36
+ }
37
+
38
+ export interface SyncAwarenessState extends SyncUserInfo {
39
+ clientID: number;
40
+ current: boolean;
41
+ }
@@ -1,6 +1,6 @@
1
1
  import UAParser from 'ua-parser-js';
2
2
 
3
- const getPaser = () => {
3
+ const getParser = () => {
4
4
  if (typeof window === 'undefined') return new UAParser('Node');
5
5
 
6
6
  let ua = navigator.userAgent;
@@ -8,11 +8,17 @@ const getPaser = () => {
8
8
  };
9
9
 
10
10
  export const getPlatform = () => {
11
- return getPaser().getOS().name;
11
+ return getParser().getOS().name;
12
12
  };
13
13
 
14
14
  export const getBrowser = () => {
15
- return getPaser().getResult().browser.name;
15
+ return getParser().getResult().browser.name;
16
+ };
17
+
18
+ export const browserInfo = {
19
+ browser: getBrowser(),
20
+ isMobile: getParser().getDevice().type === 'mobile',
21
+ os: getParser().getOS().name,
16
22
  };
17
23
 
18
24
  export const isMacOS = () => getPlatform() === 'Mac OS';
@@ -17,3 +17,24 @@ export const isMobileDevice = () => {
17
17
 
18
18
  return device.type === 'mobile';
19
19
  };
20
+
21
+ /**
22
+ * check mobile device in server
23
+ */
24
+ export const gerServerDeviceInfo = () => {
25
+ if (typeof process === 'undefined') {
26
+ throw new Error('[Server method] you are importing a server-only module outside of server');
27
+ }
28
+
29
+ const { get } = headers();
30
+ const ua = get('user-agent');
31
+
32
+ // console.debug(ua);
33
+ const parser = new UAParser(ua || '');
34
+
35
+ return {
36
+ browser: parser.getBrowser().name,
37
+ isMobile: isMobileDevice(),
38
+ os: parser.getOS().name,
39
+ };
40
+ };