@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
208 lines (178 loc) • 5.9 kB
text/typescript
import { sha256 } from 'js-sha256';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { electronIpcClient } from '@/server/modules/ElectronIPCClient';
import { inferContentTypeFromImageUrl } from '@/utils/url';
import { FileServiceImpl } from './type';
/**
* 桌面应用本地文件服务实现
*/
export class DesktopLocalFileImpl implements FileServiceImpl {
/**
* 获取本地文件的URL
* 通过 IPC 从主进程获取 HTTP URL
*/
private async getLocalFileUrl(key: string): Promise<string> {
try {
return await electronIpcClient.getFileHTTPURL(key);
} catch (e) {
console.error('[DesktopLocalFileImpl] Failed to get file HTTP URL via IPC:', e);
return '';
}
}
/**
* 创建预签名上传URL(本地版实际上是直接返回文件路径,可能需要进一步扩展)
*/
async createPreSignedUrl(key: string): Promise<string> {
// 在桌面应用本地文件实现中,不需要预签名URL
// 直接返回文件路径
return key;
}
/**
* 创建预签名预览URL(本地版是通过HTTP路径访问本地文件)
*/
async createPreSignedUrlForPreview(key: string): Promise<string> {
return this.getLocalFileUrl(key);
}
async deleteFile(key: string): Promise<any> {
return await this.deleteFiles([key]);
}
/**
* 批量删除文件
*/
async deleteFiles(keys: string[]): Promise<any> {
try {
if (!keys || keys.length === 0) return { success: true };
// 确保所有路径都是合法的desktop://路径
const invalidKeys = keys.filter((key) => !key.startsWith('desktop://'));
if (invalidKeys.length > 0) {
console.error('Invalid desktop file paths:', invalidKeys);
return {
errors: invalidKeys.map((key) => ({ message: 'Invalid desktop file path', path: key })),
success: false,
};
}
// 使用electronIpcClient的专用方法
return await electronIpcClient.deleteFiles(keys);
} catch (error) {
console.error('Failed to delete files:', error);
return {
errors: [
{
message: `Batch delete failed: ${(error as Error).message}`,
path: 'batch',
},
],
success: false,
};
}
}
/**
* 获取文件字节数组
*/
async getFileByteArray(key: string): Promise<Uint8Array> {
try {
// 从Electron获取文件的绝对路径
const filePath = await electronIpcClient.getFilePathById(key);
// 检查文件是否存在
if (!existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
return new Uint8Array();
}
// 读取文件内容并转换为Uint8Array
const buffer = readFileSync(filePath);
return new Uint8Array(buffer);
} catch (e) {
console.error('Failed to get file byte array:', e);
return new Uint8Array();
}
}
/**
* 获取文件内容
*/
async getFileContent(key: string): Promise<string> {
try {
// 从Electron获取文件的绝对路径
const filePath = await electronIpcClient.getFilePathById(key);
// 检查文件是否存在
if (!existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
return '';
}
// 读取文件内容并转换为字符串
return readFileSync(filePath, 'utf8');
} catch (e) {
console.error('Failed to get file content:', e);
return '';
}
}
/**
* 获取完整文件URL
*/
async getFullFileUrl(url?: string | null): Promise<string> {
if (!url) return '';
return this.getLocalFileUrl(url);
}
/**
* 上传内容
* 注意:这个功能可能需要扩展Electron IPC接口
*/
async uploadContent(filePath: string, content: string): Promise<any> {
// 这里需要扩展electronIpcClient以支持上传文件内容
// 例如: return electronIpcClient.uploadContent(filePath, content);
console.warn('uploadContent not implemented for Desktop local file service', filePath, content);
return;
}
/**
* 从完整URL中提取key
* 从 HTTP URL 中提取 desktop:// 格式的路径
*/
getKeyFromFullUrl(url: string): string {
try {
const urlObj = new URL(url);
const pathSegments = urlObj.pathname.split('/').filter((segment) => segment !== '');
// 移除第一个路径段(desktop-file)
pathSegments.shift();
// 重新组合剩余的路径段
const filePath = pathSegments.join('/');
// 返回 desktop:// 格式的路径
return `desktop://${filePath}`;
} catch (e) {
console.error('[DesktopLocalFileImpl] Failed to extract key from URL:', e);
return '';
}
}
/**
* 上传媒体文件
*/
async uploadMedia(key: string, buffer: Buffer): Promise<{ key: string }> {
try {
// 将 Buffer 转换为 Base64 字符串
const content = buffer.toString('base64');
// 从 key 中提取文件名
const filename = path.basename(key);
// 计算文件的 SHA256 hash
const hash = sha256(buffer);
// 根据文件URL推断 MIME 类型
const type = inferContentTypeFromImageUrl(key)!;
// 构造上传参数
const uploadParams = {
content,
filename,
hash,
path: key,
type,
};
// 调用 electronIpcClient 上传文件
const result = await electronIpcClient.createFile(uploadParams);
if (!result.success) {
throw new Error('Failed to upload file via Electron IPC');
}
console.log('[DesktopLocalFileImpl] File uploaded successfully:', result.metadata);
return { key: result.metadata.path };
} catch (error) {
console.error('[DesktopLocalFileImpl] Failed to upload media file:', error);
throw error;
}
}
}