@agentscope/studio
Version:
AgentScope Studio is a powerful local monitoring and visualization tool designed to provide real-time insights into your system's performance and behavior.
411 lines (410 loc) • 17.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunDao = void 0;
const typeorm_1 = require("typeorm");
const src_1 = require("../../../shared/src");
const Run_1 = require("../models/Run");
const RunView_1 = require("../models/RunView");
const utils_1 = require("../utils");
const Trace_1 = require("./Trace");
class RunDao {
static doesProjectExist(project) {
return __awaiter(this, void 0, void 0, function* () {
try {
const run = yield Run_1.RunTable.findOne({ where: { project } });
return run !== null;
}
catch (error) {
console.error(error);
throw error;
}
});
}
static doesRunExist(runId) {
return __awaiter(this, void 0, void 0, function* () {
try {
const run = yield Run_1.RunTable.findOne({ where: { id: runId } });
return run !== null;
}
catch (error) {
console.error(error);
throw error;
}
});
}
static addRun(runData) {
return __awaiter(this, void 0, void 0, function* () {
try {
const run = Run_1.RunTable.create(Object.assign({}, runData));
yield run.save();
}
catch (error) {
console.error(error);
throw error;
}
});
}
/**
* Retrieve paginated projects with aggregated run statistics
*
* This method performs an optimized database query to fetch project data with:
* - Count of runs by status (running, pending, finished)
* - Total number of runs per project
* - Project creation timestamp (earliest run timestamp)
* - Support for pagination, sorting, and filtering
*
* @param input - Object containing pagination, sort, and filters
* @param input.pagination - Object containing page and pageSize
* @param input.pagination.page - Current page number (1-based)
* @param input.pagination.pageSize - Number of items per page
* @param input.sort - Optional sorting configuration
* @param input.sort.field - Field to sort by (project, running, pending, finished, total, createdAt)
* @param input.sort.order - Sort direction ('asc' or 'desc')
* @param input.filters - Optional filters for querying
* @param input.filters.project - Project name filter (uses LIKE for partial matching)
*
* @returns Promise resolving to TableData structure containing:
* - list: Array of ProjectData objects
* - total: Total number of projects (before pagination)
* - page: Current page number
* - pageSize: Items per page
*
* @throws Error if database query fails
*
* @example
* const result = await RunDao.getProjects({
* pagination: { page: 1, pageSize: 10 },
* sort: { field: 'total', order: 'desc' },
* filters: { project: { operator: 'contains', value: 'agent' } }
* });
* // Returns: { list: [...], total: 25, page: 1, pageSize: 10 }
*/
static getProjects(params) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
try {
const { pagination, sort, filters } = params;
// Build base query with aggregations
let queryBuilder = Run_1.RunTable.createQueryBuilder('run')
.select('run.project', 'project')
.addSelect('SUM(CASE WHEN run.status = :runningStatus THEN 1 ELSE 0 END)', 'running')
.addSelect('SUM(CASE WHEN run.status = :pendingStatus THEN 1 ELSE 0 END)', 'pending')
.addSelect('SUM(CASE WHEN run.status = :doneStatus THEN 1 ELSE 0 END)', 'finished')
.addSelect('COUNT(*)', 'total')
.addSelect('MIN(run.timestamp)', 'createdAt')
.groupBy('run.project')
.setParameters({
runningStatus: src_1.Status.RUNNING,
pendingStatus: src_1.Status.PENDING,
doneStatus: src_1.Status.DONE,
});
// Apply filters using HAVING (since we're using GROUP BY)
if (filters === null || filters === void 0 ? void 0 : filters.project) {
const filterValue = typeof filters.project === 'object' &&
filters.project !== null &&
'value' in filters.project
? filters.project.value
: String(filters.project);
if (filterValue) {
queryBuilder = queryBuilder.having('run.project LIKE :projectFilter', { projectFilter: `%${filterValue}%` });
}
}
// Apply sorting
const sortField = (sort === null || sort === void 0 ? void 0 : sort.field) || 'createdAt';
const sortOrder = ((_a = sort === null || sort === void 0 ? void 0 : sort.order) === null || _a === void 0 ? void 0 : _a.toUpperCase()) === 'ASC' ? 'ASC' : 'DESC';
// For aggregated fields, we need to use the alias in quotes for SQLite
switch (sortField) {
case 'project':
queryBuilder.orderBy('run.project', sortOrder);
break;
case 'running':
queryBuilder.orderBy('SUM(CASE WHEN run.status = :runningStatus THEN 1 ELSE 0 END)', sortOrder);
break;
case 'pending':
queryBuilder.orderBy('SUM(CASE WHEN run.status = :pendingStatus THEN 1 ELSE 0 END)', sortOrder);
break;
case 'finished':
queryBuilder.orderBy('SUM(CASE WHEN run.status = :doneStatus THEN 1 ELSE 0 END)', sortOrder);
break;
case 'total':
queryBuilder.orderBy('COUNT(*)', sortOrder);
break;
case 'createdAt':
queryBuilder.orderBy('MIN(run.timestamp)', sortOrder);
break;
default:
queryBuilder.orderBy('MIN(run.timestamp)', 'DESC');
}
// Get total count (before pagination)
const countQuery = queryBuilder.clone();
const totalResult = yield countQuery.getRawMany();
const total = totalResult.length;
// Apply pagination
const skip = (pagination.page - 1) * pagination.pageSize;
queryBuilder.limit(pagination.pageSize).offset(skip);
// Execute query
const result = yield queryBuilder.getRawMany();
// Map results to ProjectData type
const list = result.map((row) => ({
project: row.project,
running: Number(row.running) || 0,
pending: Number(row.pending) || 0,
finished: Number(row.finished) || 0,
total: Number(row.total) || 0,
createdAt: row.createdAt,
}));
return {
list,
total,
page: pagination.page,
pageSize: pagination.pageSize,
};
}
catch (error) {
console.error('Error in getProjects:', error);
throw error;
}
});
}
static getAllProjects() {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield Run_1.RunTable.createQueryBuilder('run')
.select('DISTINCT run.project', 'project')
.addSelect((qb) => qb
.select('COUNT(*)')
.from(Run_1.RunTable, 'r')
.where('r.project = run.project')
.andWhere('r.status = :running', {
running: src_1.Status.RUNNING,
}), 'running')
.addSelect((qb) => qb
.select('COUNT(*)')
.from(Run_1.RunTable, 'r')
.where('r.project = run.project')
.andWhere('r.status = :pending', {
pending: src_1.Status.PENDING,
}), 'pending')
.addSelect((qb) => qb
.select('COUNT(*)')
.from(Run_1.RunTable, 'r')
.where('r.project = run.project')
.andWhere('r.status = :finished', {
finished: src_1.Status.DONE,
}), 'finished')
.addSelect((qb) => qb
.select('MIN(r.timestamp)')
.from(Run_1.RunTable, 'r')
.where('r.project = run.project'), 'createdAt')
.groupBy('run.project')
.getRawMany();
return result.map((row) => ({
project: row.project,
running: parseInt(row.running),
pending: parseInt(row.pending),
finished: parseInt(row.finished),
total: parseInt(row.running) +
parseInt(row.pending) +
parseInt(row.finished),
createdAt: row.createdAt,
}));
}
catch (error) {
console.error(error);
throw error;
}
});
}
/*
* Get all runs for a project
*/
static getAllProjectRuns(project) {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield Run_1.RunTable.find({
where: { project },
order: { timestamp: 'DESC' },
});
return result.map((row) => ({
id: row.id,
project: row.project,
name: row.name,
timestamp: row.timestamp,
run_dir: row.run_dir,
pid: row.pid,
status: row.status,
}));
}
catch (error) {
console.error(error);
throw error;
}
});
}
static getRunData(runId) {
return __awaiter(this, void 0, void 0, function* () {
try {
const result = yield Run_1.RunTable.findOne({
where: { id: runId },
relations: ['replies', 'replies.messages', 'inputRequests'],
});
const spans = yield Trace_1.SpanDao.getSpansByConversationId(runId);
if (result) {
return {
runData: {
id: result.id,
project: result.project,
name: result.name,
timestamp: result.timestamp,
run_dir: result.run_dir,
pid: result.pid,
status: result.status,
},
inputRequests: result.inputRequests.map((row) => ({
requestId: row.requestId,
agentId: row.agentId,
agentName: row.agentName,
structuredInput: row.structuredInput,
})),
replies: result.replies.map((row) => ({
replyId: row.replyId,
replyRole: row.replyRole,
replyName: row.replyName,
createdAt: row.createdAt,
finishedAt: row.finishedAt,
messages: row.messages.map((msg) => ({
id: msg.id,
name: msg.msg.name,
role: msg.msg.role,
content: msg.msg.content,
timestamp: msg.msg.timestamp,
metadata: msg.msg.metadata,
speech: msg.speech,
})),
})),
spans: spans,
};
}
else {
throw new Error(`Run with id ${runId} not found`);
}
}
catch (error) {
console.error(error);
throw error;
}
});
}
static changeRunStatus(runId, newStatus) {
return __awaiter(this, void 0, void 0, function* () {
try {
const run = yield Run_1.RunTable.findOne({ where: { id: runId } });
if (run) {
run.status = newStatus;
yield run.save();
}
else {
throw new Error(`Run with id ${runId} not found`);
}
}
catch (error) {
console.error(error);
throw error;
}
});
}
static updateRunStatusAtBeginning() {
return __awaiter(this, void 0, void 0, function* () {
try {
const runs = yield Run_1.RunTable.find({
where: [{ status: src_1.Status.RUNNING }, { status: src_1.Status.PENDING }],
});
for (const run of runs) {
const processExists = yield (0, utils_1.checkProcessByPid)(run.pid);
if (!processExists) {
run.status = src_1.Status.DONE;
yield run.save();
}
}
}
catch (error) {
console.error(error);
throw error;
}
});
}
static getRunViewData() {
return __awaiter(this, void 0, void 0, function* () {
// Get run view data
const runViewData = yield RunView_1.RunView.find();
// Search four projects that are updated most recently
const recentProjects = yield Run_1.RunTable.createQueryBuilder('run')
.select('run.project', 'project')
.addSelect('MAX(run.timestamp)', 'lastUpdateTime')
.addSelect('COUNT(*)', 'runCount')
// 按项目分组
.groupBy('run.project')
// 按最后更新时间降序排序
.orderBy('lastUpdateTime', 'DESC')
// 限制返回4个结果
.limit(4)
.getRawMany();
return Object.assign(Object.assign({}, runViewData[0]), { recentProjects: recentProjects.map((project) => ({
name: project.project,
lastUpdateTime: project.lastUpdateTime,
runCount: parseInt(project.runCount),
})) });
});
}
static deleteRuns(runIds) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (runIds.length > 0) {
yield Trace_1.SpanDao.deleteSpansByConversationIds(runIds);
}
const conditions = {
id: (0, typeorm_1.In)(runIds),
};
const result = yield Run_1.RunTable.delete(conditions);
return result.affected;
}
catch (error) {
console.error('Error deleting runs:', error);
throw error;
}
});
}
static deleteProjects(projects) {
return __awaiter(this, void 0, void 0, function* () {
try {
const runsToDelete = yield Run_1.RunTable.find({
where: { project: (0, typeorm_1.In)(projects) },
select: ['id'],
});
const runIds = runsToDelete.map((run) => run.id);
if (runIds.length > 0) {
yield Trace_1.SpanDao.deleteSpansByConversationIds(runIds);
}
const conditions = {
project: (0, typeorm_1.In)(projects),
};
const result = yield Run_1.RunTable.delete(conditions);
return result.affected;
}
catch (error) {
console.error('Error deleting projects:', error);
throw error;
}
});
}
}
exports.RunDao = RunDao;