bohr-agent-sdk 0.1.101__py3-none-any.whl → 0.1.102__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/METADATA +6 -2
- bohr_agent_sdk-0.1.102.dist-info/RECORD +80 -0
- dp/agent/cli/cli.py +126 -25
- dp/agent/cli/templates/__init__.py +1 -0
- dp/agent/cli/templates/calculation/simple.py.template +15 -0
- dp/agent/cli/templates/device/tescan_device.py.template +158 -0
- dp/agent/cli/templates/main.py.template +67 -0
- dp/agent/cli/templates/ui/__init__.py +1 -0
- dp/agent/cli/templates/ui/api/__init__.py +1 -0
- dp/agent/cli/templates/ui/api/config.py +32 -0
- dp/agent/cli/templates/ui/api/constants.py +61 -0
- dp/agent/cli/templates/ui/api/debug.py +257 -0
- dp/agent/cli/templates/ui/api/files.py +469 -0
- dp/agent/cli/templates/ui/api/files_upload.py +115 -0
- dp/agent/cli/templates/ui/api/files_user.py +50 -0
- dp/agent/cli/templates/ui/api/messages.py +161 -0
- dp/agent/cli/templates/ui/api/projects.py +146 -0
- dp/agent/cli/templates/ui/api/sessions.py +93 -0
- dp/agent/cli/templates/ui/api/utils.py +161 -0
- dp/agent/cli/templates/ui/api/websocket.py +184 -0
- dp/agent/cli/templates/ui/config/__init__.py +1 -0
- dp/agent/cli/templates/ui/config/agent_config.py +257 -0
- dp/agent/cli/templates/ui/frontend/index.html +13 -0
- dp/agent/cli/templates/ui/frontend/package.json +46 -0
- dp/agent/cli/templates/ui/frontend/tsconfig.json +26 -0
- dp/agent/cli/templates/ui/frontend/tsconfig.node.json +10 -0
- dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DdAmKhul.js +105 -0
- dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DfN2raU9.css +1 -0
- dp/agent/cli/templates/ui/frontend/ui-static/index.html +14 -0
- dp/agent/cli/templates/ui/frontend/vite.config.ts +37 -0
- dp/agent/cli/templates/ui/scripts/build_ui.py +56 -0
- dp/agent/cli/templates/ui/server/__init__.py +0 -0
- dp/agent/cli/templates/ui/server/app.py +98 -0
- dp/agent/cli/templates/ui/server/connection.py +210 -0
- dp/agent/cli/templates/ui/server/file_watcher.py +85 -0
- dp/agent/cli/templates/ui/server/middleware.py +43 -0
- dp/agent/cli/templates/ui/server/models.py +53 -0
- dp/agent/cli/templates/ui/server/session_manager.py +1158 -0
- dp/agent/cli/templates/ui/server/user_files.py +85 -0
- dp/agent/cli/templates/ui/server/utils.py +50 -0
- dp/agent/cli/templates/ui/test_download.py +98 -0
- dp/agent/cli/templates/ui/ui_utils.py +260 -0
- dp/agent/cli/templates/ui/websocket-server.py +87 -0
- dp/agent/server/storage/http_storage.py +1 -1
- bohr_agent_sdk-0.1.101.dist-info/RECORD +0 -40
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/WHEEL +0 -0
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/entry_points.txt +0 -0
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# File API
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
import zipfile
|
|
6
|
+
import tempfile
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List
|
|
10
|
+
from fastapi import Request, Response, File, UploadFile
|
|
11
|
+
from fastapi.responses import JSONResponse, PlainTextResponse, FileResponse, StreamingResponse
|
|
12
|
+
from bohrium_open_sdk import OpenSDK
|
|
13
|
+
|
|
14
|
+
from config.agent_config import agentconfig
|
|
15
|
+
from server.utils import get_ak_info_from_request
|
|
16
|
+
from server.user_files import UserFileManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Get sessions directory path from config
|
|
22
|
+
files_config = agentconfig.get_files_config()
|
|
23
|
+
user_working_dir = os.environ.get('USER_WORKING_DIR', os.getcwd())
|
|
24
|
+
sessions_dir = files_config.get('sessionsDir', '.agent_sessions')
|
|
25
|
+
user_file_manager = UserFileManager(user_working_dir, sessions_dir)
|
|
26
|
+
|
|
27
|
+
# Import SessionManager instance
|
|
28
|
+
from api.websocket import manager
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_user_identifier(access_key: str = None, app_key: str = None, session_id: str = None) -> str:
|
|
32
|
+
"""Get user unique identifier"""
|
|
33
|
+
# First try to get from connected context
|
|
34
|
+
if access_key:
|
|
35
|
+
cached_user_id = manager.get_user_identifier_from_request(access_key, app_key)
|
|
36
|
+
if cached_user_id:
|
|
37
|
+
return cached_user_id
|
|
38
|
+
|
|
39
|
+
# If no cache, call OpenSDK
|
|
40
|
+
if access_key and app_key:
|
|
41
|
+
try:
|
|
42
|
+
# Use OpenSDK to get user info
|
|
43
|
+
client = OpenSDK(
|
|
44
|
+
access_key=access_key,
|
|
45
|
+
app_key=app_key
|
|
46
|
+
)
|
|
47
|
+
user_info = client.user.get_info()
|
|
48
|
+
if user_info and user_info.get('code') == 0:
|
|
49
|
+
data = user_info.get('data', {})
|
|
50
|
+
bohrium_user_id = data.get('user_id')
|
|
51
|
+
if bohrium_user_id:
|
|
52
|
+
return bohrium_user_id
|
|
53
|
+
except Exception as e:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# If has session_id, use it
|
|
57
|
+
if session_id:
|
|
58
|
+
return session_id
|
|
59
|
+
|
|
60
|
+
# Generate temporary ID if none available
|
|
61
|
+
return f"user_{uuid.uuid4().hex[:8]}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def get_file_tree(request: Request, path: str = None):
|
|
65
|
+
"""Get file tree structure"""
|
|
66
|
+
try:
|
|
67
|
+
# Get user identity
|
|
68
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
69
|
+
|
|
70
|
+
# Get session_id (from cookie)
|
|
71
|
+
session_id = None
|
|
72
|
+
cookie_header = request.headers.get("cookie", "")
|
|
73
|
+
if cookie_header:
|
|
74
|
+
from http.cookies import SimpleCookie
|
|
75
|
+
simple_cookie = SimpleCookie()
|
|
76
|
+
simple_cookie.load(cookie_header)
|
|
77
|
+
if "session_id" in simple_cookie:
|
|
78
|
+
session_id = simple_cookie["session_id"].value
|
|
79
|
+
|
|
80
|
+
# Get user unique identifier
|
|
81
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
82
|
+
|
|
83
|
+
# Get user-specific file directory
|
|
84
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
85
|
+
|
|
86
|
+
def build_tree(directory: Path):
|
|
87
|
+
items = []
|
|
88
|
+
try:
|
|
89
|
+
for item in sorted(directory.iterdir()):
|
|
90
|
+
if item.name.startswith('.'):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
node = {
|
|
94
|
+
"name": item.name,
|
|
95
|
+
"path": str(item),
|
|
96
|
+
"type": "directory" if item.is_dir() else "file"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if item.is_dir():
|
|
100
|
+
node["children"] = build_tree(item)
|
|
101
|
+
else:
|
|
102
|
+
node["size"] = item.stat().st_size
|
|
103
|
+
|
|
104
|
+
items.append(node)
|
|
105
|
+
except PermissionError:
|
|
106
|
+
pass
|
|
107
|
+
return items
|
|
108
|
+
|
|
109
|
+
if path is None:
|
|
110
|
+
# Return file tree structure for user directory
|
|
111
|
+
tree = []
|
|
112
|
+
|
|
113
|
+
# User file root directory
|
|
114
|
+
if user_files_dir.exists():
|
|
115
|
+
# Build root directory node
|
|
116
|
+
root_node = {
|
|
117
|
+
"name": "工作空间",
|
|
118
|
+
"path": str(user_files_dir),
|
|
119
|
+
"type": "directory",
|
|
120
|
+
"isExpanded": True,
|
|
121
|
+
"children": build_tree(user_files_dir)
|
|
122
|
+
}
|
|
123
|
+
tree.append(root_node)
|
|
124
|
+
|
|
125
|
+
return JSONResponse(content=tree)
|
|
126
|
+
else:
|
|
127
|
+
# Handle specific path request
|
|
128
|
+
# Ensure path is within user directory
|
|
129
|
+
if os.path.isabs(path):
|
|
130
|
+
request_path = Path(path)
|
|
131
|
+
else:
|
|
132
|
+
request_path = user_files_dir / path
|
|
133
|
+
|
|
134
|
+
# Security check: ensure requested path is within user directory
|
|
135
|
+
try:
|
|
136
|
+
request_path = request_path.resolve()
|
|
137
|
+
user_files_dir_resolved = user_files_dir.resolve()
|
|
138
|
+
if not str(request_path).startswith(str(user_files_dir_resolved)):
|
|
139
|
+
return JSONResponse(content=[], status_code=403)
|
|
140
|
+
except:
|
|
141
|
+
return JSONResponse(content=[], status_code=400)
|
|
142
|
+
|
|
143
|
+
if not request_path.exists():
|
|
144
|
+
request_path.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
|
|
146
|
+
return JSONResponse(content=build_tree(request_path))
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return JSONResponse(content=[], status_code=500)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def get_file_content(request: Request, file_path: str):
|
|
153
|
+
"""Get file content"""
|
|
154
|
+
try:
|
|
155
|
+
# Get user identity
|
|
156
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
157
|
+
|
|
158
|
+
# Get session_id (from cookie)
|
|
159
|
+
session_id = None
|
|
160
|
+
cookie_header = request.headers.get("cookie", "")
|
|
161
|
+
if cookie_header:
|
|
162
|
+
from http.cookies import SimpleCookie
|
|
163
|
+
simple_cookie = SimpleCookie()
|
|
164
|
+
simple_cookie.load(cookie_header)
|
|
165
|
+
if "session_id" in simple_cookie:
|
|
166
|
+
session_id = simple_cookie["session_id"].value
|
|
167
|
+
|
|
168
|
+
# Get user unique identifier
|
|
169
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
170
|
+
|
|
171
|
+
# Get user-specific file directory
|
|
172
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
173
|
+
|
|
174
|
+
# Handle file path
|
|
175
|
+
if file_path.startswith('/'):
|
|
176
|
+
file = Path(file_path)
|
|
177
|
+
else:
|
|
178
|
+
# Relative path, based on user directory
|
|
179
|
+
file = user_files_dir / file_path
|
|
180
|
+
|
|
181
|
+
# Security check: ensure file is within user directory
|
|
182
|
+
try:
|
|
183
|
+
file_resolved = file.resolve()
|
|
184
|
+
user_files_dir_resolved = user_files_dir.resolve()
|
|
185
|
+
if not str(file_resolved).startswith(str(user_files_dir_resolved)):
|
|
186
|
+
return JSONResponse(
|
|
187
|
+
content={"error": "访问被拒绝"},
|
|
188
|
+
status_code=403
|
|
189
|
+
)
|
|
190
|
+
except:
|
|
191
|
+
return JSONResponse(
|
|
192
|
+
content={"error": "无效的文件路径"},
|
|
193
|
+
status_code=400
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if not file.exists() or not file.is_file():
|
|
197
|
+
return JSONResponse(
|
|
198
|
+
content={"error": "文件未找到"},
|
|
199
|
+
status_code=404
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Determine file type
|
|
203
|
+
suffix = file.suffix.lower()
|
|
204
|
+
|
|
205
|
+
# Text files
|
|
206
|
+
if suffix in ['.json', '.md', '.txt', '.csv', '.py', '.js', '.ts', '.log', '.xml', '.yaml', '.yml']:
|
|
207
|
+
try:
|
|
208
|
+
content = file.read_text(encoding='utf-8')
|
|
209
|
+
return PlainTextResponse(content)
|
|
210
|
+
except UnicodeDecodeError:
|
|
211
|
+
return JSONResponse(
|
|
212
|
+
content={"error": "无法解码文件内容"},
|
|
213
|
+
status_code=400
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
# Binary files
|
|
217
|
+
return FileResponse(file)
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
return JSONResponse(
|
|
221
|
+
content={"error": str(e)},
|
|
222
|
+
status_code=500
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def download_file(request: Request, file_path: str):
|
|
227
|
+
"""Download a single file"""
|
|
228
|
+
try:
|
|
229
|
+
# Get user identity
|
|
230
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
231
|
+
|
|
232
|
+
# Get session_id (from cookie)
|
|
233
|
+
session_id = None
|
|
234
|
+
cookie_header = request.headers.get("cookie", "")
|
|
235
|
+
if cookie_header:
|
|
236
|
+
from http.cookies import SimpleCookie
|
|
237
|
+
simple_cookie = SimpleCookie()
|
|
238
|
+
simple_cookie.load(cookie_header)
|
|
239
|
+
if "session_id" in simple_cookie:
|
|
240
|
+
session_id = simple_cookie["session_id"].value
|
|
241
|
+
|
|
242
|
+
# Get user unique identifier
|
|
243
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
244
|
+
|
|
245
|
+
# Get user-specific file directory
|
|
246
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
247
|
+
|
|
248
|
+
# Process file path
|
|
249
|
+
if file_path.startswith('/'):
|
|
250
|
+
file = Path(file_path)
|
|
251
|
+
else:
|
|
252
|
+
# Relative path, based on user directory
|
|
253
|
+
file = user_files_dir / file_path
|
|
254
|
+
|
|
255
|
+
# Security check: ensure file is within user directory
|
|
256
|
+
try:
|
|
257
|
+
file_resolved = file.resolve()
|
|
258
|
+
user_files_dir_resolved = user_files_dir.resolve()
|
|
259
|
+
if not str(file_resolved).startswith(str(user_files_dir_resolved)):
|
|
260
|
+
return JSONResponse(
|
|
261
|
+
content={"error": "访问被拒绝"},
|
|
262
|
+
status_code=403
|
|
263
|
+
)
|
|
264
|
+
except:
|
|
265
|
+
return JSONResponse(
|
|
266
|
+
content={"error": "无效的文件路径"},
|
|
267
|
+
status_code=400
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if not file.exists() or not file.is_file():
|
|
271
|
+
return JSONResponse(
|
|
272
|
+
content={"error": "文件未找到"},
|
|
273
|
+
status_code=404
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Get filename
|
|
277
|
+
filename = file.name
|
|
278
|
+
|
|
279
|
+
# Return file response with download headers
|
|
280
|
+
return FileResponse(
|
|
281
|
+
path=file,
|
|
282
|
+
filename=filename,
|
|
283
|
+
media_type='application/octet-stream'
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
return JSONResponse(
|
|
288
|
+
content={"error": str(e)},
|
|
289
|
+
status_code=500
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
async def download_folder(request: Request, folder_path: str):
|
|
294
|
+
"""Download folder (packaged as zip)"""
|
|
295
|
+
try:
|
|
296
|
+
# Get user identity
|
|
297
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
298
|
+
|
|
299
|
+
# Get session_id (from cookie)
|
|
300
|
+
session_id = None
|
|
301
|
+
cookie_header = request.headers.get("cookie", "")
|
|
302
|
+
if cookie_header:
|
|
303
|
+
from http.cookies import SimpleCookie
|
|
304
|
+
simple_cookie = SimpleCookie()
|
|
305
|
+
simple_cookie.load(cookie_header)
|
|
306
|
+
if "session_id" in simple_cookie:
|
|
307
|
+
session_id = simple_cookie["session_id"].value
|
|
308
|
+
|
|
309
|
+
# Get user unique identifier
|
|
310
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
311
|
+
|
|
312
|
+
# Get user-specific file directory
|
|
313
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
314
|
+
|
|
315
|
+
# Process folder path
|
|
316
|
+
if folder_path.startswith('/'):
|
|
317
|
+
folder = Path(folder_path)
|
|
318
|
+
else:
|
|
319
|
+
# Relative path, based on user directory
|
|
320
|
+
folder = user_files_dir / folder_path
|
|
321
|
+
|
|
322
|
+
# Security check: ensure folder is within user directory
|
|
323
|
+
try:
|
|
324
|
+
folder_resolved = folder.resolve()
|
|
325
|
+
user_files_dir_resolved = user_files_dir.resolve()
|
|
326
|
+
if not str(folder_resolved).startswith(str(user_files_dir_resolved)):
|
|
327
|
+
return JSONResponse(
|
|
328
|
+
content={"error": "访问被拒绝"},
|
|
329
|
+
status_code=403
|
|
330
|
+
)
|
|
331
|
+
except:
|
|
332
|
+
return JSONResponse(
|
|
333
|
+
content={"error": "无效的文件夹路径"},
|
|
334
|
+
status_code=400
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if not folder.exists() or not folder.is_dir():
|
|
338
|
+
return JSONResponse(
|
|
339
|
+
content={"error": "文件夹未找到"},
|
|
340
|
+
status_code=404
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Create temporary zip file
|
|
344
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
345
|
+
zip_filename = f"{folder.name}_{uuid.uuid4().hex[:8]}.zip"
|
|
346
|
+
zip_path = temp_dir / zip_filename
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Create zip file
|
|
350
|
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
351
|
+
for root, dirs, files in os.walk(folder):
|
|
352
|
+
# Skip hidden files and directories
|
|
353
|
+
dirs[:] = [d for d in dirs if not d.startswith('.')]
|
|
354
|
+
|
|
355
|
+
for file in files:
|
|
356
|
+
if file.startswith('.'):
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
file_path = Path(root) / file
|
|
360
|
+
# Calculate relative path
|
|
361
|
+
arcname = file_path.relative_to(folder)
|
|
362
|
+
zipf.write(file_path, arcname=str(arcname))
|
|
363
|
+
|
|
364
|
+
# Return zip file
|
|
365
|
+
def cleanup():
|
|
366
|
+
"""Clean up temporary file"""
|
|
367
|
+
try:
|
|
368
|
+
if zip_path.exists():
|
|
369
|
+
zip_path.unlink()
|
|
370
|
+
except:
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
# Read file content
|
|
374
|
+
with open(zip_path, 'rb') as f:
|
|
375
|
+
content = f.read()
|
|
376
|
+
|
|
377
|
+
# Clean up temporary file
|
|
378
|
+
cleanup()
|
|
379
|
+
|
|
380
|
+
# Return response
|
|
381
|
+
return Response(
|
|
382
|
+
content=content,
|
|
383
|
+
media_type='application/zip',
|
|
384
|
+
headers={
|
|
385
|
+
'Content-Disposition': f'attachment; filename="{folder.name}.zip"'
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
except Exception as e:
|
|
390
|
+
# Ensure temporary file cleanup
|
|
391
|
+
if zip_path.exists():
|
|
392
|
+
zip_path.unlink()
|
|
393
|
+
raise e
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
return JSONResponse(
|
|
397
|
+
content={"error": str(e)},
|
|
398
|
+
status_code=500
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
async def delete_file(request: Request, file_path: str):
|
|
403
|
+
"""Delete file or folder"""
|
|
404
|
+
try:
|
|
405
|
+
# Get user identity
|
|
406
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
407
|
+
|
|
408
|
+
# Get session_id (from cookie)
|
|
409
|
+
session_id = None
|
|
410
|
+
cookie_header = request.headers.get("cookie", "")
|
|
411
|
+
if cookie_header:
|
|
412
|
+
from http.cookies import SimpleCookie
|
|
413
|
+
simple_cookie = SimpleCookie()
|
|
414
|
+
simple_cookie.load(cookie_header)
|
|
415
|
+
if "session_id" in simple_cookie:
|
|
416
|
+
session_id = simple_cookie["session_id"].value
|
|
417
|
+
|
|
418
|
+
# Get user unique identifier
|
|
419
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
420
|
+
|
|
421
|
+
# Get user-specific file directory
|
|
422
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
423
|
+
|
|
424
|
+
# Process file path
|
|
425
|
+
if file_path.startswith('/'):
|
|
426
|
+
target_path = Path(file_path)
|
|
427
|
+
else:
|
|
428
|
+
# Relative path, based on user directory
|
|
429
|
+
target_path = user_files_dir / file_path
|
|
430
|
+
|
|
431
|
+
# Security check: ensure file is within user directory
|
|
432
|
+
try:
|
|
433
|
+
target_path_resolved = target_path.resolve()
|
|
434
|
+
user_files_dir_resolved = user_files_dir.resolve()
|
|
435
|
+
if not str(target_path_resolved).startswith(str(user_files_dir_resolved)):
|
|
436
|
+
return JSONResponse(
|
|
437
|
+
content={"error": "访问被拒绝"},
|
|
438
|
+
status_code=403
|
|
439
|
+
)
|
|
440
|
+
except:
|
|
441
|
+
return JSONResponse(
|
|
442
|
+
content={"error": "无效的文件路径"},
|
|
443
|
+
status_code=400
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Check if file exists
|
|
447
|
+
if not target_path.exists():
|
|
448
|
+
return JSONResponse(
|
|
449
|
+
content={"error": "文件或文件夹不存在"},
|
|
450
|
+
status_code=404
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Delete file or folder
|
|
454
|
+
if target_path.is_file():
|
|
455
|
+
target_path.unlink()
|
|
456
|
+
else:
|
|
457
|
+
# Recursively delete folder
|
|
458
|
+
shutil.rmtree(target_path)
|
|
459
|
+
|
|
460
|
+
return JSONResponse({
|
|
461
|
+
"success": True,
|
|
462
|
+
"message": f"成功删除: {target_path.name}"
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
except Exception as e:
|
|
466
|
+
return JSONResponse(
|
|
467
|
+
content={"error": str(e)},
|
|
468
|
+
status_code=500
|
|
469
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# File Upload API Extension
|
|
2
|
+
from typing import List
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from fastapi import Request, File, UploadFile
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from server.utils import get_ak_info_from_request
|
|
9
|
+
from server.user_files import UserFileManager
|
|
10
|
+
from config.agent_config import agentconfig
|
|
11
|
+
|
|
12
|
+
# Import required functions and variables from files.py
|
|
13
|
+
from .files import user_file_manager
|
|
14
|
+
from .utils import get_user_identifier, extract_session_id_from_request, check_project_id_required
|
|
15
|
+
from .constants import MAX_FILE_SIZE, ALLOWED_EXTENSIONS
|
|
16
|
+
from .messages import ERROR_MESSAGES, get_message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def upload_files(request: Request, files: List[UploadFile] = File(...)):
|
|
20
|
+
"""Upload files to user directory"""
|
|
21
|
+
try:
|
|
22
|
+
# Get user identity
|
|
23
|
+
access_key, app_key = get_ak_info_from_request(request.headers)
|
|
24
|
+
|
|
25
|
+
# Get session_id (from cookie)
|
|
26
|
+
session_id = extract_session_id_from_request(request)
|
|
27
|
+
|
|
28
|
+
# Get user unique identifier
|
|
29
|
+
user_identifier = get_user_identifier(access_key, app_key, session_id)
|
|
30
|
+
|
|
31
|
+
# Check if user has set project_id
|
|
32
|
+
from api.websocket import manager
|
|
33
|
+
|
|
34
|
+
has_project_id = await check_project_id_required(manager, user_identifier)
|
|
35
|
+
|
|
36
|
+
# If project_id not set, reject upload
|
|
37
|
+
if not has_project_id:
|
|
38
|
+
return JSONResponse(
|
|
39
|
+
content={
|
|
40
|
+
"error": get_message(ERROR_MESSAGES['project_id_required']),
|
|
41
|
+
"code": "PROJECT_ID_REQUIRED"
|
|
42
|
+
},
|
|
43
|
+
status_code=403
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Get user-specific file directory
|
|
47
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_identifier)
|
|
48
|
+
output_dir = user_files_dir / "output"
|
|
49
|
+
output_dir.mkdir(exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# File limits are now imported from constants
|
|
52
|
+
|
|
53
|
+
uploaded_files = []
|
|
54
|
+
|
|
55
|
+
for file in files:
|
|
56
|
+
# Verify file extension
|
|
57
|
+
file_ext = file.filename.split('.')[-1].lower() if '.' in file.filename else ''
|
|
58
|
+
if file_ext not in ALLOWED_EXTENSIONS:
|
|
59
|
+
return JSONResponse(
|
|
60
|
+
content={"error": get_message(ERROR_MESSAGES['unsupported_file_type']).format(file_ext=file_ext)},
|
|
61
|
+
status_code=400
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Read file content
|
|
65
|
+
content = await file.read()
|
|
66
|
+
|
|
67
|
+
# Verify file size
|
|
68
|
+
if len(content) > MAX_FILE_SIZE:
|
|
69
|
+
return JSONResponse(
|
|
70
|
+
content={"error": get_message(ERROR_MESSAGES['file_too_large']).format(filename=file.filename)},
|
|
71
|
+
status_code=400
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Generate safe filename
|
|
75
|
+
safe_filename = file.filename.replace('/', '_').replace('\\', '_')
|
|
76
|
+
|
|
77
|
+
# Handle filename conflicts
|
|
78
|
+
file_path = output_dir / safe_filename
|
|
79
|
+
if file_path.exists():
|
|
80
|
+
# Add timestamp to avoid overwriting
|
|
81
|
+
name_parts = safe_filename.rsplit('.', 1)
|
|
82
|
+
if len(name_parts) == 2:
|
|
83
|
+
safe_filename = f"{name_parts[0]}_{uuid.uuid4().hex[:8]}.{name_parts[1]}"
|
|
84
|
+
else:
|
|
85
|
+
safe_filename = f"{safe_filename}_{uuid.uuid4().hex[:8]}"
|
|
86
|
+
file_path = output_dir / safe_filename
|
|
87
|
+
|
|
88
|
+
# Save file
|
|
89
|
+
file_path.write_bytes(content)
|
|
90
|
+
|
|
91
|
+
# Generate relative path (relative to user_files_dir)
|
|
92
|
+
relative_path = file_path.relative_to(user_files_dir)
|
|
93
|
+
|
|
94
|
+
# Add to return list
|
|
95
|
+
uploaded_files.append({
|
|
96
|
+
"name": file.filename,
|
|
97
|
+
"saved_name": safe_filename,
|
|
98
|
+
"path": str(file_path),
|
|
99
|
+
"file_path": str(file_path.resolve()),
|
|
100
|
+
"relative_path": str(relative_path),
|
|
101
|
+
"url": f"/api/files/{user_identifier}/output/{safe_filename}",
|
|
102
|
+
"size": len(content),
|
|
103
|
+
"mime_type": file.content_type or 'application/octet-stream'
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return JSONResponse({
|
|
107
|
+
"success": True,
|
|
108
|
+
"files": uploaded_files
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return JSONResponse(
|
|
113
|
+
content={"error": str(e)},
|
|
114
|
+
status_code=500
|
|
115
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# User-specific file access API
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from fastapi.responses import JSONResponse, FileResponse
|
|
5
|
+
|
|
6
|
+
from server.user_files import UserFileManager
|
|
7
|
+
from config.agent_config import agentconfig
|
|
8
|
+
|
|
9
|
+
# Import user_file_manager from files.py
|
|
10
|
+
from .files import user_file_manager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_user_file(request: Request, user_id: str, filename: str):
|
|
14
|
+
"""Get file for specific user"""
|
|
15
|
+
try:
|
|
16
|
+
# Use user_id to directly get user file directory
|
|
17
|
+
# UserFileManager will automatically choose the correct path based on user_id type
|
|
18
|
+
user_files_dir = user_file_manager.get_user_files_dir(user_id)
|
|
19
|
+
file_path = user_files_dir / "output" / filename
|
|
20
|
+
|
|
21
|
+
# Security check
|
|
22
|
+
if not file_path.exists() or not file_path.is_file():
|
|
23
|
+
return JSONResponse(
|
|
24
|
+
content={"error": "文件未找到"},
|
|
25
|
+
status_code=404
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Verify file is indeed within user directory
|
|
29
|
+
try:
|
|
30
|
+
file_path_resolved = file_path.resolve()
|
|
31
|
+
user_dir_resolved = user_files_dir.resolve()
|
|
32
|
+
if not str(file_path_resolved).startswith(str(user_dir_resolved)):
|
|
33
|
+
return JSONResponse(
|
|
34
|
+
content={"error": "访问被拒绝"},
|
|
35
|
+
status_code=403
|
|
36
|
+
)
|
|
37
|
+
except:
|
|
38
|
+
return JSONResponse(
|
|
39
|
+
content={"error": "无效的文件路径"},
|
|
40
|
+
status_code=400
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Return file
|
|
44
|
+
return FileResponse(file_path)
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
return JSONResponse(
|
|
48
|
+
content={"error": str(e)},
|
|
49
|
+
status_code=500
|
|
50
|
+
)
|