donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
218 lines • 8.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlowsPersistenceVolatile = void 0;
const FlowNotFoundException_1 = require("../../exceptions/FlowNotFoundException");
const MiscUtils_1 = require("../../utils/MiscUtils");
/**
* A volatile (in-memory) implementation of FlowsPersistence.
*/
class FlowsPersistenceVolatile {
constructor(flows = new Map(), screenshots = new Map(), toolCalls = new Map(), videos = new Map(), files = new Map(), browserStates = new Map(), aiQueries = new Map()) {
this.flows = flows;
this.screenshots = screenshots;
this.toolCalls = toolCalls;
this.videos = videos;
this.files = files;
this.browserStates = browserStates;
this.aiQueries = aiQueries;
}
async setFlowMetadata(flowMetadata) {
this.flows.set(flowMetadata.id, { ...flowMetadata });
}
async getFlowMetadataById(flowId) {
const metadata = this.flows.get(flowId);
if (!metadata) {
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
return { ...metadata };
}
async getFlowMetadataByName(flowName) {
const matches = Array.from(this.flows.values()).filter((flow) => flow.name === flowName);
if (matches.length === 0) {
throw FlowNotFoundException_1.FlowNotFoundException.forName(flowName);
}
// Return the one with the latest startedAt
matches.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
return { ...matches[0] };
}
/**
* Get flows with pagination and filtering from in-memory storage.
*/
async getFlowsMetadata(query) {
const { limit = 20, pageToken, name, partialName, runMode, state, testId, orphaned, startedAfter, startedBefore, sortBy, sortOrder, } = query || {};
// Validate inputs.
const validLimit = Math.min(Math.max(1, limit), 100);
// Get all flows
let filteredFlows = Array.from(this.flows.values());
// Apply filters
// partialName takes precedence over name if both are provided.
if (partialName) {
const lower = partialName.toLowerCase();
filteredFlows = filteredFlows.filter((flow) => flow.name?.toLowerCase().includes(lower));
}
else if (name) {
filteredFlows = filteredFlows.filter((flow) => flow.name === name);
}
if (runMode) {
filteredFlows = filteredFlows.filter((flow) => flow.runMode === runMode);
}
if (state) {
filteredFlows = filteredFlows.filter((flow) => flow.state === state);
}
if (testId) {
filteredFlows = filteredFlows.filter((flow) => flow.testId === testId);
}
if (orphaned === true) {
filteredFlows = filteredFlows.filter((flow) => flow.testId === null || flow.testId === undefined);
}
else if (orphaned === false) {
filteredFlows = filteredFlows.filter((flow) => flow.testId !== null && flow.testId !== undefined);
}
// Apply startedAfter filter
if (startedAfter !== undefined) {
filteredFlows = filteredFlows.filter((flow) => (flow.startedAt || 0) >= startedAfter);
}
// Apply startedBefore filter
if (startedBefore !== undefined) {
filteredFlows = filteredFlows.filter((flow) => (flow.startedAt || 0) <= startedBefore);
}
const sortCol = sortBy ?? 'created_at';
const sortAsc = sortOrder === 'asc';
filteredFlows.sort((a, b) => {
const aVal = String(a[sortCol] ?? '');
const bVal = String(b[sortCol] ?? '');
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
return sortAsc ? cmp : -cmp;
});
// Parse page token to get the offset.
const offset = pageToken ? parseInt(pageToken, 10) : 0;
// Slice the array to get paginated results.
const paginatedFlows = filteredFlows.slice(offset, offset + validLimit);
// Check if there are more results.
const hasMore = offset + validLimit < filteredFlows.length;
// Calculate next page token.
const nextPageToken = hasMore
? (offset + validLimit).toString()
: undefined;
return {
items: paginatedFlows.map((flow) => ({ ...flow })), // Return copies to prevent modification
nextPageToken,
};
}
async saveScreenShot(flowId, bytes) {
if (!this.screenshots.has(flowId)) {
this.screenshots.set(flowId, new Map());
}
const imageType = MiscUtils_1.MiscUtils.detectImageType(bytes);
const filename = `${new Date().toISOString()}.screenshot.${imageType}`;
this.screenshots.get(flowId).set(filename, Buffer.from(bytes));
return filename;
}
async getScreenShot(flowId, screenShotId) {
return this.getFlowFile(flowId, screenShotId);
}
async setToolCall(flowId, toolCall) {
if (!this.toolCalls.has(flowId)) {
this.toolCalls.set(flowId, []);
}
const calls = this.toolCalls.get(flowId);
const existingIndex = calls.findIndex((c) => c.id === toolCall.id);
if (existingIndex >= 0) {
calls[existingIndex] = { ...toolCall };
}
else {
calls.push({ ...toolCall });
}
}
async getToolCalls(flowId) {
if (!this.flows.has(flowId)) {
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
const calls = this.toolCalls.get(flowId) || [];
return [...calls].sort((a, b) => (a.startedAt || 0) - (b.startedAt || 0));
}
async deleteToolCall(flowId, toolCallId) {
if (!this.flows.has(flowId)) {
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
const calls = this.toolCalls.get(flowId);
if (!calls || calls.length === 0) {
return;
}
const index = calls.findIndex((call) => call.id === toolCallId);
if (index !== -1) {
calls.splice(index, 1);
}
if (calls.length === 0) {
this.toolCalls.delete(flowId);
}
}
async setAiQuery(flowId, aiQuery) {
if (!this.aiQueries.has(flowId)) {
this.aiQueries.set(flowId, []);
}
const queries = this.aiQueries.get(flowId);
const existingIndex = queries.findIndex((q) => q.id === aiQuery.id);
if (existingIndex >= 0) {
queries[existingIndex] = { ...aiQuery };
}
else {
queries.push({ ...aiQuery });
}
}
async getAiQueries(flowId) {
if (!this.flows.has(flowId)) {
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
const queries = this.aiQueries.get(flowId) || [];
return [...queries].sort((a, b) => a.startedAt - b.startedAt);
}
async setVideo(flowId, bytes) {
this.videos.set(flowId, Buffer.from(bytes));
}
async getVideoSegment(flowId, startOffset, length) {
const video = this.videos.get(flowId);
if (!video) {
return null;
}
const totalLength = video.length;
const adjustedLength = Math.min(length, totalLength - startOffset);
return {
bytes: video.slice(startOffset, startOffset + adjustedLength),
totalLength,
startOffset,
};
}
async getFlowFile(flowId, fileId) {
const flowFiles = this.files.get(flowId);
if (!flowFiles) {
return null;
}
const file = flowFiles.get(fileId);
return file ? Buffer.from(file) : null;
}
async setFlowFile(flowId, fileId, fileBytes) {
if (!this.files.has(flowId)) {
this.files.set(flowId, new Map());
}
this.files.get(flowId).set(fileId, Buffer.from(fileBytes));
}
async setBrowserState(flowId, browserState) {
this.browserStates.set(flowId, { ...browserState });
}
async getBrowserState(flowId) {
const state = this.browserStates.get(flowId);
return state ? { ...state } : null;
}
async deleteFlow(flowId) {
this.flows.delete(flowId);
this.screenshots.delete(flowId);
this.toolCalls.delete(flowId);
this.aiQueries.delete(flowId);
this.videos.delete(flowId);
this.files.delete(flowId);
this.browserStates.delete(flowId);
}
}
exports.FlowsPersistenceVolatile = FlowsPersistenceVolatile;
//# sourceMappingURL=FlowsPersistenceVolatile.js.map