github-actions[bot]
commited on
Commit
·
01ab30b
1
Parent(s):
1e6d48a
Update from GitHub Actions
Browse files- functions/api/hf/[[path]].ts +102 -56
- package-lock.json +19 -0
- package.json +1 -0
- src/services/repoApi.ts +104 -41
- src/views/ContentView.vue +3 -4
- src/views/UploadView.vue +4 -9
functions/api/hf/[[path]].ts
CHANGED
@@ -1,13 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
-
//没有固定的文档
|
3 |
-
//接口是通过查看huggingface.co的请求来实现的
|
4 |
-
//有个huggingface hub api
|
5 |
-
//https://huggingface.co/docs/huggingface.js/hub/README
|
6 |
-
//https://github.com/huggingface/huggingface.js/tree/main/packages/hub
|
7 |
-
//https://github.com/huggingface/huggingface.js/blob/main/packages/hub/src/lib/list-files.ts
|
8 |
-
//https://github.com/huggingface/huggingface.js/blob/main/packages/hub/src/lib/download-file.ts
|
9 |
-
//https://github.com/huggingface/huggingface.js/blob/main/packages/hub/src/lib/commit.ts
|
10 |
-
//https://github.com/huggingface/huggingface.js/blob/main/packages/hub/src/lib/delete-files.ts
|
11 |
export const onRequest = async (context: RouteContext): Promise<Response> => {
|
12 |
const request = context.request;
|
13 |
const env = context.env as Env;
|
@@ -39,71 +38,118 @@ export const onRequest = async (context: RouteContext): Promise<Response> => {
|
|
39 |
headers: { 'Content-Type': 'application/json' }
|
40 |
});
|
41 |
}
|
42 |
-
// Hugging Face API 基础 URL
|
43 |
-
const hfApiBaseUrl = 'https://huggingface.co/api/datasets';
|
44 |
-
|
45 |
-
// 处理 GET 请求 - 获取文件内容或列出文件
|
46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
|
|
|
48 |
if (operation === 'raw' && request.method === 'GET') {
|
49 |
const path = pathParts.length > 6 ? pathParts.slice(6).join('/') : '';
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
57 |
}
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
64 |
}
|
65 |
|
66 |
if (operation === 'tree' && request.method === 'GET') {
|
67 |
const path = pathParts.length > 6 ? pathParts.slice(6).join('/') : '';
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
|
|
|
|
75 |
}
|
76 |
-
});
|
77 |
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
|
|
83 |
}
|
84 |
|
85 |
// 处理 POST 请求 - 上传文件
|
86 |
if (operation === 'commit' && (request.method === 'POST' || request.method === 'PUT' || request.method === 'DELETE')) {
|
87 |
-
const
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
}
|
105 |
|
106 |
-
|
107 |
// 不支持的请求方法或路径
|
108 |
return new Response(JSON.stringify({ error: '不支持的请求方法或路径' }), {
|
109 |
status: 400,
|
|
|
1 |
+
import { downloadFile, commit, listFiles, RepoType, CommitOperation } from "@huggingface/hub";
|
2 |
+
|
3 |
+
interface CommitBody {
|
4 |
+
path: string;
|
5 |
+
content?: string;
|
6 |
+
message?: string;
|
7 |
+
lfs?: boolean;
|
8 |
+
}
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
export const onRequest = async (context: RouteContext): Promise<Response> => {
|
11 |
const request = context.request;
|
12 |
const env = context.env as Env;
|
|
|
38 |
headers: { 'Content-Type': 'application/json' }
|
39 |
});
|
40 |
}
|
|
|
|
|
|
|
|
|
41 |
|
42 |
+
const apiParams = {
|
43 |
+
repo: {
|
44 |
+
name: `${owner}/${repo}`,
|
45 |
+
type: 'dataset' as RepoType
|
46 |
+
},
|
47 |
+
accessToken: hfToken,
|
48 |
+
revision: ref
|
49 |
+
};
|
50 |
|
51 |
+
// 处理 GET 请求 - 获取文件内容或列出文件
|
52 |
if (operation === 'raw' && request.method === 'GET') {
|
53 |
const path = pathParts.length > 6 ? pathParts.slice(6).join('/') : '';
|
54 |
+
|
55 |
+
try {
|
56 |
+
const response = await downloadFile({
|
57 |
+
...apiParams,
|
58 |
+
path: path
|
59 |
+
});
|
60 |
+
|
61 |
+
if (!response) {
|
62 |
+
return new Response(JSON.stringify({ error: '文件不存在' }), {
|
63 |
+
status: 404,
|
64 |
+
headers: { 'Content-Type': 'application/json' }
|
65 |
+
});
|
66 |
}
|
67 |
+
return response;
|
68 |
+
} catch (error: any) {
|
69 |
+
return new Response(JSON.stringify({ error: '获取文件失败', details: error.message }), {
|
70 |
+
status: 500,
|
71 |
+
headers: { 'Content-Type': 'application/json' }
|
72 |
+
});
|
73 |
+
}
|
74 |
}
|
75 |
|
76 |
if (operation === 'tree' && request.method === 'GET') {
|
77 |
const path = pathParts.length > 6 ? pathParts.slice(6).join('/') : '';
|
78 |
+
try {
|
79 |
+
const cursor = listFiles({
|
80 |
+
...apiParams,
|
81 |
+
path: path
|
82 |
+
});
|
83 |
+
|
84 |
+
const files = [];
|
85 |
+
for await (const entry of cursor) {
|
86 |
+
files.push(entry);
|
87 |
}
|
|
|
88 |
|
89 |
+
return new Response(JSON.stringify(files), {
|
90 |
+
headers: { 'Content-Type': 'application/json' }
|
91 |
+
});
|
92 |
+
} catch (error: any) {
|
93 |
+
return new Response(JSON.stringify({ error: '列出文件失败', details: error.message }), {
|
94 |
+
status: 500,
|
95 |
+
headers: { 'Content-Type': 'application/json' }
|
96 |
+
});
|
97 |
+
}
|
98 |
}
|
99 |
|
100 |
// 处理 POST 请求 - 上传文件
|
101 |
if (operation === 'commit' && (request.method === 'POST' || request.method === 'PUT' || request.method === 'DELETE')) {
|
102 |
+
const body = await request.json() as CommitBody;
|
103 |
+
|
104 |
+
try {
|
105 |
+
let operation: CommitOperation;
|
106 |
+
|
107 |
+
if (request.method === 'DELETE') {
|
108 |
+
operation = {
|
109 |
+
operation: 'delete',
|
110 |
+
path: body.path
|
111 |
+
};
|
112 |
+
} else {
|
113 |
+
const content = body.content || '';
|
114 |
+
let blobContent: Blob;
|
115 |
+
|
116 |
+
if (body.lfs === true && body.content) {
|
117 |
+
const binary = atob(body.content);
|
118 |
+
const bytes = new Uint8Array(binary.length);
|
119 |
+
for (let i = 0; i < binary.length; i++) {
|
120 |
+
bytes[i] = binary.charCodeAt(i);
|
121 |
+
}
|
122 |
+
blobContent = new Blob([bytes]);
|
123 |
+
} else {
|
124 |
+
blobContent = new Blob([content]);
|
125 |
+
}
|
126 |
+
|
127 |
+
operation = {
|
128 |
+
operation: 'addOrUpdate',
|
129 |
+
path: body.path,
|
130 |
+
content: blobContent
|
131 |
+
};
|
132 |
+
}
|
133 |
|
134 |
+
const response = await commit({
|
135 |
+
...apiParams,
|
136 |
+
operations: [operation],
|
137 |
+
title: `${request.method === 'DELETE' ? 'Delete' : 'Update'} ${body.path}`,
|
138 |
+
description: body.message || `Changed via API`
|
139 |
+
});
|
140 |
+
|
141 |
+
return new Response(JSON.stringify(response), {
|
142 |
+
status: 200,
|
143 |
+
headers: { 'Content-Type': 'application/json' }
|
144 |
+
});
|
145 |
+
} catch (error: any) {
|
146 |
+
return new Response(JSON.stringify({ error: '提交更改失败', details: error.message }), {
|
147 |
+
status: 500,
|
148 |
+
headers: { 'Content-Type': 'application/json' }
|
149 |
+
});
|
150 |
+
}
|
151 |
}
|
152 |
|
|
|
153 |
// 不支持的请求方法或路径
|
154 |
return new Response(JSON.stringify({ error: '不支持的请求方法或路径' }), {
|
155 |
status: 400,
|
package-lock.json
CHANGED
@@ -9,6 +9,7 @@
|
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
11 |
"@hono/node-server": "^1.13.8",
|
|
|
12 |
"@monaco-editor/loader": "^1.5.0",
|
13 |
"@tailwindcss/vite": "^4.0.14",
|
14 |
"dotenv": "^16.4.7",
|
@@ -796,6 +797,24 @@
|
|
796 |
"hono": "^4"
|
797 |
}
|
798 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
799 |
"node_modules/@img/sharp-darwin-arm64": {
|
800 |
"version": "0.33.5",
|
801 |
"resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
|
|
9 |
"version": "0.0.0",
|
10 |
"dependencies": {
|
11 |
"@hono/node-server": "^1.13.8",
|
12 |
+
"@huggingface/hub": "^1.1.2",
|
13 |
"@monaco-editor/loader": "^1.5.0",
|
14 |
"@tailwindcss/vite": "^4.0.14",
|
15 |
"dotenv": "^16.4.7",
|
|
|
797 |
"hono": "^4"
|
798 |
}
|
799 |
},
|
800 |
+
"node_modules/@huggingface/hub": {
|
801 |
+
"version": "1.1.2",
|
802 |
+
"resolved": "https://registry.npmmirror.com/@huggingface/hub/-/hub-1.1.2.tgz",
|
803 |
+
"integrity": "sha512-Jf4GhvVj9ABDw4Itb3BV1T7f22iewuZva476qTicQ4kOTbosuUuFDhsVH7ZH6rVNgg20Ll9kaNBF5CXjySIT+w==",
|
804 |
+
"license": "MIT",
|
805 |
+
"dependencies": {
|
806 |
+
"@huggingface/tasks": "^0.18.2"
|
807 |
+
},
|
808 |
+
"engines": {
|
809 |
+
"node": ">=18"
|
810 |
+
}
|
811 |
+
},
|
812 |
+
"node_modules/@huggingface/tasks": {
|
813 |
+
"version": "0.18.3",
|
814 |
+
"resolved": "https://registry.npmmirror.com/@huggingface/tasks/-/tasks-0.18.3.tgz",
|
815 |
+
"integrity": "sha512-WCIg6tOSftCkE2WxSaDIZRsrs6bkHiH2qc4t6r8L/xoebAhQmIPEDU700zXI7ac1+I6UcCponYxn/oqu3TGNxQ==",
|
816 |
+
"license": "MIT"
|
817 |
+
},
|
818 |
"node_modules/@img/sharp-darwin-arm64": {
|
819 |
"version": "0.33.5",
|
820 |
"resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
package.json
CHANGED
@@ -16,6 +16,7 @@
|
|
16 |
},
|
17 |
"dependencies": {
|
18 |
"@hono/node-server": "^1.13.8",
|
|
|
19 |
"@monaco-editor/loader": "^1.5.0",
|
20 |
"@tailwindcss/vite": "^4.0.14",
|
21 |
"dotenv": "^16.4.7",
|
|
|
16 |
},
|
17 |
"dependencies": {
|
18 |
"@hono/node-server": "^1.13.8",
|
19 |
+
"@huggingface/hub": "^1.1.2",
|
20 |
"@monaco-editor/loader": "^1.5.0",
|
21 |
"@tailwindcss/vite": "^4.0.14",
|
22 |
"dotenv": "^16.4.7",
|
src/services/repoApi.ts
CHANGED
@@ -30,6 +30,25 @@ interface IRepoApi {
|
|
30 |
updateFile(account: Account, path: string, content: string, sha: string, message?: string): Promise<any>;
|
31 |
deleteFile(account: Account, path: string, sha: string, message?: string): Promise<any>;
|
32 |
createFile(account: Account, path: string, content: string, message?: string): Promise<any>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
}
|
34 |
|
35 |
// GitHub implementation
|
@@ -60,7 +79,6 @@ class GitHubRepoApi implements IRepoApi {
|
|
60 |
}
|
61 |
|
62 |
async updateFile(account: Account, path: string, content: string, sha: string, message?: string) {
|
63 |
-
|
64 |
const encoder = new TextEncoder();
|
65 |
const bytes = encoder.encode(content);
|
66 |
const base64Content = btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
|
@@ -95,19 +113,29 @@ class GitHubRepoApi implements IRepoApi {
|
|
95 |
}
|
96 |
|
97 |
async createFile(account: Account, path: string, content: string, message?: string) {
|
|
|
|
|
|
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
const response = await fetch(
|
112 |
`${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`,
|
113 |
{
|
@@ -125,7 +153,6 @@ class GitHubRepoApi implements IRepoApi {
|
|
125 |
|
126 |
// HuggingFace implementation
|
127 |
class HuggingFaceRepoApi implements IRepoApi {
|
128 |
-
|
129 |
async getContents(account: Account, path: string = ''): Promise<RepoContent[]> {
|
130 |
const response = await fetch(
|
131 |
`${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/tree/${account.ref}/${path}`,
|
@@ -141,13 +168,14 @@ class HuggingFaceRepoApi implements IRepoApi {
|
|
141 |
return result.map((item: any) => {
|
142 |
item.sha = item.oid;
|
143 |
item.name = item.path.split('/').pop() || '';
|
|
|
144 |
return item;
|
145 |
});
|
146 |
}
|
147 |
|
148 |
async getFileContent(account: Account, path: string): Promise<RepoContent> {
|
149 |
-
const
|
150 |
-
|
151 |
{
|
152 |
method: 'GET',
|
153 |
headers: {
|
@@ -163,6 +191,24 @@ class HuggingFaceRepoApi implements IRepoApi {
|
|
163 |
throw new Error('认证失败,请重新登录');
|
164 |
}
|
165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
return {
|
167 |
content: await response.text(),
|
168 |
encoding: 'utf-8',
|
@@ -188,15 +234,10 @@ class HuggingFaceRepoApi implements IRepoApi {
|
|
188 |
Authorization: `Bearer ${account.token}`
|
189 |
},
|
190 |
body: JSON.stringify({
|
191 |
-
"
|
192 |
-
"
|
193 |
-
"
|
194 |
-
|
195 |
-
"content": content,
|
196 |
-
"encoding": "utf-8",
|
197 |
-
"path": path
|
198 |
-
}
|
199 |
-
]
|
200 |
})
|
201 |
}
|
202 |
);
|
@@ -218,13 +259,9 @@ class HuggingFaceRepoApi implements IRepoApi {
|
|
218 |
Authorization: `Bearer ${account.token}`
|
219 |
},
|
220 |
body: JSON.stringify({
|
221 |
-
"
|
222 |
-
"
|
223 |
-
"
|
224 |
-
{
|
225 |
-
"path": path
|
226 |
-
}
|
227 |
-
]
|
228 |
})
|
229 |
}
|
230 |
);
|
@@ -241,15 +278,36 @@ class HuggingFaceRepoApi implements IRepoApi {
|
|
241 |
Authorization: `Bearer ${account.token}`
|
242 |
},
|
243 |
body: JSON.stringify({
|
244 |
-
"
|
245 |
-
"
|
246 |
-
"
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
253 |
})
|
254 |
}
|
255 |
);
|
@@ -310,5 +368,10 @@ export const repoApi = {
|
|
310 |
async createFile(account: Account, path: string, content: string, message?: string) {
|
311 |
const api = RepoApiFactory.getRepoApi(account.type);
|
312 |
return api.createFile(account, path, content, message);
|
|
|
|
|
|
|
|
|
|
|
313 |
}
|
314 |
};
|
|
|
30 |
updateFile(account: Account, path: string, content: string, sha: string, message?: string): Promise<any>;
|
31 |
deleteFile(account: Account, path: string, sha: string, message?: string): Promise<any>;
|
32 |
createFile(account: Account, path: string, content: string, message?: string): Promise<any>;
|
33 |
+
createAsset(account: Account, path: string, content: Uint8Array, message?: string): Promise<any>;
|
34 |
+
}
|
35 |
+
|
36 |
+
// 浏览器环境中将 ArrayBuffer/Uint8Array 转换为 Base64 的辅助方法
|
37 |
+
function arrayBufferToBase64(buffer: Uint8Array): Promise<string> {
|
38 |
+
return new Promise((resolve, reject) => {
|
39 |
+
const blob = new Blob([buffer]);
|
40 |
+
const reader = new FileReader();
|
41 |
+
|
42 |
+
reader.onload = () => {
|
43 |
+
const dataUrl = reader.result as string;
|
44 |
+
// 移除 data URL 前缀 (例如 "data:image/png;base64,")
|
45 |
+
const base64 = dataUrl.split(',')[1];
|
46 |
+
resolve(base64);
|
47 |
+
};
|
48 |
+
|
49 |
+
reader.onerror = reject;
|
50 |
+
reader.readAsDataURL(blob);
|
51 |
+
});
|
52 |
}
|
53 |
|
54 |
// GitHub implementation
|
|
|
79 |
}
|
80 |
|
81 |
async updateFile(account: Account, path: string, content: string, sha: string, message?: string) {
|
|
|
82 |
const encoder = new TextEncoder();
|
83 |
const bytes = encoder.encode(content);
|
84 |
const base64Content = btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
|
|
|
113 |
}
|
114 |
|
115 |
async createFile(account: Account, path: string, content: string, message?: string) {
|
116 |
+
const encoder = new TextEncoder();
|
117 |
+
const bytes = encoder.encode(content);
|
118 |
+
const encodedContent = btoa(String.fromCharCode.apply(null, [...new Uint8Array(bytes)]));
|
119 |
|
120 |
+
const response = await fetch(
|
121 |
+
`${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`,
|
122 |
+
{
|
123 |
+
method: 'POST',
|
124 |
+
headers: {
|
125 |
+
'Content-Type': 'application/json',
|
126 |
+
Authorization: `Bearer ${account.token}`
|
127 |
+
},
|
128 |
+
body: JSON.stringify({ branch: account.ref, content: encodedContent, message })
|
129 |
+
}
|
130 |
+
);
|
131 |
+
return handleResponse(response);
|
132 |
+
}
|
133 |
+
|
134 |
+
|
135 |
+
|
136 |
+
async createAsset(account: Account, path: string, content: Uint8Array, message?: string) {
|
137 |
+
|
138 |
+
const encodedContent = await arrayBufferToBase64(content);
|
139 |
const response = await fetch(
|
140 |
`${API_BASE_URL}/api/github/${account.owner}/${account.repo}/${path}`,
|
141 |
{
|
|
|
153 |
|
154 |
// HuggingFace implementation
|
155 |
class HuggingFaceRepoApi implements IRepoApi {
|
|
|
156 |
async getContents(account: Account, path: string = ''): Promise<RepoContent[]> {
|
157 |
const response = await fetch(
|
158 |
`${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/tree/${account.ref}/${path}`,
|
|
|
168 |
return result.map((item: any) => {
|
169 |
item.sha = item.oid;
|
170 |
item.name = item.path.split('/').pop() || '';
|
171 |
+
item.type = item.type === 'directory' ? 'dir' : 'file';
|
172 |
return item;
|
173 |
});
|
174 |
}
|
175 |
|
176 |
async getFileContent(account: Account, path: string): Promise<RepoContent> {
|
177 |
+
const url = `${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/raw/${account.ref}/${path}`;
|
178 |
+
const response = await fetch(url,
|
179 |
{
|
180 |
method: 'GET',
|
181 |
headers: {
|
|
|
191 |
throw new Error('认证失败,请重新登录');
|
192 |
}
|
193 |
|
194 |
+
|
195 |
+
//根据响应的内容类型设置encoding
|
196 |
+
const contentType = response.headers.get('content-type');
|
197 |
+
if (contentType && contentType.startsWith('image/')) {
|
198 |
+
return {
|
199 |
+
content: await arrayBufferToBase64(await response.bytes()),
|
200 |
+
download_url: null,
|
201 |
+
encoding: 'base64',
|
202 |
+
name: '',
|
203 |
+
path: path,
|
204 |
+
sha: '',
|
205 |
+
size: 0,
|
206 |
+
url: '',
|
207 |
+
html_url: '',
|
208 |
+
git_url: '',
|
209 |
+
type: 'file'
|
210 |
+
};
|
211 |
+
}
|
212 |
return {
|
213 |
content: await response.text(),
|
214 |
encoding: 'utf-8',
|
|
|
234 |
Authorization: `Bearer ${account.token}`
|
235 |
},
|
236 |
body: JSON.stringify({
|
237 |
+
"lfs": false,
|
238 |
+
"path": path,
|
239 |
+
"content": content,
|
240 |
+
"message": message,
|
|
|
|
|
|
|
|
|
|
|
241 |
})
|
242 |
}
|
243 |
);
|
|
|
259 |
Authorization: `Bearer ${account.token}`
|
260 |
},
|
261 |
body: JSON.stringify({
|
262 |
+
"lfs": false,
|
263 |
+
"path": path,
|
264 |
+
"message": message,
|
|
|
|
|
|
|
|
|
265 |
})
|
266 |
}
|
267 |
);
|
|
|
278 |
Authorization: `Bearer ${account.token}`
|
279 |
},
|
280 |
body: JSON.stringify({
|
281 |
+
"lfs": false,
|
282 |
+
"path": path,
|
283 |
+
"content": content,
|
284 |
+
"message": message,
|
285 |
+
})
|
286 |
+
}
|
287 |
+
);
|
288 |
+
const result = await handleResponse(response);
|
289 |
+
return {
|
290 |
+
content: {
|
291 |
+
sha: result.commitOid
|
292 |
+
}
|
293 |
+
};
|
294 |
+
}
|
295 |
+
|
296 |
+
async createAsset(account: Account, path: string, content: Uint8Array, message?: string) {
|
297 |
+
const encodedContent = await arrayBufferToBase64(content);
|
298 |
+
const response = await fetch(
|
299 |
+
`${API_BASE_URL}/api/hf/${account.owner}/${account.repo}/commit/${account.ref}/${path}`,
|
300 |
+
{
|
301 |
+
method: 'POST',
|
302 |
+
headers: {
|
303 |
+
'Content-Type': 'application/json',
|
304 |
+
Authorization: `Bearer ${account.token}`
|
305 |
+
},
|
306 |
+
body: JSON.stringify({
|
307 |
+
"lfs": true,
|
308 |
+
"path": path,
|
309 |
+
"content": encodedContent,
|
310 |
+
"message": message,
|
311 |
})
|
312 |
}
|
313 |
);
|
|
|
368 |
async createFile(account: Account, path: string, content: string, message?: string) {
|
369 |
const api = RepoApiFactory.getRepoApi(account.type);
|
370 |
return api.createFile(account, path, content, message);
|
371 |
+
},
|
372 |
+
|
373 |
+
async createAsset(account: Account, path: string, content: Uint8Array, message?: string) {
|
374 |
+
const api = RepoApiFactory.getRepoApi(account.type);
|
375 |
+
return api.createAsset(account, path, content, message);
|
376 |
}
|
377 |
};
|
src/views/ContentView.vue
CHANGED
@@ -77,10 +77,7 @@ const fetchContent = async () => {
|
|
77 |
if (isImageFile.value) {
|
78 |
if (result.content) {
|
79 |
// 如果有 content,将 base64 转换为 Blob URL
|
80 |
-
const
|
81 |
-
result.content :
|
82 |
-
btoa(result.content);
|
83 |
-
const byteCharacters = atob(base64Data);
|
84 |
const byteNumbers = new Array(byteCharacters.length);
|
85 |
for (let i = 0; i < byteCharacters.length; i++) {
|
86 |
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
@@ -353,6 +350,7 @@ const isImageFile = computed(() => {
|
|
353 |
watch(() => route.query, async (query) => {
|
354 |
// Only respond to query changes when on the repo route
|
355 |
if (route.path !== '/content') return;
|
|
|
356 |
const { id, path, newFile } = query;
|
357 |
isNewFile.value = !!newFile;
|
358 |
if (id) {
|
@@ -388,6 +386,7 @@ onUnmounted(() => {
|
|
388 |
onBeforeUnmount(() => {
|
389 |
if (imageUrl.value && imageUrl.value.startsWith('blob:')) {
|
390 |
URL.revokeObjectURL(imageUrl.value);
|
|
|
391 |
}
|
392 |
});
|
393 |
</script>
|
|
|
77 |
if (isImageFile.value) {
|
78 |
if (result.content) {
|
79 |
// 如果有 content,将 base64 转换为 Blob URL
|
80 |
+
const byteCharacters = atob(result.content);
|
|
|
|
|
|
|
81 |
const byteNumbers = new Array(byteCharacters.length);
|
82 |
for (let i = 0; i < byteCharacters.length; i++) {
|
83 |
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
|
350 |
watch(() => route.query, async (query) => {
|
351 |
// Only respond to query changes when on the repo route
|
352 |
if (route.path !== '/content') return;
|
353 |
+
imageUrl.value = '';
|
354 |
const { id, path, newFile } = query;
|
355 |
isNewFile.value = !!newFile;
|
356 |
if (id) {
|
|
|
386 |
onBeforeUnmount(() => {
|
387 |
if (imageUrl.value && imageUrl.value.startsWith('blob:')) {
|
388 |
URL.revokeObjectURL(imageUrl.value);
|
389 |
+
imageUrl.value = '';
|
390 |
}
|
391 |
});
|
392 |
</script>
|
src/views/UploadView.vue
CHANGED
@@ -94,23 +94,18 @@ const handleConfirmUpload = async () => {
|
|
94 |
throw new Error('Invalid file object');
|
95 |
}
|
96 |
|
97 |
-
const content = await new Promise<
|
98 |
const reader = new FileReader();
|
99 |
reader.onload = (e) => {
|
100 |
if (e.target?.result) {
|
101 |
-
|
102 |
-
const dataUrl = e.target.result.toString();
|
103 |
-
|
104 |
-
const base64 = dataUrl.split(',')[1];
|
105 |
-
|
106 |
-
resolve(base64);
|
107 |
} else {
|
108 |
reject(new Error('Failed to read file content'));
|
109 |
}
|
110 |
};
|
111 |
reader.onerror = () => reject(reader.error);
|
112 |
// 使用 readAsDataURL 来处理所有类型的文件
|
113 |
-
reader.
|
114 |
});
|
115 |
|
116 |
const filePath = currentPath.value
|
@@ -122,7 +117,7 @@ const handleConfirmUpload = async () => {
|
|
122 |
`上传文件: ${selectedFile.value.name}`;
|
123 |
|
124 |
// GitHub API 要求 base64 编码的内容
|
125 |
-
const response = await repoApi.
|
126 |
account,
|
127 |
filePath,
|
128 |
content,
|
|
|
94 |
throw new Error('Invalid file object');
|
95 |
}
|
96 |
|
97 |
+
const content = await new Promise<Uint8Array>((resolve, reject) => {
|
98 |
const reader = new FileReader();
|
99 |
reader.onload = (e) => {
|
100 |
if (e.target?.result) {
|
101 |
+
resolve(e.target.result as Uint8Array);
|
|
|
|
|
|
|
|
|
|
|
102 |
} else {
|
103 |
reject(new Error('Failed to read file content'));
|
104 |
}
|
105 |
};
|
106 |
reader.onerror = () => reject(reader.error);
|
107 |
// 使用 readAsDataURL 来处理所有类型的文件
|
108 |
+
reader.readAsArrayBuffer(selectedFile.value as File);
|
109 |
});
|
110 |
|
111 |
const filePath = currentPath.value
|
|
|
117 |
`上传文件: ${selectedFile.value.name}`;
|
118 |
|
119 |
// GitHub API 要求 base64 编码的内容
|
120 |
+
const response = await repoApi.createAsset(
|
121 |
account,
|
122 |
filePath,
|
123 |
content,
|