@lobehub/chat 1.79.1 → 1.79.3

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 (43) hide show
  1. package/.env.desktop +7 -0
  2. package/.github/scripts/pr-release-body.js +3 -0
  3. package/CHANGELOG.md +50 -0
  4. package/changelog/v1.json +18 -0
  5. package/package.json +15 -7
  6. package/packages/electron-client-ipc/src/events/file.ts +5 -0
  7. package/packages/electron-client-ipc/src/events/index.ts +19 -1
  8. package/packages/electron-client-ipc/src/events/shortcut.ts +4 -0
  9. package/packages/electron-client-ipc/src/events/system.ts +5 -0
  10. package/packages/electron-client-ipc/src/events/update.ts +20 -0
  11. package/packages/electron-client-ipc/src/events/windows.ts +9 -0
  12. package/packages/electron-client-ipc/src/index.ts +1 -0
  13. package/packages/electron-client-ipc/src/types/file.ts +14 -0
  14. package/packages/electron-client-ipc/src/types/index.ts +4 -0
  15. package/packages/electron-client-ipc/src/types/route.ts +46 -0
  16. package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
  17. package/packages/electron-client-ipc/src/types/update.ts +23 -0
  18. package/packages/electron-client-ipc/src/useWatchBroadcast.ts +37 -0
  19. package/packages/electron-server-ipc/src/events/database.ts +4 -0
  20. package/packages/electron-server-ipc/src/events/file.ts +6 -0
  21. package/packages/electron-server-ipc/src/events/index.ts +29 -0
  22. package/packages/electron-server-ipc/src/events/storagePath.ts +4 -0
  23. package/packages/electron-server-ipc/src/index.ts +1 -0
  24. package/packages/electron-server-ipc/src/ipcClient.test.ts +3 -4
  25. package/packages/electron-server-ipc/src/ipcClient.ts +52 -24
  26. package/packages/electron-server-ipc/src/ipcServer.ts +6 -14
  27. package/packages/electron-server-ipc/src/types/file.ts +4 -0
  28. package/packages/electron-server-ipc/src/types/index.ts +14 -1
  29. package/scripts/electronWorkflow/buildElectron.ts +52 -0
  30. package/scripts/electronWorkflow/moveNextStandalone.ts +69 -0
  31. package/scripts/electronWorkflow/setDesktopVersion.ts +96 -0
  32. package/scripts/prebuild.mts +77 -0
  33. package/src/config/aiModels/tencentcloud.ts +0 -6
  34. package/src/libs/agent-runtime/azureai/index.ts +3 -1
  35. package/src/libs/agent-runtime/tencentcloud/index.ts +1 -6
  36. package/src/prompts/files/file.ts +6 -4
  37. package/src/prompts/files/image.ts +6 -3
  38. package/src/prompts/files/index.test.ts +50 -0
  39. package/src/prompts/files/index.ts +4 -2
  40. package/src/server/globalConfig/index.ts +4 -0
  41. package/src/services/chat.ts +4 -2
  42. package/src/store/global/actions/general.ts +13 -6
  43. package/packages/electron-server-ipc/src/types/event.ts +0 -18
@@ -4,16 +4,8 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
 
6
6
  import { SOCK_FILE, SOCK_INFO_FILE, WINDOW_PIPE_FILE } from './const';
7
- import { IElectronIPCMethods } from './types';
8
-
9
- export type IPCEventMethod = (
10
- params: any,
11
- context: { id: string; method: string; socket: net.Socket },
12
- ) => Promise<any>;
13
-
14
- export type ElectronIPCEventHandler = {
15
- [key in IElectronIPCMethods]: IPCEventMethod;
16
- };
7
+ import { ServerDispatchEventKey } from './events';
8
+ import { ElectronIPCEventHandler } from './types';
17
9
 
18
10
  export class ElectronIPCServer {
19
11
  private server: net.Server;
@@ -45,7 +37,7 @@ export class ElectronIPCServer {
45
37
  });
46
38
 
47
39
  this.server.listen(this.socketPath, () => {
48
- console.log(`Electron IPC server listening on ${this.socketPath}`);
40
+ console.log(`[ElectronIPCServer] Electron IPC server listening on ${this.socketPath}`);
49
41
 
50
42
  // 将套接字路径写入临时文件,供 Next.js 服务端读取
51
43
  const tempDir = os.tmpdir();
@@ -86,7 +78,7 @@ export class ElectronIPCServer {
86
78
  const { id, method, params } = request;
87
79
 
88
80
  // 根据请求方法执行相应的操作
89
- const eventHandler = this.eventHandler[method as IElectronIPCMethods];
81
+ const eventHandler = this.eventHandler[method as ServerDispatchEventKey];
90
82
  if (!eventHandler) return;
91
83
 
92
84
  try {
@@ -100,12 +92,12 @@ export class ElectronIPCServer {
100
92
 
101
93
  // 发送结果
102
94
  private sendResult(socket: net.Socket, id: string, result: any): void {
103
- socket.write(JSON.stringify({ id, result }));
95
+ socket.write(JSON.stringify({ id, result }) + '\n');
104
96
  }
105
97
 
106
98
  // 发送错误
107
99
  private sendError(socket: net.Socket, id: string, error: string): void {
108
- socket.write(JSON.stringify({ error, id }));
100
+ socket.write(JSON.stringify({ error, id }) + '\n');
109
101
  }
110
102
 
111
103
  // 关闭服务器
@@ -0,0 +1,4 @@
1
+ export interface DeleteFilesResponse {
2
+ errors?: { message: string; path: string }[];
3
+ success: boolean;
4
+ }
@@ -1 +1,14 @@
1
- export * from './event';
1
+ import net from 'node:net';
2
+
3
+ import { ServerDispatchEventKey } from '../events';
4
+
5
+ export type IPCEventMethod = (
6
+ params: any,
7
+ context: { id: string; method: string; socket: net.Socket },
8
+ ) => Promise<any>;
9
+
10
+ export type ElectronIPCEventHandler = {
11
+ [key in ServerDispatchEventKey]: IPCEventMethod;
12
+ };
13
+
14
+ export * from './file';
@@ -0,0 +1,52 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import { execSync } from 'node:child_process';
3
+ import os from 'node:os';
4
+
5
+ /**
6
+ * Build desktop application based on current operating system platform
7
+ */
8
+ const buildElectron = () => {
9
+ const platform = os.platform();
10
+ const startTime = Date.now();
11
+
12
+ console.log(`🔨 Starting to build desktop app for ${platform} platform...`);
13
+
14
+ try {
15
+ let buildCommand = '';
16
+
17
+ // Determine build command based on platform
18
+ switch (platform) {
19
+ case 'darwin': {
20
+ buildCommand = 'npm run build:mac --prefix=./apps/desktop';
21
+ console.log('📦 Building macOS desktop application...');
22
+ break;
23
+ }
24
+ case 'win32': {
25
+ buildCommand = 'npm run build:win --prefix=./apps/desktop';
26
+ console.log('📦 Building Windows desktop application...');
27
+ break;
28
+ }
29
+ case 'linux': {
30
+ buildCommand = 'npm run build:linux --prefix=./apps/desktop';
31
+ console.log('📦 Building Linux desktop application...');
32
+ break;
33
+ }
34
+ default: {
35
+ throw new Error(`Unsupported platform: ${platform}`);
36
+ }
37
+ }
38
+
39
+ // Execute build command
40
+ execSync(buildCommand, { stdio: 'inherit' });
41
+
42
+ const endTime = Date.now();
43
+ const buildTime = ((endTime - startTime) / 1000).toFixed(2);
44
+ console.log(`✅ Desktop application build completed! (${buildTime}s)`);
45
+ } catch (error) {
46
+ console.error('❌ Build failed:', error);
47
+ process.exit(1);
48
+ }
49
+ };
50
+
51
+ // Execute build
52
+ buildElectron();
@@ -0,0 +1,69 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import fs from 'fs-extra';
3
+ import { execSync } from 'node:child_process';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ const rootDir = path.resolve(__dirname, '../..');
8
+
9
+ // 定义源目录和目标目录
10
+ const sourceDir: string = path.join(rootDir, '.next/standalone');
11
+ const targetDir: string = path.join(rootDir, 'apps/desktop/dist/next');
12
+
13
+ // 向 sourceDir 写入 .env 文件
14
+ const env = fs.readFileSync(path.join(rootDir, '.env.desktop'), 'utf8');
15
+
16
+ fs.writeFileSync(path.join(sourceDir, '.env'), env, 'utf8');
17
+ console.log(`⚓️ Inject .env successful`);
18
+
19
+ // 确保目标目录的父目录存在
20
+ fs.ensureDirSync(path.dirname(targetDir));
21
+
22
+ // 如果目标目录已存在,先删除它
23
+ if (fs.existsSync(targetDir)) {
24
+ console.log(`🗑️ Target directory ${targetDir} already exists, deleting...`);
25
+ try {
26
+ fs.removeSync(targetDir);
27
+ console.log(`✅ Old target directory removed successfully`);
28
+ } catch (error) {
29
+ console.warn(`⚠️ Failed to delete target directory: ${error}`);
30
+ console.log('🔄 Trying to delete using system command...');
31
+ try {
32
+ if (os.platform() === 'win32') {
33
+ execSync(`rmdir /S /Q "${targetDir}"`, { stdio: 'inherit' });
34
+ } else {
35
+ execSync(`rm -rf "${targetDir}"`, { stdio: 'inherit' });
36
+ }
37
+ console.log('✅ Successfully deleted old target directory');
38
+ } catch (cmdError) {
39
+ console.error(`❌ Unable to delete target directory, might need manual cleanup: ${cmdError}`);
40
+ }
41
+ }
42
+ }
43
+
44
+ console.log(`🚚 Moving ${sourceDir} to ${targetDir}...`);
45
+
46
+ try {
47
+ // 使用 fs-extra 的 move 方法
48
+ fs.moveSync(sourceDir, targetDir, { overwrite: true });
49
+ console.log(`✅ Directory moved successfully!`);
50
+ } catch (error) {
51
+ console.error('❌ fs-extra move failed:', error);
52
+ console.log('🔄 Trying to move using system command...');
53
+
54
+ try {
55
+ // 使用系统命令进行移动
56
+ if (os.platform() === 'win32') {
57
+ execSync(`move "${sourceDir}" "${targetDir}"`, { stdio: 'inherit' });
58
+ } else {
59
+ execSync(`mv "${sourceDir}" "${targetDir}"`, { stdio: 'inherit' });
60
+ }
61
+ console.log('✅ System command move completed successfully!');
62
+ } catch (mvError) {
63
+ console.error('❌ Failed to move directory:', mvError);
64
+ console.log('💡 Try running manually: sudo mv ' + sourceDir + ' ' + targetDir);
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ console.log(`🎉 Move completed!`);
@@ -0,0 +1,96 @@
1
+ /* eslint-disable unicorn/no-process-exit */
2
+ import fs from 'fs-extra';
3
+ import path from 'node:path';
4
+
5
+ // 获取脚本的命令行参数
6
+ const version = process.argv[2];
7
+ const isPr = process.argv[3] === 'true';
8
+
9
+ if (!version) {
10
+ console.error('Missing version parameter, usage: bun run setDesktopVersion.ts <version> [isPr]');
11
+ process.exit(1);
12
+ }
13
+
14
+ // 获取根目录
15
+ const rootDir = path.resolve(__dirname, '../..');
16
+
17
+ // 桌面应用 package.json 的路径
18
+ const desktopPackageJsonPath = path.join(rootDir, 'apps/desktop/package.json');
19
+
20
+ // 更新应用图标
21
+ function updateAppIcon() {
22
+ try {
23
+ const buildDir = path.join(rootDir, 'apps/desktop/build');
24
+
25
+ // 定义需要处理的图标映射,考虑到大小写敏感性
26
+ const iconMappings = [
27
+ // { ext: '.ico', nightly: 'icon-nightly.ico', normal: 'icon.ico' },
28
+ { ext: '.png', nightly: 'icon-nightly.png', normal: 'icon.png' },
29
+ { ext: '.icns', nightly: 'Icon-nightly.icns', normal: 'Icon.icns' },
30
+ ];
31
+
32
+ // 处理每种图标格式
33
+ for (const mapping of iconMappings) {
34
+ const sourceFile = path.join(buildDir, mapping.nightly);
35
+ const targetFile = path.join(buildDir, mapping.normal);
36
+
37
+ // 检查源文件是否存在
38
+ if (fs.existsSync(sourceFile)) {
39
+ // 只有当源文件和目标文件不同,才进行复制
40
+ if (sourceFile !== targetFile) {
41
+ fs.copyFileSync(sourceFile, targetFile);
42
+ console.log(`Updated app icon: ${targetFile}`);
43
+ }
44
+ } else {
45
+ console.warn(`Warning: Source icon not found: ${sourceFile}`);
46
+ }
47
+ }
48
+ } catch (error) {
49
+ console.error('Error updating icons:', error);
50
+ // 继续处理,不终止程序
51
+ }
52
+ }
53
+
54
+ function updateVersion() {
55
+ try {
56
+ // 确保文件存在
57
+ if (!fs.existsSync(desktopPackageJsonPath)) {
58
+ console.error(`Error: File not found ${desktopPackageJsonPath}`);
59
+ process.exit(1);
60
+ }
61
+
62
+ // 读取 package.json 文件
63
+ const packageJson = fs.readJSONSync(desktopPackageJsonPath);
64
+
65
+ // 更新版本号
66
+ packageJson.version = version;
67
+ packageJson.productName = 'LobeHub';
68
+ packageJson.name = 'lobehub-desktop';
69
+
70
+ // 如果是 PR 构建,设置为 Nightly 版本
71
+ if (isPr) {
72
+ // 修改包名,添加 -nightly 后缀
73
+ if (!packageJson.name.endsWith('-nightly')) {
74
+ packageJson.name = `${packageJson.name}-nightly`;
75
+ }
76
+
77
+ // 修改产品名称为 LobeHub Nightly
78
+ packageJson.productName = 'LobeHub-Nightly';
79
+
80
+ console.log('🌙 Setting as Nightly version with modified package name and productName');
81
+
82
+ // 使用 nightly 图标替换常规图标
83
+ updateAppIcon();
84
+ }
85
+
86
+ // 写回文件
87
+ fs.writeJsonSync(desktopPackageJsonPath, packageJson, { spaces: 2 });
88
+
89
+ console.log(`Desktop app version updated to: ${version}, isPr: ${isPr}`);
90
+ } catch (error) {
91
+ console.error('Error updating version:', error);
92
+ process.exit(1);
93
+ }
94
+ }
95
+
96
+ updateVersion();
@@ -0,0 +1,77 @@
1
+ import * as dotenv from 'dotenv';
2
+ import { existsSync } from 'node:fs';
3
+ import { rm } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
7
+
8
+ dotenv.config();
9
+ // 创建需要排除的特性映射
10
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
11
+ const partialBuildPages = [
12
+ // no need for desktop
13
+ {
14
+ name: 'changelog',
15
+ disabled: isDesktop,
16
+ paths: ['src/app/[variants]/@modal/(.)changelog', 'src/app/[variants]/(main)/changelog'],
17
+ },
18
+ {
19
+ name: 'auth',
20
+ disabled: isDesktop,
21
+ paths: ['src/app/[variants]/(auth)'],
22
+ },
23
+ {
24
+ name: 'mobile',
25
+ disabled: isDesktop,
26
+ paths: ['src/app/[variants]/(main)/(mobile)'],
27
+ },
28
+ {
29
+ name: 'api-webhooks',
30
+ disabled: isDesktop,
31
+ paths: ['src/app/(backend)/api/webhooks'],
32
+ },
33
+
34
+ // no need for web
35
+ {
36
+ name: 'desktop-devtools',
37
+ disabled: !isDesktop,
38
+ paths: ['src/app/desktop'],
39
+ },
40
+ {
41
+ name: 'desktop-trpc',
42
+ disabled: !isDesktop,
43
+ paths: ['src/app/(backend)/trpc/desktop'],
44
+ },
45
+ ];
46
+ /* eslint-enable */
47
+
48
+ /**
49
+ * 删除指定的目录
50
+ */
51
+ const removeDirectories = async () => {
52
+ // 遍历 partialBuildPages 数组
53
+ for (const page of partialBuildPages) {
54
+ // 检查是否需要禁用该功能
55
+ if (page.disabled) {
56
+ for (const dirPath of page.paths) {
57
+ const fullPath = path.resolve(process.cwd(), dirPath);
58
+
59
+ // 检查目录是否存在
60
+ if (existsSync(fullPath)) {
61
+ try {
62
+ // 递归删除目录
63
+ await rm(fullPath, { force: true, recursive: true });
64
+ console.log(`♻️ Removed ${dirPath} successfully`);
65
+ } catch (error) {
66
+ console.error(`Failed to remove directory ${dirPath}:`, error);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ };
73
+
74
+ // 执行删除操作
75
+ console.log('Starting prebuild cleanup...');
76
+ await removeDirectories();
77
+ console.log('Prebuild cleanup completed.');
@@ -20,9 +20,6 @@ const tencentCloudChatModels: AIChatModelCard[] = [
20
20
  type: 'chat',
21
21
  },
22
22
  {
23
- abilities: {
24
- functionCall: true,
25
- },
26
23
  contextWindowTokens: 65_536,
27
24
  description:
28
25
  'DeepSeek-V3-0324 为671B 参数 MoE 模型,在编程与技术能力、上下文理解与长文本处理等方面优势突出。',
@@ -37,9 +34,6 @@ const tencentCloudChatModels: AIChatModelCard[] = [
37
34
  type: 'chat',
38
35
  },
39
36
  {
40
- abilities: {
41
- functionCall: true,
42
- },
43
37
  contextWindowTokens: 65_536,
44
38
  description:
45
39
  'DeepSeek-V3 是一款拥有 6710 亿参数的混合专家(MoE)语言模型,采用多头潜在注意力(MLA)和 DeepSeekMoE 架构,结合无辅助损失的负载平衡策略,优化推理和训练效率。通过在 14.8 万亿高质量tokens上预训练,并进行监督微调和强化学习,DeepSeek-V3 在性能上超越其他开源模型,接近领先闭源模型。',
@@ -34,7 +34,7 @@ export class LobeAzureAI implements LobeRuntimeAI {
34
34
  baseURL: string;
35
35
 
36
36
  async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
37
- const { messages, model, ...params } = payload;
37
+ const { messages, model, temperature, top_p, ...params } = payload;
38
38
  // o1 series models on Azure OpenAI does not support streaming currently
39
39
  const enableStreaming = model.includes('o1') ? false : (params.stream ?? true);
40
40
 
@@ -56,7 +56,9 @@ export class LobeAzureAI implements LobeRuntimeAI {
56
56
  model,
57
57
  ...params,
58
58
  stream: enableStreaming,
59
+ temperature: model.includes('o3') ? undefined : temperature,
59
60
  tool_choice: params.tools ? 'auto' : undefined,
61
+ top_p: model.includes('o3') ? undefined : top_p,
60
62
  },
61
63
  });
62
64
 
@@ -15,10 +15,6 @@ export const LobeTencentCloudAI = LobeOpenAICompatibleFactory({
15
15
  models: async ({ client }) => {
16
16
  const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
17
17
 
18
- const functionCallKeywords = [
19
- 'deepseek-v3',
20
- ];
21
-
22
18
  const reasoningKeywords = [
23
19
  'deepseek-r1',
24
20
  ];
@@ -35,8 +31,7 @@ export const LobeTencentCloudAI = LobeOpenAICompatibleFactory({
35
31
  displayName: knownModel?.displayName ?? undefined,
36
32
  enabled: knownModel?.enabled || false,
37
33
  functionCall:
38
- functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword))
39
- || knownModel?.abilities?.functionCall
34
+ knownModel?.abilities?.functionCall
40
35
  || false,
41
36
  id: model.id,
42
37
  reasoning:
@@ -1,14 +1,16 @@
1
1
  import { ChatFileItem } from '@/types/message';
2
2
 
3
- const filePrompt = (item: ChatFileItem) =>
4
- `<file id="${item.id}" name="${item.name}" type="${item.fileType}" size="${item.size}" url="${item.url}"></file>`;
3
+ const filePrompt = (item: ChatFileItem, addUrl: boolean) =>
4
+ addUrl
5
+ ? `<file id="${item.id}" name="${item.name}" type="${item.fileType}" size="${item.size}" url="${item.url}"></file>`
6
+ : `<file id="${item.id}" name="${item.name}" type="${item.fileType}" size="${item.size}"></file>`;
5
7
 
6
- export const filePrompts = (fileList: ChatFileItem[]) => {
8
+ export const filePrompts = (fileList: ChatFileItem[], addUrl: boolean) => {
7
9
  if (fileList.length === 0) return '';
8
10
 
9
11
  const prompt = `<files>
10
12
  <files_docstring>here are user upload files you can refer to</files_docstring>
11
- ${fileList.map((item) => filePrompt(item)).join('\n')}
13
+ ${fileList.map((item) => filePrompt(item, addUrl)).join('\n')}
12
14
  </files>`;
13
15
 
14
16
  return prompt.trim();
@@ -1,13 +1,16 @@
1
1
  import { ChatImageItem } from '@/types/message';
2
2
 
3
- const imagePrompt = (item: ChatImageItem) => `<image name="${item.alt}" url="${item.url}"></image>`;
3
+ const imagePrompt = (item: ChatImageItem, attachUrl: boolean) =>
4
+ attachUrl
5
+ ? `<image name="${item.alt}" url="${item.url}"></image>`
6
+ : `<image name="${item.alt}"></image>`;
4
7
 
5
- export const imagesPrompts = (imageList: ChatImageItem[]) => {
8
+ export const imagesPrompts = (imageList: ChatImageItem[], attachUrl: boolean) => {
6
9
  if (imageList.length === 0) return '';
7
10
 
8
11
  const prompt = `<images>
9
12
  <images_docstring>here are user upload images you can refer to</images_docstring>
10
- ${imageList.map((item) => imagePrompt(item)).join('\n')}
13
+ ${imageList.map((item) => imagePrompt(item, attachUrl)).join('\n')}
11
14
  </images>`;
12
15
 
13
16
  return prompt.trim();
@@ -135,4 +135,54 @@ describe('filesPrompts', () => {
135
135
  expect(result).toMatch(/<image.*?>.*<image.*?>/s); // Check for multiple image tags
136
136
  expect(result).toMatch(/<file.*?>.*<file.*?>/s); // Check for multiple file tags
137
137
  });
138
+
139
+ it('should handle without url', () => {
140
+ const images: ChatImageItem[] = [
141
+ mockImage,
142
+ {
143
+ id: 'img-2',
144
+ alt: 'second image',
145
+ url: 'https://example.com/image2.jpg',
146
+ },
147
+ ];
148
+
149
+ const files: ChatFileItem[] = [
150
+ mockFile,
151
+ {
152
+ id: 'file-2',
153
+ name: 'document.docx',
154
+ fileType: 'application/docx',
155
+ size: 2048,
156
+ url: 'https://example.com/document.docx',
157
+ },
158
+ ];
159
+
160
+ const result = filesPrompts({
161
+ imageList: images,
162
+ fileList: files,
163
+ addUrl: false,
164
+ });
165
+
166
+ expect(result).toMatchInlineSnapshot(`
167
+ "<!-- SYSTEM CONTEXT (NOT PART OF USER QUERY) -->
168
+ <context.instruction>following part contains context information injected by the system. Please follow these instructions:
169
+
170
+ 1. Always prioritize handling user-visible content.
171
+ 2. the context is only required when user's queries rely on it.
172
+ </context.instruction>
173
+ <files_info>
174
+ <images>
175
+ <images_docstring>here are user upload images you can refer to</images_docstring>
176
+ <image name="test image"></image>
177
+ <image name="second image"></image>
178
+ </images>
179
+ <files>
180
+ <files_docstring>here are user upload files you can refer to</files_docstring>
181
+ <file id="file-1" name="test.pdf" type="application/pdf" size="1024"></file>
182
+ <file id="file-2" name="document.docx" type="application/docx" size="2048"></file>
183
+ </files>
184
+ </files_info>
185
+ <!-- END SYSTEM CONTEXT -->"
186
+ `);
187
+ });
138
188
  });
@@ -6,7 +6,9 @@ import { imagesPrompts } from './image';
6
6
  export const filesPrompts = ({
7
7
  imageList,
8
8
  fileList,
9
+ addUrl = true,
9
10
  }: {
11
+ addUrl?: boolean;
10
12
  fileList?: ChatFileItem[];
11
13
  imageList: ChatImageItem[];
12
14
  }) => {
@@ -19,8 +21,8 @@ export const filesPrompts = ({
19
21
  2. the context is only required when user's queries rely on it.
20
22
  </context.instruction>
21
23
  <files_info>
22
- ${imagesPrompts(imageList)}
23
- ${fileList ? filePrompts(fileList) : ''}
24
+ ${imagesPrompts(imageList, addUrl)}
25
+ ${fileList ? filePrompts(fileList, addUrl) : ''}
24
26
  </files_info>
25
27
  <!-- END SYSTEM CONTEXT -->`;
26
28
 
@@ -43,6 +43,10 @@ export const getServerGlobalConfig = async () => {
43
43
  openai: {
44
44
  enabled: isDesktop ? false : undefined,
45
45
  },
46
+ tencentcloud: {
47
+ enabledKey: 'ENABLED_TENCENT_CLOUD',
48
+ modelListKey: 'TENCENT_CLOUD_MODEL_LIST',
49
+ },
46
50
  volcengine: {
47
51
  withDeploymentName: true,
48
52
  },
@@ -8,7 +8,7 @@ import { INBOX_GUIDE_SYSTEMROLE } from '@/const/guide';
8
8
  import { INBOX_SESSION_ID } from '@/const/session';
9
9
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
10
10
  import { TracePayload, TraceTagMap } from '@/const/trace';
11
- import { isDeprecatedEdition, isServerMode } from '@/const/version';
11
+ import { isDeprecatedEdition, isDesktop, isServerMode } from '@/const/version';
12
12
  import {
13
13
  AgentRuntime,
14
14
  AgentRuntimeError,
@@ -480,7 +480,9 @@ class ChatService {
480
480
 
481
481
  const imageList = m.imageList || [];
482
482
 
483
- const filesContext = isServerMode ? filesPrompts({ fileList: m.fileList, imageList }) : '';
483
+ const filesContext = isServerMode
484
+ ? filesPrompts({ addUrl: !isDesktop, fileList: m.fileList, imageList })
485
+ : '';
484
486
  return [
485
487
  { text: (m.content + '\n\n' + filesContext).trim(), type: 'text' },
486
488
  ...imageList.map(
@@ -5,7 +5,7 @@ import { SWRResponse } from 'swr';
5
5
  import type { StateCreator } from 'zustand/vanilla';
6
6
 
7
7
  import { LOBE_THEME_APPEARANCE } from '@/const/theme';
8
- import { CURRENT_VERSION } from '@/const/version';
8
+ import { CURRENT_VERSION, isDesktop } from '@/const/version';
9
9
  import { useOnlyFetchOnceSWR } from '@/libs/swr';
10
10
  import { globalService } from '@/services/global';
11
11
  import type { SystemStatus } from '@/store/global/initialState';
@@ -37,6 +37,18 @@ export const generalActionSlice: StateCreator<
37
37
  get().updateSystemStatus({ language: locale });
38
38
 
39
39
  switchLang(locale);
40
+
41
+ if (isDesktop) {
42
+ (async () => {
43
+ try {
44
+ const { dispatch } = await import('@lobechat/electron-client-ipc');
45
+
46
+ await dispatch('updateLocale', locale);
47
+ } catch (error) {
48
+ console.error('Failed to update locale in main process:', error);
49
+ }
50
+ })();
51
+ }
40
52
  },
41
53
  switchThemeMode: (themeMode) => {
42
54
  get().updateSystemStatus({ themeMode });
@@ -44,7 +56,6 @@ export const generalActionSlice: StateCreator<
44
56
  setCookie(LOBE_THEME_APPEARANCE, themeMode === 'auto' ? undefined : themeMode);
45
57
  },
46
58
  updateSystemStatus: (status, action) => {
47
- // Status cannot be modified when it is not initialized
48
59
  if (!get().isStatusInit) return;
49
60
 
50
61
  const nextStatus = merge(get().status, status);
@@ -60,19 +71,15 @@ export const generalActionSlice: StateCreator<
60
71
  enabledCheck ? 'checkLatestVersion' : null,
61
72
  async () => globalService.getLatestVersion(),
62
73
  {
63
- // check latest version every 30 minutes
64
74
  focusThrottleInterval: 1000 * 60 * 30,
65
75
  onSuccess: (data: string) => {
66
76
  if (!valid(CURRENT_VERSION) || !valid(data)) return;
67
77
 
68
- // Parse versions to ensure we're working with valid SemVer objects
69
78
  const currentVersion = parse(CURRENT_VERSION);
70
79
  const latestVersion = parse(data);
71
80
 
72
81
  if (!currentVersion || !latestVersion) return;
73
82
 
74
- // only compare major and minor versions
75
- // solve the problem of frequent patch updates
76
83
  const currentMajorMinor = `${currentVersion.major}.${currentVersion.minor}.0`;
77
84
  const latestMajorMinor = `${latestVersion.major}.${latestVersion.minor}.0`;
78
85
 
@@ -1,18 +0,0 @@
1
- /* eslint-disable typescript-sort-keys/interface, sort-keys-fix/sort-keys-fix */
2
- export const ElectronIPCMethods = {
3
- getDatabasePath: 'getDatabasePath',
4
- getUserDataPath: 'getUserDataPath',
5
-
6
- getDatabaseSchemaHash: 'getDatabaseSchemaHash',
7
- setDatabaseSchemaHash: 'setDatabaseSchemaHash',
8
- } as const;
9
-
10
- export type IElectronIPCMethods = keyof typeof ElectronIPCMethods;
11
-
12
- export interface IpcDispatchEvent {
13
- getDatabasePath: () => Promise<string>;
14
- getUserDataPath: () => Promise<string>;
15
-
16
- getDatabaseSchemaHash: () => Promise<string | undefined>;
17
- setDatabaseSchemaHash: (hash: string) => Promise<void>;
18
- }