Add attachment text extraction, auto web news skill, and chat/voice UI updates.
Simplify asset upload temp paths, refresh deploy docs for Aliyun DNS topology, and ship MindSpace content-scan and auth improvements. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+19
-32
@@ -3,12 +3,13 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs';
|
||||
import { runBasicFileScan } from './mindspace-scan.mjs';
|
||||
import { extractAttachmentText } from './mindspace-attachment-text.mjs';
|
||||
import {
|
||||
assetThumbnailKey,
|
||||
ensureHtmlThumbnail,
|
||||
scheduleHtmlThumbnail,
|
||||
} from './mindspace-thumbnails.mjs';
|
||||
import { mirrorAssetToZone, removeZoneMirror, resolveUserWorkspaceRoot } from './user-space.mjs';
|
||||
import { removeZoneMirror, resolveUserWorkspaceRoot } from './user-space.mjs';
|
||||
import { canPreviewAsset, renderAssetPreviewHtml } from './mindspace-asset-preview.mjs';
|
||||
import { createWorkspaceAssetSync } from './mindspace-workspace-sync.mjs';
|
||||
|
||||
@@ -257,7 +258,7 @@ export function createAssetService(pool, options = {}) {
|
||||
|
||||
const uploadId = idFactory();
|
||||
const now = Date.now();
|
||||
const temporaryStorageKey = path.posix.join('tmp', userId, `${uploadId}.upload`);
|
||||
const temporaryStorageKey = path.posix.join('users', userId, 'temp', `${uploadId}.upload`);
|
||||
await conn.query(
|
||||
`UPDATE h5_user_spaces
|
||||
SET reserved_bytes = reserved_bytes + ?, updated_at = ?
|
||||
@@ -349,9 +350,8 @@ export function createAssetService(pool, options = {}) {
|
||||
};
|
||||
|
||||
const completeUpload = async (userId, uploadId) => {
|
||||
const conn = await pool.getConnection();
|
||||
let temporaryPath;
|
||||
let finalPath;
|
||||
const conn = await pool.getConnection();
|
||||
let temporaryPath;
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
const [rows] = await conn.query(
|
||||
@@ -389,20 +389,9 @@ export function createAssetService(pool, options = {}) {
|
||||
|
||||
const assetId = idFactory();
|
||||
const versionId = idFactory();
|
||||
const finalStorageKey = path.posix.join(
|
||||
'users',
|
||||
userId,
|
||||
'assets',
|
||||
assetId,
|
||||
'versions',
|
||||
versionId,
|
||||
);
|
||||
const finalStorageKey = upload.temporary_storage_key;
|
||||
temporaryPath = absoluteStoragePath(upload.temporary_storage_key);
|
||||
finalPath = absoluteStoragePath(finalStorageKey);
|
||||
await fs.mkdir(path.dirname(finalPath), { recursive: true });
|
||||
await fs.rename(temporaryPath, finalPath);
|
||||
|
||||
const fileBuffer = await fs.readFile(finalPath);
|
||||
const fileBuffer = await fs.readFile(temporaryPath);
|
||||
const scan = runBasicFileScan(fileBuffer, {
|
||||
filename: upload.filename,
|
||||
mimeType: upload.detected_mime_type,
|
||||
@@ -469,11 +458,6 @@ export function createAssetService(pool, options = {}) {
|
||||
[now, assetId, uploadId, userId],
|
||||
);
|
||||
await conn.commit();
|
||||
await mirrorToUserWorkspace(userId, {
|
||||
categoryCode: upload.category_code,
|
||||
filename: upload.filename,
|
||||
sourcePath: finalPath,
|
||||
});
|
||||
return {
|
||||
id: assetId,
|
||||
categoryId: upload.category_id,
|
||||
@@ -494,10 +478,6 @@ export function createAssetService(pool, options = {}) {
|
||||
};
|
||||
} catch (error) {
|
||||
await conn.rollback();
|
||||
if (finalPath && temporaryPath) {
|
||||
await fs.mkdir(path.dirname(temporaryPath), { recursive: true }).catch(() => {});
|
||||
await fs.rename(finalPath, temporaryPath).catch(() => {});
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
conn.release();
|
||||
@@ -820,11 +800,6 @@ export function createAssetService(pool, options = {}) {
|
||||
[buffer.length, now, category.space_id, userId],
|
||||
);
|
||||
await conn.commit();
|
||||
await mirrorToUserWorkspace(userId, {
|
||||
categoryCode: category.category_code,
|
||||
filename: normalizedFilename,
|
||||
sourcePath: finalPath,
|
||||
});
|
||||
if (detectedMimeType === 'text/html') {
|
||||
scheduleHtmlThumbnail(storageRoot, assetThumbnailKey(userId, assetId), buffer.toString('utf8'), {
|
||||
title: displayName || normalizedFilename,
|
||||
@@ -891,6 +866,17 @@ export function createAssetService(pool, options = {}) {
|
||||
return renderAssetPreviewHtml({ asset, buffer, downloadUrl });
|
||||
};
|
||||
|
||||
const extractAssetText = async (userId, assetId) => {
|
||||
const { asset, path: assetPath } = await readAsset(userId, assetId);
|
||||
const buffer = await fs.readFile(assetPath);
|
||||
const extracted = extractAttachmentText(buffer, asset.mimeType, asset.filename);
|
||||
return {
|
||||
asset: assetResponse(asset),
|
||||
...extracted,
|
||||
charCount: extracted.text.length,
|
||||
};
|
||||
};
|
||||
|
||||
const expireStaleUploads = async (now = Date.now()) => {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT id, user_id, space_id, reserved_bytes, temporary_storage_key, status
|
||||
@@ -952,6 +938,7 @@ export function createAssetService(pool, options = {}) {
|
||||
deleteAsset,
|
||||
readAsset,
|
||||
renderAssetPreview,
|
||||
extractAssetText,
|
||||
renderAssetThumbnail,
|
||||
syncWorkspaceAssets: workspaceSync.syncUserWorkspace,
|
||||
expireStaleUploads,
|
||||
|
||||
Reference in New Issue
Block a user