i0110 commited on
Commit
3a76a5a
·
verified ·
1 Parent(s): a8e0323

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +207 -26
server.js CHANGED
@@ -1,10 +1,11 @@
1
  const express = require('express');
2
  const path = require('path');
3
  const axios = require('axios');
 
4
  const app = express();
5
  const port = process.env.PORT || 8080;
6
 
7
- // 从环境变量 HF_USER 获取用户和对应的 API Token 映射
8
  const userTokenMapping = {};
9
  const usernames = [];
10
  const hfUserConfig = process.env.HF_USER || '';
@@ -22,6 +23,30 @@ if (hfUserConfig) {
22
  });
23
  }
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  // 提供静态文件(前端文件)
26
  app.use(express.static(path.join(__dirname, 'public')));
27
 
@@ -30,36 +55,196 @@ app.get('/api/config', (req, res) => {
30
  res.json({ usernames: usernames.join(',') });
31
  });
32
 
33
- // 代理 HuggingFace API:获取用户的 spaces 列表(包括私有)
34
  app.get('/api/proxy/spaces', async (req, res) => {
35
  try {
36
- const { author } = req.query;
37
- if (!author) {
38
- return res.status(400).json({ error: '缺少 author 参数' });
39
  }
40
 
41
- const token = userTokenMapping[author];
42
- let response;
43
- if (!token) {
44
- console.warn(`用户 ${author} 没有配置 API Token,将尝试无认证访问公开数据`);
45
- response = await axios.get(`https://huggingface.co/api/spaces?author=${author}`);
46
- } else {
47
- console.log(`用户 ${author} 使用 Token 访问私有数据`);
48
- response = await axios.get(`https://huggingface.co/api/spaces?author=${author}`, {
49
- headers: {
50
- 'Authorization': `Bearer ${token}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
- });
 
 
53
  }
54
- console.log(`获取 spaces 列表成功 (${author}): 共 ${response.data.length} 个实例`);
55
- res.json(response.data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  } catch (error) {
57
- console.error(`代理获取 spaces 列表失败 (${req.query.author}):`, error.message, error.response?.status || '无状态码');
58
- res.status(error.response?.status || 500).json({ error: '获取 spaces 列表失败', details: error.message });
59
  }
60
  });
61
 
62
- // 代理 HuggingFace API:获取实时监控数据(SSE 需要特殊处理)
63
  app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
64
  try {
65
  const { username, instanceId } = req.params;
@@ -69,12 +254,10 @@ app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
69
  let headers = {};
70
  if (token) {
71
  headers = { 'Authorization': `Bearer ${token}` };
72
- console.log(`用戶 ${username}/${instanceId} 使用 Token 連接監控數據`);
73
  } else {
74
  console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
75
  }
76
 
77
- // 发起请求并设置流式响应
78
  const response = await axios({
79
  method: 'get',
80
  url,
@@ -82,7 +265,6 @@ app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
82
  responseType: 'stream'
83
  });
84
 
85
- // 将响应流转发到客户端
86
  res.set({
87
  'Content-Type': 'text/event-stream',
88
  'Cache-Control': 'no-cache',
@@ -90,7 +272,6 @@ app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
90
  });
91
  response.data.pipe(res);
92
 
93
- // 监听错误
94
  response.data.on('error', (err) => {
95
  console.error(`直播监控数据流错误 (${username}/${instanceId}):`, err);
96
  res.status(500).end();
@@ -100,7 +281,7 @@ app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
100
  response.data.destroy();
101
  });
102
  } catch (error) {
103
- console.error(`代理获取直播监控数据失败 (${req.params.username}/${req.params.instanceId}):`, error.message, error.response?.status || '无状态码');
104
  res.status(error.response?.status || 500).json({ error: '获取监控数据失败', details: error.message });
105
  }
106
  });
 
1
  const express = require('express');
2
  const path = require('path');
3
  const axios = require('axios');
4
+ const { HubApi } = require('@huggingface/hub');
5
  const app = express();
6
  const port = process.env.PORT || 8080;
7
 
8
+ // 从环境变量获取 HuggingFace 用户名和对应的 API Token 映射
9
  const userTokenMapping = {};
10
  const usernames = [];
11
  const hfUserConfig = process.env.HF_USER || '';
 
23
  });
24
  }
25
 
26
+ // 缓存管理
27
+ class SpaceCache {
28
+ constructor() {
29
+ this.spaces = {};
30
+ this.lastUpdate = null;
31
+ }
32
+
33
+ updateAll(spacesData) {
34
+ this.spaces = spacesData.reduce((acc, space) => ({ ...acc, [space.repo_id]: space }), {});
35
+ this.lastUpdate = Date.now();
36
+ }
37
+
38
+ getAll() {
39
+ return Object.values(this.spaces);
40
+ }
41
+
42
+ isExpired(expireMinutes = 5) {
43
+ if (!this.lastUpdate) return true;
44
+ return (Date.now() - this.lastUpdate) > (expireMinutes * 60 * 1000);
45
+ }
46
+ }
47
+
48
+ const spaceCache = new SpaceCache();
49
+
50
  // 提供静态文件(前端文件)
51
  app.use(express.static(path.join(__dirname, 'public')));
52
 
 
55
  res.json({ usernames: usernames.join(',') });
56
  });
57
 
58
+ // 获取所有 spaces 列表(包括私有)
59
  app.get('/api/proxy/spaces', async (req, res) => {
60
  try {
61
+ if (!spaceCache.isExpired()) {
62
+ console.log('从缓存获取 Spaces 数据');
63
+ return res.json(spaceCache.getAll());
64
  }
65
 
66
+ const allSpaces = [];
67
+ for (const username of usernames) {
68
+ const token = userTokenMapping[username];
69
+ if (!token) {
70
+ console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
71
+ }
72
+
73
+ try {
74
+ const hubApi = new HubApi({ accessToken: token || undefined });
75
+ const spaces = await hubApi.listSpaces({ author: username });
76
+ console.log(`获取到 ${spaces.length} 个 Spaces for ${username}`);
77
+
78
+ for (const space of spaces) {
79
+ try {
80
+ const spaceInfo = await hubApi.getSpaceInfo({ repoId: space.id });
81
+ const spaceRuntime = spaceInfo.runtime || {};
82
+
83
+ allSpaces.push({
84
+ repo_id: spaceInfo.id,
85
+ name: spaceInfo.cardData?.title || spaceInfo.id.split('/')[1],
86
+ owner: spaceInfo.author,
87
+ username: username,
88
+ token: token || '',
89
+ url: `https://${spaceInfo.author}-${spaceInfo.id.split('/')[1]}.hf.space`,
90
+ status: spaceRuntime.stage || 'unknown',
91
+ last_modified: spaceInfo.lastModified || 'unknown',
92
+ created_at: spaceInfo.createdAt || 'unknown',
93
+ sdk: spaceInfo.sdk || 'unknown',
94
+ tags: spaceInfo.tags || [],
95
+ private: spaceInfo.private || false,
96
+ app_port: spaceInfo.cardData?.app_port || 'unknown'
97
+ });
98
+ } catch (error) {
99
+ console.error(`处理 Space ${space.id} 失败:`, error.message);
100
+ }
101
  }
102
+ } catch (error) {
103
+ console.error(`获取 Spaces 列表失败 for ${username}:`, error.message);
104
+ }
105
  }
106
+
107
+ allSpaces.sort((a, b) => a.name.localeCompare(b.name));
108
+ spaceCache.updateAll(allSpaces);
109
+ console.log(`总共获取到 ${allSpaces.length} 个 Spaces`);
110
+ res.json(allSpaces);
111
+ } catch (error) {
112
+ console.error(`代理获取 spaces 列表失败:`, error.message);
113
+ res.status(500).json({ error: '获取 spaces 列表失败', details: error.message });
114
+ }
115
+ });
116
+
117
+ // 代理重启 Space
118
+ app.post('/api/proxy/restart/:repoId', async (req, res) => {
119
+ try {
120
+ const { repoId } = req.params;
121
+ const spaces = spaceCache.getAll();
122
+ const space = spaces.find(s => s.repo_id === repoId);
123
+ if (!space || !space.token) {
124
+ return res.status(404).json({ error: 'Space 未找到或无 Token 配置' });
125
+ }
126
+
127
+ const hubApi = new HubApi({ accessToken: space.token });
128
+ await hubApi.restartSpace({ repoId });
129
+ res.json({ success: true, message: `Space ${repoId} 重启成功` });
130
+ } catch (error) {
131
+ console.error(`重启 space 失败 (${req.params.repoId}):`, error.message);
132
+ res.status(error.response?.status || 500).json({ error: '重启 space 失败', details: error.message });
133
+ }
134
+ });
135
+
136
+ // 代理重建 Space
137
+ app.post('/api/proxy/rebuild/:repoId', async (req, res) => {
138
+ try {
139
+ const { repoId } = req.params;
140
+ const spaces = spaceCache.getAll();
141
+ const space = spaces.find(s => s.repo_id === repoId);
142
+ if (!space || !space.token) {
143
+ return res.status(404).json({ error: 'Space 未找到或无 Token 配置' });
144
+ }
145
+
146
+ const hubApi = new HubApi({ accessToken: space.token });
147
+ await hubApi.restartSpace({ repoId, factoryReboot: true });
148
+ res.json({ success: true, message: `Space ${repoId} 重建成功` });
149
+ } catch (error) {
150
+ console.error(`重建 space 失败 (${req.params.repoId}):`, error.message);
151
+ res.status(error.response?.status || 500).json({ error: '重建 space 失败', details: error.message });
152
+ }
153
+ });
154
+
155
+ // 外部 API 服务(类似于 Flask 的 /api/v1)
156
+ app.get('/api/v1/info/:token', async (req, res) => {
157
+ try {
158
+ const { token } = req.params;
159
+ const authHeader = req.headers.authorization;
160
+ if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== process.env.API_KEY) {
161
+ return res.status(401).json({ error: '无效的 API 密钥' });
162
+ }
163
+
164
+ const hubApi = new HubApi({ accessToken: token });
165
+ const userInfo = await hubApi.whoami();
166
+ const username = userInfo.name;
167
+ const spaces = await hubApi.listSpaces({ author: username });
168
+ const spaceList = [];
169
+
170
+ for (const space of spaces) {
171
+ try {
172
+ const spaceInfo = await hubApi.getSpaceInfo({ repoId: space.id });
173
+ spaceList.push(spaceInfo.id);
174
+ } catch (error) {
175
+ console.error(`获取 Space 信息失败 (${space.id}):`, error.message);
176
+ }
177
+ }
178
+
179
+ res.json({ spaces: spaceList, total: spaceList.length });
180
+ } catch (error) {
181
+ console.error(`获取 spaces 列表失败 (外部 API):`, error.message);
182
+ res.status(500).json({ error: error.message });
183
+ }
184
+ });
185
+
186
+ app.get('/api/v1/info/:token/:spaceId(*) ', async (req, res) => {
187
+ try {
188
+ const { token, spaceId } = req.params;
189
+ const authHeader = req.headers.authorization;
190
+ if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== process.env.API_KEY) {
191
+ return res.status(401).json({ error: '无效的 API 密钥' });
192
+ }
193
+
194
+ const hubApi = new HubApi({ accessToken: token });
195
+ const spaceInfo = await hubApi.getSpaceInfo({ repoId: spaceId });
196
+ const spaceRuntime = spaceInfo.runtime || {};
197
+
198
+ res.json({
199
+ id: spaceInfo.id,
200
+ status: spaceRuntime.stage || 'unknown',
201
+ last_modified: spaceInfo.lastModified || null,
202
+ created_at: spaceInfo.createdAt || null,
203
+ sdk: spaceInfo.sdk || 'unknown',
204
+ tags: spaceInfo.tags || [],
205
+ private: spaceInfo.private || false
206
+ });
207
+ } catch (error) {
208
+ console.error(`获取 space 信息失败 (外部 API):`, error.message);
209
+ res.status(error.response?.status || 404).json({ error: error.message });
210
+ }
211
+ });
212
+
213
+ app.post('/api/v1/action/:token/:spaceId(*)/restart', async (req, res) => {
214
+ try {
215
+ const { token, spaceId } = req.params;
216
+ const authHeader = req.headers.authorization;
217
+ if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== process.env.API_KEY) {
218
+ return res.status(401).json({ error: '无效的 API 密钥' });
219
+ }
220
+
221
+ const hubApi = new HubApi({ accessToken: token });
222
+ await hubApi.restartSpace({ repoId: spaceId });
223
+ res.json({ success: true, message: `Space ${spaceId} 重启成功` });
224
+ } catch (error) {
225
+ console.error(`重启 space 失败 (外部 API):`, error.message);
226
+ res.status(error.response?.status || 500).json({ success: false, error: error.message });
227
+ }
228
+ });
229
+
230
+ app.post('/api/v1/action/:token/:spaceId(*)/rebuild', async (req, res) => {
231
+ try {
232
+ const { token, spaceId } = req.params;
233
+ const authHeader = req.headers.authorization;
234
+ if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== process.env.API_KEY) {
235
+ return res.status(401).json({ error: '无效的 API 密钥' });
236
+ }
237
+
238
+ const hubApi = new HubApi({ accessToken: token });
239
+ await hubApi.restartSpace({ repoId: spaceId, factoryReboot: true });
240
+ res.json({ success: true, message: `Space ${spaceId} 重建成功` });
241
  } catch (error) {
242
+ console.error(`重建 space 失败 (外部 API):`, error.message);
243
+ res.status(error.response?.status || 500).json({ success: false, error: error.message });
244
  }
245
  });
246
 
247
+ // 代理 HuggingFace API:获取实时监控数据(SSE
248
  app.get('/api/proxy/live-metrics/:username/:instanceId', async (req, res) => {
249
  try {
250
  const { username, instanceId } = req.params;
 
254
  let headers = {};
255
  if (token) {
256
  headers = { 'Authorization': `Bearer ${token}` };
 
257
  } else {
258
  console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
259
  }
260
 
 
261
  const response = await axios({
262
  method: 'get',
263
  url,
 
265
  responseType: 'stream'
266
  });
267
 
 
268
  res.set({
269
  'Content-Type': 'text/event-stream',
270
  'Cache-Control': 'no-cache',
 
272
  });
273
  response.data.pipe(res);
274
 
 
275
  response.data.on('error', (err) => {
276
  console.error(`直播监控数据流错误 (${username}/${instanceId}):`, err);
277
  res.status(500).end();
 
281
  response.data.destroy();
282
  });
283
  } catch (error) {
284
+ console.error(`代理获取直播监控数据失败 (${req.params.username}/${req.params.instanceId}):`, error.message);
285
  res.status(error.response?.status || 500).json({ error: '获取监控数据失败', details: error.message });
286
  }
287
  });