|
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; |
|
} |
|
|
|
|
|
interface IRepoApi { |
|
getContents(account: Account, path: string): Promise<RepoContent[]>; |
|
getFileContent(account: Account, path: string): Promise<RepoContent>; |
|
updateFile(account: Account, path: string, content: string, sha: string, message?: string): Promise<any>; |
|
deleteFile(account: Account, path: string, sha: string, message?: string): Promise<any>; |
|
createFile(account: Account, path: string, content: string, message?: string): Promise<any>; |
|
createAsset(account: Account, path: string, content: Uint8Array, message?: string): Promise<any>; |
|
} |
|
|
|
|
|
function arrayBufferToBase64(buffer: Uint8Array): Promise<string> { |
|
return new Promise((resolve, reject) => { |
|
const blob = new Blob([buffer]); |
|
const reader = new FileReader(); |
|
|
|
reader.onload = () => { |
|
const dataUrl = reader.result as string; |
|
|
|
const base64 = dataUrl.split(',')[1]; |
|
resolve(base64); |
|
}; |
|
|
|
reader.onerror = reject; |
|
reader.readAsDataURL(blob); |
|
}); |
|
} |
|
|
|
|
|
class GitHubRepoApi implements IRepoApi { |
|
async getContents(account: Account, path: string = ''): Promise<RepoContent[]> { |
|
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<RepoContent> { |
|
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); |
|
} |
|
} |
|
|
|
|
|
class HuggingFaceRepoApi implements IRepoApi { |
|
async getContents(account: Account, path: string = ''): Promise<RepoContent[]> { |
|
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<RepoContent> { |
|
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('认证失败,请重新登录'); |
|
} |
|
|
|
|
|
|
|
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 |
|
} |
|
}; |
|
} |
|
} |
|
|
|
|
|
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}`); |
|
} |
|
} |
|
} |
|
|
|
|
|
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<RepoContent> { |
|
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); |
|
} |
|
}; |
|
|