mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 00:10:22 +08:00
* refactor(FileManager): streamline file path handling in FilesPage and ImageBlock components * refactor(file): implement getSafeFilePath utility for consistent file path handling across loaders and preprocessors * refactor(FileStorage): replace getSafeFilePath with fileStorage.getFilePathById for consistent file path retrieval across services * refactor(file): unify file path retrieval across loaders and preprocessors for improved consistency * refactor(Inputbar, MessageEditor): replace getFileExtension with file.ext for improved file type handling * refactor(FileStorage): simplify getFilePathById method by removing redundant checks for file path retrieval * fix(FileStorage): update getFilePathById to ensure file.path is consistent with generated filePath * refactor(FileStorage): simplify getFilePathById method by removing unnecessary file path consistency checks * fix(FileStorage): update duplicate file check to use file.path for accurate detection * fix(FileStorage): correct file path usage in uploadFile method for accurate duplicate detection * fix(loader): update file path retrieval to use file.path for consistency across loaders
194 lines
5.6 KiB
TypeScript
194 lines
5.6 KiB
TypeScript
import { File, Files, FileState, GoogleGenAI } from '@google/genai'
|
|
import { loggerService } from '@logger'
|
|
import { fileStorage } from '@main/services/FileStorage'
|
|
import { FileListResponse, FileMetadata, FileUploadResponse, Provider } from '@types'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
import { CacheService } from '../CacheService'
|
|
import { BaseFileService } from './BaseFileService'
|
|
|
|
const logger = loggerService.withContext('GeminiService')
|
|
|
|
export class GeminiService extends BaseFileService {
|
|
private static readonly FILE_LIST_CACHE_KEY = 'gemini_file_list'
|
|
private static readonly FILE_CACHE_DURATION = 48 * 60 * 60 * 1000
|
|
private static readonly LIST_CACHE_DURATION = 3000
|
|
|
|
protected readonly fileManager: Files
|
|
|
|
constructor(provider: Provider) {
|
|
super(provider)
|
|
this.fileManager = new GoogleGenAI({
|
|
vertexai: false,
|
|
apiKey: provider.apiKey,
|
|
httpOptions: {
|
|
baseUrl: provider.apiHost
|
|
}
|
|
}).files
|
|
}
|
|
|
|
async uploadFile(file: FileMetadata): Promise<FileUploadResponse> {
|
|
try {
|
|
const uploadResult = await this.fileManager.upload({
|
|
file: fileStorage.getFilePathById(file),
|
|
config: {
|
|
mimeType: 'application/pdf',
|
|
name: file.id,
|
|
displayName: file.origin_name
|
|
}
|
|
})
|
|
|
|
// 根据文件状态设置响应状态
|
|
let status: 'success' | 'processing' | 'failed' | 'unknown'
|
|
switch (uploadResult.state) {
|
|
case FileState.ACTIVE:
|
|
status = 'success'
|
|
break
|
|
case FileState.PROCESSING:
|
|
status = 'processing'
|
|
break
|
|
case FileState.FAILED:
|
|
status = 'failed'
|
|
break
|
|
default:
|
|
status = 'unknown'
|
|
}
|
|
|
|
const response: FileUploadResponse = {
|
|
fileId: uploadResult.name || '',
|
|
displayName: file.origin_name,
|
|
status,
|
|
originalFile: {
|
|
type: 'gemini',
|
|
file: uploadResult
|
|
}
|
|
}
|
|
|
|
// 只缓存成功的文件
|
|
if (status === 'success') {
|
|
const cacheKey = `${GeminiService.FILE_LIST_CACHE_KEY}_${response.fileId}`
|
|
CacheService.set<FileUploadResponse>(cacheKey, response, GeminiService.FILE_CACHE_DURATION)
|
|
}
|
|
|
|
return response
|
|
} catch (error) {
|
|
logger.error('Error uploading file to Gemini:', error as Error)
|
|
return {
|
|
fileId: '',
|
|
displayName: file.origin_name,
|
|
status: 'failed',
|
|
originalFile: undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
async retrieveFile(fileId: string): Promise<FileUploadResponse> {
|
|
try {
|
|
const cachedResponse = CacheService.get<FileUploadResponse>(`${GeminiService.FILE_LIST_CACHE_KEY}_${fileId}`)
|
|
logger.debug('[GeminiService] cachedResponse', cachedResponse)
|
|
if (cachedResponse) {
|
|
return cachedResponse
|
|
}
|
|
const files: File[] = []
|
|
|
|
for await (const f of await this.fileManager.list()) {
|
|
files.push(f)
|
|
}
|
|
logger.debug('files', files)
|
|
const file = files
|
|
.filter((file) => file.state === FileState.ACTIVE)
|
|
.find((file) => file.name?.substring(6) === fileId) // 去掉 files/ 前缀
|
|
logger.debug('file', file)
|
|
if (file) {
|
|
return {
|
|
fileId: fileId,
|
|
displayName: file.displayName || '',
|
|
status: 'success',
|
|
originalFile: {
|
|
type: 'gemini',
|
|
file
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
fileId: fileId,
|
|
displayName: '',
|
|
status: 'failed',
|
|
originalFile: undefined
|
|
}
|
|
} catch (error) {
|
|
logger.error('Error retrieving file from Gemini:', error as Error)
|
|
return {
|
|
fileId: fileId,
|
|
displayName: '',
|
|
status: 'failed',
|
|
originalFile: undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
async listFiles(): Promise<FileListResponse> {
|
|
try {
|
|
const cachedList = CacheService.get<FileListResponse>(GeminiService.FILE_LIST_CACHE_KEY)
|
|
if (cachedList) {
|
|
return cachedList
|
|
}
|
|
const geminiFiles: File[] = []
|
|
|
|
for await (const f of await this.fileManager.list()) {
|
|
geminiFiles.push(f)
|
|
}
|
|
const fileList: FileListResponse = {
|
|
files: geminiFiles
|
|
.filter((file) => file.state === FileState.ACTIVE)
|
|
.map((file) => {
|
|
// 更新单个文件的缓存
|
|
const fileResponse: FileUploadResponse = {
|
|
fileId: file.name || uuidv4(),
|
|
displayName: file.displayName || '',
|
|
status: 'success',
|
|
originalFile: {
|
|
type: 'gemini',
|
|
file
|
|
}
|
|
}
|
|
CacheService.set(
|
|
`${GeminiService.FILE_LIST_CACHE_KEY}_${file.name}`,
|
|
fileResponse,
|
|
GeminiService.FILE_CACHE_DURATION
|
|
)
|
|
|
|
return {
|
|
id: file.name || uuidv4(),
|
|
displayName: file.displayName || '',
|
|
size: Number(file.sizeBytes),
|
|
status: 'success',
|
|
originalFile: {
|
|
type: 'gemini',
|
|
file
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// 更新文件列表缓存
|
|
CacheService.set(GeminiService.FILE_LIST_CACHE_KEY, fileList, GeminiService.LIST_CACHE_DURATION)
|
|
return fileList
|
|
} catch (error) {
|
|
logger.error('Error listing files from Gemini:', error as Error)
|
|
return { files: [] }
|
|
}
|
|
}
|
|
|
|
async deleteFile(fileId: string): Promise<void> {
|
|
try {
|
|
await this.fileManager.delete({ name: fileId })
|
|
logger.debug(`File ${fileId} deleted from Gemini`)
|
|
} catch (error) {
|
|
logger.error('Error deleting file from Gemini:', error as Error)
|
|
throw error
|
|
}
|
|
}
|
|
}
|