@lobehub/chat 1.103.1 → 1.104.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 (135) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/apps/desktop/build/icon-beta.ico +0 -0
  3. package/apps/desktop/build/icon-dev.ico +0 -0
  4. package/apps/desktop/build/icon-nightly.ico +0 -0
  5. package/apps/desktop/build/icon.ico +0 -0
  6. package/apps/desktop/electron.vite.config.ts +4 -2
  7. package/apps/desktop/package.json +1 -0
  8. package/apps/desktop/src/main/appBrowsers.ts +2 -2
  9. package/apps/desktop/src/main/const/env.ts +5 -4
  10. package/apps/desktop/src/main/const/store.ts +1 -0
  11. package/apps/desktop/src/main/const/theme.ts +11 -0
  12. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +1 -1
  13. package/apps/desktop/src/main/controllers/NotificationCtr.ts +2 -4
  14. package/apps/desktop/src/main/controllers/ShortcutCtr.ts +9 -1
  15. package/apps/desktop/src/main/controllers/SystemCtr.ts +4 -0
  16. package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -9
  17. package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +14 -11
  18. package/apps/desktop/src/main/controllers/index.ts +1 -1
  19. package/apps/desktop/src/main/core/App.ts +9 -10
  20. package/apps/desktop/src/main/core/{Browser.ts → browser/Browser.ts} +129 -88
  21. package/apps/desktop/src/main/core/{BrowserManager.ts → browser/BrowserManager.ts} +13 -3
  22. package/apps/desktop/src/main/core/{StaticFileServerManager.ts → infrastructure/StaticFileServerManager.ts} +13 -7
  23. package/apps/desktop/src/main/core/{StoreManager.ts → infrastructure/StoreManager.ts} +1 -1
  24. package/apps/desktop/src/main/core/{UpdaterManager.ts → infrastructure/UpdaterManager.ts} +1 -1
  25. package/apps/desktop/src/main/core/{MenuManager.ts → ui/MenuManager.ts} +2 -2
  26. package/apps/desktop/src/main/core/{ShortcutManager.ts → ui/ShortcutManager.ts} +78 -6
  27. package/apps/desktop/src/main/core/{Tray.ts → ui/Tray.ts} +61 -59
  28. package/apps/desktop/src/main/core/{TrayManager.ts → ui/TrayManager.ts} +5 -5
  29. package/apps/desktop/src/main/shortcuts/config.ts +4 -2
  30. package/apps/desktop/src/main/types/store.ts +1 -0
  31. package/changelog/v1.json +21 -0
  32. package/docs/development/basic/add-new-image-model.mdx +162 -0
  33. package/docs/development/basic/add-new-image-model.zh-CN.mdx +162 -0
  34. package/docs/usage/providers/fal.mdx +1 -1
  35. package/docs/usage/providers/fal.zh-CN.mdx +1 -1
  36. package/locales/ar/hotkey.json +10 -4
  37. package/locales/ar/setting.json +12 -1
  38. package/locales/bg-BG/hotkey.json +10 -4
  39. package/locales/bg-BG/setting.json +12 -1
  40. package/locales/de-DE/hotkey.json +10 -4
  41. package/locales/de-DE/setting.json +12 -1
  42. package/locales/en-US/hotkey.json +10 -4
  43. package/locales/en-US/setting.json +12 -1
  44. package/locales/es-ES/hotkey.json +10 -4
  45. package/locales/es-ES/setting.json +12 -1
  46. package/locales/fa-IR/hotkey.json +10 -4
  47. package/locales/fa-IR/setting.json +12 -1
  48. package/locales/fr-FR/hotkey.json +10 -4
  49. package/locales/fr-FR/setting.json +12 -1
  50. package/locales/it-IT/hotkey.json +10 -4
  51. package/locales/it-IT/setting.json +12 -1
  52. package/locales/ja-JP/hotkey.json +10 -4
  53. package/locales/ja-JP/setting.json +12 -1
  54. package/locales/ko-KR/hotkey.json +10 -4
  55. package/locales/ko-KR/setting.json +12 -1
  56. package/locales/nl-NL/hotkey.json +10 -4
  57. package/locales/nl-NL/setting.json +12 -1
  58. package/locales/pl-PL/hotkey.json +10 -4
  59. package/locales/pl-PL/setting.json +12 -1
  60. package/locales/pt-BR/hotkey.json +10 -4
  61. package/locales/pt-BR/setting.json +12 -1
  62. package/locales/ru-RU/hotkey.json +10 -4
  63. package/locales/ru-RU/setting.json +12 -1
  64. package/locales/tr-TR/hotkey.json +10 -4
  65. package/locales/tr-TR/setting.json +12 -1
  66. package/locales/vi-VN/hotkey.json +10 -4
  67. package/locales/vi-VN/setting.json +12 -1
  68. package/locales/zh-CN/hotkey.json +10 -4
  69. package/locales/zh-CN/setting.json +12 -1
  70. package/locales/zh-TW/hotkey.json +10 -4
  71. package/locales/zh-TW/setting.json +12 -1
  72. package/package.json +66 -66
  73. package/packages/electron-client-ipc/src/events/shortcut.ts +3 -1
  74. package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
  75. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +3 -1
  76. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +4 -2
  77. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
  78. package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +39 -3
  79. package/src/app/[variants]/(main)/image/features/GenerationFeed/ReferenceImages.tsx +122 -0
  80. package/src/app/[variants]/(main)/settings/hotkey/features/Conversation.tsx +3 -11
  81. package/src/app/[variants]/(main)/settings/hotkey/features/Desktop.tsx +92 -0
  82. package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +3 -11
  83. package/src/app/[variants]/(main)/settings/hotkey/page.tsx +3 -0
  84. package/src/config/aiModels/fal.ts +31 -7
  85. package/src/config/aiModels/openai.ts +10 -1
  86. package/src/const/desktop.ts +9 -0
  87. package/src/const/hotkeys.ts +20 -16
  88. package/src/features/ElectronTitlebar/WinControl/index.tsx +85 -90
  89. package/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts +10 -5
  90. package/src/features/ImageTopicPanel/index.tsx +0 -1
  91. package/src/features/PluginDevModal/index.tsx +3 -1
  92. package/src/features/User/UserPanel/useMenu.tsx +2 -2
  93. package/src/features/User/__tests__/UserAvatar.test.tsx +5 -4
  94. package/src/libs/model-runtime/fal/index.ts +1 -1
  95. package/src/libs/model-runtime/types/image.ts +1 -1
  96. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +1 -1
  97. package/src/libs/model-runtime/utils/response.ts +2 -0
  98. package/src/libs/model-runtime/utils/streams/google-ai.test.ts +46 -0
  99. package/src/libs/model-runtime/utils/streams/google-ai.ts +4 -4
  100. package/src/libs/model-runtime/utils/streams/vertex-ai.ts +6 -8
  101. package/src/libs/standard-parameters/{meta-schema.test.ts → index.test.ts} +1 -1
  102. package/src/libs/standard-parameters/index.ts +152 -1
  103. package/src/locales/default/hotkey.ts +13 -5
  104. package/src/locales/default/setting.ts +11 -0
  105. package/src/server/ld.test.ts +4 -3
  106. package/src/server/routers/async/image.ts +1 -1
  107. package/src/services/__tests__/chat.test.ts +3 -4
  108. package/src/services/electron/settings.ts +19 -1
  109. package/src/store/chat/slices/message/selectors.test.ts +2 -3
  110. package/src/store/chat/slices/plugin/action.test.ts +2 -1
  111. package/src/store/electron/actions/settings.ts +42 -1
  112. package/src/store/electron/initialState.ts +9 -1
  113. package/src/store/electron/selectors/__tests__/desktopState.test.ts +6 -17
  114. package/src/store/electron/selectors/hotkey.ts +11 -0
  115. package/src/store/electron/selectors/index.ts +1 -0
  116. package/src/store/image/slices/generationConfig/action.test.ts +2 -2
  117. package/src/store/image/slices/generationConfig/action.ts +1 -1
  118. package/src/store/image/slices/generationConfig/hooks.test.ts +2 -2
  119. package/src/store/image/slices/generationConfig/hooks.ts +1 -4
  120. package/src/store/image/slices/generationConfig/initialState.ts +2 -2
  121. package/src/store/image/slices/generationConfig/selectors.test.ts +2 -2
  122. package/src/store/image/slices/generationConfig/selectors.ts +1 -1
  123. package/src/store/user/slices/auth/selectors.test.ts +3 -2
  124. package/src/types/generation/index.ts +1 -0
  125. package/src/types/hotkey.ts +18 -4
  126. package/docs/development/basic/add-new-ai-image-model.mdx +0 -36
  127. package/docs/development/basic/add-new-ai-image-model.zh-CN.mdx +0 -0
  128. package/src/config/paramsSchemas/fal/flux-kontext-dev.ts +0 -8
  129. package/src/config/paramsSchemas/fal/flux-pro-kontext.ts +0 -11
  130. package/src/config/paramsSchemas/fal/flux-schnell.ts +0 -9
  131. package/src/config/paramsSchemas/fal/imagen4.ts +0 -10
  132. package/src/config/paramsSchemas/openai/gpt-image-1.ts +0 -10
  133. package/src/libs/standard-parameters/meta-schema.ts +0 -147
  134. /package/apps/desktop/src/main/core/{I18nManager.ts → infrastructure/I18nManager.ts} +0 -0
  135. /package/apps/desktop/src/main/core/{IoCContainer.ts → infrastructure/IoCContainer.ts} +0 -0
@@ -186,6 +186,52 @@ describe('GoogleGenerativeAIStream', () => {
186
186
  ]);
187
187
  });
188
188
 
189
+ it('should handle token count with cached token count', async () => {
190
+ vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
191
+
192
+ const data = {
193
+ candidates: [{ content: { role: 'model' }, finishReason: 'STOP', index: 0 }],
194
+ usageMetadata: {
195
+ promptTokenCount: 15725,
196
+ candidatesTokenCount: 1053,
197
+ totalTokenCount: 16778,
198
+ cachedContentTokenCount: 14286,
199
+ promptTokensDetails: [{ modality: 'TEXT', tokenCount: 15725 }],
200
+ cacheTokensDetails: [{ modality: 'TEXT', tokenCount: 14286 }],
201
+ },
202
+ modelVersion: 'gemini-2.0-flash-exp',
203
+ };
204
+
205
+ const mockGoogleStream = new ReadableStream({
206
+ start(controller) {
207
+ controller.enqueue(data);
208
+
209
+ controller.close();
210
+ },
211
+ });
212
+
213
+ const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
214
+
215
+ const decoder = new TextDecoder();
216
+ const chunks = [];
217
+
218
+ // @ts-ignore
219
+ for await (const chunk of protocolStream) {
220
+ chunks.push(decoder.decode(chunk, { stream: true }));
221
+ }
222
+
223
+ expect(chunks).toEqual([
224
+ // stop
225
+ 'id: chat_1\n',
226
+ 'event: stop\n',
227
+ `data: "STOP"\n\n`,
228
+ // usage
229
+ 'id: chat_1\n',
230
+ 'event: usage\n',
231
+ `data: {"inputCachedTokens":14286,"inputTextTokens":15725,"outputTextTokens":1053,"totalInputTokens":15725,"totalOutputTokens":1053,"totalTokens":16778}\n\n`,
232
+ ]);
233
+ });
234
+
189
235
  it('should handle stop with content', async () => {
190
236
  vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
191
237
 
@@ -33,10 +33,10 @@ const transformGoogleGenerativeAIStream = (
33
33
  { data: candidate.finishReason, id: context?.id, type: 'stop' },
34
34
  {
35
35
  data: {
36
- // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
37
- inputImageTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'IMAGE')
36
+ inputCachedTokens: usage.cachedContentTokenCount,
37
+ inputImageTokens: usage.promptTokensDetails?.find((i) => i.modality === 'IMAGE')
38
38
  ?.tokenCount,
39
- inputTextTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'TEXT')
39
+ inputTextTokens: usage.promptTokensDetails?.find((i) => i.modality === 'TEXT')
40
40
  ?.tokenCount,
41
41
  outputReasoningTokens: reasoningTokens,
42
42
  outputTextTokens,
@@ -79,7 +79,7 @@ const transformGoogleGenerativeAIStream = (
79
79
  // 首先检查是否为 reasoning 内容 (thought: true)
80
80
  if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
81
81
  for (const part of candidate.content.parts) {
82
- if (part && part.text && (part as any).thought === true) {
82
+ if (part && part.text && part.thought === true) {
83
83
  return { data: part.text, id: context.id, type: 'reasoning' };
84
84
  }
85
85
  }
@@ -31,13 +31,11 @@ const transformVertexAIStream = (
31
31
  { data: candidate.finishReason, id: context?.id, type: 'stop' },
32
32
  {
33
33
  data: {
34
- // TODO: Google SDK 0.24.0 don't have promptTokensDetails types
35
- inputImageTokens: (usage as any).promptTokensDetails?.find(
36
- (i: any) => i.modality === 'IMAGE',
37
- )?.tokenCount,
38
- inputTextTokens: (usage as any).promptTokensDetails?.find(
39
- (i: any) => i.modality === 'TEXT',
40
- )?.tokenCount,
34
+ inputCachedTokens: usage.cachedContentTokenCount,
35
+ inputImageTokens: usage.promptTokensDetails?.find((i) => i.modality === 'IMAGE')
36
+ ?.tokenCount,
37
+ inputTextTokens: usage.promptTokensDetails?.find((i) => i.modality === 'TEXT')
38
+ ?.tokenCount,
41
39
  outputReasoningTokens,
42
40
  outputTextTokens,
43
41
  totalInputTokens: usage.promptTokenCount,
@@ -56,7 +54,7 @@ const transformVertexAIStream = (
56
54
  candidate.content.parts.length > 0
57
55
  ) {
58
56
  for (const part of candidate.content.parts) {
59
- if (part && part.text && (part as any).thought === true) {
57
+ if (part && part.text && part.thought === true) {
60
58
  return { data: part.text, id: context.id, type: 'reasoning' };
61
59
  }
62
60
  }
@@ -6,7 +6,7 @@ import {
6
6
  type RuntimeImageGenParams,
7
7
  extractDefaultValues,
8
8
  validateModelParamsSchema,
9
- } from './meta-schema';
9
+ } from './index';
10
10
 
11
11
  describe('meta-schema', () => {
12
12
  describe('ModelParamsMetaSchema', () => {
@@ -1 +1,152 @@
1
- export * from './meta-schema';
1
+ import type { Simplify } from 'type-fest';
2
+ import { z } from 'zod';
3
+
4
+ export const MAX_SEED = 2 ** 31 - 1;
5
+
6
+ // 定义顶层的元规范 - 平铺结构
7
+ export const ModelParamsMetaSchema = z.object({
8
+ aspectRatio: z
9
+ .object({
10
+ default: z.string(),
11
+ description: z.string().optional(),
12
+ enum: z.array(z.string()),
13
+ type: z.literal('string').optional(),
14
+ })
15
+ .optional(),
16
+
17
+ cfg: z
18
+ .object({
19
+ default: z.number(),
20
+ description: z.string().optional(),
21
+ max: z.number(),
22
+ min: z.number(),
23
+ step: z.number(),
24
+ type: z.literal('number').optional(),
25
+ })
26
+ .optional(),
27
+
28
+ height: z
29
+ .object({
30
+ default: z.number(),
31
+ description: z.string().optional(),
32
+ max: z.number(),
33
+ min: z.number(),
34
+ step: z.number().optional().default(1),
35
+ type: z.literal('number').optional(),
36
+ })
37
+ .optional(),
38
+
39
+ imageUrl: z
40
+ .object({
41
+ default: z.string().nullable().optional(),
42
+ description: z.string().optional(),
43
+ type: z.tuple([z.literal('string'), z.literal('null')]).optional(),
44
+ })
45
+ .optional(),
46
+
47
+ imageUrls: z
48
+ .object({
49
+ default: z.array(z.string()),
50
+ description: z.string().optional(),
51
+ type: z.literal('array').optional(),
52
+ })
53
+ .optional(),
54
+
55
+ /**
56
+ * Prompt 是唯一一个每个模型都有的参数
57
+ */
58
+ prompt: z.object({
59
+ default: z.string().optional().default(''),
60
+ description: z.string().optional(),
61
+ type: z.literal('string').optional(),
62
+ }),
63
+
64
+ seed: z
65
+ .object({
66
+ default: z.number().nullable().default(null),
67
+ description: z.string().optional(),
68
+ max: z.number().optional().default(MAX_SEED),
69
+ min: z.number().optional().default(0),
70
+ type: z.tuple([z.literal('number'), z.literal('null')]).optional(),
71
+ })
72
+ .optional(),
73
+
74
+ size: z
75
+ .object({
76
+ default: z.string(),
77
+ description: z.string().optional(),
78
+ enum: z.array(z.string()),
79
+ type: z.literal('string').optional(),
80
+ })
81
+ .optional(),
82
+
83
+ steps: z
84
+ .object({
85
+ default: z.number(),
86
+ description: z.string().optional(),
87
+ max: z.number(),
88
+ min: z.number(),
89
+ step: z.number().optional().default(1),
90
+ type: z.literal('number').optional(),
91
+ })
92
+ .optional(),
93
+
94
+ width: z
95
+ .object({
96
+ default: z.number(),
97
+ description: z.string().optional(),
98
+ max: z.number(),
99
+ min: z.number(),
100
+ step: z.number().optional().default(1),
101
+ type: z.literal('number').optional(),
102
+ })
103
+ .optional(),
104
+ });
105
+ // 导出推断出的类型,供定义对象使用
106
+ export type ModelParamsSchema = z.input<typeof ModelParamsMetaSchema>;
107
+ export type ModelParamsOutputSchema = z.output<typeof ModelParamsMetaSchema>;
108
+ export type ModelParamsKeys = Simplify<keyof ModelParamsOutputSchema>;
109
+
110
+ type TypeMapping<T> = T extends 'string'
111
+ ? string
112
+ : T extends 'number'
113
+ ? number
114
+ : T extends ['number', 'null']
115
+ ? number | null
116
+ : T extends ['string', 'null']
117
+ ? string | null
118
+ : T extends 'string'
119
+ ? string
120
+ : T extends 'boolean'
121
+ ? boolean
122
+ : never;
123
+ type TypeType<K extends ModelParamsKeys> = NonNullable<ModelParamsOutputSchema[K]>['type'];
124
+ type DefaultType<K extends ModelParamsKeys> = NonNullable<ModelParamsOutputSchema[K]>['default'];
125
+ type _StandardImageGenerationParameters<P extends ModelParamsKeys = ModelParamsKeys> = {
126
+ [key in P]: NonNullable<TypeType<key>> extends 'array'
127
+ ? DefaultType<key>
128
+ : TypeMapping<TypeType<key>>;
129
+ };
130
+
131
+ export type RuntimeImageGenParams = Pick<_StandardImageGenerationParameters, 'prompt'> &
132
+ Partial<Omit<_StandardImageGenerationParameters, 'prompt'>>;
133
+ export type RuntimeImageGenParamsKeys = keyof RuntimeImageGenParams;
134
+ export type RuntimeImageGenParamsValue = RuntimeImageGenParams[RuntimeImageGenParamsKeys];
135
+
136
+ // 验证函数
137
+ export function validateModelParamsSchema(paramsSchema: unknown): ModelParamsOutputSchema {
138
+ return ModelParamsMetaSchema.parse(paramsSchema);
139
+ }
140
+
141
+ /**
142
+ * 从参数定义对象提取默认值
143
+ */
144
+ export function extractDefaultValues(paramsSchema: ModelParamsSchema) {
145
+ // 部分默认值从 ModelParamsMetaSchema 中获取
146
+ const schemaWithDefault = ModelParamsMetaSchema.parse(paramsSchema);
147
+ return Object.fromEntries(
148
+ Object.entries(schemaWithDefault).map(([key, value]) => {
149
+ return [key, value.default];
150
+ }),
151
+ ) as RuntimeImageGenParams;
152
+ }
@@ -1,6 +1,8 @@
1
1
  import { HotkeyI18nTranslations } from '@/types/hotkey';
2
2
 
3
- const hotkey: HotkeyI18nTranslations = {
3
+ const hotkey: HotkeyI18nTranslations & {
4
+ desktop: Record<string, { desc?: string; title: string }>;
5
+ } = {
4
6
  addUserMessage: {
5
7
  desc: '将当前输入内容添加为用户消息,但不触发生成',
6
8
  title: '添加一条用户消息',
@@ -9,6 +11,16 @@ const hotkey: HotkeyI18nTranslations = {
9
11
  desc: '清空当前会话的消息和上传的文件',
10
12
  title: '清空会话消息',
11
13
  },
14
+ desktop: {
15
+ openSettings: {
16
+ desc: '打开应用设置页面',
17
+ title: '应用设置',
18
+ },
19
+ showApp: {
20
+ desc: '全局快捷键显示或隐藏主窗口',
21
+ title: '显示/隐藏主窗口',
22
+ },
23
+ },
12
24
  editMessage: {
13
25
  desc: '通过按住 Alt 并双击消息进入编辑模式',
14
26
  title: '编辑消息',
@@ -21,10 +33,6 @@ const hotkey: HotkeyI18nTranslations = {
21
33
  desc: '查看所有快捷键的使用说明',
22
34
  title: '打开快捷键帮助',
23
35
  },
24
- openSettings: {
25
- desc: '打开应用设置页面',
26
- title: '应用设置',
27
- },
28
36
  regenerateMessage: {
29
37
  desc: '重新生成最后一条消息',
30
38
  title: '重新生成消息',
@@ -45,14 +45,25 @@ export default {
45
45
  },
46
46
  hotkey: {
47
47
  conflicts: '与现有快捷键冲突',
48
+ errors: {
49
+ CONFLICT: '快捷键冲突:该快捷键已被其他功能占用',
50
+ INVALID_FORMAT: '快捷键格式无效:请使用正确的格式(如 CommandOrControl+E)',
51
+ INVALID_ID: '无效的快捷键ID',
52
+ NO_MODIFIER: '快捷键必须包含修饰键(Ctrl、Alt、Shift等)',
53
+ SYSTEM_OCCUPIED: '快捷键已被系统或其他应用程序占用',
54
+ UNKNOWN: '更新失败:未知错误',
55
+ },
48
56
  group: {
49
57
  conversation: '会话',
58
+ desktop: '桌面端',
50
59
  essential: '基础',
51
60
  },
52
61
  invalidCombination: '快捷键需要至少包含一个修饰键 (Ctrl, Alt, Shift) 和一个常规键',
53
62
  record: '按下按键以录制快捷键',
54
63
  reset: '重置为默认快捷键',
55
64
  title: '快捷键',
65
+ updateError: '快捷键更新失败:网络或系统错误',
66
+ updateSuccess: '快捷键更新成功',
56
67
  },
57
68
  llm: {
58
69
  aesGcm: '您的秘钥与代理地址等将使用 <1>AES-GCM</1> 加密算法进行加密',
@@ -1,6 +1,7 @@
1
1
  // @vitest-environment node
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
+ import { BRANDING_NAME } from '@/const/branding';
4
5
  import { DEFAULT_LANG } from '@/const/locale';
5
6
 
6
7
  import { AUTHOR_LIST, Ld } from './ld';
@@ -57,7 +58,7 @@ describe('Ld', () => {
57
58
  });
58
59
 
59
60
  expect(webpage['@type']).toBe('WebPage');
60
- expect(webpage.name).toBe('Test Page · LobeChat');
61
+ expect(webpage.name).toBe(`Test Page · ${BRANDING_NAME}`);
61
62
  expect(webpage.description).toBe('Test Description');
62
63
  });
63
64
  });
@@ -79,7 +80,7 @@ describe('Ld', () => {
79
80
  const website = ld.genWebSite();
80
81
 
81
82
  expect(website['@type']).toBe('WebSite');
82
- expect(website.name).toBe('LobeChat');
83
+ expect(website.name).toBe(BRANDING_NAME);
83
84
  });
84
85
  });
85
86
 
@@ -95,7 +96,7 @@ describe('Ld', () => {
95
96
  });
96
97
 
97
98
  expect(article['@type']).toBe('Article');
98
- expect(article.headline).toBe('Test Article · LobeChat');
99
+ expect(article.headline).toBe(`Test Article · ${BRANDING_NAME}`);
99
100
  expect(article.author['@type']).toBe('Person');
100
101
  });
101
102
  });
@@ -5,7 +5,7 @@ import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '@/database/models/asyncTask'
5
5
  import { FileModel } from '@/database/models/file';
6
6
  import { GenerationModel } from '@/database/models/generation';
7
7
  import { AgentRuntimeErrorType } from '@/libs/model-runtime/error';
8
- import { RuntimeImageGenParams } from '@/libs/standard-parameters/meta-schema';
8
+ import { RuntimeImageGenParams } from '@/libs/standard-parameters/index';
9
9
  import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async';
10
10
  import { initAgentRuntimeWithUserPayload } from '@/server/modules/AgentRuntime';
11
11
  import { GenerationService } from '@/server/services/generation';
@@ -4,6 +4,7 @@ import { merge } from 'lodash-es';
4
4
  import OpenAI from 'openai';
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
6
 
7
+ import { DEFAULT_USER_AVATAR } from '@/const/meta';
7
8
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
8
9
  import {
9
10
  LobeAnthropicAI,
@@ -31,8 +32,6 @@ import { aiModelSelectors } from '@/store/aiInfra';
31
32
  import { useToolStore } from '@/store/tool';
32
33
  import { toolSelectors } from '@/store/tool/selectors';
33
34
  import { UserStore } from '@/store/user';
34
- import { useUserStore } from '@/store/user';
35
- import { modelConfigSelectors } from '@/store/user/selectors';
36
35
  import { UserSettingsState, initialSettingsState } from '@/store/user/slices/settings/initialState';
37
36
  import { DalleManifest } from '@/tools/dalle';
38
37
  import { WebBrowsingManifest } from '@/tools/web-browsing';
@@ -671,7 +670,7 @@ describe('ChatService', () => {
671
670
  updatedAt: 1702723964330,
672
671
  extra: {},
673
672
  meta: {
674
- avatar: '😀',
673
+ avatar: DEFAULT_USER_AVATAR,
675
674
  },
676
675
  },
677
676
  ] as ChatMessage[];
@@ -913,7 +912,7 @@ describe('ChatService', () => {
913
912
  updatedAt: 1702723964330,
914
913
  extra: {},
915
914
  meta: {
916
- avatar: '😀',
915
+ avatar: DEFAULT_USER_AVATAR,
917
916
  },
918
917
  },
919
918
  ] as ChatMessage[];
@@ -1,4 +1,8 @@
1
- import { NetworkProxySettings, dispatch } from '@lobechat/electron-client-ipc';
1
+ import {
2
+ NetworkProxySettings,
3
+ ShortcutUpdateResult,
4
+ dispatch,
5
+ } from '@lobechat/electron-client-ipc';
2
6
 
3
7
  class DesktopSettingsService {
4
8
  /**
@@ -15,6 +19,20 @@ class DesktopSettingsService {
15
19
  return dispatch('setProxySettings', data);
16
20
  };
17
21
 
22
+ /**
23
+ * 获取桌面热键配置
24
+ */
25
+ getDesktopHotkeys = async () => {
26
+ return dispatch('getShortcutsConfig');
27
+ };
28
+
29
+ /**
30
+ * 更新桌面热键配置
31
+ */
32
+ updateDesktopHotkey = async (id: string, accelerator: string): Promise<ShortcutUpdateResult> => {
33
+ return dispatch('updateShortcutConfig', { accelerator, id });
34
+ };
35
+
18
36
  /**
19
37
  * 测试代理连接
20
38
  */
@@ -10,7 +10,6 @@ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
10
10
  import { createServerConfigStore } from '@/store/serverConfig/store';
11
11
  import { LobeAgentConfig } from '@/types/agent';
12
12
  import { ChatMessage } from '@/types/message';
13
- import { MetaData } from '@/types/meta';
14
13
  import { merge } from '@/utils/merge';
15
14
 
16
15
  import { chatSelectors } from './selectors';
@@ -91,7 +90,7 @@ const mockedChats = [
91
90
  content: 'Function Message',
92
91
  role: 'tool',
93
92
  meta: {
94
- avatar: '🤯',
93
+ avatar: DEFAULT_INBOX_AVATAR,
95
94
  backgroundColor: 'rgba(0,0,0,0)',
96
95
  description: 'inbox.desc',
97
96
  title: 'inbox.title',
@@ -234,7 +233,7 @@ describe('chatSelectors', () => {
234
233
  content: 'Function Message',
235
234
  role: 'tool',
236
235
  meta: {
237
- avatar: '🤯',
236
+ avatar: DEFAULT_INBOX_AVATAR,
238
237
  backgroundColor: 'rgba(0,0,0,0)',
239
238
  description: 'inbox.desc',
240
239
  title: 'inbox.title',
@@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { Mock, afterEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { LOADING_FLAT } from '@/const/message';
5
+ import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
5
6
  import { PLUGIN_SCHEMA_API_MD5_PREFIX, PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
6
7
  import { chatService } from '@/services/chat';
7
8
  import { messageService } from '@/services/message';
@@ -66,7 +67,7 @@ describe('ChatPluginAction', () => {
66
67
  {
67
68
  ...toolMessage,
68
69
  meta: {
69
- avatar: '🤯',
70
+ avatar: DEFAULT_INBOX_AVATAR,
70
71
  backgroundColor: 'rgba(0,0,0,0)',
71
72
  description: undefined,
72
73
  title: undefined,
@@ -1,4 +1,4 @@
1
- import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
1
+ import { NetworkProxySettings, ShortcutUpdateResult } from '@lobechat/electron-client-ipc';
2
2
  import isEqual from 'fast-deep-equal';
3
3
  import useSWR, { SWRResponse, mutate } from 'swr';
4
4
  import type { StateCreator } from 'zustand/vanilla';
@@ -11,12 +11,16 @@ import type { ElectronStore } from '../store';
11
11
  * 设置操作
12
12
  */
13
13
  export interface ElectronSettingsAction {
14
+ refreshDesktopHotkeys: () => Promise<void>;
14
15
  refreshProxySettings: () => Promise<void>;
15
16
  setProxySettings: (params: Partial<NetworkProxySettings>) => Promise<void>;
17
+ updateDesktopHotkey: (id: string, accelerator: string) => Promise<ShortcutUpdateResult>;
18
+ useFetchDesktopHotkeys: () => SWRResponse;
16
19
  useGetProxySettings: () => SWRResponse;
17
20
  }
18
21
 
19
22
  const ELECTRON_PROXY_SETTINGS_KEY = 'electron:getProxySettings';
23
+ const ELECTRON_DESKTOP_HOTKEYS_KEY = 'electron:getDesktopHotkeys';
20
24
 
21
25
  export const settingsSlice: StateCreator<
22
26
  ElectronStore,
@@ -24,6 +28,10 @@ export const settingsSlice: StateCreator<
24
28
  [],
25
29
  ElectronSettingsAction
26
30
  > = (set, get) => ({
31
+ refreshDesktopHotkeys: async () => {
32
+ await mutate(ELECTRON_DESKTOP_HOTKEYS_KEY);
33
+ },
34
+
27
35
  refreshProxySettings: async () => {
28
36
  await mutate(ELECTRON_PROXY_SETTINGS_KEY);
29
37
  },
@@ -40,6 +48,39 @@ export const settingsSlice: StateCreator<
40
48
  }
41
49
  },
42
50
 
51
+ updateDesktopHotkey: async (id, accelerator) => {
52
+ try {
53
+ // 更新热键配置
54
+ const result = await desktopSettingsService.updateDesktopHotkey(id, accelerator);
55
+
56
+ // 如果更新成功,刷新状态
57
+ if (result.success) {
58
+ await get().refreshDesktopHotkeys();
59
+ }
60
+
61
+ return result;
62
+ } catch (error) {
63
+ console.error('桌面热键更新失败:', error);
64
+ return {
65
+ errorType: 'UNKNOWN' as const,
66
+ success: false,
67
+ };
68
+ }
69
+ },
70
+
71
+ useFetchDesktopHotkeys: () =>
72
+ useSWR<Record<string, string>>(
73
+ ELECTRON_DESKTOP_HOTKEYS_KEY,
74
+ async () => desktopSettingsService.getDesktopHotkeys(),
75
+ {
76
+ onSuccess: (data) => {
77
+ if (!isEqual(data, get().desktopHotkeys)) {
78
+ set({ desktopHotkeys: data, isDesktopHotkeysInit: true });
79
+ }
80
+ },
81
+ },
82
+ ),
83
+
43
84
  useGetProxySettings: () =>
44
85
  useSWR<NetworkProxySettings>(
45
86
  ELECTRON_PROXY_SETTINGS_KEY,
@@ -1,4 +1,8 @@
1
- import { DataSyncConfig, ElectronAppState, NetworkProxySettings } from '@lobechat/electron-client-ipc';
1
+ import {
2
+ DataSyncConfig,
3
+ ElectronAppState,
4
+ NetworkProxySettings,
5
+ } from '@lobechat/electron-client-ipc';
2
6
 
3
7
  export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERROR';
4
8
 
@@ -14,8 +18,10 @@ export const defaultProxySettings: NetworkProxySettings = {
14
18
  export interface ElectronState {
15
19
  appState: ElectronAppState;
16
20
  dataSyncConfig: DataSyncConfig;
21
+ desktopHotkeys: Record<string, string>;
17
22
  isAppStateInit?: boolean;
18
23
  isConnectingServer?: boolean;
24
+ isDesktopHotkeysInit: boolean;
19
25
  isInitRemoteServerConfig: boolean;
20
26
  isSyncActive?: boolean;
21
27
  proxySettings: NetworkProxySettings;
@@ -25,8 +31,10 @@ export interface ElectronState {
25
31
  export const initialState: ElectronState = {
26
32
  appState: {},
27
33
  dataSyncConfig: { storageMode: 'local' },
34
+ desktopHotkeys: {},
28
35
  isAppStateInit: false,
29
36
  isConnectingServer: false,
37
+ isDesktopHotkeysInit: false,
30
38
  isInitRemoteServerConfig: false,
31
39
  isSyncActive: false,
32
40
  proxySettings: defaultProxySettings,
@@ -1,14 +1,14 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { ElectronState, defaultProxySettings } from '@/store/electron/initialState';
3
+ import { ElectronState, defaultProxySettings, initialState } from '@/store/electron/initialState';
4
+ import { merge } from '@/utils/merge';
4
5
 
5
6
  import { desktopStateSelectors } from '../desktopState';
6
7
 
7
8
  describe('desktopStateSelectors', () => {
8
9
  describe('usePath', () => {
9
10
  it('should return userPath from appState', () => {
10
- const state: ElectronState = {
11
- isAppStateInit: false,
11
+ const state: ElectronState = merge(initialState, {
12
12
  appState: {
13
13
  userPath: {
14
14
  desktop: '/test/desktop',
@@ -21,12 +21,7 @@ describe('desktopStateSelectors', () => {
21
21
  videos: '/test/videos',
22
22
  },
23
23
  },
24
- dataSyncConfig: {
25
- storageMode: 'local',
26
- },
27
- isInitRemoteServerConfig: false,
28
- proxySettings: defaultProxySettings,
29
- };
24
+ });
30
25
 
31
26
  expect(desktopStateSelectors.usePath(state)).toEqual({
32
27
  desktop: '/test/desktop',
@@ -41,15 +36,9 @@ describe('desktopStateSelectors', () => {
41
36
  });
42
37
 
43
38
  it('should handle undefined userPath', () => {
44
- const state: ElectronState = {
39
+ const state: ElectronState = merge(initialState, {
45
40
  appState: {},
46
- isAppStateInit: false,
47
- dataSyncConfig: {
48
- storageMode: 'local',
49
- },
50
- isInitRemoteServerConfig: false,
51
- proxySettings: defaultProxySettings,
52
- };
41
+ });
53
42
 
54
43
  expect(desktopStateSelectors.usePath(state)).toBeUndefined();
55
44
  });
@@ -0,0 +1,11 @@
1
+ import { DEFAULT_DESKTOP_HOTKEY_CONFIG } from '@/const/desktop';
2
+ import { ElectronState } from '@/store/electron/initialState';
3
+ import { merge } from '@/utils/merge';
4
+
5
+ const hotkeys = (s: ElectronState) => merge(DEFAULT_DESKTOP_HOTKEY_CONFIG, s.desktopHotkeys);
6
+ const isHotkeysInit = (s: ElectronState) => s.isDesktopHotkeysInit;
7
+
8
+ export const desktopHotkeysSelectors = {
9
+ hotkeys,
10
+ isHotkeysInit,
11
+ };
@@ -1,2 +1,3 @@
1
1
  export * from './desktopState';
2
+ export * from './hotkey';
2
3
  export * from './sync';
@@ -1,12 +1,12 @@
1
1
  import { act, renderHook } from '@testing-library/react';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { fluxSchnellParamsSchema } from '@/config/paramsSchemas/fal/flux-schnell';
4
+ import { fluxSchnellParamsSchema } from '@/config/aiModels/fal';
5
5
  import {
6
6
  ModelParamsSchema,
7
7
  RuntimeImageGenParams,
8
8
  extractDefaultValues,
9
- } from '@/libs/standard-parameters/meta-schema';
9
+ } from '@/libs/standard-parameters/index';
10
10
  import { useImageStore } from '@/store/image';
11
11
  import { AIImageModelCard } from '@/types/aiModel';
12
12