Stream Jupyter workspace file downloads

This commit is contained in:
wuyang6
2026-05-13 18:15:53 +08:00
parent ef703ff49b
commit f2989cd48b
3 changed files with 111 additions and 6 deletions
+65 -6
View File
@@ -83,6 +83,9 @@ FEISHU_SPREADSHEET_MAX_CELL_CHARS = 5000
FEISHU_SPREADSHEET_WRITE_BATCH_ROWS = 500
FEISHU_ONLINE_DOCS_FILENAME = 'online-docs.json'
JUPYTER_WORKSPACE_FILENAME = 'jupyter_workspace.json'
JUPYTER_STREAM_CHUNK_BYTES = 1024 * 1024
FEISHU_REMOTE_DOC_MAX_BYTES = 50 * 1024 * 1024
FEISHU_REMOTE_SHEET_MAX_BYTES = 100 * 1024 * 1024
@dataclass
@@ -1096,11 +1099,20 @@ def create_app(state: AgentState) -> FastAPI:
if runtime is None:
raise HTTPException(status_code=404, detail='Jupyter runtime is not connected')
try:
data, filename = runtime.read_file_bytes(path)
response, filename = runtime.open_file_stream(path)
except JupyterRuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return Response(
data,
def stream_chunks() -> Any:
try:
for chunk in response.iter_content(chunk_size=JUPYTER_STREAM_CHUNK_BYTES):
if chunk:
yield chunk
finally:
response.close()
return StreamingResponse(
stream_chunks(),
media_type=_content_type_for_filename(filename),
headers={
'content-disposition': (
@@ -3050,9 +3062,27 @@ def _materialize_jupyter_file_for_feishu(
if runtime is None:
raise HTTPException(status_code=404, detail='Jupyter runtime is not connected')
try:
data, filename = runtime.read_file_bytes(remote_path)
info = runtime.file_info(remote_path)
filename = _safe_uploaded_filename(str(info.get('name') or 'remote-file'))
suffix = Path(filename).suffix.lower()
max_bytes = (
FEISHU_REMOTE_SHEET_MAX_BYTES
if suffix in FEISHU_SPREADSHEET_SUFFIXES
else FEISHU_REMOTE_DOC_MAX_BYTES
)
size = info.get('size')
if isinstance(size, int) and size > max_bytes:
raise HTTPException(
status_code=413,
detail=(
f'远端文件过大,当前在线文档转换上限为 '
f'{_format_bytes(max_bytes)},请先在 Jupyter 侧裁剪或抽样后再转换。'
),
)
response, stream_filename = runtime.open_file_stream(remote_path)
except JupyterRuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
filename = filename or _safe_uploaded_filename(stream_filename)
target_dir = (
state.account_paths(account_id)['base']
/ 'integrations'
@@ -3061,8 +3091,27 @@ def _materialize_jupyter_file_for_feishu(
/ session_id
)
target_dir.mkdir(parents=True, exist_ok=True)
target = target_dir / _safe_uploaded_filename(filename)
target.write_bytes(data)
target = target_dir / filename
written = 0
try:
with target.open('wb') as handle:
for chunk in response.iter_content(chunk_size=JUPYTER_STREAM_CHUNK_BYTES):
if not chunk:
continue
written += len(chunk)
if written > max_bytes:
handle.close()
target.unlink(missing_ok=True)
raise HTTPException(
status_code=413,
detail=(
f'远端文件过大,当前在线文档转换上限为 '
f'{_format_bytes(max_bytes)},请先在 Jupyter 侧裁剪或抽样后再转换。'
),
)
handle.write(chunk)
finally:
response.close()
return target
@@ -3080,6 +3129,16 @@ def _safe_uploaded_filename(filename: str) -> str:
return cleaned[:180] or 'remote-file'
def _format_bytes(value: int) -> str:
if value >= 1024 * 1024 * 1024:
return f'{value / (1024 * 1024 * 1024):.1f}GB'
if value >= 1024 * 1024:
return f'{value / (1024 * 1024):.0f}MB'
if value >= 1024:
return f'{value / 1024:.0f}KB'
return f'{value}B'
def _resolve_account_file(state: AgentState, account_id: str | None, raw_path: str) -> Path:
path = Path(raw_path).expanduser().resolve()
account_base = state.account_paths(account_id)['base'].resolve()