fix: sync bridge artifacts into sidebar
This commit is contained in:
parent
af01142441
commit
be24832f80
@ -327,6 +327,12 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
'';
|
||||
}
|
||||
|
||||
double? assistantArtifactSyncAtMsForSession(String sessionKey) {
|
||||
return taskThreadForSessionInternal(
|
||||
normalizedAssistantSessionKeyInternal(sessionKey),
|
||||
)?.lastArtifactSyncAtMs;
|
||||
}
|
||||
|
||||
Future<AssistantArtifactSnapshot> loadAssistantArtifactSnapshot({
|
||||
String? sessionKey,
|
||||
}) {
|
||||
|
||||
@ -304,6 +304,10 @@ extension AssistantPageStateClosureInternal on AssistantPageStateInternal {
|
||||
workspaceKind: controller.assistantWorkspaceKindForSession(
|
||||
controller.currentSessionKey,
|
||||
),
|
||||
artifactSyncAtMs: controller
|
||||
.assistantArtifactSyncAtMsForSession(
|
||||
controller.currentSessionKey,
|
||||
),
|
||||
onCollapse: () {
|
||||
setState(() {
|
||||
artifactPaneCollapsedInternal = true;
|
||||
|
||||
@ -420,7 +420,11 @@ class GoTaskServiceArtifact {
|
||||
}
|
||||
|
||||
return GoTaskServiceArtifact(
|
||||
relativePath: json['relativePath']?.toString().trim() ?? '',
|
||||
relativePath:
|
||||
json['relativePath']?.toString().trim() ??
|
||||
json['path']?.toString().trim() ??
|
||||
json['name']?.toString().trim() ??
|
||||
'',
|
||||
label: json['label']?.toString().trim() ?? '',
|
||||
contentType: json['contentType']?.toString().trim() ?? '',
|
||||
encoding: json['encoding']?.toString().trim() ?? '',
|
||||
@ -544,17 +548,26 @@ class GoTaskServiceResult {
|
||||
}
|
||||
|
||||
Object? _firstGoTaskArtifactList(Map<String, dynamic> result) {
|
||||
final artifacts = <Object?>[];
|
||||
for (final candidate in <Object?>[
|
||||
result['artifacts'],
|
||||
result['files'],
|
||||
result['attachments'],
|
||||
_castMap(result['payload'])['artifacts'],
|
||||
_castMap(result['payload'])['files'],
|
||||
_castMap(result['payload'])['attachments'],
|
||||
_castMap(result['result'])['artifacts'],
|
||||
_castMap(result['result'])['files'],
|
||||
_castMap(result['result'])['attachments'],
|
||||
_castMap(result['data'])['artifacts'],
|
||||
_castMap(result['data'])['files'],
|
||||
_castMap(result['data'])['attachments'],
|
||||
]) {
|
||||
if (candidate is List) {
|
||||
return candidate;
|
||||
artifacts.addAll(candidate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return artifacts.isEmpty ? null : artifacts;
|
||||
}
|
||||
|
||||
String? goTaskServiceGatewayEntryState({
|
||||
|
||||
@ -29,6 +29,7 @@ class AssistantArtifactSidebar extends StatefulWidget {
|
||||
required this.threadTitle,
|
||||
required this.workspacePath,
|
||||
required this.workspaceKind,
|
||||
required this.artifactSyncAtMs,
|
||||
required this.onCollapse,
|
||||
required this.loadSnapshot,
|
||||
required this.loadPreview,
|
||||
@ -39,6 +40,7 @@ class AssistantArtifactSidebar extends StatefulWidget {
|
||||
final String threadTitle;
|
||||
final String workspacePath;
|
||||
final WorkspaceRefKind workspaceKind;
|
||||
final double? artifactSyncAtMs;
|
||||
final VoidCallback onCollapse;
|
||||
final AssistantArtifactSnapshotLoader loadSnapshot;
|
||||
final AssistantArtifactPreviewLoader loadPreview;
|
||||
@ -69,7 +71,8 @@ class _AssistantArtifactSidebarState extends State<AssistantArtifactSidebar> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.sessionKey != widget.sessionKey ||
|
||||
oldWidget.workspacePath != widget.workspacePath ||
|
||||
oldWidget.workspaceKind != widget.workspaceKind) {
|
||||
oldWidget.workspaceKind != widget.workspaceKind ||
|
||||
oldWidget.artifactSyncAtMs != widget.artifactSyncAtMs) {
|
||||
_activeTab = AssistantArtifactSidebarTab.files;
|
||||
_selectedEntry = null;
|
||||
_preview = const AssistantArtifactPreview.empty();
|
||||
@ -537,10 +540,7 @@ class AssistantArtifactSidebarRevealButton extends StatelessWidget {
|
||||
side: BorderSide.none,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.keyboard_double_arrow_left_rounded,
|
||||
size: 20,
|
||||
),
|
||||
icon: const Icon(Icons.keyboard_double_arrow_left_rounded, size: 20),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
73
test/features/assistant/assistant_artifact_sidebar_test.dart
Normal file
73
test/features/assistant/assistant_artifact_sidebar_test.dart
Normal file
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/runtime/assistant_artifacts.dart';
|
||||
import 'package:xworkmate/runtime/runtime_models.dart';
|
||||
import 'package:xworkmate/theme/app_theme.dart';
|
||||
import 'package:xworkmate/widgets/assistant_artifact_sidebar.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('refreshes snapshot when artifact sync timestamp changes', (
|
||||
tester,
|
||||
) async {
|
||||
var loadCount = 0;
|
||||
Future<AssistantArtifactSnapshot> loadSnapshot() async {
|
||||
loadCount += 1;
|
||||
return AssistantArtifactSnapshot(
|
||||
workspacePath: '/tmp/thread',
|
||||
workspaceKind: WorkspaceRefKind.localPath,
|
||||
fileEntries: <AssistantArtifactEntry>[
|
||||
AssistantArtifactEntry(
|
||||
id: 'entry-$loadCount',
|
||||
label: 'artifact-$loadCount.txt',
|
||||
relativePath: 'artifact-$loadCount.txt',
|
||||
kind: AssistantArtifactEntryKind.file,
|
||||
mimeType: 'text/plain',
|
||||
previewable: true,
|
||||
workspacePath: '/tmp/thread',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(artifactSyncAtMs: 1, loadSnapshot: loadSnapshot),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(loadCount, 1);
|
||||
expect(find.text('artifact-1.txt'), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(artifactSyncAtMs: 2, loadSnapshot: loadSnapshot),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(loadCount, 2);
|
||||
expect(find.text('artifact-2.txt'), findsAtLeastNWidgets(1));
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildTestApp({
|
||||
required double artifactSyncAtMs,
|
||||
required Future<AssistantArtifactSnapshot> Function() loadSnapshot,
|
||||
}) {
|
||||
return MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Material(
|
||||
child: SizedBox(
|
||||
width: 460,
|
||||
height: 640,
|
||||
child: AssistantArtifactSidebar(
|
||||
sessionKey: 'session-1',
|
||||
threadTitle: 'Thread',
|
||||
workspacePath: '/tmp/thread',
|
||||
workspaceKind: WorkspaceRefKind.localPath,
|
||||
artifactSyncAtMs: artifactSyncAtMs,
|
||||
onCollapse: () {},
|
||||
loadSnapshot: loadSnapshot,
|
||||
loadPreview: (_) async => const AssistantArtifactPreview.empty(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -162,6 +162,49 @@ void main() {
|
||||
expect(result.artifacts.single.relativePath, 'hello.txt');
|
||||
expect(result.artifacts.single.content, 'nested artifact body');
|
||||
});
|
||||
|
||||
test('uses bridge files and attachments aliases as artifacts', () {
|
||||
final result = goTaskServiceResultFromAcpResponse(<String, dynamic>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': 'request-id',
|
||||
'result': <String, dynamic>{
|
||||
'success': true,
|
||||
'message': 'created files',
|
||||
'payload': <String, dynamic>{
|
||||
'files': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'path': 'reports/summary.pdf',
|
||||
'downloadUrl':
|
||||
'https://xworkmate-bridge.svc.plus/artifacts/summary.pdf',
|
||||
'contentType': 'application/pdf',
|
||||
},
|
||||
],
|
||||
},
|
||||
'data': <String, dynamic>{
|
||||
'attachments': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'name': 'deck.pptx',
|
||||
'content': 'pptx-body',
|
||||
'contentType':
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}, route: GoTaskServiceRoute.externalAcpSingle);
|
||||
|
||||
expect(result.message, 'created files');
|
||||
expect(
|
||||
result.artifacts.map((item) => item.relativePath),
|
||||
containsAll(<String>['reports/summary.pdf', 'deck.pptx']),
|
||||
);
|
||||
expect(
|
||||
result.artifacts
|
||||
.singleWhere((item) => item.relativePath == 'reports/summary.pdf')
|
||||
.downloadUrl,
|
||||
'https://xworkmate-bridge.svc.plus/artifacts/summary.pdf',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('GatewayAcpClient authorization', () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user