refactor(fileService): integrate Mistral and Gemini file services

- Added Mistral and Gemini file services to handle file uploads, retrieval, listing, and deletion.
- Refactored IPC handlers to support new file service architecture.
- Updated preload API to accommodate new file service methods.
- Removed deprecated Gemini service implementation.
- Enhanced type definitions for file handling to support local and remote sources.
This commit is contained in:
suyao
2025-03-24 01:47:57 +08:00
parent 4775a3a77d
commit 64838cb3fb
21 changed files with 450 additions and 124 deletions
+2 -1
View File
@@ -63,6 +63,7 @@
"@llm-tools/embedjs-loader-web": "^0.1.28",
"@llm-tools/embedjs-loader-xml": "^0.1.28",
"@llm-tools/embedjs-openai": "^0.1.28",
"@mistralai/mistralai": "^1.5.2",
"@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch",
"adm-zip": "^0.5.16",
"docx": "^9.0.2",
@@ -109,11 +110,11 @@
"@types/md5": "^2.3.5",
"@types/node": "^18.19.9",
"@types/pako": "^1.0.2",
"@types/pdf-parse": "^1.1.4",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/react-infinite-scroll-component": "^5.0.0",
"@types/tinycolor2": "^1",
"@types/pdf-parse": "^1.1.4",
"@vitejs/plugin-react": "^4.2.1",
"antd": "^5.22.5",
"applescript": "^1.0.0",
+23 -9
View File
@@ -2,7 +2,7 @@ import fs from 'node:fs'
import { isMac, isWin } from '@main/constant'
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
import { MCPServer, Shortcut, ThemeMode } from '@types'
import { LocalFileSource, MCPServer, Shortcut, ThemeMode } from '@types'
import { BrowserWindow, ipcMain, session, shell } from 'electron'
import log from 'electron-log'
@@ -12,9 +12,9 @@ import BackupManager from './services/BackupManager'
import { configManager } from './services/ConfigManager'
import CopilotService from './services/CopilotService'
import { ExportService } from './services/ExportService'
import { FileServiceManager } from './services/file/FileServiceManager'
import FileService from './services/FileService'
import FileStorage from './services/FileStorage'
import { GeminiService } from './services/GeminiService'
import KnowledgeService from './services/KnowledgeService'
import MCPService from './services/MCPService'
import { ProxyConfig, proxyManager } from './services/ProxyManager'
@@ -185,6 +185,27 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle('file:copy', fileManager.copyFile)
ipcMain.handle('file:binaryFile', fileManager.binaryFile)
// file service
ipcMain.handle('file-service:upload', async (_, type: string, apiKey: string, file: LocalFileSource) => {
const service = FileServiceManager.getInstance().getService(type, apiKey)
return await service.uploadFile(file)
})
ipcMain.handle('file-service:list', async (_, type: string, apiKey: string) => {
const service = FileServiceManager.getInstance().getService(type, apiKey)
return await service.listFiles()
})
ipcMain.handle('file-service:delete', async (_, type: string, apiKey: string, fileId: string) => {
const service = FileServiceManager.getInstance().getService(type, apiKey)
return await service.deleteFile(fileId)
})
ipcMain.handle('file-service:retrieve', async (_, type: string, apiKey: string, fileId: string) => {
const service = FileServiceManager.getInstance().getService(type, apiKey)
return await service.retrieveFile(fileId)
})
// fs
ipcMain.handle('fs:read', FileService.readFile)
@@ -240,13 +261,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}
})
// gemini
ipcMain.handle('gemini:upload-file', GeminiService.uploadFile)
ipcMain.handle('gemini:base64-file', GeminiService.base64File)
ipcMain.handle('gemini:retrieve-file', GeminiService.retrieveFile)
ipcMain.handle('gemini:list-files', GeminiService.listFiles)
ipcMain.handle('gemini:delete-file', GeminiService.deleteFile)
// mini window
ipcMain.handle('miniwindow:show', () => windowService.showMiniWindow())
ipcMain.handle('miniwindow:hide', () => windowService.hideMiniWindow())
+7 -10
View File
@@ -1,21 +1,18 @@
import fs from 'node:fs'
import { windowService } from '@main/services/WindowService'
import { FileType, KnowledgeBaseParams } from '@types'
import { FileSource, OcrProvider } from '@types'
import Logger from 'electron-log'
import pdfParse from 'pdf-parse'
export default abstract class BaseOcrProvider {
protected base: KnowledgeBaseParams
constructor(base: KnowledgeBaseParams) {
if (!base) {
throw new Error('KnowledgeBaseParams is required')
protected provider: OcrProvider
constructor(provider: OcrProvider) {
if (!provider) {
throw new Error('Ocr provider is not set')
}
if (!base.ocrProvider || base.ocrProvider?.apiKey === '') {
throw new Error('Ocr provider is not set or apiKey is empty')
}
this.base = base
this.provider = provider
}
abstract parseFile(sourceId: string, file: FileType): Promise<{ processedFile: FileType }>
abstract parseFile(sourceId: string, file: FileSource): Promise<{ processedFile: FileSource }>
/**
* 辅助方法:延迟执行
*/
+3 -3
View File
@@ -1,10 +1,10 @@
import { FileType, KnowledgeBaseParams } from '@types'
import { FileType, OcrProvider } from '@types'
import BaseOcrProvider from './BaseOcrProvider'
export default class DefaultOcrProvider extends BaseOcrProvider {
constructor(base: KnowledgeBaseParams) {
super(base)
constructor(provider: OcrProvider) {
super(provider)
}
public parseFile(): Promise<{ processedFile: FileType }> {
throw new Error('Method not implemented.')
+8 -8
View File
@@ -1,7 +1,7 @@
import fs from 'node:fs'
import path from 'node:path'
import { FileType, KnowledgeBaseParams } from '@types'
import { FileType, OcrProvider } from '@types'
import AdmZip from 'adm-zip'
import axios, { AxiosRequestConfig } from 'axios'
import Logger from 'electron-log'
@@ -30,8 +30,8 @@ type ParsedFileResponse = {
}
export default class Doc2xOcrProvider extends BaseOcrProvider {
constructor(base: KnowledgeBaseParams) {
super(base)
constructor(provider: OcrProvider) {
super(provider)
}
public async parseFile(sourceId: string, file: FileType): Promise<{ processedFile: FileType }> {
@@ -130,7 +130,7 @@ export default class Doc2xOcrProvider extends BaseOcrProvider {
private async preupload(): Promise<PreuploadResponse> {
const config = this.createAuthConfig()
const endpoint = `${this.base.ocrProvider?.apiHost}/api/v2/parse/preupload`
const endpoint = `${this.provider.apiHost}/api/v2/parse/preupload`
try {
const { data } = await axios.post<ApiResponse<PreuploadResponse>>(endpoint, null, config)
@@ -162,7 +162,7 @@ export default class Doc2xOcrProvider extends BaseOcrProvider {
private async getStatus(uid: string): Promise<StatusResponse> {
const config = this.createAuthConfig()
const endpoint = `${this.base.ocrProvider?.apiHost}/api/v2/parse/status?uid=${uid}`
const endpoint = `${this.provider.apiHost}/api/v2/parse/status?uid=${uid}`
try {
const response = await axios.get<ApiResponse<StatusResponse>>(endpoint, config)
@@ -195,7 +195,7 @@ export default class Doc2xOcrProvider extends BaseOcrProvider {
filename: fileName
}
const endpoint = `${this.base.ocrProvider?.apiHost}/api/v2/convert/parse`
const endpoint = `${this.provider.apiHost}/api/v2/convert/parse`
try {
const response = await axios.post<ApiResponse<any>>(endpoint, payload, config)
@@ -211,7 +211,7 @@ export default class Doc2xOcrProvider extends BaseOcrProvider {
private async getParsedFile(uid: string): Promise<ParsedFileResponse> {
const config = this.createAuthConfig()
const endpoint = `${this.base.ocrProvider?.apiHost}/api/v2/convert/parse/result?uid=${uid}`
const endpoint = `${this.provider.apiHost}/api/v2/convert/parse/result?uid=${uid}`
try {
const response = await axios.get<ApiResponse<ParsedFileResponse>>(endpoint, config)
@@ -259,7 +259,7 @@ export default class Doc2xOcrProvider extends BaseOcrProvider {
private createAuthConfig(): AxiosRequestConfig {
return {
headers: {
Authorization: `Bearer ${this.base.ocrProvider?.apiKey}`
Authorization: `Bearer ${this.provider.apiKey}`
}
}
}
+8 -6
View File
@@ -1,14 +1,16 @@
import { KnowledgeBaseParams } from '@types'
import { OcrProvider } from '@types'
import BaseOcrProvider from './BaseOcrProvider'
import DefaultOcrProvider from './DefaultOcrProvider'
import Doc2xOcrProvider from './Doc2xOcrProvider'
import MistralOcrProvider from './MistralOcrProvider'
export default class OcrProviderFactory {
static create(base: KnowledgeBaseParams): BaseOcrProvider {
if (base.ocrProvider?.id === 'doc2x') {
return new Doc2xOcrProvider(base)
static create(provider: OcrProvider): BaseOcrProvider {
if (provider.id === 'doc2x') {
return new Doc2xOcrProvider(provider)
} else if (provider.id === 'mistral') {
return new MistralOcrProvider(provider)
}
return new DefaultOcrProvider(base)
return new DefaultOcrProvider(provider)
}
}
-63
View File
@@ -1,63 +0,0 @@
import { FileMetadataResponse, FileState, GoogleAIFileManager } from '@google/generative-ai/server'
import { FileType } from '@types'
import fs from 'fs'
import { CacheService } from './CacheService'
export class GeminiService {
private static readonly FILE_LIST_CACHE_KEY = 'gemini_file_list'
private static readonly CACHE_DURATION = 3000
static async uploadFile(_: Electron.IpcMainInvokeEvent, file: FileType, apiKey: string) {
const fileManager = new GoogleAIFileManager(apiKey)
const uploadResult = await fileManager.uploadFile(file.path, {
mimeType: 'application/pdf',
displayName: file.origin_name
})
return uploadResult
}
static async base64File(_: Electron.IpcMainInvokeEvent, file: FileType) {
return {
data: Buffer.from(fs.readFileSync(file.path)).toString('base64'),
mimeType: 'application/pdf'
}
}
static async retrieveFile(
_: Electron.IpcMainInvokeEvent,
file: FileType,
apiKey: string
): Promise<FileMetadataResponse | undefined> {
const fileManager = new GoogleAIFileManager(apiKey)
const cachedResponse = CacheService.get<any>(GeminiService.FILE_LIST_CACHE_KEY)
if (cachedResponse) {
return GeminiService.processResponse(cachedResponse, file)
}
const response = await fileManager.listFiles()
CacheService.set(GeminiService.FILE_LIST_CACHE_KEY, response, GeminiService.CACHE_DURATION)
return GeminiService.processResponse(response, file)
}
private static processResponse(response: any, file: FileType) {
if (response.files) {
return response.files
.filter((file) => file.state === FileState.ACTIVE)
.find((i) => i.displayName === file.origin_name && Number(i.sizeBytes) === file.size)
}
return undefined
}
static async listFiles(_: Electron.IpcMainInvokeEvent, apiKey: string) {
const fileManager = new GoogleAIFileManager(apiKey)
return await fileManager.listFiles()
}
static async deleteFile(_: Electron.IpcMainInvokeEvent, apiKey: string, fileId: string) {
const fileManager = new GoogleAIFileManager(apiKey)
await fileManager.deleteFile(fileId)
}
}
+31
View File
@@ -0,0 +1,31 @@
import { Mistral } from '@mistralai/mistralai'
export class MistralClientManager {
private static instance: MistralClientManager
private client: Mistral | null = null
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static getInstance(): MistralClientManager {
if (!MistralClientManager.instance) {
MistralClientManager.instance = new MistralClientManager()
}
return MistralClientManager.instance
}
public initializeClient(apiKey: string): void {
if (!this.client) {
this.client = new Mistral({
apiKey
})
}
}
public getClient(): Mistral {
if (!this.client) {
throw new Error('Mistral client not initialized. Call initializeClient first.')
}
return this.client
}
}
+13
View File
@@ -0,0 +1,13 @@
import { FileListResponse, FileUploadResponse, LocalFileSource } from '@types'
export abstract class BaseFileService {
protected readonly apiKey: string
protected constructor(apiKey: string) {
this.apiKey = apiKey
}
abstract uploadFile(file: LocalFileSource): Promise<FileUploadResponse>
abstract deleteFile(fileId: string): Promise<void>
abstract listFiles(): Promise<FileListResponse>
abstract retrieveFile(fileId: string): Promise<FileUploadResponse>
}
@@ -0,0 +1,37 @@
import { BaseFileService } from './BaseFileService'
import { GeminiService } from './GeminiService'
import { MistralService } from './MistralService'
export class FileServiceManager {
private static instance: FileServiceManager
private services: Map<string, BaseFileService> = new Map()
private constructor() {}
static getInstance(): FileServiceManager {
if (!this.instance) {
this.instance = new FileServiceManager()
}
return this.instance
}
getService(type: string, apiKey: string): BaseFileService {
let service = this.services.get(type)
if (!service) {
switch (type) {
case 'gemini':
service = new GeminiService(apiKey)
break
case 'mistral':
service = new MistralService(apiKey)
break
default:
throw new Error(`Unsupported service type: ${type}`)
}
this.services.set(type, service)
}
return service
}
}
+141
View File
@@ -0,0 +1,141 @@
import { FileState, GoogleAIFileManager } from '@google/generative-ai/server'
import { FileListResponse, FileUploadResponse, LocalFileSource } from '@types'
import { CacheService } from '../CacheService'
import { BaseFileService } from './BaseFileService'
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: GoogleAIFileManager
constructor(apiKey: string) {
super(apiKey)
this.fileManager = new GoogleAIFileManager(apiKey)
}
async uploadFile(file: LocalFileSource): Promise<FileUploadResponse> {
const uploadResult = await this.fileManager.uploadFile(file.path, {
mimeType: 'application/pdf',
displayName: file.origin_name
})
// 根据文件状态设置响应状态
let status: 'success' | 'processing' | 'failed' | 'unknown'
switch (uploadResult.file.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.file.name,
displayName: file.origin_name,
status,
originalFile: uploadResult
}
// 只缓存成功的文件
if (status === 'success') {
const cacheKey = `${GeminiService.FILE_LIST_CACHE_KEY}_${response.fileId}`
CacheService.set(cacheKey, response, GeminiService.FILE_CACHE_DURATION)
}
return response
}
async retrieveFile(fileId: string): Promise<FileUploadResponse> {
const cachedResponse = CacheService.get<any>(`${GeminiService.FILE_LIST_CACHE_KEY}_${fileId}`)
if (cachedResponse) {
return cachedResponse
}
const response = await this.fileManager.getFile(fileId)
// 根据文件状态设置响应状态
let status: 'success' | 'processing' | 'failed' | 'unknown'
switch (response.state) {
case FileState.ACTIVE:
status = 'success'
break
case FileState.PROCESSING:
status = 'processing'
break
case FileState.FAILED:
status = 'failed'
break
default:
status = 'unknown'
}
const fileResponse: FileUploadResponse = {
fileId,
displayName: response.displayName || '',
status,
originalFile: response
}
// 只缓存成功的文件
if (status === 'success') {
CacheService.set(
`${GeminiService.FILE_LIST_CACHE_KEY}_${fileId}`,
fileResponse,
GeminiService.FILE_CACHE_DURATION
)
}
return fileResponse
}
async listFiles(): Promise<FileListResponse> {
const cachedList = CacheService.get<FileListResponse>(GeminiService.FILE_LIST_CACHE_KEY)
if (cachedList) {
return cachedList
}
const response = await this.fileManager.listFiles()
const fileList: FileListResponse = {
files: response.files
.filter((file) => file.state === FileState.ACTIVE)
.map((file) => {
// 更新单个文件的缓存
const fileResponse: FileUploadResponse = {
fileId: file.name,
displayName: file.displayName || '',
status: 'success',
originalFile: file
}
CacheService.set(
`${GeminiService.FILE_LIST_CACHE_KEY}_${file.name}`,
fileResponse,
GeminiService.FILE_CACHE_DURATION
)
return {
id: file.name,
displayName: file.displayName || '',
size: Number(file.sizeBytes),
status: 'success',
originalFile: file
}
})
}
// 更新文件列表缓存
CacheService.set(GeminiService.FILE_LIST_CACHE_KEY, fileList, GeminiService.LIST_CACHE_DURATION)
return fileList
}
async deleteFile(fileId: string): Promise<void> {
await this.fileManager.deleteFile(fileId)
}
}
+84
View File
@@ -0,0 +1,84 @@
import { FileListResponse, FileUploadResponse, LocalFileSource } from '@types'
import { fileFrom } from 'fetch-blob/from.js'
import { MistralClientManager } from '../MistralClientManager'
import { BaseFileService } from './BaseFileService'
export class MistralService extends BaseFileService {
private readonly client
constructor(apiKey: string) {
super(apiKey)
const clientManager = MistralClientManager.getInstance()
clientManager.initializeClient(apiKey)
this.client = clientManager.getClient()
}
async uploadFile(file: LocalFileSource): Promise<FileUploadResponse> {
try {
const blob = await fileFrom(file.path)
const response = await this.client.files.upload({
file: blob,
purpose: 'ocr'
})
return {
fileId: response.id,
displayName: file.origin_name,
status: 'success'
}
} catch (error) {
console.error('Error uploading file:', error)
return {
fileId: '',
displayName: file.origin_name,
status: 'failed'
}
}
}
async listFiles(): Promise<FileListResponse> {
try {
const response = await this.client.files.list({})
return {
files: response.data.map((file) => ({
id: file.id,
displayName: file.filename || '',
size: 0, // Size information not available in SDK response
status: 'success' // All listed files are processed
}))
}
} catch (error) {
console.error('Error listing files:', error)
return { files: [] }
}
}
async deleteFile(fileId: string): Promise<void> {
try {
await this.client.files.delete({
fileId
})
} catch (error) {
console.error('Error deleting file:', error)
throw error
}
}
async retrieveFile(fileId: string): Promise<FileUploadResponse> {
try {
const response = await this.client.files.retrieve({
fileId
})
return {
fileId: response.id,
displayName: response.filename || '',
status: 'success' // Retrieved files are always processed
}
} catch (error) {
console.error('Error retrieving file:', error)
throw error
}
}
}
+5 -6
View File
@@ -117,12 +117,11 @@ declare global {
setMinimumSize: (width: number, height: number) => Promise<void>
resetMinimumSize: () => Promise<void>
}
gemini: {
uploadFile: (file: FileType, apiKey: string) => Promise<UploadFileResponse>
retrieveFile: (file: FileType, apiKey: string) => Promise<FileMetadataResponse | undefined>
base64File: (file: FileType) => Promise<{ data: string; mimeType: string }>
listFiles: (apiKey: string) => Promise<ListFilesResponse>
deleteFile: (apiKey: string, fileId: string) => Promise<void>
fileService: {
upload: (type: string, apiKey: string, file: FileType) => Promise<UploadFileResponse>
retrieve: (type: string, apiKey: string, fileId: string) => Promise<FileMetadataResponse | undefined>
list: (type: string, apiKey: string) => Promise<ListFilesResponse>
delete: (type: string, apiKey: string, fileId: string) => Promise<void>
}
selectionMenu: {
action: (action: string) => Promise<void>
+8 -6
View File
@@ -91,12 +91,14 @@ const api = {
setMinimumSize: (width: number, height: number) => ipcRenderer.invoke('window:set-minimum-size', width, height),
resetMinimumSize: () => ipcRenderer.invoke('window:reset-minimum-size')
},
gemini: {
uploadFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:upload-file', file, apiKey),
base64File: (file: FileType) => ipcRenderer.invoke('gemini:base64-file', file),
retrieveFile: (file: FileType, apiKey: string) => ipcRenderer.invoke('gemini:retrieve-file', file, apiKey),
listFiles: (apiKey: string) => ipcRenderer.invoke('gemini:list-files', apiKey),
deleteFile: (apiKey: string, fileId: string) => ipcRenderer.invoke('gemini:delete-file', apiKey, fileId)
fileService: {
upload: (type: string, apiKey: string, file: FileType) =>
ipcRenderer.invoke('file-service:upload', type, apiKey, file),
list: (type: string, apiKey: string) => ipcRenderer.invoke('file-service:list', type, apiKey),
delete: (type: string, apiKey: string, fileId: string) =>
ipcRenderer.invoke('file-service:delete', type, apiKey, fileId),
retrieve: (type: string, apiKey: string, fileId: string) =>
ipcRenderer.invoke('file-service:retrieve', type, apiKey, fileId)
},
selectionMenu: {
action: (action: string) => ipcRenderer.invoke('selection-menu:action', action)
+2 -2
View File
@@ -19,7 +19,7 @@ const GeminiFiles: FC<GeminiFilesProps> = ({ id }) => {
const [loading, setLoading] = useState(false)
const fetchFiles = useCallback(async () => {
const { files } = await window.api.gemini.listFiles(provider.apiKey)
const { files } = await window.api.fileService.list(provider.type, provider.apiKey)
files && setFiles(files.filter((file) => file.state === 'ACTIVE'))
}, [provider])
@@ -57,7 +57,7 @@ const GeminiFiles: FC<GeminiFilesProps> = ({ id }) => {
style={{ cursor: 'pointer', color: 'var(--color-error)' }}
onClick={() => {
setFiles(files.filter((file) => file.name !== record.name))
window.api.gemini.deleteFile(provider.apiKey, record.name).catch((error) => {
window.api.fileService.delete(provider.type, provider.apiKey, record.name).catch((error) => {
console.error('Failed to delete file:', error)
setFiles((prev) => [...prev, record])
})
@@ -107,7 +107,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
count: 1,
origin_name: file.name,
type: file.type as FileTypes,
created_at: new Date().toISOString()
created_at: new Date().toISOString(),
source: 'local' as const
}))
.filter(({ ext }) => fileTypes.includes(ext))
const uploadedFiles = await FileManager.uploadFiles(_files)
+4 -3
View File
@@ -32,6 +32,7 @@ import {
} from '@renderer/services/MessagesService'
import { Assistant, FileType, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeSpecialCharactersForTopicName } from '@renderer/utils'
import { fileToBase64 } from '@renderer/utils/file'
import {
callMCPTool,
geminiFunctionCallToMcpTool,
@@ -74,7 +75,7 @@ export default class GeminiProvider extends BaseProvider {
const isSmallFile = file.size < smallFileSize
if (isSmallFile) {
const { data, mimeType } = await window.api.gemini.base64File(file)
const { data, mimeType } = await fileToBase64(file.path)
return {
inlineData: {
data,
@@ -84,7 +85,7 @@ export default class GeminiProvider extends BaseProvider {
}
// Retrieve file from Gemini uploaded files
const fileMetadata = await window.api.gemini.retrieveFile(file, this.apiKey)
const fileMetadata = await window.api.fileService.retrieve(this.provider.type, this.apiKey, file.id)
if (fileMetadata) {
return {
@@ -96,7 +97,7 @@ export default class GeminiProvider extends BaseProvider {
}
// If file is not found, upload it to Gemini
const uploadResult = await window.api.gemini.uploadFile(file, this.apiKey)
const uploadResult = await window.api.fileService.upload(this.provider.type, this.apiKey, file)
return {
fileData: {
+51 -6
View File
@@ -128,7 +128,7 @@ export type Provider = {
isNotSupportArrayContent?: boolean
}
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai'
export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'qwenlm' | 'azure-openai' | 'mistral'
export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling'
@@ -171,17 +171,62 @@ export type MinAppType = {
style?: React.CSSProperties
}
export interface FileType {
interface BaseFileSource {
id: string
name: string
origin_name: string
path: string
type: FileTypes
size: number
ext: string
type: FileTypes
source: 'local' | 'remote'
}
export interface RemoteFileSource extends BaseFileSource {
source: 'remote'
url: string
status: 'pending' | 'downloading' | 'downloaded' | 'error'
downloadProgress?: number
localPath?: string // 下载后的本地路径
}
export interface FileUploadResponse {
fileId: string
displayName: string
status: 'success' | 'processing' | 'failed' | 'unknown'
originalFile?: any // 保留原始响应,以备需要
}
export interface FileListResponse {
files: Array<{
id: string
displayName: string
size?: number
status: 'success' | 'processing' | 'failed' | 'unknown'
originalFile?: any // 保留原始文件对象
}>
}
export interface LocalFileSource extends BaseFileSource {
origin_name: string
path: string
created_at: string
count: number
tokens?: number
source: 'local'
}
// 联合类型,表示一个文件可以是本地的或远程的
export type FileSource = LocalFileSource | RemoteFileSource
// 为了保持向后兼容
export type FileType = LocalFileSource
// 类型保护函数,用于区分文件类型
export const isLocalFile = (file: FileSource): file is LocalFileSource => {
return file.source === 'local'
}
export const isRemoteFile = (file: FileSource): file is RemoteFileSource => {
return file.source === 'remote'
}
export enum FileTypes {
@@ -295,7 +340,6 @@ export type KnowledgeBaseParams = {
rerankModelProvider?: string
topN?: number
preprocessing?: boolean
ocrProvider?: OcrProvider
}
export interface OcrProvider {
@@ -303,6 +347,7 @@ export interface OcrProvider {
name: string
apiKey?: string
apiHost?: string
model?: string
}
export type GenerateImageParams = {
+9
View File
@@ -0,0 +1,9 @@
import fs from 'fs'
export const fileToBase64 = async (filePath: string) => {
const buffer = await fs.promises.readFile(filePath)
return {
data: buffer.toString('base64'),
mimeType: 'application/pdf'
}
}
+12
View File
@@ -1985,6 +1985,17 @@ __metadata:
languageName: node
linkType: hard
"@mistralai/mistralai@npm:^1.5.2":
version: 1.5.2
resolution: "@mistralai/mistralai@npm:1.5.2"
dependencies:
zod-to-json-schema: "npm:^3.24.1"
peerDependencies:
zod: ">= 3"
checksum: 10c0/d33a8a71adac4d2074ea4bfa09605b1c2158b5ffeaa9a78f3d9602c822cf0885df660b09f2372f17d8a81e78fa64795f31d9fad0cc40a1fab57ca0a4df9dc009
languageName: node
linkType: hard
"@modelcontextprotocol/sdk@npm:1.6.1":
version: 1.6.1
resolution: "@modelcontextprotocol/sdk@npm:1.6.1"
@@ -3345,6 +3356,7 @@ __metadata:
"@llm-tools/embedjs-loader-web": "npm:^0.1.28"
"@llm-tools/embedjs-loader-xml": "npm:^0.1.28"
"@llm-tools/embedjs-openai": "npm:^0.1.28"
"@mistralai/mistralai": "npm:^1.5.2"
"@modelcontextprotocol/sdk": "patch:@modelcontextprotocol/sdk@npm%3A1.6.1#~/.yarn/patches/@modelcontextprotocol-sdk-npm-1.6.1-b46313efe7.patch"
"@notionhq/client": "npm:^2.2.15"
"@reduxjs/toolkit": "npm:^2.2.5"