xworkmate-app/lib/web/web_workspace_controllers.dart
2026-03-28 22:30:44 +08:00

168 lines
5.2 KiB
Dart

import '../runtime/runtime_models.dart';
class WebTasksController {
List<DerivedTaskItem> queueInternal = const <DerivedTaskItem>[];
List<DerivedTaskItem> runningInternal = const <DerivedTaskItem>[];
List<DerivedTaskItem> historyInternal = const <DerivedTaskItem>[];
List<DerivedTaskItem> failedInternal = const <DerivedTaskItem>[];
List<DerivedTaskItem> scheduledInternal = const <DerivedTaskItem>[];
List<DerivedTaskItem> get queue => queueInternal;
List<DerivedTaskItem> get running => runningInternal;
List<DerivedTaskItem> get history => historyInternal;
List<DerivedTaskItem> get failed => failedInternal;
List<DerivedTaskItem> get scheduled => scheduledInternal;
int get totalCount =>
queueInternal.length +
runningInternal.length +
historyInternal.length +
failedInternal.length;
void recompute({
required List<TaskThread> threads,
required List<GatewayCronJobSummary> cronJobs,
required String currentSessionKey,
required Set<String> pendingSessionKeys,
}) {
final sorted = threads.toList(growable: false)
..sort(
(left, right) =>
(right.updatedAtMs ?? 0).compareTo(left.updatedAtMs ?? 0),
);
final queue = <DerivedTaskItem>[];
final running = <DerivedTaskItem>[];
final history = <DerivedTaskItem>[];
final failed = <DerivedTaskItem>[];
for (final thread in sorted) {
final item = DerivedTaskItem(
id: thread.sessionKey,
title: thread.title.trim().isEmpty ? 'Untitled task' : thread.title,
owner: 'Assistant',
status: statusForThreadInternal(
thread: thread,
currentSessionKey: currentSessionKey,
pendingSessionKeys: pendingSessionKeys,
),
surface: surfaceForTargetInternal(thread.executionTarget),
startedAtLabel: timeLabelInternal(thread.updatedAtMs),
durationLabel: durationLabelInternal(thread.updatedAtMs),
summary: summaryForThreadInternal(thread),
sessionKey: thread.sessionKey,
);
switch (item.status) {
case 'Running':
running.add(item);
case 'Failed':
failed.add(item);
case 'Queued':
queue.add(item);
default:
history.add(item);
}
}
queueInternal = queue;
runningInternal = running;
historyInternal = history;
failedInternal = failed;
scheduledInternal = cronJobs
.map(
(job) => DerivedTaskItem(
id: job.id,
title: job.name,
owner: job.agentId?.trim().isNotEmpty == true
? job.agentId!
: 'Cron',
status: job.enabled ? 'Scheduled' : 'Disabled',
surface: 'Cron',
startedAtLabel: timeLabelInternal(job.nextRunAtMs?.toDouble()),
durationLabel: job.scheduleLabel,
summary:
job.description ??
job.lastError ??
job.lastStatus ??
'Scheduled automation',
sessionKey: 'cron:${job.id}',
),
)
.toList(growable: false);
}
String statusForThreadInternal({
required TaskThread thread,
required String currentSessionKey,
required Set<String> pendingSessionKeys,
}) {
final messages = thread.messages;
if (pendingSessionKeys.contains(thread.sessionKey) ||
thread.sessionKey == currentSessionKey &&
messages.any((item) => item.pending)) {
return 'Running';
}
if (messages.any((item) => item.error)) {
return 'Failed';
}
if (messages.isEmpty) {
return 'Queued';
}
return 'Open';
}
String surfaceForTargetInternal(AssistantExecutionTarget? target) {
return switch (target) {
AssistantExecutionTarget.local => 'Local Gateway',
AssistantExecutionTarget.remote => 'Remote Gateway',
_ => 'Single Agent',
};
}
String summaryForThreadInternal(TaskThread thread) {
final latest = thread.messages.isEmpty ? null : thread.messages.last;
final text = latest?.text.trim() ?? '';
if (text.isNotEmpty) {
return text;
}
if (thread.importedSkills.isNotEmpty) {
return 'Skills: ${thread.importedSkills.length}';
}
return 'No activity yet';
}
String timeLabelInternal(double? timestampMs) {
if (timestampMs == null) {
return 'Unknown';
}
final date = DateTime.fromMillisecondsSinceEpoch(timestampMs.toInt());
return '${date.month}/${date.day} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
}
String durationLabelInternal(double? timestampMs) {
if (timestampMs == null) {
return 'n/a';
}
final delta = DateTime.now().difference(
DateTime.fromMillisecondsSinceEpoch(timestampMs.toInt()),
);
if (delta.inMinutes < 1) {
return 'just now';
}
if (delta.inHours < 1) {
return '${delta.inMinutes}m ago';
}
if (delta.inDays < 1) {
return '${delta.inHours}h ago';
}
return '${delta.inDays}d ago';
}
}
class WebSkillsController {
WebSkillsController(this.onRefreshInternal);
final Future<void> Function(String? agentId) onRefreshInternal;
Future<void> refresh({String? agentId}) {
return onRefreshInternal(agentId);
}
}