import router from '../router'; import { API_BASE_URL, getHeaders, handleResponse } from './util'; export interface Account { owner: string; repo: string; ref: string; type: "github" | "hf"; token: string; } export interface RepoContent { name: string; path: string; sha: string; size: number; url: string; html_url: string; git_url: string; download_url: string | null; type: "file" | "dir"; content?: string; encoding?: string; } // Base interface for repo operations interface IRepoApi { getContents(account: Account, path: string): Promise; getFileContent(account: Account, path: string): Promise; updateFile(account: Account, path: string, content: string, sha: string, message?: string): Promise; deleteFile(account: Account, path: string, sha: string, message?: string): Promise; createFile(account: Account, path: string, content: string, message?: string): Promise; createAsset(account: Account, path: string, content: Uint8Array, message?: string): Promise; } // 浏览器环境中将 ArrayBuffer/Uint8Array 转换为 Base64 的辅助方法 function arrayBufferToBase64(buffer: Uint8Array): Promise { return new Promise((resolve, reject) => { const blob = new Blob([buffer]); const reader = new FileReader(); reader.onload = () => { const dataUrl = reader.result as string; // 移除 data URL 前缀 (例如 "data:image/png;base64,") const base64 = dataUrl.split(',')[1]; resolve(base64); }; reader.onerror = reject; reader.readAsDataURL(blob); }); } // GitHub implementation class GitHubRepoApi implements IRepoApi { async getContents(account: Account, path: string = ''): Promise { const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}?ref=${account.ref}`, { headers: { Authorization: `Bearer ${account.token}` } } ); return await handleResponse(response); } async getFileContent(account: Account, path: string): Promise { const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}?ref=${account.ref}`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` } } ); return await handleResponse(response); } async updateFile(account: Account, path: string, content: string, sha: string, message?: string) { const encoder = new TextEncoder(); const bytes = encoder.encode(content); const base64Content = btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)])); const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ branch: account.ref, content: base64Content, message, sha }) } ); return handleResponse(response); } async deleteFile(account: Account, path: string, sha: string, message?: string) { const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ branch: account.ref, sha, message }) } ); return handleResponse(response); } async createFile(account: Account, path: string, content: string, message?: string) { const encoder = new TextEncoder(); const bytes = encoder.encode(content); const encodedContent = btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)])); const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ branch: account.ref, content: encodedContent, message }) } ); return handleResponse(response); } async createAsset(account: Account, path: string, content: Uint8Array, message?: string) { const encodedContent = await arrayBufferToBase64(content); const response = await fetch( `${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ branch: account.ref, content: encodedContent, message }) } ); return handleResponse(response); } } // HuggingFace implementation class HuggingFaceRepoApi implements IRepoApi { async getContents(account: Account, path: string = ''): Promise { const response = await fetch( `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/tree/${account.ref}/${path}`, { method: 'GET', headers: { Authorization: `Bearer ${account.token}` } } ); let result = await handleResponse(response); return result.map((item: any) => { item.sha = item.oid; item.name = item.path.split('/').pop() || ''; item.type = item.type === 'directory' ? 'dir' : 'file'; return item; }); } async getFileContent(account: Account, path: string): Promise { const url = `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/raw/${account.ref}/${path}`; const response = await fetch(url, { method: 'GET', headers: { Authorization: `Bearer ${account.token}` } } ); if (response.status === 401) { localStorage.removeItem('isAuthenticated'); localStorage.removeItem('token'); router.push('/login'); throw new Error('认证失败,请重新登录'); } //根据响应的内容类型设置encoding const contentType = response.headers.get('content-type'); if (contentType && contentType.startsWith('image/')) { return { content: await arrayBufferToBase64(await response.bytes()), download_url: null, encoding: 'base64', name: '', path: path, sha: '', size: 0, url: '', html_url: '', git_url: '', type: 'file' }; } return { content: await response.text(), encoding: 'utf-8', name: '', path: path, sha: '', size: 0, url: '', html_url: '', git_url: '', download_url: null, type: 'file' }; } async updateFile(account: Account, path: string, content: string, sha: string, message?: string) { const response = await fetch( `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/commit/${account.ref}/${path}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ "lfs": false, "path": path, "content": content, "message": message, }) } ); const result = await handleResponse(response); return { content: { sha: result.commitOid } }; } async deleteFile(account: Account, path: string, sha: string, message?: string) { const response = await fetch( `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/commit/${account.ref}/${path}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ "lfs": false, "path": path, "message": message, }) } ); return handleResponse(response); } async createFile(account: Account, path: string, content: string, message?: string) { const response = await fetch( `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/commit/${account.ref}/${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ "lfs": false, "path": path, "content": content, "message": message, }) } ); const result = await handleResponse(response); return { content: { sha: result.commitOid } }; } async createAsset(account: Account, path: string, content: Uint8Array, message?: string) { const encodedContent = await arrayBufferToBase64(content); const response = await fetch( `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/commit/${account.ref}/${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${account.token}` }, body: JSON.stringify({ "lfs": true, "path": path, "content": encodedContent, "message": message, }) } ); const result = await handleResponse(response); return { content: { sha: result.commitOid } }; } } // Factory to get the appropriate repo API implementation class RepoApiFactory { private static githubInstance: GitHubRepoApi; private static huggingFaceInstance: HuggingFaceRepoApi; static getRepoApi(type: "github" | "hf"): IRepoApi { switch (type) { case "github": if (!this.githubInstance) { this.githubInstance = new GitHubRepoApi(); } return this.githubInstance; case "hf": if (!this.huggingFaceInstance) { this.huggingFaceInstance = new HuggingFaceRepoApi(); } return this.huggingFaceInstance; default: throw new Error(`Unsupported repository type: ${type}`); } } } // Exported API object that uses the factory export const repoApi = { async getContents(account: Account, path: string = '') { const api = RepoApiFactory.getRepoApi(account.type); return api.getContents(account, path); }, async getFileContent(account: Account, path: string): Promise { const api = RepoApiFactory.getRepoApi(account.type); return api.getFileContent(account, path); }, async updateFile(account: Account, path: string, content: string, sha: string, message?: string) { const api = RepoApiFactory.getRepoApi(account.type); return api.updateFile(account, path, content, sha, message); }, async deleteFile(account: Account, path: string, sha: string, message?: string) { const api = RepoApiFactory.getRepoApi(account.type); return api.deleteFile(account, path, sha, message); }, async createFile(account: Account, path: string, content: string, message?: string) { const api = RepoApiFactory.getRepoApi(account.type); return api.createFile(account, path, content, message); }, async createAsset(account: Account, path: string, content: Uint8Array, message?: string) { const api = RepoApiFactory.getRepoApi(account.type); return api.createAsset(account, path, content, message); } };