feat: implement OpenClaw process owner and runtime path utilities
- Add OpenClawProcessOwner class to manage the lifecycle of the OpenClaw process. - Introduce utility functions for managing OpenClaw runtime paths. - Update session store to normalize agent session keys and migrate existing keys. - Refactor main process to handle local provider API routing through a new dispatch function. - Enhance token usage writer to utilize a new session key parsing function. - Create agents management store to handle agent data and interactions. - Update chat store to integrate agent selection and session management. - Introduce AgentsSection component for displaying agent information in the UI. - Refactor HomePage to support agent selection and display current agent. - Update routing to reflect new agents page structure.
This commit is contained in:
115
electron/api/routes/files.ts
Normal file
115
electron/api/routes/files.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { app, nativeImage } from 'electron';
|
||||
import { extname, join } from 'node:path';
|
||||
import type { HostApiContext } from '../context';
|
||||
import type { NormalizedHostApiRequest } from '../route-utils';
|
||||
import { ok, parseJsonBody } from '../route-utils';
|
||||
|
||||
const EXT_MIME_MAP: Record<string, string> = {
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.txt': 'text/plain',
|
||||
'.json': 'application/json',
|
||||
'.pdf': 'application/pdf',
|
||||
};
|
||||
|
||||
function getMimeType(ext: string): string {
|
||||
return EXT_MIME_MAP[ext.toLowerCase()] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
function mimeToExt(mimeType: string): string {
|
||||
for (const [ext, mime] of Object.entries(EXT_MIME_MAP)) {
|
||||
if (mime === mimeType) return ext;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const OUTBOUND_DIR = join(app.getPath('userData'), 'openclaw-media', 'outbound');
|
||||
|
||||
async function generateImagePreview(filePath: string, mimeType: string): Promise<string | null> {
|
||||
try {
|
||||
const image = nativeImage.createFromPath(filePath);
|
||||
if (image.isEmpty()) return null;
|
||||
|
||||
const size = image.getSize();
|
||||
const maxDim = 512;
|
||||
const normalized = size.width > maxDim || size.height > maxDim
|
||||
? (size.width >= size.height ? image.resize({ width: maxDim }) : image.resize({ height: maxDim }))
|
||||
: image;
|
||||
|
||||
return `data:${mimeType};base64,${normalized.toPNG().toString('base64')}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleFileRoutes(
|
||||
request: NormalizedHostApiRequest,
|
||||
_ctx: HostApiContext,
|
||||
) {
|
||||
const { pathname, method } = request;
|
||||
|
||||
if (pathname === '/api/files/stage-buffer' && method === 'POST') {
|
||||
const body = parseJsonBody<{ base64: string; fileName: string; mimeType: string }>(request.body);
|
||||
const fsP = await import('node:fs/promises');
|
||||
await fsP.mkdir(OUTBOUND_DIR, { recursive: true });
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const ext = extname(body.fileName) || mimeToExt(body.mimeType);
|
||||
const stagedPath = join(OUTBOUND_DIR, `${id}${ext}`);
|
||||
const buffer = Buffer.from(body.base64 || '', 'base64');
|
||||
await fsP.writeFile(stagedPath, buffer);
|
||||
|
||||
const mimeType = body.mimeType || getMimeType(ext);
|
||||
const preview = mimeType.startsWith('image/')
|
||||
? await generateImagePreview(stagedPath, mimeType)
|
||||
: null;
|
||||
|
||||
return ok({
|
||||
id,
|
||||
fileName: body.fileName,
|
||||
mimeType,
|
||||
fileSize: buffer.length,
|
||||
stagedPath,
|
||||
preview,
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname === '/api/files/stage-paths' && method === 'POST') {
|
||||
const body = parseJsonBody<{ filePaths: string[] }>(request.body);
|
||||
const fsP = await import('node:fs/promises');
|
||||
await fsP.mkdir(OUTBOUND_DIR, { recursive: true });
|
||||
|
||||
const results = [];
|
||||
for (const filePath of body.filePaths || []) {
|
||||
const id = crypto.randomUUID();
|
||||
const ext = extname(filePath);
|
||||
const stagedPath = join(OUTBOUND_DIR, `${id}${ext}`);
|
||||
await fsP.copyFile(filePath, stagedPath);
|
||||
const stats = await fsP.stat(stagedPath);
|
||||
const mimeType = getMimeType(ext);
|
||||
const fileName = filePath.split(/[\\/]/).pop() || 'file';
|
||||
const preview = mimeType.startsWith('image/')
|
||||
? await generateImagePreview(stagedPath, mimeType)
|
||||
: null;
|
||||
|
||||
results.push({
|
||||
id,
|
||||
fileName,
|
||||
mimeType,
|
||||
fileSize: stats.size,
|
||||
stagedPath,
|
||||
preview,
|
||||
});
|
||||
}
|
||||
|
||||
return ok(results);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user