xworkmate-app/lib/web/web_workspace_controllers.dart
2026-03-24 20:48:59 +08:00

164 lines
5.0 KiB
Dart

import '../runtime/runtime_models.dart';
class WebTasksController {
List<DerivedTaskItem> _queue = const <DerivedTaskItem>[];
List<DerivedTaskItem> _running = const <DerivedTaskItem>[];
List<DerivedTaskItem> _history = const <DerivedTaskItem>[];
List<DerivedTaskItem> _failed = const <DerivedTaskItem>[];
List<DerivedTaskItem> _scheduled = const <DerivedTaskItem>[];
List<DerivedTaskItem> get queue => _queue;
List<DerivedTaskItem> get running => _running;
List<DerivedTaskItem> get history => _history;
List<DerivedTaskItem> get failed => _failed;
List<DerivedTaskItem> get scheduled => _scheduled;
int get totalCount =>
_queue.length + _running.length + _history.length + _failed.length;
void recompute({
required List<AssistantThreadRecord> 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: _statusForThread(
thread: thread,
currentSessionKey: currentSessionKey,
pendingSessionKeys: pendingSessionKeys,
),
surface: _surfaceForTarget(thread.executionTarget),
startedAtLabel: _timeLabel(thread.updatedAtMs),
durationLabel: _durationLabel(thread.updatedAtMs),
summary: _summaryForThread(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);
}
}
_queue = queue;
_running = running;
_history = history;
_failed = failed;
_scheduled = 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: _timeLabel(job.nextRunAtMs?.toDouble()),
durationLabel: job.scheduleLabel,
summary:
job.description ??
job.lastError ??
job.lastStatus ??
'Scheduled automation',
sessionKey: 'cron:${job.id}',
),
)
.toList(growable: false);
}
String _statusForThread({
required AssistantThreadRecord 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 _surfaceForTarget(AssistantExecutionTarget? target) {
return switch (target) {
AssistantExecutionTarget.local => 'Local Gateway',
AssistantExecutionTarget.remote => 'Remote Gateway',
_ => 'Single Agent',
};
}
String _summaryForThread(AssistantThreadRecord 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 _timeLabel(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 _durationLabel(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._onRefresh);
final Future<void> Function(String? agentId) _onRefresh;
Future<void> refresh({String? agentId}) {
return _onRefresh(agentId);
}
}