@lobehub/chat 1.79.2 → 1.79.4

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 (41) 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/xai.ts +79 -18
  34. package/src/libs/agent-runtime/azureai/index.ts +3 -1
  35. package/src/prompts/files/file.ts +6 -4
  36. package/src/prompts/files/image.ts +6 -3
  37. package/src/prompts/files/index.test.ts +50 -0
  38. package/src/prompts/files/index.ts +4 -2
  39. package/src/services/chat.ts +4 -2
  40. package/src/store/global/actions/general.ts +13 -6
  41. 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.');
@@ -1,51 +1,68 @@
1
1
  import { AIChatModelCard } from '@/types/aiModel';
2
-
2
+ // https://docs.x.ai/docs/models
3
3
  const xaiChatModels: AIChatModelCard[] = [
4
4
  {
5
5
  abilities: {
6
6
  functionCall: true,
7
7
  },
8
8
  contextWindowTokens: 131_072,
9
- description: '拥有与 Grok 2 相当的性能,但具有更高的效率、速度和功能。',
10
- displayName: 'Grok Beta',
9
+ description: '旗舰级模型,擅长数据提取、编程和文本摘要等企业级应用,拥有金融、医疗、法律和科学等领域的深厚知识。',
10
+ displayName: 'Grok 3 Beta',
11
11
  enabled: true,
12
- id: 'grok-beta',
12
+ id: 'grok-3-beta',
13
13
  pricing: {
14
- input: 5,
14
+ input: 3,
15
15
  output: 15,
16
16
  },
17
+ releasedAt: '2025-04-03',
17
18
  type: 'chat',
18
19
  },
19
20
  {
20
21
  abilities: {
21
22
  functionCall: true,
22
- vision: true,
23
23
  },
24
- contextWindowTokens: 8192,
25
- description: '最新的图像理解模型,可以处理各种各样的视觉信息,包括文档、图表、截图和照片等。',
26
- displayName: 'Grok Vision Beta',
27
- enabled: true,
28
- id: 'grok-vision-beta',
24
+ contextWindowTokens: 131_072,
25
+ description: '旗舰级模型,擅长数据提取、编程和文本摘要等企业级应用,拥有金融、医疗、法律和科学等领域的深厚知识。',
26
+ displayName: 'Grok 3 Beta (Fast mode)',
27
+ id: 'grok-3-fast-beta',
29
28
  pricing: {
30
29
  input: 5,
31
- output: 15,
30
+ output: 25,
32
31
  },
32
+ releasedAt: '2025-04-03',
33
33
  type: 'chat',
34
34
  },
35
35
  {
36
36
  abilities: {
37
37
  functionCall: true,
38
+ reasoning: true,
38
39
  },
39
40
  contextWindowTokens: 131_072,
40
- description: '该模型在准确性、指令遵循和多语言能力方面有所改进。',
41
- displayName: 'Grok 2 1212',
41
+ description: '轻量级模型,回话前会先思考。运行快速、智能,适用于不需要深层领域知识的逻辑任务,并能获取原始的思维轨迹。',
42
+ displayName: 'Grok 3 Mini Beta',
42
43
  enabled: true,
43
- id: 'grok-2-1212',
44
+ id: 'grok-3-mini-beta',
44
45
  pricing: {
45
- input: 2,
46
- output: 10,
46
+ input: 0.3,
47
+ output: 0.5,
47
48
  },
48
- releasedAt: '2024-12-12',
49
+ releasedAt: '2025-04-03',
50
+ type: 'chat',
51
+ },
52
+ {
53
+ abilities: {
54
+ functionCall: true,
55
+ reasoning: true,
56
+ },
57
+ contextWindowTokens: 131_072,
58
+ description: '轻量级模型,回话前会先思考。运行快速、智能,适用于不需要深层领域知识的逻辑任务,并能获取原始的思维轨迹。',
59
+ displayName: 'Grok 3 Mini Beta (Fast mode)',
60
+ id: 'grok-3-mini-fast-beta',
61
+ pricing: {
62
+ input: 0.6,
63
+ output: 4,
64
+ },
65
+ releasedAt: '2025-04-03',
49
66
  type: 'chat',
50
67
  },
51
68
  {
@@ -65,6 +82,50 @@ const xaiChatModels: AIChatModelCard[] = [
65
82
  releasedAt: '2024-12-12',
66
83
  type: 'chat',
67
84
  },
85
+ {
86
+ abilities: {
87
+ functionCall: true,
88
+ },
89
+ contextWindowTokens: 131_072,
90
+ description: '该模型在准确性、指令遵循和多语言能力方面有所改进。',
91
+ displayName: 'Grok 2 1212',
92
+ id: 'grok-2-1212', // legacy
93
+ pricing: {
94
+ input: 2,
95
+ output: 10,
96
+ },
97
+ releasedAt: '2024-12-12',
98
+ type: 'chat',
99
+ },
100
+ {
101
+ abilities: {
102
+ functionCall: true,
103
+ },
104
+ contextWindowTokens: 131_072,
105
+ description: '拥有与 Grok 2 相当的性能,但具有更高的效率、速度和功能。',
106
+ displayName: 'Grok Beta',
107
+ id: 'grok-beta', // legacy
108
+ pricing: {
109
+ input: 5,
110
+ output: 15,
111
+ },
112
+ type: 'chat',
113
+ },
114
+ {
115
+ abilities: {
116
+ functionCall: true,
117
+ vision: true,
118
+ },
119
+ contextWindowTokens: 8192,
120
+ description: '最新的图像理解模型,可以处理各种各样的视觉信息,包括文档、图表、截图和照片等。',
121
+ displayName: 'Grok Vision Beta',
122
+ id: 'grok-vision-beta', // legacy
123
+ pricing: {
124
+ input: 5,
125
+ output: 15,
126
+ },
127
+ type: 'chat',
128
+ },
68
129
  ];
69
130
 
70
131
  export const allModels = [...xaiChatModels];
@@ -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
 
@@ -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
 
@@ -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(