@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.
- package/.env.desktop +7 -0
- package/.github/scripts/pr-release-body.js +3 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +15 -7
- package/packages/electron-client-ipc/src/events/file.ts +5 -0
- package/packages/electron-client-ipc/src/events/index.ts +19 -1
- package/packages/electron-client-ipc/src/events/shortcut.ts +4 -0
- package/packages/electron-client-ipc/src/events/system.ts +5 -0
- package/packages/electron-client-ipc/src/events/update.ts +20 -0
- package/packages/electron-client-ipc/src/events/windows.ts +9 -0
- package/packages/electron-client-ipc/src/index.ts +1 -0
- package/packages/electron-client-ipc/src/types/file.ts +14 -0
- package/packages/electron-client-ipc/src/types/index.ts +4 -0
- package/packages/electron-client-ipc/src/types/route.ts +46 -0
- package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
- package/packages/electron-client-ipc/src/types/update.ts +23 -0
- package/packages/electron-client-ipc/src/useWatchBroadcast.ts +37 -0
- package/packages/electron-server-ipc/src/events/database.ts +4 -0
- package/packages/electron-server-ipc/src/events/file.ts +6 -0
- package/packages/electron-server-ipc/src/events/index.ts +29 -0
- package/packages/electron-server-ipc/src/events/storagePath.ts +4 -0
- package/packages/electron-server-ipc/src/index.ts +1 -0
- package/packages/electron-server-ipc/src/ipcClient.test.ts +3 -4
- package/packages/electron-server-ipc/src/ipcClient.ts +52 -24
- package/packages/electron-server-ipc/src/ipcServer.ts +6 -14
- package/packages/electron-server-ipc/src/types/file.ts +4 -0
- package/packages/electron-server-ipc/src/types/index.ts +14 -1
- package/scripts/electronWorkflow/buildElectron.ts +52 -0
- package/scripts/electronWorkflow/moveNextStandalone.ts +69 -0
- package/scripts/electronWorkflow/setDesktopVersion.ts +96 -0
- package/scripts/prebuild.mts +77 -0
- package/src/config/aiModels/tencentcloud.ts +0 -6
- package/src/libs/agent-runtime/azureai/index.ts +3 -1
- package/src/libs/agent-runtime/tencentcloud/index.ts +1 -6
- package/src/prompts/files/file.ts +6 -4
- package/src/prompts/files/image.ts +6 -3
- package/src/prompts/files/index.test.ts +50 -0
- package/src/prompts/files/index.ts +4 -2
- package/src/server/globalConfig/index.ts +4 -0
- package/src/services/chat.ts +4 -2
- package/src/store/global/actions/general.ts +13 -6
- 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 {
|
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
|
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
|
// 关闭服务器
|
@@ -1 +1,14 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
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) =>
|
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
|
},
|
package/src/services/chat.ts
CHANGED
@@ -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
|
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
|
-
}
|