git-proxy / src /services /repoApi.ts
github-actions[bot]
Update from GitHub Actions
01ab30b
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<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>;
}
// 浏览器环境中将 ArrayBuffer/Uint8Array 转换为 Base64 的辅助方法
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;
// 移除 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<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);
}
}
// HuggingFace implementation
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('认证失败,请重新登录');
}
//根据响应的内容类型设置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<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);
}
};