Compare commits
154 Commits
codex/clie
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91f68576dd | ||
|
|
f84dc1d44e | ||
|
|
d0b7223b53 | ||
|
|
3a41eb40d0 | ||
|
|
fd06f3bce3 | ||
|
|
de33710423 | ||
|
|
87b440e93f | ||
|
|
fd3b149172 | ||
|
|
ff7576f33d | ||
|
|
053a321ec1 | ||
|
|
6ad88cb6c4 | ||
|
|
e57971488c | ||
|
|
642521035b | ||
|
|
b64f8e35c3 | ||
|
|
a62b749ef2 | ||
|
|
cc55fd819f | ||
|
|
39371861f3 | ||
|
|
c01c699410 | ||
|
|
89eec8afc1 | ||
|
|
8b651e4ae1 | ||
|
|
dd6c10f44b | ||
|
|
797ffeee82 | ||
|
|
4fdc95da75 | ||
|
|
b9f49d2c0e | ||
|
|
7ea963dc9a | ||
|
|
5ab6bc0fd0 | ||
|
|
c9d8aa3b93 | ||
|
|
95838dba04 | ||
|
|
f08b0c39d9 | ||
|
|
de501c0252 | ||
|
|
b6b0a9dcb3 | ||
|
|
d5025c0ebc | ||
|
|
d86f82a4c5 | ||
|
|
b0f0b95c2a | ||
|
|
1daf58a94c | ||
|
|
479e2e209d | ||
|
|
ca746366f6 | ||
|
|
8ed4e0ce3b | ||
|
|
bd7d7b4913 | ||
|
|
26fbf51fc8 | ||
|
|
8a5d5d6474 | ||
|
|
b3f21adecb | ||
|
|
be901ee112 | ||
|
|
48ac9bdf19 | ||
|
|
1a8e0fb51f | ||
|
|
871bd715c4 | ||
|
|
6323987a0d | ||
|
|
81d2797035 | ||
|
|
fffa0ebe50 | ||
|
|
9011b91dba | ||
|
|
3a159e2f5a | ||
|
|
e473b5c147 | ||
|
|
e57c94c19d | ||
|
|
84d34236e8 | ||
|
|
245a210b25 | ||
|
|
8278fdc81c | ||
|
|
4c862ef4b7 | ||
|
|
d292c34579 | ||
|
|
43418d0453 | ||
|
|
e9c76c59ca | ||
|
|
8ab3d73fb1 | ||
|
|
751ce45619 | ||
|
|
c400367d22 | ||
|
|
6f2ccdf783 | ||
|
|
909f11dae5 | ||
|
|
a6e0246418 | ||
|
|
d679639175 | ||
|
|
3ac350eb27 | ||
|
|
5b78189c9c | ||
|
|
1271e4967b | ||
|
|
5b2f14123e | ||
|
|
13e48df63d | ||
|
|
4f489f402a | ||
|
|
914ed81f19 | ||
|
|
d12db03216 | ||
|
|
047651e404 | ||
|
|
ed2f19d257 | ||
|
|
362f397e37 | ||
|
|
cc36d8dcaf | ||
|
|
91540dec0c | ||
|
|
0d2008b35c | ||
|
|
ba806f779e | ||
|
|
d1ae9421c4 | ||
|
|
755a8f041d | ||
|
|
23c3577ef4 | ||
|
|
dc8cd09535 | ||
|
|
5160a73254 | ||
|
|
4d8b8e4ad5 | ||
|
|
9ad7881378 | ||
|
|
702ea05d4d | ||
|
|
fd130f1f60 | ||
|
|
ddd39396f7 | ||
|
|
ef8252e2dd | ||
|
|
5ebc0a640b | ||
|
|
f35d945d9c | ||
|
|
e4715bbabb | ||
|
|
726369f5a9 | ||
|
|
2f9af0b173 | ||
|
|
eb8ea9974f | ||
|
|
16eb8c691e | ||
|
|
48e5830b9e | ||
|
|
451cda7aa6 | ||
|
|
913c6da8f3 | ||
|
|
38e5bdbc04 | ||
|
|
e4652ace1c | ||
|
|
820b1b6f13 | ||
|
|
76a82f6203 | ||
|
|
3bab6af63c | ||
|
|
ff25c2fef7 | ||
|
|
710bf72353 | ||
|
|
8dc9ce73b9 | ||
|
|
700f6a40b8 | ||
|
|
f4af77cb2b | ||
|
|
ee51fcdbeb | ||
|
|
0f0bffe695 | ||
|
|
e37024bdbe | ||
|
|
5e76828afe | ||
|
|
6deea42de1 | ||
|
|
09d65701a3 | ||
|
|
fa5522e32e | ||
|
|
757858a9aa | ||
|
|
aee8af0877 | ||
|
|
b07f133ed6 | ||
|
|
8ff1c57b83 | ||
|
|
bf25bc5a47 | ||
|
|
029ca6e7f6 | ||
|
|
7eb1498412 | ||
|
|
2bf9192fc5 | ||
|
|
df2ab51e70 | ||
|
|
f8564b631d | ||
|
|
bdcb5c6c14 | ||
|
|
6b033da313 | ||
|
|
38ece7e3d3 | ||
|
|
cef3fe4a43 | ||
|
|
646901b5fa | ||
|
|
508406ff10 | ||
|
|
0672e82fc7 | ||
|
|
ac1a6fd0e6 | ||
|
|
e2a91d88e8 | ||
|
|
dc14418963 | ||
|
|
17475bf0d7 | ||
|
|
b7aecad092 | ||
|
|
35d2cb5ea5 | ||
|
|
ab1d022905 | ||
|
|
b9132c421d | ||
|
|
f8048724c4 | ||
|
|
512c7ab095 | ||
|
|
2373e0b5f3 | ||
|
|
40151c3902 | ||
|
|
15a3285ed5 | ||
|
|
31b4b5a281 | ||
|
|
2eb9a99da3 | ||
|
|
c6fdff20e8 | ||
|
|
2c3f2c8c54 |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 403 KiB |
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name:
|
||||
description:
|
||||
license:
|
||||
---
|
||||
|
||||
# Processing Guide
|
||||
|
||||
## Overview
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Skill template",
|
||||
"config": {}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Helper script for this skill
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Process script placeholder")
|
||||
@@ -0,0 +1,3 @@
|
||||
# API Documentation
|
||||
|
||||
Add interface descriptions related to skills here.
|
||||
@@ -104,6 +104,10 @@
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-bill-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.xspaceagi.agent.core.adapter.application;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionResultDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminRecordRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminRecordResultDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBillingEstimateDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePlanBusinessViewDetailDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePolicySnapshotPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface DigitalEmployeeSyncApplicationService {
|
||||
DigitalEmployeeSyncResponseDto syncOutbox(DigitalEmployeeSyncRequestDto request);
|
||||
|
||||
DigitalEmployeeSyncRecordPageDto queryRecords(
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
String outboxId,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
);
|
||||
|
||||
DigitalEmployeeBusinessViewPageDto queryBusinessViews(
|
||||
String entityType,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityId,
|
||||
String status,
|
||||
String syncStatus,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
);
|
||||
|
||||
DigitalEmployeePlanBusinessViewDetailDto queryPlanBusinessViewDetail(
|
||||
String planId,
|
||||
String clientKey,
|
||||
String deviceId
|
||||
);
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto queryAdminWorkbench(
|
||||
String view,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
String status,
|
||||
String kind,
|
||||
String category,
|
||||
String level,
|
||||
String priority,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
);
|
||||
|
||||
DigitalEmployeePolicySnapshotPageDto queryAdminPolicySnapshots(
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String status,
|
||||
String policyKind,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
);
|
||||
|
||||
DigitalEmployeeAdminActionResultDto createAdminAction(DigitalEmployeeAdminActionRequestDto request);
|
||||
|
||||
DigitalEmployeeAdminActionPageDto queryPendingAdminActions(
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
);
|
||||
|
||||
DigitalEmployeeAdminActionResultDto markAdminActionResult(
|
||||
Long actionId,
|
||||
String status,
|
||||
List<String> reasonCodes
|
||||
);
|
||||
|
||||
DigitalEmployeeAdminRecordResultDto recordDailyReportSendResult(DigitalEmployeeAdminRecordRequestDto request);
|
||||
|
||||
DigitalEmployeeBillingEstimateDto queryBillingEstimates(
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo
|
||||
);
|
||||
|
||||
DigitalEmployeeAdminRecordResultDto recordAuditArchive(DigitalEmployeeAdminRecordRequestDto request);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminActionDto {
|
||||
private Long id;
|
||||
@JsonProperty("client_key_masked")
|
||||
private String clientKeyMasked;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
private String action;
|
||||
private String status;
|
||||
@JsonProperty("reason_codes")
|
||||
private List<String> reasonCodes;
|
||||
@JsonProperty("request_payload")
|
||||
private Map<String, Object> requestPayload;
|
||||
@JsonProperty("created_at")
|
||||
private Date createdAt;
|
||||
@JsonProperty("processed_at")
|
||||
private Date processedAt;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminActionPageDto {
|
||||
private Long total;
|
||||
@JsonProperty("page_no")
|
||||
private Long pageNo;
|
||||
@JsonProperty("page_size")
|
||||
private Long pageSize;
|
||||
private List<DigitalEmployeeAdminActionDto> records;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DigitalEmployeeAdminActionRequestDto {
|
||||
@JsonProperty("sync_record_id")
|
||||
private Long syncRecordId;
|
||||
@JsonProperty("client_key")
|
||||
private String clientKey;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
private String action;
|
||||
@JsonProperty("request_payload")
|
||||
private Map<String, Object> requestPayload;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminActionResultDto {
|
||||
private Boolean ok;
|
||||
private String status;
|
||||
@JsonProperty("reason_codes")
|
||||
private List<String> reasonCodes;
|
||||
private DigitalEmployeeAdminActionDto action;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DigitalEmployeeAdminRecordRequestDto {
|
||||
@JsonAlias("sync_record_id")
|
||||
private Long syncRecordId;
|
||||
@JsonAlias("client_key")
|
||||
private String clientKey;
|
||||
@JsonAlias("device_id")
|
||||
private String deviceId;
|
||||
@JsonAlias("entity_type")
|
||||
private String entityType;
|
||||
@JsonAlias("entity_id")
|
||||
private String entityId;
|
||||
private String status;
|
||||
private String endpoint;
|
||||
@JsonAlias("remote_id")
|
||||
private String remoteId;
|
||||
@JsonAlias("archive_location")
|
||||
private String archiveLocation;
|
||||
@JsonAlias("archive_summary")
|
||||
private Map<String, Object> archiveSummary;
|
||||
private String reason;
|
||||
@JsonProperty("request_payload")
|
||||
@JsonAlias("requestPayload")
|
||||
private Map<String, Object> requestPayload;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminRecordResultDto {
|
||||
private Boolean ok;
|
||||
private String status;
|
||||
@JsonProperty("reason_codes")
|
||||
private List<String> reasonCodes;
|
||||
private DigitalEmployeeAdminActionDto record;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminWorkbenchPageDto {
|
||||
private Long total;
|
||||
@JsonProperty("page_no")
|
||||
private Long pageNo;
|
||||
@JsonProperty("page_size")
|
||||
private Long pageSize;
|
||||
private List<DigitalEmployeeAdminWorkbenchRowDto> records;
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeAdminWorkbenchRowDto {
|
||||
private String view;
|
||||
@JsonProperty("sync_record_id")
|
||||
private Long syncRecordId;
|
||||
@JsonProperty("sync_record_url")
|
||||
private String syncRecordUrl;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
private String operation;
|
||||
private String title;
|
||||
private String status;
|
||||
private String kind;
|
||||
private String category;
|
||||
private String level;
|
||||
private String priority;
|
||||
private String summary;
|
||||
@JsonProperty("plan_id")
|
||||
private String planId;
|
||||
@JsonProperty("task_id")
|
||||
private String taskId;
|
||||
@JsonProperty("run_id")
|
||||
private String runId;
|
||||
@JsonProperty("step_id")
|
||||
private String stepId;
|
||||
@JsonProperty("dependency_status")
|
||||
private String dependencyStatus;
|
||||
@JsonProperty("lease_state")
|
||||
private String leaseState;
|
||||
@JsonProperty("lease_source")
|
||||
private String leaseSource;
|
||||
@JsonProperty("dispatch_status")
|
||||
private String dispatchStatus;
|
||||
@JsonProperty("dispatch_policy_key")
|
||||
private String dispatchPolicyKey;
|
||||
@JsonProperty("orchestration_id")
|
||||
private String orchestrationId;
|
||||
@JsonProperty("trigger_source")
|
||||
private String triggerSource;
|
||||
@JsonProperty("approval_id")
|
||||
private String approvalId;
|
||||
@JsonProperty("approval_policy_id")
|
||||
private String approvalPolicyId;
|
||||
@JsonProperty("conflict_policy_id")
|
||||
private String conflictPolicyId;
|
||||
@JsonProperty("decision_guard_status")
|
||||
private String decisionGuardStatus;
|
||||
@JsonProperty("batch_accept_remote_disabled")
|
||||
private Boolean batchAcceptRemoteDisabled;
|
||||
@JsonProperty("auto_decision_disabled")
|
||||
private Boolean autoDecisionDisabled;
|
||||
@JsonProperty("policy_reason")
|
||||
private String policyReason;
|
||||
@JsonProperty("governance_policy_id")
|
||||
private String governancePolicyId;
|
||||
@JsonProperty("policy_guard_status")
|
||||
private String policyGuardStatus;
|
||||
@JsonProperty("hard_interrupt_disabled")
|
||||
private Boolean hardInterruptDisabled;
|
||||
@JsonProperty("artifact_id")
|
||||
private String artifactId;
|
||||
@JsonProperty("artifact_graph_id")
|
||||
private String artifactGraphId;
|
||||
@JsonProperty("artifact_group_id")
|
||||
private String artifactGroupId;
|
||||
@JsonProperty("group_key")
|
||||
private String groupKey;
|
||||
@JsonProperty("related_artifact_ids")
|
||||
private List<String> relatedArtifactIds;
|
||||
@JsonProperty("artifact_count")
|
||||
private Long artifactCount;
|
||||
@JsonProperty("device_count")
|
||||
private Long deviceCount;
|
||||
@JsonProperty("version_count")
|
||||
private Long versionCount;
|
||||
@JsonProperty("latest_artifact_id")
|
||||
private String latestArtifactId;
|
||||
@JsonProperty("version_key")
|
||||
private String versionKey;
|
||||
@JsonProperty("cleanup_request_id")
|
||||
private String cleanupRequestId;
|
||||
@JsonProperty("cleanup_eligible_count")
|
||||
private Long cleanupEligibleCount;
|
||||
@JsonProperty("batch_cleanup_disabled")
|
||||
private Boolean batchCleanupDisabled;
|
||||
@JsonProperty("retention_conflict_status")
|
||||
private String retentionConflictStatus;
|
||||
@JsonProperty("cleanup_conflict_status")
|
||||
private String cleanupConflictStatus;
|
||||
@JsonProperty("remote_delete_disabled")
|
||||
private Boolean remoteDeleteDisabled;
|
||||
@JsonProperty("notification_id")
|
||||
private String notificationId;
|
||||
@JsonProperty("collaboration_thread_id")
|
||||
private String collaborationThreadId;
|
||||
@JsonProperty("subscription_policy_id")
|
||||
private String subscriptionPolicyId;
|
||||
@JsonProperty("subscription_conflict_status")
|
||||
private String subscriptionConflictStatus;
|
||||
@JsonProperty("notification_policy_id")
|
||||
private String notificationPolicyId;
|
||||
@JsonProperty("subscription_status")
|
||||
private String subscriptionStatus;
|
||||
@JsonProperty("desktop_enabled")
|
||||
private Boolean desktopEnabled;
|
||||
@JsonProperty("minimum_level")
|
||||
private String minimumLevel;
|
||||
private String channel;
|
||||
@JsonProperty("delivery_status")
|
||||
private String deliveryStatus;
|
||||
@JsonProperty("delivery_attempt_id")
|
||||
private String deliveryAttemptId;
|
||||
@JsonProperty("send_request_id")
|
||||
private String sendRequestId;
|
||||
@JsonProperty("real_push_disabled")
|
||||
private Boolean realPushDisabled;
|
||||
@JsonProperty("conflict_status")
|
||||
private String conflictStatus;
|
||||
@JsonProperty("report_id")
|
||||
private String reportId;
|
||||
@JsonProperty("daily_report_fact_id")
|
||||
private String dailyReportFactId;
|
||||
@JsonProperty("daily_report_table")
|
||||
private String dailyReportTable;
|
||||
@JsonProperty("daily_report_materialized")
|
||||
private Boolean dailyReportMaterialized;
|
||||
@JsonProperty("daily_report_physical_fact_status")
|
||||
private String dailyReportPhysicalFactStatus;
|
||||
@JsonProperty("send_record_id")
|
||||
private String sendRecordId;
|
||||
@JsonProperty("send_channel")
|
||||
private String sendChannel;
|
||||
@JsonProperty("send_request_status")
|
||||
private String sendRequestStatus;
|
||||
@JsonProperty("send_record_status")
|
||||
private String sendRecordStatus;
|
||||
@JsonProperty("formal_daily_report_page_disabled")
|
||||
private Boolean formalDailyReportPageDisabled;
|
||||
@JsonProperty("external_send_disabled")
|
||||
private Boolean externalSendDisabled;
|
||||
@JsonProperty("third_party_channel_disabled")
|
||||
private Boolean thirdPartyChannelDisabled;
|
||||
@JsonProperty("approval_decision_status")
|
||||
private String approvalDecisionStatus;
|
||||
@JsonProperty("advanced_query_supported")
|
||||
private Boolean advancedQuerySupported;
|
||||
@JsonProperty("aggregation_scope")
|
||||
private String aggregationScope;
|
||||
@JsonProperty("materialized_source")
|
||||
private String materializedSource;
|
||||
@JsonProperty("daily_report_approval_id")
|
||||
private String dailyReportApprovalId;
|
||||
@JsonProperty("daily_report_approval_action")
|
||||
private String dailyReportApprovalAction;
|
||||
@JsonProperty("daily_report_approval_actor")
|
||||
private String dailyReportApprovalActor;
|
||||
@JsonProperty("daily_report_approval_reason")
|
||||
private String dailyReportApprovalReason;
|
||||
@JsonProperty("daily_report_approval_recorded_at")
|
||||
private String dailyReportApprovalRecordedAt;
|
||||
@JsonProperty("route_decision_id")
|
||||
private String routeDecisionId;
|
||||
@JsonProperty("service_id")
|
||||
private String serviceId;
|
||||
@JsonProperty("service_lease_id")
|
||||
private String serviceLeaseId;
|
||||
@JsonProperty("lease_holder")
|
||||
private String leaseHolder;
|
||||
@JsonProperty("lease_expires_at")
|
||||
private String leaseExpiresAt;
|
||||
@JsonProperty("restart_record_id")
|
||||
private String restartRecordId;
|
||||
@JsonProperty("restart_count")
|
||||
private Long restartCount;
|
||||
@JsonProperty("last_restart_at")
|
||||
private String lastRestartAt;
|
||||
@JsonProperty("recovery_policy_id")
|
||||
private String recoveryPolicyId;
|
||||
@JsonProperty("auto_recovery_status")
|
||||
private String autoRecoveryStatus;
|
||||
@JsonProperty("service_start_disabled")
|
||||
private Boolean serviceStartDisabled;
|
||||
@JsonProperty("service_stop_disabled")
|
||||
private Boolean serviceStopDisabled;
|
||||
@JsonProperty("lease_takeover_disabled")
|
||||
private Boolean leaseTakeoverDisabled;
|
||||
@JsonProperty("auto_recovery_disabled")
|
||||
private Boolean autoRecoveryDisabled;
|
||||
@JsonProperty("recovery_action_request_id")
|
||||
private String recoveryActionRequestId;
|
||||
@JsonProperty("start_request_id")
|
||||
private String startRequestId;
|
||||
@JsonProperty("stop_request_id")
|
||||
private String stopRequestId;
|
||||
@JsonProperty("lease_takeover_request_id")
|
||||
private String leaseTakeoverRequestId;
|
||||
@JsonProperty("auto_recovery_request_id")
|
||||
private String autoRecoveryRequestId;
|
||||
@JsonProperty("start_service_disabled")
|
||||
private Boolean startServiceDisabled;
|
||||
@JsonProperty("stop_service_disabled")
|
||||
private Boolean stopServiceDisabled;
|
||||
@JsonProperty("memory_id")
|
||||
private String memoryId;
|
||||
@JsonProperty("memory_key")
|
||||
private String memoryKey;
|
||||
@JsonProperty("governance_status")
|
||||
private String governanceStatus;
|
||||
@JsonProperty("merge_candidate_count")
|
||||
private Long mergeCandidateCount;
|
||||
@JsonProperty("duplicate_memory_ids")
|
||||
private List<String> duplicateMemoryIds;
|
||||
@JsonProperty("latest_memory_id")
|
||||
private String latestMemoryId;
|
||||
@JsonProperty("last_governance_action")
|
||||
private String lastGovernanceAction;
|
||||
@JsonProperty("last_reviewed_at")
|
||||
private String lastReviewedAt;
|
||||
@JsonProperty("memory_policy_id")
|
||||
private String memoryPolicyId;
|
||||
@JsonProperty("memory_policy_status")
|
||||
private String memoryPolicyStatus;
|
||||
@JsonProperty("merge_policy_id")
|
||||
private String mergePolicyId;
|
||||
@JsonProperty("merge_action_disabled")
|
||||
private Boolean mergeActionDisabled;
|
||||
@JsonProperty("remote_memory_policy_disabled")
|
||||
private Boolean remoteMemoryPolicyDisabled;
|
||||
@JsonProperty("skill_id")
|
||||
private String skillId;
|
||||
@JsonProperty("skill_version")
|
||||
private String skillVersion;
|
||||
@JsonProperty("install_status")
|
||||
private String installStatus;
|
||||
@JsonProperty("permission_scope")
|
||||
private String permissionScope;
|
||||
@JsonProperty("policy_source")
|
||||
private String policySource;
|
||||
@JsonProperty("policy_status")
|
||||
private String policyStatus;
|
||||
@JsonProperty("skill_install_request_id")
|
||||
private String skillInstallRequestId;
|
||||
@JsonProperty("skill_upgrade_request_id")
|
||||
private String skillUpgradeRequestId;
|
||||
@JsonProperty("permission_policy_id")
|
||||
private String permissionPolicyId;
|
||||
@JsonProperty("permission_edit_request_id")
|
||||
private String permissionEditRequestId;
|
||||
@JsonProperty("install_action_disabled")
|
||||
private Boolean installActionDisabled;
|
||||
@JsonProperty("upgrade_action_disabled")
|
||||
private Boolean upgradeActionDisabled;
|
||||
@JsonProperty("permission_edit_disabled")
|
||||
private Boolean permissionEditDisabled;
|
||||
@JsonProperty("tool_name")
|
||||
private String toolName;
|
||||
@JsonProperty("server_id")
|
||||
private String serverId;
|
||||
@JsonProperty("audit_archive_id")
|
||||
private String auditArchiveId;
|
||||
@JsonProperty("archive_status")
|
||||
private String archiveStatus;
|
||||
@JsonProperty("archive_location")
|
||||
private String archiveLocation;
|
||||
@JsonProperty("billing_estimate_id")
|
||||
private String billingEstimateId;
|
||||
@JsonProperty("billing_source")
|
||||
private String billingSource;
|
||||
private Boolean estimated;
|
||||
@JsonProperty("diagnostic_id")
|
||||
private String diagnosticId;
|
||||
@JsonProperty("diagnostic_kind")
|
||||
private String diagnosticKind;
|
||||
@JsonProperty("issue_code")
|
||||
private String issueCode;
|
||||
@JsonProperty("repair_action")
|
||||
private String repairAction;
|
||||
@JsonProperty("repair_status")
|
||||
private String repairStatus;
|
||||
@JsonProperty("repair_reason")
|
||||
private String repairReason;
|
||||
@JsonProperty("safe_repair_disabled")
|
||||
private Boolean safeRepairDisabled;
|
||||
@JsonProperty("billing_actual_disabled")
|
||||
private Boolean billingActualDisabled;
|
||||
@JsonProperty("sandbox_archive_file_disabled")
|
||||
private Boolean sandboxArchiveFileDisabled;
|
||||
@JsonProperty("auto_repair_execution_disabled")
|
||||
private Boolean autoRepairExecutionDisabled;
|
||||
@JsonProperty("repair_approval_required")
|
||||
private Boolean repairApprovalRequired;
|
||||
@JsonProperty("recommended_action")
|
||||
private String recommendedAction;
|
||||
@JsonProperty("policy_kind")
|
||||
private String policyKind;
|
||||
@JsonProperty("policy_key")
|
||||
private String policyKey;
|
||||
@JsonProperty("policy_edit_request_id")
|
||||
private String policyEditRequestId;
|
||||
@JsonProperty("policy_edit_status")
|
||||
private String policyEditStatus;
|
||||
@JsonProperty("policy_edit_reason")
|
||||
private String policyEditReason;
|
||||
@JsonProperty("merge_request_id")
|
||||
private String mergeRequestId;
|
||||
@JsonProperty("memory_merge_request_id")
|
||||
private String memoryMergeRequestId;
|
||||
@JsonProperty("remote_memory_policy_request_id")
|
||||
private String remoteMemoryPolicyRequestId;
|
||||
@JsonProperty("merge_recommendation")
|
||||
private String mergeRecommendation;
|
||||
@JsonProperty("remote_policy_edit_disabled")
|
||||
private Boolean remotePolicyEditDisabled;
|
||||
@JsonProperty("auto_merge_disabled")
|
||||
private Boolean autoMergeDisabled;
|
||||
@JsonProperty("approval_policy_edit_disabled")
|
||||
private Boolean approvalPolicyEditDisabled;
|
||||
@JsonProperty("route_policy_edit_disabled")
|
||||
private Boolean routePolicyEditDisabled;
|
||||
@JsonProperty("route_recovery_disabled")
|
||||
private Boolean routeRecoveryDisabled;
|
||||
@JsonProperty("high_risk_execution_disabled")
|
||||
private Boolean highRiskExecutionDisabled;
|
||||
@JsonProperty("schedule_id")
|
||||
private String scheduleId;
|
||||
@JsonProperty("job_id")
|
||||
private String jobId;
|
||||
@JsonProperty("revision_id")
|
||||
private String revisionId;
|
||||
@JsonProperty("revision_fact_id")
|
||||
private String revisionFactId;
|
||||
@JsonProperty("revision_status")
|
||||
private String revisionStatus;
|
||||
@JsonProperty("revision_materialized")
|
||||
private Boolean revisionMaterialized;
|
||||
@JsonProperty("base_revision_id")
|
||||
private String baseRevisionId;
|
||||
@JsonProperty("changed_fields")
|
||||
private List<String> changedFields;
|
||||
@JsonProperty("occurred_at")
|
||||
private String occurredAt;
|
||||
@JsonProperty("synced_at")
|
||||
private Date syncedAt;
|
||||
@JsonProperty("client_key_masked")
|
||||
private String clientKeyMasked;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("business_view")
|
||||
private Map<String, Object> businessView;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeBillingEstimateDto {
|
||||
@JsonProperty("total_tokens")
|
||||
private Long totalTokens;
|
||||
@JsonProperty("input_tokens")
|
||||
private Long inputTokens;
|
||||
@JsonProperty("output_tokens")
|
||||
private Long outputTokens;
|
||||
@JsonProperty("estimated_cost_usd")
|
||||
private BigDecimal estimatedCostUsd;
|
||||
@JsonProperty("request_count")
|
||||
private Long requestCount;
|
||||
private Boolean estimated;
|
||||
@JsonProperty("by_model")
|
||||
private Map<String, ModelEstimate> byModel;
|
||||
@JsonProperty("updated_at")
|
||||
private Date updatedAt;
|
||||
@JsonProperty("source_records")
|
||||
private List<Long> sourceRecords;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class ModelEstimate {
|
||||
@JsonProperty("total_tokens")
|
||||
private Long totalTokens;
|
||||
@JsonProperty("input_tokens")
|
||||
private Long inputTokens;
|
||||
@JsonProperty("output_tokens")
|
||||
private Long outputTokens;
|
||||
@JsonProperty("estimated_cost_usd")
|
||||
private BigDecimal estimatedCostUsd;
|
||||
@JsonProperty("request_count")
|
||||
private Long requestCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeBusinessViewPageDto {
|
||||
private Long total;
|
||||
@JsonProperty("page_no")
|
||||
private Long pageNo;
|
||||
@JsonProperty("page_size")
|
||||
private Long pageSize;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> records;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeBusinessViewRowDto {
|
||||
@JsonProperty("sync_record_id")
|
||||
private Long syncRecordId;
|
||||
@JsonProperty("sync_record_url")
|
||||
private String syncRecordUrl;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
private String operation;
|
||||
private String title;
|
||||
private String status;
|
||||
private String summary;
|
||||
@JsonProperty("plan_id")
|
||||
private String planId;
|
||||
@JsonProperty("task_id")
|
||||
private String taskId;
|
||||
@JsonProperty("run_id")
|
||||
private String runId;
|
||||
private String kind;
|
||||
@JsonProperty("occurred_at")
|
||||
private String occurredAt;
|
||||
@JsonProperty("started_at")
|
||||
private String startedAt;
|
||||
@JsonProperty("finished_at")
|
||||
private String finishedAt;
|
||||
@JsonProperty("duration_ms")
|
||||
private Long durationMs;
|
||||
@JsonProperty("sync_status")
|
||||
private String syncStatus;
|
||||
@JsonProperty("client_key_masked")
|
||||
private String clientKeyMasked;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("synced_at")
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
@JsonProperty("business_view")
|
||||
private Map<String, Object> businessView;
|
||||
private Map<String, Object> payload;
|
||||
@JsonProperty("task_count")
|
||||
private Integer taskCount;
|
||||
@JsonProperty("run_count")
|
||||
private Integer runCount;
|
||||
@JsonProperty("latest_event_at")
|
||||
private String latestEventAt;
|
||||
@JsonProperty("last_event_message")
|
||||
private String lastEventMessage;
|
||||
@JsonProperty("advanced_query_supported")
|
||||
private Boolean advancedQuerySupported;
|
||||
@JsonProperty("aggregation_scope")
|
||||
private String aggregationScope;
|
||||
@JsonProperty("materialized_source")
|
||||
private String materializedSource;
|
||||
private Boolean materialized;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeePlanBusinessViewDetailDto {
|
||||
private DigitalEmployeeBusinessViewRowDto plan;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> tasks;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> runs;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> events;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> artifacts;
|
||||
private List<DigitalEmployeeBusinessViewRowDto> approvals;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeePolicySnapshotPageDto {
|
||||
private Long total;
|
||||
@JsonProperty("page_no")
|
||||
private Long pageNo;
|
||||
@JsonProperty("page_size")
|
||||
private Long pageSize;
|
||||
private List<DigitalEmployeePolicySnapshotRowDto> records;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeePolicySnapshotRowDto {
|
||||
@JsonProperty("policy_kind")
|
||||
private String policyKind;
|
||||
@JsonProperty("policy_key")
|
||||
private String policyKey;
|
||||
private String status;
|
||||
private String summary;
|
||||
@JsonProperty("client_count")
|
||||
private Integer clientCount;
|
||||
@JsonProperty("device_count")
|
||||
private Integer deviceCount;
|
||||
@JsonProperty("conflict_keys")
|
||||
private List<String> conflictKeys;
|
||||
@JsonProperty("latest_synced_at")
|
||||
private Date latestSyncedAt;
|
||||
@JsonProperty("recommended_resolution")
|
||||
private String recommendedResolution;
|
||||
@JsonProperty("route_decision_count")
|
||||
private Integer routeDecisionCount;
|
||||
@JsonProperty("blocked_count")
|
||||
private Integer blockedCount;
|
||||
@JsonProperty("approval_required_count")
|
||||
private Integer approvalRequiredCount;
|
||||
@JsonProperty("missing_context_count")
|
||||
private Integer missingContextCount;
|
||||
@JsonProperty("not_mapped_count")
|
||||
private Integer notMappedCount;
|
||||
@JsonProperty("intervention_conflict_status")
|
||||
private String interventionConflictStatus;
|
||||
@JsonProperty("approval_policy_id")
|
||||
private String approvalPolicyId;
|
||||
@JsonProperty("conflict_policy_id")
|
||||
private String conflictPolicyId;
|
||||
@JsonProperty("decision_guard_status")
|
||||
private String decisionGuardStatus;
|
||||
@JsonProperty("batch_accept_remote_disabled")
|
||||
private Boolean batchAcceptRemoteDisabled;
|
||||
@JsonProperty("auto_decision_disabled")
|
||||
private Boolean autoDecisionDisabled;
|
||||
@JsonProperty("policy_reason")
|
||||
private String policyReason;
|
||||
@JsonProperty("dispatch_policy_key")
|
||||
private String dispatchPolicyKey;
|
||||
@JsonProperty("max_per_sweep")
|
||||
private Long maxPerSweep;
|
||||
@JsonProperty("auto_dispatch_ready_steps")
|
||||
private Boolean autoDispatchReadySteps;
|
||||
@JsonProperty("catch_up_on_startup")
|
||||
private Boolean catchUpOnStartup;
|
||||
@JsonProperty("max_run_history")
|
||||
private Long maxRunHistory;
|
||||
@JsonProperty("sync_record_url")
|
||||
private String syncRecordUrl;
|
||||
@JsonProperty("client_key_masked")
|
||||
private String clientKeyMasked;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("business_view")
|
||||
private Map<String, Object> businessView;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeSyncAckDto {
|
||||
@JsonProperty("outbox_id")
|
||||
private String outboxId;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
@JsonProperty("remote_id")
|
||||
private String remoteId;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DigitalEmployeeSyncItemDto {
|
||||
@JsonAlias("outbox_id")
|
||||
private String outboxId;
|
||||
@JsonAlias("entity_type")
|
||||
private String entityType;
|
||||
@JsonAlias("entity_id")
|
||||
private String entityId;
|
||||
private String operation;
|
||||
@JsonAlias("contract_version")
|
||||
private String contractVersion;
|
||||
@JsonAlias("entity_summary")
|
||||
private Map<String, Object> entitySummary;
|
||||
@JsonAlias("business_view")
|
||||
private Map<String, Object> businessView;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeSyncRecordDto {
|
||||
private Long id;
|
||||
@JsonProperty("client_key")
|
||||
private String clientKey;
|
||||
@JsonProperty("device_id")
|
||||
private String deviceId;
|
||||
@JsonProperty("outbox_id")
|
||||
private String outboxId;
|
||||
@JsonProperty("entity_type")
|
||||
private String entityType;
|
||||
@JsonProperty("entity_id")
|
||||
private String entityId;
|
||||
private String operation;
|
||||
@JsonProperty("contract_version")
|
||||
private String contractVersion;
|
||||
@JsonProperty("entity_summary")
|
||||
private String entitySummary;
|
||||
@JsonProperty("business_view")
|
||||
private String businessView;
|
||||
private String payload;
|
||||
@JsonProperty("synced_at")
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeSyncRecordPageDto {
|
||||
private Long total;
|
||||
@JsonProperty("page_no")
|
||||
private Long pageNo;
|
||||
@JsonProperty("page_size")
|
||||
private Long pageSize;
|
||||
private List<DigitalEmployeeSyncRecordDto> records;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DigitalEmployeeSyncRequestDto {
|
||||
@JsonAlias("client_key")
|
||||
private String clientKey;
|
||||
@JsonAlias("device_id")
|
||||
private String deviceId;
|
||||
private List<DigitalEmployeeSyncItemDto> items;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.xspaceagi.agent.core.adapter.dto.digital;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DigitalEmployeeSyncResponseDto {
|
||||
private List<DigitalEmployeeSyncAckDto> acked;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeAdminAction;
|
||||
|
||||
public interface DigitalEmployeeAdminActionRepository extends IService<DigitalEmployeeAdminAction> {
|
||||
IPage<DigitalEmployeeAdminAction> queryActions(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String status,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
|
||||
public interface DigitalEmployeeArtifactLifecycleRepository extends IService<DigitalEmployeeArtifactLifecycle> {
|
||||
void upsertBySyncRecord(DigitalEmployeeArtifactLifecycle lifecycle);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface DigitalEmployeeBusinessFactRepository extends IService<DigitalEmployeeBusinessFact> {
|
||||
void upsertBySyncRecord(DigitalEmployeeBusinessFact fact);
|
||||
|
||||
IPage<DigitalEmployeeBusinessFact> queryFacts(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
|
||||
public interface DigitalEmployeeDailyReportFactRepository extends IService<DigitalEmployeeDailyReportFact> {
|
||||
void upsertBySyncRecord(DigitalEmployeeDailyReportFact fact);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface DigitalEmployeeSyncRecordRepository extends IService<DigitalEmployeeSyncRecord> {
|
||||
IPage<DigitalEmployeeSyncRecord> queryRecords(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
String outboxId,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
);
|
||||
|
||||
IPage<DigitalEmployeeSyncRecord> queryBusinessRecords(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_admin_action")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeAdminAction {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long userId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String entityType;
|
||||
private String entityId;
|
||||
private String action;
|
||||
private String status;
|
||||
private String reasonCodes;
|
||||
private String requestPayload;
|
||||
private Date processedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_artifact_lifecycle")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeArtifactLifecycle {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String artifactId;
|
||||
private String planId;
|
||||
private String taskId;
|
||||
private String runId;
|
||||
private String status;
|
||||
private String retentionStatus;
|
||||
private String cleanupStatus;
|
||||
private String deletedAt;
|
||||
private String versionKey;
|
||||
private String source;
|
||||
private String businessView;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_business_fact")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeBusinessFact {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long userId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String outboxId;
|
||||
private String entityType;
|
||||
private String entityId;
|
||||
private String operation;
|
||||
private String title;
|
||||
private String status;
|
||||
private String summary;
|
||||
private String planId;
|
||||
private String taskId;
|
||||
private String runId;
|
||||
private String kind;
|
||||
private String category;
|
||||
private String occurredAt;
|
||||
private String startedAt;
|
||||
private String finishedAt;
|
||||
private Long durationMs;
|
||||
private String syncStatus;
|
||||
private String businessView;
|
||||
private String payload;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_daily_report_fact")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeDailyReportFact {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long syncRecordId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String reportId;
|
||||
private String dailyReportFactId;
|
||||
private String planId;
|
||||
private String status;
|
||||
private String physicalFactStatus;
|
||||
private String deliveryStatus;
|
||||
private String approvalStatus;
|
||||
private String approvalDecisionStatus;
|
||||
private String confirmedAt;
|
||||
private String reportDate;
|
||||
private String sendRecordId;
|
||||
private String sendChannel;
|
||||
private String sendRequestStatus;
|
||||
private String sendRecordStatus;
|
||||
private String materializedSource;
|
||||
private String businessView;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.xspaceagi.agent.core.adapter.repository.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@TableName("digital_employee_sync_record")
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DigitalEmployeeSyncRecord {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
@TableField(value = "_tenant_id")
|
||||
private Long tenantId;
|
||||
private Long userId;
|
||||
private String clientKey;
|
||||
private String deviceId;
|
||||
private String outboxId;
|
||||
private String entityType;
|
||||
private String entityId;
|
||||
private String operation;
|
||||
private String contractVersion;
|
||||
private String entitySummary;
|
||||
private String businessView;
|
||||
private String payload;
|
||||
private Date syncedAt;
|
||||
private Date modified;
|
||||
private Date created;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeAdminAction;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeAdminActionMapper extends BaseMapper<DigitalEmployeeAdminAction> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeArtifactLifecycleMapper extends BaseMapper<DigitalEmployeeArtifactLifecycle> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeBusinessFactMapper extends BaseMapper<DigitalEmployeeBusinessFact> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeDailyReportFactMapper extends BaseMapper<DigitalEmployeeDailyReportFact> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.xspaceagi.agent.core.infra.dao.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DigitalEmployeeSyncRecordMapper extends BaseMapper<DigitalEmployeeSyncRecord> {
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeAdminActionRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeAdminAction;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeAdminActionMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeAdminActionRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeAdminActionMapper, DigitalEmployeeAdminAction>
|
||||
implements DigitalEmployeeAdminActionRepository {
|
||||
|
||||
@Override
|
||||
public IPage<DigitalEmployeeAdminAction> queryActions(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String status,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
LambdaQueryWrapper<DigitalEmployeeAdminAction> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeAdminAction>()
|
||||
.eq(tenantId != null, DigitalEmployeeAdminAction::getTenantId, tenantId)
|
||||
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeAdminAction::getClientKey, clientKey)
|
||||
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeAdminAction::getDeviceId, deviceId)
|
||||
.eq(StringUtils.isNotBlank(status), DigitalEmployeeAdminAction::getStatus, status)
|
||||
.orderByAsc(DigitalEmployeeAdminAction::getCreated)
|
||||
.orderByAsc(DigitalEmployeeAdminAction::getId);
|
||||
return page(new Page<>(pageNo, pageSize), queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeArtifactLifecycleRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeArtifactLifecycle;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeArtifactLifecycleMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeArtifactLifecycleRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeArtifactLifecycleMapper, DigitalEmployeeArtifactLifecycle>
|
||||
implements DigitalEmployeeArtifactLifecycleRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeArtifactLifecycle lifecycle) {
|
||||
if (lifecycle == null || lifecycle.getTenantId() == null || lifecycle.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeArtifactLifecycle existing = getOne(new LambdaQueryWrapper<DigitalEmployeeArtifactLifecycle>()
|
||||
.eq(DigitalEmployeeArtifactLifecycle::getTenantId, lifecycle.getTenantId())
|
||||
.eq(DigitalEmployeeArtifactLifecycle::getSyncRecordId, lifecycle.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(lifecycle);
|
||||
} else {
|
||||
lifecycle.setId(existing.getId());
|
||||
lifecycle.setCreated(existing.getCreated());
|
||||
updateById(lifecycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeBusinessFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeBusinessFact;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeBusinessFactMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeBusinessFactRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeBusinessFactMapper, DigitalEmployeeBusinessFact>
|
||||
implements DigitalEmployeeBusinessFactRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeBusinessFact fact) {
|
||||
if (fact == null || fact.getTenantId() == null || fact.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeBusinessFact existing = getOne(new LambdaQueryWrapper<DigitalEmployeeBusinessFact>()
|
||||
.eq(DigitalEmployeeBusinessFact::getTenantId, fact.getTenantId())
|
||||
.eq(DigitalEmployeeBusinessFact::getSyncRecordId, fact.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(fact);
|
||||
} else {
|
||||
fact.setId(existing.getId());
|
||||
fact.setCreated(existing.getCreated());
|
||||
updateById(fact);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DigitalEmployeeBusinessFact> queryFacts(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
LambdaQueryWrapper<DigitalEmployeeBusinessFact> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeBusinessFact>()
|
||||
.eq(tenantId != null, DigitalEmployeeBusinessFact::getTenantId, tenantId)
|
||||
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeBusinessFact::getClientKey, clientKey)
|
||||
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeBusinessFact::getDeviceId, deviceId)
|
||||
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeBusinessFact::getEntityType, entityType)
|
||||
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeBusinessFact::getEntityId, entityId)
|
||||
.eq(StringUtils.isNotBlank(syncStatus), DigitalEmployeeBusinessFact::getSyncStatus, syncStatus)
|
||||
.ge(updatedFrom != null, DigitalEmployeeBusinessFact::getSyncedAt, updatedFrom)
|
||||
.le(updatedTo != null, DigitalEmployeeBusinessFact::getSyncedAt, updatedTo)
|
||||
.orderByDesc(DigitalEmployeeBusinessFact::getSyncedAt)
|
||||
.orderByDesc(DigitalEmployeeBusinessFact::getId);
|
||||
return page(new Page<>(pageNo, pageSize), queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeDailyReportFactRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeDailyReportFact;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeDailyReportFactMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeDailyReportFactRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeDailyReportFactMapper, DigitalEmployeeDailyReportFact>
|
||||
implements DigitalEmployeeDailyReportFactRepository {
|
||||
|
||||
@Override
|
||||
public void upsertBySyncRecord(DigitalEmployeeDailyReportFact fact) {
|
||||
if (fact == null || fact.getTenantId() == null || fact.getSyncRecordId() == null) return;
|
||||
DigitalEmployeeDailyReportFact existing = getOne(new LambdaQueryWrapper<DigitalEmployeeDailyReportFact>()
|
||||
.eq(DigitalEmployeeDailyReportFact::getTenantId, fact.getTenantId())
|
||||
.eq(DigitalEmployeeDailyReportFact::getSyncRecordId, fact.getSyncRecordId()), false);
|
||||
if (existing == null) {
|
||||
save(fact);
|
||||
} else {
|
||||
fact.setId(existing.getId());
|
||||
fact.setCreated(existing.getCreated());
|
||||
updateById(fact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.xspaceagi.agent.core.infra.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.xspaceagi.agent.core.adapter.repository.DigitalEmployeeSyncRecordRepository;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.DigitalEmployeeSyncRecord;
|
||||
import com.xspaceagi.agent.core.infra.dao.mapper.DigitalEmployeeSyncRecordMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
public class DigitalEmployeeSyncRecordRepositoryImpl
|
||||
extends ServiceImpl<DigitalEmployeeSyncRecordMapper, DigitalEmployeeSyncRecord>
|
||||
implements DigitalEmployeeSyncRecordRepository {
|
||||
|
||||
@Override
|
||||
public IPage<DigitalEmployeeSyncRecord> queryRecords(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
String outboxId,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
LambdaQueryWrapper<DigitalEmployeeSyncRecord> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeSyncRecord>()
|
||||
.eq(tenantId != null, DigitalEmployeeSyncRecord::getTenantId, tenantId)
|
||||
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeSyncRecord::getClientKey, clientKey)
|
||||
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeSyncRecord::getDeviceId, deviceId)
|
||||
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeSyncRecord::getEntityType, entityType)
|
||||
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeSyncRecord::getEntityId, entityId)
|
||||
.eq(StringUtils.isNotBlank(outboxId), DigitalEmployeeSyncRecord::getOutboxId, outboxId)
|
||||
.orderByDesc(DigitalEmployeeSyncRecord::getSyncedAt)
|
||||
.orderByDesc(DigitalEmployeeSyncRecord::getId);
|
||||
return page(new Page<>(pageNo, pageSize), queryWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<DigitalEmployeeSyncRecord> queryBusinessRecords(
|
||||
Long tenantId,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityType,
|
||||
String entityId,
|
||||
Date updatedFrom,
|
||||
Date updatedTo,
|
||||
String syncStatus,
|
||||
long pageNo,
|
||||
long pageSize
|
||||
) {
|
||||
LambdaQueryWrapper<DigitalEmployeeSyncRecord> queryWrapper = new LambdaQueryWrapper<DigitalEmployeeSyncRecord>()
|
||||
.eq(tenantId != null, DigitalEmployeeSyncRecord::getTenantId, tenantId)
|
||||
.eq(StringUtils.isNotBlank(clientKey), DigitalEmployeeSyncRecord::getClientKey, clientKey)
|
||||
.eq(StringUtils.isNotBlank(deviceId), DigitalEmployeeSyncRecord::getDeviceId, deviceId)
|
||||
.eq(StringUtils.isNotBlank(entityType), DigitalEmployeeSyncRecord::getEntityType, entityType)
|
||||
.eq(StringUtils.isNotBlank(entityId), DigitalEmployeeSyncRecord::getEntityId, entityId)
|
||||
.ge(updatedFrom != null, DigitalEmployeeSyncRecord::getSyncedAt, updatedFrom)
|
||||
.le(updatedTo != null, DigitalEmployeeSyncRecord::getSyncedAt, updatedTo)
|
||||
.orderByDesc(DigitalEmployeeSyncRecord::getSyncedAt)
|
||||
.orderByDesc(DigitalEmployeeSyncRecord::getId);
|
||||
return page(new Page<>(pageNo, pageSize), queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminActionResultDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/admin/actions")
|
||||
public class DigitalEmployeeAdminActionController {
|
||||
|
||||
private static final String CLIENT_KEY_HEADER = "X-Qiming-Client-Key";
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public ReqResult<DigitalEmployeeAdminActionResultDto> createAction(
|
||||
@RequestBody(required = false) DigitalEmployeeAdminActionRequestDto request
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.createAdminAction(request));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/pending", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeAdminActionPageDto> pendingActions(
|
||||
@RequestHeader(name = CLIENT_KEY_HEADER, required = false) String headerClientKey,
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
String normalizedClientKey = StringUtils.defaultIfBlank(clientKey, headerClientKey);
|
||||
if (StringUtils.isBlank(normalizedClientKey)) {
|
||||
return ReqResult.create("0001", "客户端 Key 缺失", null);
|
||||
}
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryPendingAdminActions(
|
||||
normalizedClientKey.trim(),
|
||||
deviceId,
|
||||
pageNo,
|
||||
pageSize
|
||||
));
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/{actionId}/result", method = RequestMethod.POST)
|
||||
public ReqResult<DigitalEmployeeAdminActionResultDto> markResult(
|
||||
@PathVariable("actionId") Long actionId,
|
||||
@RequestBody(required = false) Map<String, Object> body
|
||||
) {
|
||||
String status = body == null ? null : String.valueOf(body.get("status"));
|
||||
List<String> reasonCodes = reasonCodes(body == null ? null : body.get("reason_codes"));
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.markAdminActionResult(actionId, status, reasonCodes));
|
||||
}
|
||||
|
||||
private List<String> reasonCodes(Object value) {
|
||||
if (value instanceof List<?> list) {
|
||||
return list.stream().map(String::valueOf).toList();
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePolicySnapshotPageDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/admin/policies")
|
||||
public class DigitalEmployeeAdminPolicyController {
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(path = "/snapshots", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeePolicySnapshotPageDto> querySnapshots(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "policy_kind", required = false) String policyKind,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryAdminPolicySnapshots(
|
||||
clientKey,
|
||||
deviceId,
|
||||
status,
|
||||
policyKind,
|
||||
parseDate(updatedFrom),
|
||||
parseDate(updatedTo),
|
||||
pageNo,
|
||||
pageSize
|
||||
));
|
||||
}
|
||||
|
||||
private Date parseDate(String value) {
|
||||
if (StringUtils.isBlank(value)) return null;
|
||||
String trimmed = value.trim();
|
||||
try {
|
||||
return Date.from(Instant.parse(trimmed));
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
} catch (Exception ignoredAgain) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminRecordRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminRecordResultDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBillingEstimateDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/admin")
|
||||
public class DigitalEmployeeAdminRecordController {
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(path = "/daily-reports/send-records", method = RequestMethod.POST)
|
||||
public ReqResult<DigitalEmployeeAdminRecordResultDto> recordDailyReportSendResult(
|
||||
@RequestBody(required = false) DigitalEmployeeAdminRecordRequestDto request
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.recordDailyReportSendResult(request));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(path = "/billing/estimates", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBillingEstimateDto> queryBillingEstimates(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryBillingEstimates(
|
||||
clientKey,
|
||||
deviceId,
|
||||
parseDate(updatedFrom),
|
||||
parseDate(updatedTo)
|
||||
));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(path = "/audit-archives", method = RequestMethod.POST)
|
||||
public ReqResult<DigitalEmployeeAdminRecordResultDto> recordAuditArchive(
|
||||
@RequestBody(required = false) DigitalEmployeeAdminRecordRequestDto request
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.recordAuditArchive(request));
|
||||
}
|
||||
|
||||
private Date parseDate(String value) {
|
||||
if (StringUtils.isBlank(value)) return null;
|
||||
String trimmed = value.trim();
|
||||
try {
|
||||
return Date.from(Instant.parse(trimmed));
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
} catch (Exception ignoredAgain) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeAdminWorkbenchPageDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/admin/workbench")
|
||||
public class DigitalEmployeeAdminWorkbenchController {
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST)
|
||||
@RequestMapping(path = "/{view}", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeAdminWorkbenchPageDto> queryWorkbench(
|
||||
@PathVariable("view") String view,
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_type", required = false) String entityType,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "kind", required = false) String kind,
|
||||
@RequestParam(name = "category", required = false) String category,
|
||||
@RequestParam(name = "level", required = false) String level,
|
||||
@RequestParam(name = "priority", required = false) String priority,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryAdminWorkbench(
|
||||
view,
|
||||
clientKey,
|
||||
deviceId,
|
||||
entityType,
|
||||
entityId,
|
||||
status,
|
||||
kind,
|
||||
category,
|
||||
level,
|
||||
priority,
|
||||
parseDate(updatedFrom),
|
||||
parseDate(updatedTo),
|
||||
pageNo,
|
||||
pageSize
|
||||
));
|
||||
}
|
||||
|
||||
private Date parseDate(String value) {
|
||||
if (StringUtils.isBlank(value)) return null;
|
||||
String trimmed = value.trim();
|
||||
try {
|
||||
return Date.from(Instant.parse(trimmed));
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
} catch (Exception ignoredAgain) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeBusinessViewPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeePlanBusinessViewDetailDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee")
|
||||
public class DigitalEmployeeBusinessViewController {
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/plans", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryPlans(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("plan", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/plans/{plan_id}", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeePlanBusinessViewDetailDto> queryPlanDetail(
|
||||
@PathVariable("plan_id") String planId,
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryPlanBusinessViewDetail(planId, clientKey, deviceId));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/tasks", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryTasks(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("task", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/runs", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryRuns(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("run", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/events", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryEvents(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("event", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/artifacts", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryArtifacts(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("artifact", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/approvals", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeBusinessViewPageDto> queryApprovals(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "status", required = false) String status,
|
||||
@RequestParam(name = "sync_status", required = false) String syncStatus,
|
||||
@RequestParam(name = "updated_from", required = false) String updatedFrom,
|
||||
@RequestParam(name = "updated_to", required = false) String updatedTo,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(query("approval", clientKey, deviceId, entityId, status, syncStatus, updatedFrom, updatedTo, pageNo, pageSize));
|
||||
}
|
||||
|
||||
private DigitalEmployeeBusinessViewPageDto query(
|
||||
String entityType,
|
||||
String clientKey,
|
||||
String deviceId,
|
||||
String entityId,
|
||||
String status,
|
||||
String syncStatus,
|
||||
String updatedFrom,
|
||||
String updatedTo,
|
||||
Long pageNo,
|
||||
Long pageSize
|
||||
) {
|
||||
return digitalEmployeeSyncApplicationService.queryBusinessViews(
|
||||
entityType,
|
||||
clientKey,
|
||||
deviceId,
|
||||
entityId,
|
||||
status,
|
||||
syncStatus,
|
||||
parseDate(updatedFrom),
|
||||
parseDate(updatedTo),
|
||||
pageNo,
|
||||
pageSize
|
||||
);
|
||||
}
|
||||
|
||||
private Date parseDate(String value) {
|
||||
if (StringUtils.isBlank(value)) return null;
|
||||
String trimmed = value.trim();
|
||||
try {
|
||||
return Date.from(Instant.parse(trimmed));
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||
} catch (Exception ignoredAgain) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.xspaceagi.agent.web.ui.controller;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.DigitalEmployeeSyncApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRecordPageDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncRequestDto;
|
||||
import com.xspaceagi.agent.core.adapter.dto.digital.DigitalEmployeeSyncResponseDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/digital-employee/sync")
|
||||
public class DigitalEmployeeSyncController {
|
||||
|
||||
private static final String CLIENT_KEY_HEADER = "X-Qiming-Client-Key";
|
||||
|
||||
@Resource
|
||||
private DigitalEmployeeSyncApplicationService digitalEmployeeSyncApplicationService;
|
||||
|
||||
@RequestMapping(path = "/outbox", method = RequestMethod.POST)
|
||||
public ReqResult<DigitalEmployeeSyncResponseDto> syncOutbox(
|
||||
@RequestHeader(name = CLIENT_KEY_HEADER, required = false) String headerClientKey,
|
||||
@RequestBody(required = false) DigitalEmployeeSyncRequestDto request
|
||||
) {
|
||||
if (StringUtils.isBlank(headerClientKey)) {
|
||||
return ReqResult.create("0001", "客户端 Key header 缺失", null);
|
||||
}
|
||||
if (request == null) {
|
||||
request = new DigitalEmployeeSyncRequestDto();
|
||||
}
|
||||
String normalizedHeaderClientKey = headerClientKey.trim();
|
||||
if (StringUtils.isBlank(request.getClientKey())) {
|
||||
request.setClientKey(normalizedHeaderClientKey);
|
||||
} else if (!StringUtils.equals(request.getClientKey().trim(), normalizedHeaderClientKey)) {
|
||||
return ReqResult.create("0001", "客户端 Key header 与请求体不一致", null);
|
||||
} else {
|
||||
request.setClientKey(request.getClientKey().trim());
|
||||
}
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.syncOutbox(request));
|
||||
}
|
||||
|
||||
@RequireResource(CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST)
|
||||
@RequestMapping(path = "/records", method = RequestMethod.GET)
|
||||
public ReqResult<DigitalEmployeeSyncRecordPageDto> queryRecords(
|
||||
@RequestParam(name = "client_key", required = false) String clientKey,
|
||||
@RequestParam(name = "device_id", required = false) String deviceId,
|
||||
@RequestParam(name = "entity_type", required = false) String entityType,
|
||||
@RequestParam(name = "entity_id", required = false) String entityId,
|
||||
@RequestParam(name = "outbox_id", required = false) String outboxId,
|
||||
@RequestParam(name = "page_no", required = false) Long pageNo,
|
||||
@RequestParam(name = "page_size", required = false) Long pageSize
|
||||
) {
|
||||
return ReqResult.success(digitalEmployeeSyncApplicationService.queryRecords(
|
||||
clientKey,
|
||||
deviceId,
|
||||
entityType,
|
||||
entityId,
|
||||
outboxId,
|
||||
pageNo,
|
||||
pageSize
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -417,6 +417,16 @@ public enum ResourceEnum {
|
||||
CONTENT_SKILL_QUERY_DETAIL(ResourceTypeEnum.OPERATION, "content_skill_query_detail", "查询详情", "content_skill"),
|
||||
CONTENT_SKILL_DELETE(ResourceTypeEnum.OPERATION, "content_skill_delete", "删除", "content_skill"),
|
||||
|
||||
// 2级模块 - 数字员工同步记录
|
||||
CONTENT_DIGITAL_EMPLOYEE_SYNC(ResourceTypeEnum.MODULE, "content_digital_employee_sync", "数字员工同步记录", "content_manage"),
|
||||
|
||||
CONTENT_DIGITAL_EMPLOYEE_SYNC_QUERY_LIST(ResourceTypeEnum.OPERATION, "content_digital_employee_sync_query_list", "查询列表", "content_digital_employee_sync"),
|
||||
|
||||
// 2级模块 - 数字员工运营台
|
||||
CONTENT_DIGITAL_EMPLOYEE_OPS(ResourceTypeEnum.MODULE, "content_digital_employee_ops", "数字员工运营台", "content_manage"),
|
||||
|
||||
CONTENT_DIGITAL_EMPLOYEE_OPS_QUERY_LIST(ResourceTypeEnum.OPERATION, "content_digital_employee_ops_query_list", "查询列表", "content_digital_employee_ops"),
|
||||
|
||||
// ================== 多语言管理模块 ==================
|
||||
I18N_LANG_MANAGE(ResourceTypeEnum.MODULE, "i18n_lang_manage", "多语言管理模块", "root"),
|
||||
I18N_LANG_QUERY(ResourceTypeEnum.OPERATION, "i18n_lang_query", "查询", "i18n_lang_manage"),
|
||||
|
||||
24
qiming-backend/sql/update-20260605.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE `digital_employee_sync_record`
|
||||
(
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`_tenant_id` BIGINT DEFAULT NULL COMMENT '租户ID',
|
||||
`user_id` BIGINT DEFAULT NULL COMMENT '用户ID',
|
||||
`client_key` VARCHAR(128) NOT NULL COMMENT '客户端配置Key',
|
||||
`device_id` VARCHAR(128) DEFAULT NULL COMMENT '设备ID',
|
||||
`outbox_id` VARCHAR(255) NOT NULL COMMENT '客户端outbox ID',
|
||||
`entity_type` VARCHAR(32) NOT NULL COMMENT '实体类型:plan/task/run/event/artifact/approval',
|
||||
`entity_id` VARCHAR(255) NOT NULL COMMENT '客户端实体ID',
|
||||
`operation` VARCHAR(32) NOT NULL COMMENT '操作类型',
|
||||
`payload` JSON DEFAULT NULL COMMENT '实体快照',
|
||||
`synced_at` DATETIME NOT NULL COMMENT '最近同步时间',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_client_outbox` (`client_key`, `outbox_id`),
|
||||
KEY `idx_client_entity` (`client_key`, `entity_type`, `entity_id`),
|
||||
KEY `idx_tenant_client_entity_time` (`_tenant_id`, `client_key`, `entity_type`, `entity_id`, `synced_at`),
|
||||
KEY `idx_tenant_user_time` (`_tenant_id`, `user_id`, `synced_at`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_unicode_ci
|
||||
COMMENT = '数字员工客户端同步记录';
|
||||
75
qiming-backend/sql/update-20260606.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
-- Add digital employee sync records menu and resource.
|
||||
-- Idempotent for existing tenants: creates menu/resource rows, binds menu to resource,
|
||||
-- and grants super_admin default access. Other roles can be granted this menu
|
||||
-- from the menu permission management UI.
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync', '数字员工同步记录', '数字员工同步记录', 1, 1, 90, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync_query_list', '查询列表', '查询数字员工同步记录', 1, 2, 1, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_digital_employee_sync' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_sync_query_list' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (`parent_id`, `code`, `name`, `description`, `source`, `path`, `open_type`, `icon`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_sync', '数字员工同步记录', '数字员工同步记录', 1, '/system/content/content-digital-employee-sync', 1, '', 90, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu m
|
||||
WHERE m.`_tenant_id` = parent.`_tenant_id` AND m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu_resource (`menu_id`, `resource_id`, `resource_bind_type`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT m.id, r.id, 1, m.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu m
|
||||
JOIN sys_resource r ON r.`_tenant_id` = m.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
WHERE m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu_resource mr
|
||||
WHERE mr.`_tenant_id` = m.`_tenant_id` AND mr.`menu_id` = m.id AND mr.`resource_id` = r.id AND mr.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_menu_resource mr
|
||||
JOIN sys_menu m ON m.id = mr.menu_id AND m.`_tenant_id` = mr.`_tenant_id` AND m.`code` = 'content_digital_employee_sync' AND m.`yn` = 1
|
||||
JOIN sys_resource r ON r.id = mr.resource_id AND r.`_tenant_id` = mr.`_tenant_id` AND r.`code` = 'content_digital_employee_sync' AND r.`yn` = 1
|
||||
SET mr.`resource_bind_type` = 1,
|
||||
mr.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE mr.`yn` = 1;
|
||||
|
||||
INSERT INTO sys_role_menu (`role_id`, `menu_id`, `menu_bind_type`, `resource_tree_json`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT role.id,
|
||||
menu.id,
|
||||
1,
|
||||
JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
menu.`_tenant_id`,
|
||||
0,
|
||||
'system',
|
||||
1
|
||||
FROM sys_role role
|
||||
JOIN sys_menu menu ON menu.`_tenant_id` = role.`_tenant_id` AND menu.`code` = 'content_digital_employee_sync' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = role.`_tenant_id` AND resource.`code` = 'content_digital_employee_sync' AND resource.`yn` = 1
|
||||
WHERE role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu rm
|
||||
WHERE rm.`_tenant_id` = role.`_tenant_id` AND rm.`role_id` = role.id AND rm.`menu_id` = menu.id AND rm.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_role_menu rm
|
||||
JOIN sys_role role ON role.id = rm.role_id AND role.`_tenant_id` = rm.`_tenant_id` AND role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
JOIN sys_menu menu ON menu.id = rm.menu_id AND menu.`_tenant_id` = rm.`_tenant_id` AND menu.`code` = 'content_digital_employee_sync' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = rm.`_tenant_id` AND resource.`code` = 'content_digital_employee_sync' AND resource.`yn` = 1
|
||||
SET rm.`menu_bind_type` = 1,
|
||||
rm.`resource_tree_json` = JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
rm.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE rm.`yn` = 1;
|
||||
193
qiming-backend/sql/update-20260612.sql
Normal file
@@ -0,0 +1,193 @@
|
||||
ALTER TABLE `digital_employee_sync_record`
|
||||
ADD COLUMN `contract_version` VARCHAR(128) DEFAULT NULL COMMENT '客户端同步契约版本' AFTER `operation`,
|
||||
ADD COLUMN `entity_summary` JSON DEFAULT NULL COMMENT '客户端实体摘要' AFTER `contract_version`,
|
||||
ADD COLUMN `business_view` JSON DEFAULT NULL COMMENT '客户端业务视图投影' AFTER `entity_summary`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `digital_employee_admin_action` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`_tenant_id` BIGINT DEFAULT NULL COMMENT '租户 ID',
|
||||
`user_id` BIGINT DEFAULT NULL COMMENT '创建用户 ID',
|
||||
`client_key` VARCHAR(128) DEFAULT NULL COMMENT '客户端 Key',
|
||||
`device_id` VARCHAR(128) DEFAULT NULL COMMENT '设备 ID',
|
||||
`entity_type` VARCHAR(64) DEFAULT NULL COMMENT '实体类型',
|
||||
`entity_id` VARCHAR(128) DEFAULT NULL COMMENT '实体 ID',
|
||||
`action` VARCHAR(64) DEFAULT NULL COMMENT '管理端动作',
|
||||
`status` VARCHAR(32) DEFAULT NULL COMMENT '状态 pending/applied/rejected/failed',
|
||||
`reason_codes` JSON DEFAULT NULL COMMENT '原因码',
|
||||
`request_payload` JSON DEFAULT NULL COMMENT '请求附加参数',
|
||||
`processed_at` DATETIME DEFAULT NULL COMMENT '客户端处理时间',
|
||||
`modified` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
`created` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_de_admin_action_pending` (`_tenant_id`, `client_key`, `device_id`, `status`, `created`),
|
||||
KEY `idx_de_admin_action_entity` (`_tenant_id`, `entity_type`, `entity_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字员工管理端远程动作意图';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `digital_employee_business_fact` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`_tenant_id` BIGINT NOT NULL COMMENT '租户ID',
|
||||
`user_id` BIGINT DEFAULT NULL COMMENT '用户ID',
|
||||
`sync_record_id` BIGINT NOT NULL COMMENT '同步记录ID',
|
||||
`client_key` VARCHAR(255) DEFAULT NULL COMMENT '客户端Key',
|
||||
`device_id` VARCHAR(255) DEFAULT NULL COMMENT '设备ID',
|
||||
`outbox_id` VARCHAR(255) DEFAULT NULL COMMENT '客户端outbox ID',
|
||||
`entity_type` VARCHAR(64) NOT NULL COMMENT '实体类型',
|
||||
`entity_id` VARCHAR(255) NOT NULL COMMENT '实体ID',
|
||||
`operation` VARCHAR(64) DEFAULT NULL COMMENT '操作',
|
||||
`title` VARCHAR(512) DEFAULT NULL COMMENT '标题',
|
||||
`status` VARCHAR(128) DEFAULT NULL COMMENT '业务状态',
|
||||
`summary` VARCHAR(1024) DEFAULT NULL COMMENT '摘要',
|
||||
`plan_id` VARCHAR(255) DEFAULT NULL COMMENT '计划ID',
|
||||
`task_id` VARCHAR(255) DEFAULT NULL COMMENT '任务ID',
|
||||
`run_id` VARCHAR(255) DEFAULT NULL COMMENT '运行ID',
|
||||
`kind` VARCHAR(255) DEFAULT NULL COMMENT '类型',
|
||||
`category` VARCHAR(128) DEFAULT NULL COMMENT '分类',
|
||||
`occurred_at` VARCHAR(64) DEFAULT NULL COMMENT '发生时间',
|
||||
`started_at` VARCHAR(64) DEFAULT NULL COMMENT '开始时间',
|
||||
`finished_at` VARCHAR(64) DEFAULT NULL COMMENT '结束时间',
|
||||
`duration_ms` BIGINT DEFAULT NULL COMMENT '耗时毫秒',
|
||||
`sync_status` VARCHAR(128) DEFAULT NULL COMMENT '同步状态',
|
||||
`business_view` JSON DEFAULT NULL COMMENT '业务视图快照',
|
||||
`payload` JSON DEFAULT NULL COMMENT '安全payload快照',
|
||||
`synced_at` DATETIME DEFAULT NULL COMMENT '同步时间',
|
||||
`modified` DATETIME NOT NULL COMMENT '修改时间',
|
||||
`created` DATETIME NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_de_business_fact_record` (`_tenant_id`, `sync_record_id`),
|
||||
KEY `idx_de_business_fact_entity` (`_tenant_id`, `entity_type`, `entity_id`),
|
||||
KEY `idx_de_business_fact_plan` (`_tenant_id`, `plan_id`, `entity_type`),
|
||||
KEY `idx_de_business_fact_client` (`_tenant_id`, `client_key`, `device_id`, `synced_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字员工业务事实物化表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `digital_employee_artifact_lifecycle` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`_tenant_id` BIGINT NOT NULL COMMENT '租户ID',
|
||||
`sync_record_id` BIGINT NOT NULL COMMENT '同步记录ID',
|
||||
`client_key` VARCHAR(255) DEFAULT NULL COMMENT '客户端Key',
|
||||
`device_id` VARCHAR(255) DEFAULT NULL COMMENT '设备ID',
|
||||
`artifact_id` VARCHAR(255) NOT NULL COMMENT '产物ID',
|
||||
`plan_id` VARCHAR(255) DEFAULT NULL COMMENT '计划ID',
|
||||
`task_id` VARCHAR(255) DEFAULT NULL COMMENT '任务ID',
|
||||
`run_id` VARCHAR(255) DEFAULT NULL COMMENT '运行ID',
|
||||
`status` VARCHAR(128) DEFAULT NULL COMMENT '状态',
|
||||
`retention_status` VARCHAR(128) DEFAULT NULL COMMENT '保留状态',
|
||||
`cleanup_status` VARCHAR(128) DEFAULT NULL COMMENT '清理状态',
|
||||
`deleted_at` VARCHAR(64) DEFAULT NULL COMMENT '删除时间',
|
||||
`version_key` VARCHAR(255) DEFAULT NULL COMMENT '版本Key',
|
||||
`source` VARCHAR(255) DEFAULT NULL COMMENT '来源',
|
||||
`business_view` JSON DEFAULT NULL COMMENT '业务视图快照',
|
||||
`synced_at` DATETIME DEFAULT NULL COMMENT '同步时间',
|
||||
`modified` DATETIME NOT NULL COMMENT '修改时间',
|
||||
`created` DATETIME NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_de_artifact_lifecycle_record` (`_tenant_id`, `sync_record_id`),
|
||||
KEY `idx_de_artifact_lifecycle_artifact` (`_tenant_id`, `artifact_id`),
|
||||
KEY `idx_de_artifact_lifecycle_plan` (`_tenant_id`, `plan_id`, `task_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字员工产物生命周期物化表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `digital_employee_daily_report_fact` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`_tenant_id` BIGINT NOT NULL COMMENT '租户ID',
|
||||
`sync_record_id` BIGINT NOT NULL COMMENT '同步记录ID',
|
||||
`client_key` VARCHAR(255) DEFAULT NULL COMMENT '客户端Key',
|
||||
`device_id` VARCHAR(255) DEFAULT NULL COMMENT '设备ID',
|
||||
`report_id` VARCHAR(255) NOT NULL COMMENT '日报ID',
|
||||
`daily_report_fact_id` VARCHAR(255) DEFAULT NULL COMMENT '日报事实ID',
|
||||
`plan_id` VARCHAR(255) DEFAULT NULL COMMENT '计划ID',
|
||||
`status` VARCHAR(128) DEFAULT NULL COMMENT '状态',
|
||||
`physical_fact_status` VARCHAR(128) DEFAULT NULL COMMENT '物理事实状态',
|
||||
`delivery_status` VARCHAR(128) DEFAULT NULL COMMENT '交付状态',
|
||||
`approval_status` VARCHAR(128) DEFAULT NULL COMMENT '审批状态',
|
||||
`approval_decision_status` VARCHAR(128) DEFAULT NULL COMMENT '审批裁决状态',
|
||||
`confirmed_at` VARCHAR(64) DEFAULT NULL COMMENT '确认时间',
|
||||
`report_date` VARCHAR(64) DEFAULT NULL COMMENT '日报日期',
|
||||
`send_record_id` VARCHAR(255) DEFAULT NULL COMMENT '发送记录ID',
|
||||
`send_channel` VARCHAR(128) DEFAULT NULL COMMENT '发送渠道',
|
||||
`send_request_status` VARCHAR(128) DEFAULT NULL COMMENT '发送请求状态',
|
||||
`send_record_status` VARCHAR(128) DEFAULT NULL COMMENT '发送记录状态',
|
||||
`materialized_source` VARCHAR(128) DEFAULT NULL COMMENT '物化来源',
|
||||
`business_view` JSON DEFAULT NULL COMMENT '业务视图快照',
|
||||
`synced_at` DATETIME DEFAULT NULL COMMENT '同步时间',
|
||||
`modified` DATETIME NOT NULL COMMENT '修改时间',
|
||||
`created` DATETIME NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_de_daily_report_fact_record` (`_tenant_id`, `sync_record_id`),
|
||||
KEY `idx_de_daily_report_fact_report` (`_tenant_id`, `report_id`),
|
||||
KEY `idx_de_daily_report_fact_client` (`_tenant_id`, `client_key`, `device_id`, `synced_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字员工日报事实物化表';
|
||||
|
||||
-- Add digital employee admin operations workbench menu and resource.
|
||||
-- Idempotent for existing tenants: creates menu/resource rows, binds menu to resource,
|
||||
-- and grants super_admin default access. Other roles can be granted this menu
|
||||
-- from the menu permission management UI.
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_ops', '数字员工运营台', '数字员工运营台', 1, 1, 91, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_resource (`parent_id`, `code`, `name`, `description`, `source`, `type`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_ops_query_list', '查询列表', '查询数字员工运营台', 1, 2, 1, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_resource parent
|
||||
WHERE parent.`code` = 'content_digital_employee_ops' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_resource r
|
||||
WHERE r.`_tenant_id` = parent.`_tenant_id` AND r.`code` = 'content_digital_employee_ops_query_list' AND r.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (`parent_id`, `code`, `name`, `description`, `source`, `path`, `open_type`, `icon`, `sort_index`, `status`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT parent.id, 'content_digital_employee_ops', '数字员工运营台', '数字员工运营台', 1, '/system/content/content-digital-employee-ops', 1, '', 91, 1, parent.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu parent
|
||||
WHERE parent.`code` = 'content_manage' AND parent.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu m
|
||||
WHERE m.`_tenant_id` = parent.`_tenant_id` AND m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu_resource (`menu_id`, `resource_id`, `resource_bind_type`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT m.id, r.id, 1, m.`_tenant_id`, 0, 'system', 1
|
||||
FROM sys_menu m
|
||||
JOIN sys_resource r ON r.`_tenant_id` = m.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
|
||||
WHERE m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu_resource mr
|
||||
WHERE mr.`_tenant_id` = m.`_tenant_id` AND mr.`menu_id` = m.id AND mr.`resource_id` = r.id AND mr.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_menu_resource mr
|
||||
JOIN sys_menu m ON m.id = mr.menu_id AND m.`_tenant_id` = mr.`_tenant_id` AND m.`code` = 'content_digital_employee_ops' AND m.`yn` = 1
|
||||
JOIN sys_resource r ON r.id = mr.resource_id AND r.`_tenant_id` = mr.`_tenant_id` AND r.`code` = 'content_digital_employee_ops' AND r.`yn` = 1
|
||||
SET mr.`resource_bind_type` = 1,
|
||||
mr.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE mr.`yn` = 1;
|
||||
|
||||
INSERT INTO sys_role_menu (`role_id`, `menu_id`, `menu_bind_type`, `resource_tree_json`, `_tenant_id`, `creator_id`, `creator`, `yn`)
|
||||
SELECT role.id,
|
||||
menu.id,
|
||||
1,
|
||||
JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
menu.`_tenant_id`,
|
||||
0,
|
||||
'system',
|
||||
1
|
||||
FROM sys_role role
|
||||
JOIN sys_menu menu ON menu.`_tenant_id` = role.`_tenant_id` AND menu.`code` = 'content_digital_employee_ops' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = role.`_tenant_id` AND resource.`code` = 'content_digital_employee_ops' AND resource.`yn` = 1
|
||||
WHERE role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu rm
|
||||
WHERE rm.`_tenant_id` = role.`_tenant_id` AND rm.`role_id` = role.id AND rm.`menu_id` = menu.id AND rm.`yn` = 1
|
||||
);
|
||||
|
||||
UPDATE sys_role_menu rm
|
||||
JOIN sys_role role ON role.id = rm.role_id AND role.`_tenant_id` = rm.`_tenant_id` AND role.`code` = 'super_admin' AND role.`yn` = 1
|
||||
JOIN sys_menu menu ON menu.id = rm.menu_id AND menu.`_tenant_id` = rm.`_tenant_id` AND menu.`code` = 'content_digital_employee_ops' AND menu.`yn` = 1
|
||||
JOIN sys_resource resource ON resource.`_tenant_id` = rm.`_tenant_id` AND resource.`code` = 'content_digital_employee_ops' AND resource.`yn` = 1
|
||||
SET rm.`menu_bind_type` = 1,
|
||||
rm.`resource_tree_json` = JSON_ARRAY(JSON_OBJECT('id', resource.id, 'code', resource.`code`, 'resourceBindType', 1)),
|
||||
rm.`modified` = CURRENT_TIMESTAMP
|
||||
WHERE rm.`yn` = 1;
|
||||
@@ -33,7 +33,8 @@
|
||||
"serve": "http-server ./dist",
|
||||
"setup": "max setup",
|
||||
"start": "npm run dev",
|
||||
"test": "vitest",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"verify-commit": "max verify-commit"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -140,14 +141,15 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild": "^0.27.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^27.3.0",
|
||||
"jest-axe": "^10.0.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier-plugin-organize-imports": "^3.2.2",
|
||||
"prettier-plugin-packagejson": "^2.4.3",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^5.0.3",
|
||||
"vitest": "^4.0.15"
|
||||
"vitest": "^0.34.6"
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
import { XProTable } from '@/components/ProComponents';
|
||||
import WorkspaceLayout from '@/components/WorkspaceLayout';
|
||||
import { SUCCESS_CODE } from '@/constants/codes.constants';
|
||||
import { apiDigitalEmployeeSyncRecordList } from '@/services/systemManage';
|
||||
import type { DigitalEmployeeSyncRecordInfo } from '@/types/interfaces/systemManage';
|
||||
import type {
|
||||
ActionType,
|
||||
FormInstance,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Button, Drawer, message, Space, Tag, Typography } from 'antd';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useModel, useSearchParams } from 'umi';
|
||||
|
||||
const ENTITY_TYPE_OPTIONS = {
|
||||
plan: { text: 'Plan' },
|
||||
task: { text: 'Task' },
|
||||
run: { text: 'Run' },
|
||||
event: { text: 'Event' },
|
||||
artifact: { text: 'Artifact' },
|
||||
approval: { text: 'Approval' },
|
||||
};
|
||||
|
||||
const ENTITY_TYPE_TONE: Record<string, string> = {
|
||||
plan: 'blue',
|
||||
task: 'geekblue',
|
||||
run: 'purple',
|
||||
event: 'cyan',
|
||||
artifact: 'green',
|
||||
approval: 'orange',
|
||||
};
|
||||
|
||||
const STATUS_TONE: Record<string, string> = {
|
||||
running: 'processing',
|
||||
pending: 'warning',
|
||||
completed: 'success',
|
||||
success: 'success',
|
||||
synced: 'success',
|
||||
failed: 'error',
|
||||
error: 'error',
|
||||
cancelled: 'default',
|
||||
};
|
||||
|
||||
interface PayloadObject {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface SyncBusinessSummary {
|
||||
title: string;
|
||||
description: string;
|
||||
status?: string;
|
||||
statusTone: string;
|
||||
meta: string[];
|
||||
}
|
||||
|
||||
function formatPayload(payload?: string): string {
|
||||
if (!payload) return '{}';
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(payload), null, 2);
|
||||
} catch {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
function parsePayload(payload?: string): unknown {
|
||||
if (!payload) return null;
|
||||
try {
|
||||
return JSON.parse(payload);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isPayloadObject(value: unknown): value is PayloadObject {
|
||||
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function valueAsString(value: unknown): string {
|
||||
if (typeof value === 'string') return value.trim();
|
||||
if (typeof value === 'number' || typeof value === 'boolean')
|
||||
return String(value);
|
||||
return '';
|
||||
}
|
||||
|
||||
function pickString(payload: PayloadObject, keys: string[]): string {
|
||||
for (const key of keys) {
|
||||
const value = valueAsString(payload[key]);
|
||||
if (value) return value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function compactSummaryText(value: string, maxLength = 96): string {
|
||||
const text = value.replace(/\s+/g, ' ').trim();
|
||||
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
||||
}
|
||||
|
||||
function maskClientKey(value?: string): string {
|
||||
const key = value?.trim();
|
||||
if (!key) return '--';
|
||||
if (key.length <= 4) return '****';
|
||||
if (key.length <= 10) return `${key.slice(0, 2)}****${key.slice(-2)}`;
|
||||
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
||||
}
|
||||
|
||||
function entityLabel(entityType: string): string {
|
||||
return (
|
||||
ENTITY_TYPE_OPTIONS[entityType as keyof typeof ENTITY_TYPE_OPTIONS]?.text ||
|
||||
entityType ||
|
||||
'Entity'
|
||||
);
|
||||
}
|
||||
|
||||
function statusTone(status?: string): string {
|
||||
return status ? STATUS_TONE[status.toLowerCase()] || 'default' : 'default';
|
||||
}
|
||||
|
||||
function buildBusinessSummary(
|
||||
record: DigitalEmployeeSyncRecordInfo,
|
||||
): SyncBusinessSummary {
|
||||
const payload = parsePayload(record.payload);
|
||||
const entity = entityLabel(record.entity_type);
|
||||
if (!isPayloadObject(payload)) {
|
||||
return {
|
||||
title: `${entity} ${record.entity_id}`,
|
||||
description: 'Payload 暂无法解析,查看详情可读取原始内容。',
|
||||
status: record.operation,
|
||||
statusTone: 'default',
|
||||
meta: [record.outbox_id],
|
||||
};
|
||||
}
|
||||
|
||||
const status = pickString(payload, ['status', 'currentStatus', 'kind']);
|
||||
const title =
|
||||
pickString(payload, ['title', 'name', 'message', 'objective']) ||
|
||||
`${entity} ${record.entity_id}`;
|
||||
const description = compactSummaryText(
|
||||
pickString(payload, ['objective', 'summary', 'message', 'uri']) ||
|
||||
record.outbox_id,
|
||||
);
|
||||
const meta = [
|
||||
pickString(payload, ['planId', 'plan_id']) &&
|
||||
`Plan ${pickString(payload, ['planId', 'plan_id'])}`,
|
||||
pickString(payload, ['taskId', 'task_id']) &&
|
||||
`Task ${pickString(payload, ['taskId', 'task_id'])}`,
|
||||
pickString(payload, ['runId', 'run_id']) &&
|
||||
`Run ${pickString(payload, ['runId', 'run_id'])}`,
|
||||
pickString(payload, ['assignedSkillId']) &&
|
||||
`Skill ${pickString(payload, ['assignedSkillId'])}`,
|
||||
pickString(payload, ['occurred_at', 'started_at', 'finished_at']),
|
||||
].filter((item): item is string => Boolean(item));
|
||||
|
||||
return {
|
||||
title: compactSummaryText(title, 72),
|
||||
description,
|
||||
status: status || record.operation,
|
||||
statusTone: statusTone(status),
|
||||
meta: meta.slice(0, 3),
|
||||
};
|
||||
}
|
||||
|
||||
function renderBusinessSummary(record: DigitalEmployeeSyncRecordInfo) {
|
||||
const summary = buildBusinessSummary(record);
|
||||
return (
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<Space size={6} wrap>
|
||||
<Typography.Text strong ellipsis style={{ maxWidth: 260 }}>
|
||||
{summary.title}
|
||||
</Typography.Text>
|
||||
{summary.status && (
|
||||
<Tag color={summary.statusTone}>{summary.status}</Tag>
|
||||
)}
|
||||
</Space>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
ellipsis
|
||||
style={{ display: 'block', maxWidth: 330, fontSize: 12 }}
|
||||
>
|
||||
{summary.description}
|
||||
</Typography.Text>
|
||||
{summary.meta.length > 0 && (
|
||||
<Space size={[4, 4]} wrap style={{ marginTop: 4 }}>
|
||||
{summary.meta.map((item) => (
|
||||
<Tag key={item} color="default" style={{ marginInlineEnd: 0 }}>
|
||||
{item}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function readSearchValue(
|
||||
params: URLSearchParams,
|
||||
key: string,
|
||||
): string | undefined {
|
||||
const value = params.get(key)?.trim();
|
||||
return value || undefined;
|
||||
}
|
||||
|
||||
const DigitalEmployeeSync: React.FC = () => {
|
||||
const { hasPermission } = useModel('menuModel');
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<FormInstance>();
|
||||
const autoOpenedOutboxRef = useRef<string | null>(null);
|
||||
const location = useLocation();
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
const [payloadRecord, setPayloadRecord] =
|
||||
useState<DigitalEmployeeSyncRecordInfo | null>(null);
|
||||
const formattedPayload = useMemo(
|
||||
() => (payloadRecord ? formatPayload(payloadRecord.payload) : ''),
|
||||
[payloadRecord],
|
||||
);
|
||||
|
||||
const formInitialValues = useMemo(() => {
|
||||
const params = new URLSearchParams(location.search || '');
|
||||
const values = {
|
||||
client_key: readSearchValue(params, 'client_key'),
|
||||
device_id: readSearchValue(params, 'device_id'),
|
||||
entity_type: readSearchValue(params, 'entity_type'),
|
||||
entity_id: readSearchValue(params, 'entity_id'),
|
||||
outbox_id: readSearchValue(params, 'outbox_id'),
|
||||
};
|
||||
return Object.values(values).some(Boolean) ? values : undefined;
|
||||
}, [location.search]);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
formRef.current?.resetFields();
|
||||
formRef.current?.setFieldsValue({
|
||||
client_key: undefined,
|
||||
device_id: undefined,
|
||||
entity_type: undefined,
|
||||
entity_id: undefined,
|
||||
outbox_id: undefined,
|
||||
});
|
||||
actionRef.current?.setPageInfo?.({ current: 1, pageSize: 15 });
|
||||
autoOpenedOutboxRef.current = null;
|
||||
if (location.search) {
|
||||
setSearchParams(new URLSearchParams());
|
||||
return;
|
||||
}
|
||||
actionRef.current?.reload();
|
||||
}, [location.search, setSearchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
autoOpenedOutboxRef.current = null;
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
const state = location.state as any;
|
||||
if (state?._t) {
|
||||
handleReset();
|
||||
}
|
||||
}, [location.state, handleReset]);
|
||||
|
||||
const columns: ProColumns<DigitalEmployeeSyncRecordInfo>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '客户端 Key',
|
||||
dataIndex: 'client_key',
|
||||
width: 190,
|
||||
ellipsis: true,
|
||||
renderText: (value) => maskClientKey(value),
|
||||
fieldProps: {
|
||||
placeholder: '输入客户端 Key',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备 ID',
|
||||
dataIndex: 'device_id',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
fieldProps: {
|
||||
placeholder: '输入设备 ID',
|
||||
allowClear: true,
|
||||
},
|
||||
renderText: (value) => value || '--',
|
||||
},
|
||||
{
|
||||
title: '实体类型',
|
||||
dataIndex: 'entity_type',
|
||||
width: 120,
|
||||
valueType: 'select',
|
||||
valueEnum: ENTITY_TYPE_OPTIONS,
|
||||
fieldProps: {
|
||||
placeholder: '选择实体类型',
|
||||
allowClear: true,
|
||||
},
|
||||
render: (_, record) => (
|
||||
<Tag color={ENTITY_TYPE_TONE[record.entity_type] || 'default'}>
|
||||
{record.entity_type || '--'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '实体 ID',
|
||||
dataIndex: 'entity_id',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
fieldProps: {
|
||||
placeholder: '输入实体 ID',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '业务摘要',
|
||||
dataIndex: 'payload',
|
||||
width: 360,
|
||||
hideInSearch: true,
|
||||
render: (_, record) => renderBusinessSummary(record),
|
||||
},
|
||||
{
|
||||
title: 'Outbox ID',
|
||||
dataIndex: 'outbox_id',
|
||||
width: 220,
|
||||
ellipsis: true,
|
||||
fieldProps: {
|
||||
placeholder: '输入 Outbox ID',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
hideInSearch: true,
|
||||
renderText: (value) => value || '--',
|
||||
},
|
||||
{
|
||||
title: '同步时间',
|
||||
dataIndex: 'synced_at',
|
||||
align: 'center',
|
||||
width: 170,
|
||||
hideInSearch: true,
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
align: 'center',
|
||||
width: 170,
|
||||
hideInSearch: true,
|
||||
valueType: 'dateTime',
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
render: (_, record) => (
|
||||
<Button type="link" onClick={() => setPayloadRecord(record)}>
|
||||
查看
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const request = async (params: {
|
||||
pageSize?: number;
|
||||
current?: number;
|
||||
client_key?: string;
|
||||
device_id?: string;
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
outbox_id?: string;
|
||||
}) => {
|
||||
const response = await apiDigitalEmployeeSyncRecordList({
|
||||
pageNo: params.current || 1,
|
||||
pageSize: params.pageSize || 15,
|
||||
clientKey: params.client_key,
|
||||
deviceId: params.device_id,
|
||||
entityType: params.entity_type,
|
||||
entityId: params.entity_id,
|
||||
outboxId: params.outbox_id,
|
||||
});
|
||||
|
||||
if (response.code !== SUCCESS_CODE) {
|
||||
message.error(response.message || '查询数字员工同步记录失败');
|
||||
}
|
||||
|
||||
const records = response.data?.records || [];
|
||||
const outboxId = params.outbox_id?.trim();
|
||||
const matchedRecord = outboxId
|
||||
? records.find((record) => record.outbox_id === outboxId)
|
||||
: null;
|
||||
if (
|
||||
response.code === SUCCESS_CODE &&
|
||||
outboxId &&
|
||||
matchedRecord &&
|
||||
autoOpenedOutboxRef.current !== outboxId
|
||||
) {
|
||||
autoOpenedOutboxRef.current = outboxId;
|
||||
setPayloadRecord(matchedRecord);
|
||||
}
|
||||
|
||||
return {
|
||||
data: records,
|
||||
total: response.data?.total || 0,
|
||||
success: response.code === SUCCESS_CODE,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkspaceLayout title="数字员工同步记录" hideScroll>
|
||||
<XProTable<DigitalEmployeeSyncRecordInfo>
|
||||
key={location.pathname + (location.search || '')}
|
||||
actionRef={actionRef}
|
||||
formRef={formRef}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
request={request}
|
||||
onReset={handleReset}
|
||||
form={
|
||||
formInitialValues ? { initialValues: formInitialValues } : undefined
|
||||
}
|
||||
showQueryButtons={hasPermission(
|
||||
'content_digital_employee_sync_query_list',
|
||||
)}
|
||||
/>
|
||||
<Drawer
|
||||
width={720}
|
||||
title="同步详情"
|
||||
open={Boolean(payloadRecord)}
|
||||
onClose={() => setPayloadRecord(null)}
|
||||
>
|
||||
{payloadRecord && (
|
||||
<>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{renderBusinessSummary(payloadRecord)}
|
||||
</div>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>客户端 Key:</Typography.Text>
|
||||
{maskClientKey(payloadRecord.client_key)}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>设备 ID:</Typography.Text>
|
||||
{payloadRecord.device_id || '--'}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>实体:</Typography.Text>
|
||||
{payloadRecord.entity_type} / {payloadRecord.entity_id}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>Outbox:</Typography.Text>
|
||||
{payloadRecord.outbox_id}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph copyable={{ text: formattedPayload }}>
|
||||
<pre
|
||||
style={{
|
||||
maxHeight: 'calc(100vh - 260px)',
|
||||
overflow: 'auto',
|
||||
margin: 0,
|
||||
padding: 16,
|
||||
background: '#f6f8fa',
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: 6,
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{formattedPayload}
|
||||
</pre>
|
||||
</Typography.Paragraph>
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
</WorkspaceLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DigitalEmployeeSync;
|
||||
@@ -351,6 +351,18 @@ const routes = [
|
||||
name: getRouteLabel('PC.Routes.contentSkill'),
|
||||
component: '@/pages/SystemManagement/Content/Skill',
|
||||
},
|
||||
{
|
||||
path: 'content-digital-employee-sync',
|
||||
name: '数字员工同步记录',
|
||||
component:
|
||||
'@/pages/SystemManagement/Content/DigitalEmployeeSync',
|
||||
},
|
||||
{
|
||||
path: 'content-digital-employee-ops',
|
||||
name: '数字员工运营台',
|
||||
component:
|
||||
'@/pages/SystemManagement/Content/DigitalEmployeeOps',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -53,6 +53,10 @@ const MENU_ICON_MAP: Record<string, string> = {
|
||||
system_log_query: 'icons-nav-log',
|
||||
// 内容管理
|
||||
content_manage: 'icons-nav-cube',
|
||||
// 数字员工同步记录
|
||||
content_digital_employee_sync: 'icons-nav-robot',
|
||||
// 数字员工运营台
|
||||
content_digital_employee_ops: 'icons-nav-robot',
|
||||
// 支付与收益(开发者)
|
||||
dev_payment_earnings: 'icons-nav-my-earnings',
|
||||
// 订阅与积分(管理员)
|
||||
|
||||
@@ -7,7 +7,19 @@ import type { Page, RequestResponse } from '@/types/interfaces/request';
|
||||
import type {
|
||||
AccessStatsResult,
|
||||
AddSystemUserParams,
|
||||
DigitalEmployeeAdminActionResult,
|
||||
DigitalEmployeeAdminActionSubmitParams,
|
||||
DigitalEmployeeAdminRecordResult,
|
||||
DigitalEmployeeAdminRecordSubmitParams,
|
||||
ConversationStatsResult,
|
||||
DigitalEmployeeBillingEstimate,
|
||||
DigitalEmployeeBillingEstimateParams,
|
||||
DigitalEmployeeAdminWorkbenchListParams,
|
||||
DigitalEmployeeAdminWorkbenchPage,
|
||||
DigitalEmployeePolicySnapshotListParams,
|
||||
DigitalEmployeePolicySnapshotPage,
|
||||
DigitalEmployeeSyncRecordListParams,
|
||||
DigitalEmployeeSyncRecordPage,
|
||||
ModelConfigDto,
|
||||
NotifyMessageSendParams,
|
||||
OnlineMonitorInfo,
|
||||
@@ -264,6 +276,142 @@ export async function apiSystemResourceAgentDelete(data: {
|
||||
});
|
||||
}
|
||||
|
||||
// 查询数字员工同步记录
|
||||
export async function apiDigitalEmployeeSyncRecordList(
|
||||
data: DigitalEmployeeSyncRecordListParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeSyncRecordPage>> {
|
||||
return request('/api/digital-employee/sync/records', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
outbox_id: data.outboxId,
|
||||
page_no: data.pageNo,
|
||||
page_size: data.pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 查询数字员工正式运营台
|
||||
export async function apiDigitalEmployeeAdminWorkbenchList(
|
||||
data: DigitalEmployeeAdminWorkbenchListParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeAdminWorkbenchPage>> {
|
||||
return request(`/api/digital-employee/admin/workbench/${data.view}`, {
|
||||
method: 'GET',
|
||||
params: {
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
status: data.status,
|
||||
kind: data.kind,
|
||||
category: data.category,
|
||||
level: data.level,
|
||||
priority: data.priority,
|
||||
updated_from: data.updatedFrom,
|
||||
updated_to: data.updatedTo,
|
||||
page_no: data.pageNo,
|
||||
page_size: data.pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 查询数字员工跨设备策略快照冲突
|
||||
export async function apiDigitalEmployeeAdminPolicySnapshots(
|
||||
data: DigitalEmployeePolicySnapshotListParams,
|
||||
): Promise<RequestResponse<DigitalEmployeePolicySnapshotPage>> {
|
||||
return request('/api/digital-employee/admin/policies/snapshots', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
status: data.status,
|
||||
policy_kind: data.policyKind,
|
||||
updated_from: data.updatedFrom,
|
||||
updated_to: data.updatedTo,
|
||||
page_no: data.pageNo,
|
||||
page_size: data.pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 记录数字员工管理端低风险动作意图
|
||||
export async function apiDigitalEmployeeAdminActionSubmit(
|
||||
data: DigitalEmployeeAdminActionSubmitParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeAdminActionResult>> {
|
||||
return request('/api/digital-employee/admin/actions', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
sync_record_id: data.syncRecordId,
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
action: data.action,
|
||||
request_payload: data.requestPayload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 记录数字员工日报发送结果
|
||||
export async function apiDigitalEmployeeAdminDailyReportSendRecord(
|
||||
data: DigitalEmployeeAdminRecordSubmitParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeAdminRecordResult>> {
|
||||
return request('/api/digital-employee/admin/daily-reports/send-records', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
sync_record_id: data.syncRecordId,
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
status: data.status,
|
||||
endpoint: data.endpoint,
|
||||
remote_id: data.remoteId,
|
||||
reason: data.reason,
|
||||
request_payload: data.requestPayload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 查询数字员工账单估算
|
||||
export async function apiDigitalEmployeeAdminBillingEstimates(
|
||||
data: DigitalEmployeeBillingEstimateParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeBillingEstimate>> {
|
||||
return request('/api/digital-employee/admin/billing/estimates', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
updated_from: data.updatedFrom,
|
||||
updated_to: data.updatedTo,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 记录数字员工审计归档摘要
|
||||
export async function apiDigitalEmployeeAdminAuditArchive(
|
||||
data: DigitalEmployeeAdminRecordSubmitParams,
|
||||
): Promise<RequestResponse<DigitalEmployeeAdminRecordResult>> {
|
||||
return request('/api/digital-employee/admin/audit-archives', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
sync_record_id: data.syncRecordId,
|
||||
client_key: data.clientKey,
|
||||
device_id: data.deviceId,
|
||||
entity_type: data.entityType,
|
||||
entity_id: data.entityId,
|
||||
status: data.status,
|
||||
archive_location: data.archiveLocation,
|
||||
archive_summary: data.archiveSummary,
|
||||
reason: data.reason,
|
||||
request_payload: data.requestPayload,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 查询网页应用列表
|
||||
export async function apiSystemResourceWebappList(
|
||||
data: SystemWebappListParams,
|
||||
|
||||
@@ -516,6 +516,430 @@ export interface SystemAgentListParams extends SystemPaginationParams {
|
||||
// 智能体列表分页响应
|
||||
export type SystemAgentPage = SystemPageResult<SystemAgentInfo>;
|
||||
|
||||
// 数字员工同步记录
|
||||
export interface DigitalEmployeeSyncRecordInfo {
|
||||
id: number;
|
||||
client_key: string;
|
||||
device_id?: string;
|
||||
outbox_id: string;
|
||||
entity_type:
|
||||
| 'plan'
|
||||
| 'task'
|
||||
| 'run'
|
||||
| 'event'
|
||||
| 'artifact'
|
||||
| 'approval'
|
||||
| string;
|
||||
entity_id: string;
|
||||
operation: string;
|
||||
payload: string;
|
||||
synced_at: string;
|
||||
modified: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
// 数字员工同步记录查询参数
|
||||
export interface DigitalEmployeeSyncRecordListParams
|
||||
extends SystemPaginationParams {
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
outboxId?: string;
|
||||
}
|
||||
|
||||
// 数字员工同步记录分页响应
|
||||
export interface DigitalEmployeeSyncRecordPage {
|
||||
total: number;
|
||||
page_no: number;
|
||||
page_size: number;
|
||||
records: DigitalEmployeeSyncRecordInfo[];
|
||||
}
|
||||
|
||||
export type DigitalEmployeeAdminWorkbenchView =
|
||||
| 'business'
|
||||
| 'interventions'
|
||||
| 'approval_governance'
|
||||
| 'approval_policies'
|
||||
| 'service_operations'
|
||||
| 'artifact_lifecycle'
|
||||
| 'notifications'
|
||||
| 'notification_policies'
|
||||
| 'audits'
|
||||
| 'daily_reports'
|
||||
| 'governance_commands'
|
||||
| 'route_governance'
|
||||
| 'policy_conflicts'
|
||||
| 'memory_governance'
|
||||
| 'memory_policies'
|
||||
| 'skill_audits'
|
||||
| 'policy_center'
|
||||
| 'scheduler_operations'
|
||||
| 'plan_step_dispatch'
|
||||
| 'plan_governance'
|
||||
| 'plan_revisions'
|
||||
| 'governance_policies'
|
||||
| 'plan_revision_materialization';
|
||||
|
||||
export interface DigitalEmployeeAdminWorkbenchRow {
|
||||
view: DigitalEmployeeAdminWorkbenchView | string;
|
||||
sync_record_id: number;
|
||||
sync_record_url?: string;
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
operation?: string;
|
||||
title?: string;
|
||||
status?: string;
|
||||
kind?: string;
|
||||
category?: string;
|
||||
level?: string;
|
||||
priority?: string;
|
||||
summary?: string;
|
||||
plan_id?: string;
|
||||
task_id?: string;
|
||||
run_id?: string;
|
||||
step_id?: string;
|
||||
dependency_status?: string;
|
||||
lease_state?: string;
|
||||
lease_source?: string;
|
||||
dispatch_status?: string;
|
||||
dispatch_policy_key?: string;
|
||||
max_per_sweep?: number;
|
||||
auto_dispatch_ready_steps?: boolean;
|
||||
catch_up_on_startup?: boolean;
|
||||
max_run_history?: number;
|
||||
orchestration_id?: string;
|
||||
trigger_source?: string;
|
||||
approval_id?: string;
|
||||
approval_policy_id?: string;
|
||||
conflict_policy_id?: string;
|
||||
decision_guard_status?: string;
|
||||
batch_accept_remote_disabled?: boolean;
|
||||
auto_decision_disabled?: boolean;
|
||||
policy_reason?: string;
|
||||
governance_policy_id?: string;
|
||||
policy_guard_status?: string;
|
||||
hard_interrupt_disabled?: boolean;
|
||||
artifact_id?: string;
|
||||
artifact_graph_id?: string;
|
||||
artifact_group_id?: string;
|
||||
group_key?: string;
|
||||
related_artifact_ids?: string[];
|
||||
artifact_count?: number;
|
||||
device_count?: number;
|
||||
version_count?: number;
|
||||
latest_artifact_id?: string;
|
||||
version_key?: string;
|
||||
cleanup_request_id?: string;
|
||||
cleanup_eligible_count?: number;
|
||||
batch_cleanup_disabled?: boolean;
|
||||
retention_conflict_status?: string;
|
||||
cleanup_conflict_status?: string;
|
||||
remote_delete_disabled?: boolean;
|
||||
notification_id?: string;
|
||||
collaboration_thread_id?: string;
|
||||
subscription_policy_id?: string;
|
||||
subscription_conflict_status?: string;
|
||||
notification_policy_id?: string;
|
||||
subscription_status?: string;
|
||||
desktop_enabled?: boolean;
|
||||
minimum_level?: string;
|
||||
channel?: string;
|
||||
delivery_status?: string;
|
||||
delivery_attempt_id?: string;
|
||||
send_request_id?: string;
|
||||
real_push_disabled?: boolean;
|
||||
conflict_status?: string;
|
||||
report_id?: string;
|
||||
daily_report_fact_id?: string;
|
||||
daily_report_table?: string;
|
||||
daily_report_materialized?: boolean;
|
||||
daily_report_physical_fact_status?: string;
|
||||
send_record_id?: string;
|
||||
send_channel?: string;
|
||||
send_request_status?: string;
|
||||
send_record_status?: string;
|
||||
formal_daily_report_page_disabled?: boolean;
|
||||
external_send_disabled?: boolean;
|
||||
third_party_channel_disabled?: boolean;
|
||||
approval_decision_status?: string;
|
||||
advanced_query_supported?: boolean;
|
||||
aggregation_scope?: string;
|
||||
materialized_source?: string;
|
||||
daily_report_approval_id?: string;
|
||||
daily_report_approval_action?: string;
|
||||
daily_report_approval_actor?: string;
|
||||
daily_report_approval_reason?: string;
|
||||
daily_report_approval_recorded_at?: string;
|
||||
route_decision_id?: string;
|
||||
service_id?: string;
|
||||
service_lease_id?: string;
|
||||
lease_holder?: string;
|
||||
lease_expires_at?: string;
|
||||
restart_record_id?: string;
|
||||
restart_count?: number;
|
||||
last_restart_at?: string;
|
||||
recovery_policy_id?: string;
|
||||
auto_recovery_status?: string;
|
||||
service_start_disabled?: boolean;
|
||||
service_stop_disabled?: boolean;
|
||||
lease_takeover_disabled?: boolean;
|
||||
auto_recovery_disabled?: boolean;
|
||||
recovery_action_request_id?: string;
|
||||
start_request_id?: string;
|
||||
stop_request_id?: string;
|
||||
lease_takeover_request_id?: string;
|
||||
auto_recovery_request_id?: string;
|
||||
start_service_disabled?: boolean;
|
||||
stop_service_disabled?: boolean;
|
||||
memory_id?: string;
|
||||
memory_key?: string;
|
||||
governance_status?: string;
|
||||
merge_candidate_count?: number;
|
||||
duplicate_memory_ids?: string[];
|
||||
latest_memory_id?: string;
|
||||
last_governance_action?: string;
|
||||
last_reviewed_at?: string;
|
||||
memory_policy_id?: string;
|
||||
memory_policy_status?: string;
|
||||
merge_policy_id?: string;
|
||||
merge_action_disabled?: boolean;
|
||||
remote_memory_policy_disabled?: boolean;
|
||||
skill_id?: string;
|
||||
skill_version?: string;
|
||||
install_status?: string;
|
||||
permission_scope?: string;
|
||||
policy_source?: string;
|
||||
policy_status?: string;
|
||||
skill_install_request_id?: string;
|
||||
skill_upgrade_request_id?: string;
|
||||
permission_policy_id?: string;
|
||||
permission_edit_request_id?: string;
|
||||
install_action_disabled?: boolean;
|
||||
upgrade_action_disabled?: boolean;
|
||||
permission_edit_disabled?: boolean;
|
||||
tool_name?: string;
|
||||
server_id?: string;
|
||||
audit_archive_id?: string;
|
||||
archive_status?: string;
|
||||
archive_location?: string;
|
||||
billing_estimate_id?: string;
|
||||
billing_source?: string;
|
||||
estimated?: boolean;
|
||||
diagnostic_id?: string;
|
||||
diagnostic_kind?: string;
|
||||
issue_code?: string;
|
||||
repair_action?: string;
|
||||
repair_status?: string;
|
||||
repair_reason?: string;
|
||||
safe_repair_disabled?: boolean;
|
||||
billing_actual_disabled?: boolean;
|
||||
sandbox_archive_file_disabled?: boolean;
|
||||
auto_repair_execution_disabled?: boolean;
|
||||
repair_approval_required?: boolean;
|
||||
recommended_action?: string;
|
||||
policy_kind?: string;
|
||||
policy_key?: string;
|
||||
policy_edit_request_id?: string;
|
||||
policy_edit_status?: string;
|
||||
policy_edit_reason?: string;
|
||||
merge_request_id?: string;
|
||||
memory_merge_request_id?: string;
|
||||
remote_memory_policy_request_id?: string;
|
||||
merge_recommendation?: string;
|
||||
remote_policy_edit_disabled?: boolean;
|
||||
auto_merge_disabled?: boolean;
|
||||
approval_policy_edit_disabled?: boolean;
|
||||
route_policy_edit_disabled?: boolean;
|
||||
route_recovery_disabled?: boolean;
|
||||
high_risk_execution_disabled?: boolean;
|
||||
schedule_id?: string;
|
||||
job_id?: string;
|
||||
revision_id?: string;
|
||||
revision_fact_id?: string;
|
||||
revision_status?: string;
|
||||
revision_materialized?: boolean;
|
||||
base_revision_id?: string;
|
||||
changed_fields?: string[];
|
||||
conflict_keys?: string[];
|
||||
recommended_resolution?: string;
|
||||
route_decision_count?: number;
|
||||
blocked_count?: number;
|
||||
approval_required_count?: number;
|
||||
missing_context_count?: number;
|
||||
not_mapped_count?: number;
|
||||
intervention_conflict_status?: string;
|
||||
occurred_at?: string;
|
||||
synced_at?: string;
|
||||
latest_synced_at?: string;
|
||||
materialized?: boolean;
|
||||
client_key_masked?: string;
|
||||
device_id?: string;
|
||||
business_view?: Record<string, unknown>;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminWorkbenchListParams
|
||||
extends SystemPaginationParams {
|
||||
view: DigitalEmployeeAdminWorkbenchView;
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
status?: string;
|
||||
kind?: string;
|
||||
category?: string;
|
||||
level?: string;
|
||||
priority?: string;
|
||||
updatedFrom?: string;
|
||||
updatedTo?: string;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminWorkbenchPage {
|
||||
total: number;
|
||||
page_no: number;
|
||||
page_size: number;
|
||||
records: DigitalEmployeeAdminWorkbenchRow[];
|
||||
}
|
||||
|
||||
export interface DigitalEmployeePolicySnapshotRow {
|
||||
policy_kind?: string;
|
||||
policy_key?: string;
|
||||
status?: string;
|
||||
summary?: string;
|
||||
client_count?: number;
|
||||
device_count?: number;
|
||||
conflict_keys?: string[];
|
||||
latest_synced_at?: string;
|
||||
recommended_resolution?: string;
|
||||
route_decision_count?: number;
|
||||
blocked_count?: number;
|
||||
approval_required_count?: number;
|
||||
missing_context_count?: number;
|
||||
not_mapped_count?: number;
|
||||
intervention_conflict_status?: string;
|
||||
memory_policy_id?: string;
|
||||
memory_policy_status?: string;
|
||||
merge_policy_id?: string;
|
||||
merge_action_disabled?: boolean;
|
||||
remote_memory_policy_disabled?: boolean;
|
||||
approval_policy_id?: string;
|
||||
conflict_policy_id?: string;
|
||||
decision_guard_status?: string;
|
||||
batch_accept_remote_disabled?: boolean;
|
||||
auto_decision_disabled?: boolean;
|
||||
policy_reason?: string;
|
||||
policy_edit_request_id?: string;
|
||||
policy_edit_status?: string;
|
||||
policy_edit_reason?: string;
|
||||
merge_request_id?: string;
|
||||
merge_recommendation?: string;
|
||||
remote_policy_edit_disabled?: boolean;
|
||||
auto_merge_disabled?: boolean;
|
||||
approval_policy_edit_disabled?: boolean;
|
||||
route_policy_edit_disabled?: boolean;
|
||||
route_recovery_disabled?: boolean;
|
||||
high_risk_execution_disabled?: boolean;
|
||||
dispatch_policy_key?: string;
|
||||
max_per_sweep?: number;
|
||||
auto_dispatch_ready_steps?: boolean;
|
||||
catch_up_on_startup?: boolean;
|
||||
max_run_history?: number;
|
||||
sync_record_url?: string;
|
||||
client_key_masked?: string;
|
||||
device_id?: string;
|
||||
business_view?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeePolicySnapshotListParams
|
||||
extends SystemPaginationParams {
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
status?: string;
|
||||
policyKind?: string;
|
||||
updatedFrom?: string;
|
||||
updatedTo?: string;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeePolicySnapshotPage {
|
||||
total: number;
|
||||
page_no: number;
|
||||
page_size: number;
|
||||
records: DigitalEmployeePolicySnapshotRow[];
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminActionSubmitParams {
|
||||
syncRecordId?: number;
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
action: string;
|
||||
requestPayload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminActionResult {
|
||||
ok: boolean;
|
||||
status?: string;
|
||||
reason_codes?: string[];
|
||||
action?: {
|
||||
id: number;
|
||||
status: string;
|
||||
action: string;
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminRecordSubmitParams {
|
||||
syncRecordId?: number;
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
status?: string;
|
||||
endpoint?: string;
|
||||
remoteId?: string;
|
||||
archiveLocation?: string;
|
||||
archiveSummary?: Record<string, unknown>;
|
||||
reason?: string;
|
||||
requestPayload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeAdminRecordResult {
|
||||
ok: boolean;
|
||||
status?: string;
|
||||
reason_codes?: string[];
|
||||
record?: DigitalEmployeeAdminActionResult['action'];
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeBillingEstimateParams {
|
||||
clientKey?: string;
|
||||
deviceId?: string;
|
||||
updatedFrom?: string;
|
||||
updatedTo?: string;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeBillingModelEstimate {
|
||||
total_tokens?: number;
|
||||
input_tokens?: number;
|
||||
output_tokens?: number;
|
||||
estimated_cost_usd?: number | string;
|
||||
request_count?: number;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeBillingEstimate {
|
||||
total_tokens?: number;
|
||||
input_tokens?: number;
|
||||
output_tokens?: number;
|
||||
estimated_cost_usd?: number | string;
|
||||
request_count?: number;
|
||||
estimated?: boolean;
|
||||
by_model?: Record<string, DigitalEmployeeBillingModelEstimate>;
|
||||
updated_at?: string;
|
||||
source_records?: number[];
|
||||
}
|
||||
|
||||
// 网页应用信息
|
||||
export type SystemWebappInfo = SystemResourceInfo;
|
||||
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
/**
|
||||
* Axe smoke coverage for low-level shared UI components.
|
||||
*/
|
||||
import {
|
||||
ActionMenu,
|
||||
CopyButton,
|
||||
CopyIconButton,
|
||||
MenuListItem,
|
||||
SecondMenuItem,
|
||||
} from '@/components/base';
|
||||
import ActionMenu from '@/components/base/ActionMenu';
|
||||
import CopyButton from '@/components/base/CopyButton';
|
||||
import CopyIconButton from '@/components/base/CopyIconButton';
|
||||
import MenuListItem from '@/components/base/MenuListItem';
|
||||
import SecondMenuItem from '@/components/base/SecondMenuItem';
|
||||
import { render } from '@testing-library/react';
|
||||
import { axe } from 'jest-axe';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/components/base/SvgIcon', () => ({
|
||||
default: ({ name }: { name: string }) => (
|
||||
<span aria-hidden="true" data-icon={name} />
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@/services/i18nRuntime', () => ({
|
||||
dict: (key: string) => key,
|
||||
}));
|
||||
|
||||
const assertNoViolations = async (element: HTMLElement) => {
|
||||
const results = await axe(element);
|
||||
expect(results.violations).toEqual([]);
|
||||
|
||||
@@ -1,4 +1,52 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { TextDecoder, TextEncoder } from 'node:util';
|
||||
|
||||
Object.assign(globalThis, { TextDecoder, TextEncoder });
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
Object.assign(window, { TextDecoder, TextEncoder });
|
||||
}
|
||||
|
||||
class MemoryStorage implements Storage {
|
||||
private readonly store = new Map<string, string>();
|
||||
|
||||
get length() {
|
||||
return this.store.size;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.store.clear();
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.store.get(key) ?? null;
|
||||
}
|
||||
|
||||
key(index: number) {
|
||||
return Array.from(this.store.keys())[index] ?? null;
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
this.store.delete(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.store.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const ensureStorage = (name: 'localStorage' | 'sessionStorage') => {
|
||||
const current = globalThis[name];
|
||||
if (current) return;
|
||||
|
||||
Object.defineProperty(globalThis, name, {
|
||||
configurable: true,
|
||||
value: new MemoryStorage(),
|
||||
});
|
||||
};
|
||||
|
||||
ensureStorage('localStorage');
|
||||
ensureStorage('sessionStorage');
|
||||
|
||||
// antd 响应式依赖 matchMedia,测试环境补齐
|
||||
if (typeof window !== 'undefined' && !window.matchMedia) {
|
||||
|
||||
@@ -11,6 +11,11 @@ export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
environmentOptions: {
|
||||
jsdom: {
|
||||
url: 'http://localhost/',
|
||||
},
|
||||
},
|
||||
setupFiles: './tests/setupTests.ts',
|
||||
exclude: [...configDefaults.exclude, 'tests/**/*[Vv]2*.test.{ts,tsx}'],
|
||||
alias: {
|
||||
|
||||
3
qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="./logo.png" />
|
||||
<title>数字员工</title>
|
||||
<script>
|
||||
window.__QIMINGCLAW_EMBEDDED_DIGITAL__ = true;
|
||||
try {
|
||||
window.localStorage.setItem("qimingclaw_digital_token", "qimingclaw_embedded_digital_employee");
|
||||
window.localStorage.removeItem("zeroclaw_token");
|
||||
window.sessionStorage.setItem("qimingclaw_digital_logged_in", "1");
|
||||
window.sessionStorage.removeItem("sgrobot_logged_in");
|
||||
} catch (error) {
|
||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 372 KiB |
4054
qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/package-lock.json
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@qiming-ai/sgrobot-digital-embedded",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -b && vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"animejs": "^4.4.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@types/node": "^25.3.0",
|
||||
"@types/react": "^19.0.7",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# qimingclaw embedded patches
|
||||
|
||||
This directory records qimingclaw-only adaptations around the sgRobot digital employee UI.
|
||||
|
||||
- `src/main.tsx` mounts the digital employee page directly at `#/digital`.
|
||||
- `src/lib/basePath.ts` fixes static assets under `/sgrobot-digital`.
|
||||
- `src/lib/tauri.ts` disables sgRobot/Tauri gateway detection inside the Electron webview.
|
||||
- `src/lib/qimingclawAdapter.ts` provides the first qimingclaw-native digital employee API projection.
|
||||
- `src/lib/api.ts` calls the qimingclaw adapter before falling back to normal HTTP fetch.
|
||||
- `index.html` injects a local token so copied sgRobot pages do not show the pairing screen.
|
||||
- `window.QimingClawBridge.digital.getSnapshot()` is read when the page runs inside qimingclaw's Electron webview.
|
||||
|
||||
Run `npm run sync:sgrobot-digital` from `crates/agent-electron-client` after changing the upstream sgRobot digital UI.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Digital Employee Static Assets
|
||||
|
||||
This directory is owned by the React digital employee frontend.
|
||||
|
||||
- `avatars/` keeps the original digital employee character images used by the home page `Avatar3D` stage.
|
||||
- Do not place the legacy Vue standalone application bundle here.
|
||||
- If an avatar file is renamed, update `src/pages/digital/CommandDeck.tsx` in the same change.
|
||||
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 483 KiB |
|
After Width: | Height: | Size: 507 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 552 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 543 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
@@ -0,0 +1,29 @@
|
||||
import Gem from './Gem';
|
||||
import Stage from './Stage';
|
||||
|
||||
interface Avatar3DProps {
|
||||
avatarSrc: string;
|
||||
altText?: string;
|
||||
statusLabel?: string;
|
||||
}
|
||||
|
||||
export default function Avatar3D({ avatarSrc, altText = '数字员工', statusLabel }: Avatar3DProps) {
|
||||
return (
|
||||
<div className="de-avatar-container" aria-hidden="true">
|
||||
<div className="de-avatar-glass-space">
|
||||
<div className="de-avatar-roof">
|
||||
<div className="de-avatar-roof-face" />
|
||||
<div className="de-avatar-roof-glow" />
|
||||
</div>
|
||||
<div className="de-avatar-glass-left" />
|
||||
<div className="de-avatar-glass-right" />
|
||||
<Gem className="de-gem-1" />
|
||||
<Gem className="de-gem-2" />
|
||||
<Gem className="de-gem-3" />
|
||||
</div>
|
||||
{statusLabel && <div className="de-avatar-state-label">{statusLabel}</div>}
|
||||
<img src={avatarSrc} alt={altText} className="de-avatar-img" />
|
||||
<Stage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface CardTitleBarProps {
|
||||
title: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export default function CardTitleBar({ title, children }: CardTitleBarProps) {
|
||||
return (
|
||||
<div className="de-card-title-bar">
|
||||
<span className="de-card-title-bar-text">{title}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
buildDemoFlowEvents,
|
||||
buildDemoPlanMessages,
|
||||
buildDemoRuns,
|
||||
buildDemoTasks,
|
||||
getDemoMissions,
|
||||
} from '@/pages/digital/demoScenario';
|
||||
import {
|
||||
displayBackendMessage,
|
||||
displayBusinessText,
|
||||
displayEventKind,
|
||||
displayStatus,
|
||||
type Mission,
|
||||
} from '@/lib/consoleDataAdapter';
|
||||
import { getDebugPlanFlow, getDebugTasksForPlan, getPlanMessages, type PlanMessage } from '@/lib/api';
|
||||
import type { DebugRun, DebugTask, FlowResponse } from '@/types/api';
|
||||
import StatusBadge from './StatusBadge';
|
||||
|
||||
interface FlowDetailPanelProps {
|
||||
mission: Mission;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function stepVariant(status: string): 'info' | 'success' | 'warning' | 'danger' {
|
||||
switch (status) {
|
||||
case 'succeeded': case 'completed': case 'Succeeded': return 'success';
|
||||
case 'failed': return 'danger';
|
||||
case 'Failed': return 'danger';
|
||||
case 'running': case 'active': case 'Running': return 'info';
|
||||
default: return 'warning';
|
||||
}
|
||||
}
|
||||
|
||||
function fmtTime(ts?: string | null): string {
|
||||
if (!ts) return '未记录';
|
||||
return new Date(ts).toLocaleString('zh-CN', { hour12: false });
|
||||
}
|
||||
|
||||
function missionPeriodLabel(mission: Mission): string {
|
||||
const text = `${mission.id} ${mission.title} ${mission.objective}`.toLowerCase();
|
||||
if (text.includes('month') || text.includes('monthly') || text.includes('月')) return '月度任务';
|
||||
if (text.includes('week') || text.includes('weekly') || text.includes('周')) return '周任务';
|
||||
if (text.includes('临时') || text.includes('adhoc') || text.includes('hotlist') || text.includes('monitor')) return '临时任务';
|
||||
return '日常任务';
|
||||
}
|
||||
|
||||
function isDone(status: string): boolean {
|
||||
return ['succeeded', 'success', 'finished', 'complete', 'completed'].includes(status.toLowerCase());
|
||||
}
|
||||
|
||||
function isFailed(status: string): boolean {
|
||||
return ['failed', 'error', 'cancelled', 'canceled', 'aborted', 'rejected', 'deadletter'].includes(status.toLowerCase());
|
||||
}
|
||||
|
||||
function progressText(mission: Mission): string {
|
||||
const total = mission.steps.length;
|
||||
if (total === 0) return displayStatus(mission.status);
|
||||
const done = mission.steps.filter((step) => isDone(step.status)).length;
|
||||
const failed = mission.steps.filter((step) => isFailed(step.status)).length;
|
||||
const running = mission.steps.filter((step) => ['running', 'active', 'queued', 'leased'].includes(step.status.toLowerCase())).length;
|
||||
const parts = [`已完成 ${done}/${total}`];
|
||||
if (failed > 0) parts.push(`失败 ${failed}`);
|
||||
if (running > 0 && failed === 0) parts.push(`处理中 ${running}`);
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function businessTimelineMessage(message: string): string {
|
||||
return message
|
||||
.replace(/^启动计划:/, '接收委托:')
|
||||
.replace(/^准备步骤:/, '准备工作:')
|
||||
.replace(/^创建任务:/, '安排工作:')
|
||||
.replace(/^任务失败:/, '工作失败:')
|
||||
.replace(/^开始处理:/, '开始处理:')
|
||||
.replace(/^完成任务:/, '完成工作:')
|
||||
.replace(/^调用技能:/, '数字员工处理:')
|
||||
.replace(/^执行开始:/, '开始执行:')
|
||||
.replace(/^执行完成:/, '执行完成:')
|
||||
.replace(/^执行失败:/, '执行失败:')
|
||||
.replace(/,将使用[^,。]+/, '')
|
||||
.replace(/,分配给[^,。]+/, '');
|
||||
}
|
||||
|
||||
function isFailureMessage(message: string): boolean {
|
||||
return /失败|failed|error|timeout|unavailable/i.test(message);
|
||||
}
|
||||
|
||||
function businessKindLabel(kind: string, message = ''): string {
|
||||
if (isFailureMessage(message)) return '失败';
|
||||
if (kind === 'artifact') return '产物';
|
||||
if (kind === 'task_summary') return '结果';
|
||||
if (kind === 'task_running' || kind === 'run') return '处理中';
|
||||
if (kind === 'task_done' || kind === 'run_done') return '完成';
|
||||
return '过程';
|
||||
}
|
||||
|
||||
function messageRoleLabel(role: string): string {
|
||||
if (role === 'user') return '用户';
|
||||
if (role === 'assistant') return '数字员工';
|
||||
if (role === 'system') return '系统';
|
||||
return '消息';
|
||||
}
|
||||
|
||||
type DetailEvent = {
|
||||
event_id: string;
|
||||
kind: string;
|
||||
occurred_at: string | null;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type ApprovalSummary = {
|
||||
id: string;
|
||||
status: string;
|
||||
note: string;
|
||||
requestedAt: string | null;
|
||||
decidedAt: string | null;
|
||||
decidedBy: string | null;
|
||||
runId: string;
|
||||
taskId: string | null;
|
||||
planId: string | null;
|
||||
targetKind: string;
|
||||
targetId: string;
|
||||
};
|
||||
|
||||
function skillLabel(skillId: string | null | undefined): string {
|
||||
return displayBusinessText(skillId || '', skillId || '自动化技能');
|
||||
}
|
||||
|
||||
function toolBusinessMessage(toolName: string, success: string, elapsedMs: string): string {
|
||||
const result = success === 'true' ? '成功' : '失败';
|
||||
if (toolName.includes('zhihu-hotlist') && toolName.includes('extract_hotlist')) {
|
||||
return `调用技能:知乎热榜采集,结果:${result},耗时 ${elapsedMs}ms`;
|
||||
}
|
||||
if (toolName.includes('office-export-xlsx') && toolName.includes('export_xlsx')) {
|
||||
return `调用技能:Excel 导出,结果:${result},耗时 ${elapsedMs}ms`;
|
||||
}
|
||||
return `调用技能:${toolName},结果:${result},耗时 ${elapsedMs}ms`;
|
||||
}
|
||||
|
||||
function valueText(value: unknown): string {
|
||||
return typeof value === 'string' ? value : '';
|
||||
}
|
||||
|
||||
function eventTime(event: DetailEvent): string {
|
||||
return event.occurred_at || '';
|
||||
}
|
||||
|
||||
function earliestTime(values: Array<string | null | undefined>): string | null {
|
||||
return values
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.sort((a, b) => a.localeCompare(b))[0] || null;
|
||||
}
|
||||
|
||||
function latestByResultSubject(events: DetailEvent[]): DetailEvent[] {
|
||||
const bySubject = new Map<string, DetailEvent>();
|
||||
for (const event of events) {
|
||||
const subject = event.message.split(',结果:')[0]?.split(',状态:')[0] || event.message;
|
||||
const current = bySubject.get(subject);
|
||||
if (!current || eventTime(event).localeCompare(eventTime(current)) > 0) {
|
||||
bySubject.set(subject, event);
|
||||
}
|
||||
}
|
||||
return Array.from(bySubject.values());
|
||||
}
|
||||
|
||||
function approvalSummariesFromRuns(runs: DebugRun[]): ApprovalSummary[] {
|
||||
const byId = new Map<string, ApprovalSummary>();
|
||||
for (const run of runs) {
|
||||
for (const approval of run.approvals || []) {
|
||||
const id = valueText(approval.approval_id) || `approval-${run.run_id}`;
|
||||
byId.set(id, {
|
||||
id,
|
||||
status: valueText(approval.status) || 'requested',
|
||||
note: valueText(approval.note) || '等待人工审批',
|
||||
requestedAt: valueText(approval.requested_at) || null,
|
||||
decidedAt: valueText(approval.decided_at) || null,
|
||||
decidedBy: valueText(approval.decided_by) || null,
|
||||
runId: valueText(approval.run_id) || run.run_id,
|
||||
taskId: valueText(approval.task_id) || run.task_id || null,
|
||||
planId: valueText(approval.plan_id) || run.plan_id || null,
|
||||
targetKind: valueText(approval.target_kind) || 'task',
|
||||
targetId: valueText(approval.target_id) || run.task_id || run.run_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(byId.values()).sort((a, b) => (b.requestedAt || '').localeCompare(a.requestedAt || ''));
|
||||
}
|
||||
|
||||
function objectValue(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function normalizeOutputPath(value: string): string {
|
||||
return value.replace(/\\\\/g, '\\');
|
||||
}
|
||||
|
||||
function buildOperationalTimeline(
|
||||
mission: Mission,
|
||||
tasks: DebugTask[],
|
||||
runs: DebugRun[],
|
||||
messages: PlanMessage[],
|
||||
flowEvents?: FlowResponse['events'],
|
||||
): DetailEvent[] {
|
||||
const events: DetailEvent[] = [];
|
||||
const add = (kind: string, occurredAt: string | null | undefined, message: string, id: string) => {
|
||||
if (!message.trim()) return;
|
||||
events.push({ event_id: id, kind, occurred_at: occurredAt ?? null, message });
|
||||
};
|
||||
const addOutputFacts = (rawOutput: string, occurredAt: string | null | undefined, idPrefix: string, sourceName: string) => {
|
||||
let parsedOutputPath = false;
|
||||
try {
|
||||
const parsed = objectValue(JSON.parse(rawOutput));
|
||||
if (parsed) {
|
||||
const label = typeof parsed.label === 'string' ? parsed.label.trim() : '';
|
||||
const value = parsed.value;
|
||||
if (label && value !== undefined && value !== null) {
|
||||
add('task_summary', occurredAt, `${label}:${String(value)}`, `metric-${idPrefix}-${sourceName}`);
|
||||
}
|
||||
const outputPath = typeof parsed.output_path === 'string' ? parsed.output_path.trim() : '';
|
||||
if (outputPath) {
|
||||
parsedOutputPath = true;
|
||||
add('artifact', occurredAt, `产物生成:${normalizeOutputPath(outputPath)}`, `artifact-${idPrefix}-${sourceName}`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Some execution messages are human-readable text. Regex extraction below handles those.
|
||||
}
|
||||
|
||||
const rowCountMatch = rawOutput.match(/"row_count"\s*:\s*(\d+)/);
|
||||
if (rowCountMatch?.[1]) {
|
||||
add('task_summary', occurredAt, `数据处理完成:共 ${rowCountMatch[1]} 条记录`, `rows-${idPrefix}-${sourceName}`);
|
||||
}
|
||||
const pathMatch = rawOutput.match(/"output_path"\s*:\s*"([^"]+\.(?:xlsx|xls|csv|json|html|pdf|png|jpg|jpeg|txt))"/i);
|
||||
const outputPath = pathMatch?.[1];
|
||||
if (outputPath && !parsedOutputPath) {
|
||||
add('artifact', occurredAt, `产物生成:${normalizeOutputPath(outputPath)}`, `artifact-regex-${idPrefix}-${sourceName}`);
|
||||
}
|
||||
};
|
||||
|
||||
add('plan', mission.createdAt || mission.updatedAt, `启动计划:${mission.title},目标:${mission.objective}`, `plan-${mission.id}`);
|
||||
|
||||
if (tasks.length === 0) {
|
||||
for (const step of mission.steps) {
|
||||
const skills = step.preferredSkills.map(skillLabel).join('、') || '自动化技能';
|
||||
add('task', mission.updatedAt || mission.createdAt, `准备步骤:${step.title},将使用 ${skills}`, `step-${step.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const task of tasks) {
|
||||
const skill = skillLabel(task.assigned_skill_id);
|
||||
const taskRuns = runs.filter((run) => run.task_id === task.task_id);
|
||||
const firstRunAt = earliestTime(taskRuns.map((run) => run.created_at));
|
||||
const taskCreatedAt = earliestTime([task.created_at, task.started_at, firstRunAt]);
|
||||
add('task', taskCreatedAt, `创建任务:${task.title || '子任务'},分配给 ${skill}`, `task-create-${task.task_id}`);
|
||||
if (task.started_at) add('task_running', task.started_at, `开始处理:${task.title || '子任务'}`, `task-start-${task.task_id}`);
|
||||
if (task.finished_at) {
|
||||
const taskDoneVerb = isFailed(task.status) ? '任务失败' : '完成任务';
|
||||
const errorDetail = isFailed(task.status) && task.error_message ? `,原因:${task.error_message}` : '';
|
||||
add('task_done', task.finished_at, `${taskDoneVerb}:${task.title || '子任务'},状态:${displayStatus(task.status)}${errorDetail}`, `task-finish-${task.task_id}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const run of runs) {
|
||||
const task = tasks.find((item) => item.task_id === run.task_id);
|
||||
const taskTitle = task?.title || '任务执行';
|
||||
const runSkill = skillLabel(run.skill_id || task?.assigned_skill_id);
|
||||
const hasSessionToolForTask = messages.some((message) => (message.content || '').includes(`[Task ${run.task_id}] tool_call`));
|
||||
if (!hasSessionToolForTask) {
|
||||
add('skill_tool', run.created_at, `调用技能:${runSkill},处理:${taskTitle}`, `run-skill-${run.run_id}`);
|
||||
}
|
||||
add('run', run.created_at, `执行开始:${taskTitle},第 ${run.attempt_no || 1} 次`, `run-start-${run.run_id}`);
|
||||
if (run.completed_at) {
|
||||
const runDoneVerb = isFailed(run.lifecycle) ? '执行失败' : '执行完成';
|
||||
add('run_done', run.completed_at, `${runDoneVerb}:${taskTitle},结果:${displayStatus(run.lifecycle)}`, `run-done-${run.run_id}`);
|
||||
}
|
||||
for (const [index, runEvent] of (run.events || []).entries()) {
|
||||
addOutputFacts(
|
||||
valueText(runEvent.message),
|
||||
runEvent.occurred_at || run.completed_at || run.created_at,
|
||||
`run-${run.run_id}-${runEvent.event_id || index}`,
|
||||
run.skill_id || task?.assigned_skill_id || 'run',
|
||||
);
|
||||
}
|
||||
for (const [index, artifact] of (run.artifacts || []).entries()) {
|
||||
const uri = valueText(artifact.uri);
|
||||
const name = valueText(artifact.name) || uri;
|
||||
if (uri || name) {
|
||||
add('artifact', run.completed_at || run.created_at, `产物生成:${name}${uri && uri !== name ? `(${uri})` : ''}`, `run-artifact-${run.run_id}-${index}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const message of messages) {
|
||||
const content = message.content || '';
|
||||
const toolMatch = content.match(/\[Task ([^\]]+)\] tool_call name=([^\s]+) success=(true|false) elapsed_ms=(\d+) args=(.*?) output=(.*)$/s);
|
||||
if (toolMatch) {
|
||||
const taskId = toolMatch[1] || '';
|
||||
const toolName = toolMatch[2] || '';
|
||||
const success = toolMatch[3] || 'false';
|
||||
const elapsedMs = toolMatch[4] || '0';
|
||||
const output = toolMatch[6] || '';
|
||||
add('skill_tool', message.created_at, toolBusinessMessage(toolName, success, elapsedMs), `tool-${taskId}-${message.created_at}-${toolName}`);
|
||||
addOutputFacts(output, message.created_at, `message-${message.created_at}`, toolName);
|
||||
continue;
|
||||
}
|
||||
const hotlistCount = content.match(/共采集到\s*\**\s*(\d+)\s*条/);
|
||||
if (hotlistCount?.[1]) {
|
||||
add('task_summary', message.created_at, `采集完成:获得 ${hotlistCount[1]} 条知乎热榜数据`, `summary-hotlist-${message.created_at}`);
|
||||
continue;
|
||||
}
|
||||
if (content.includes('[Task ') && content.includes('Excel')) {
|
||||
add('task_summary', message.created_at, '导出完成:Excel 文件已生成', `summary-excel-${message.created_at}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (events.length <= 1) {
|
||||
for (const event of flowEvents || []) {
|
||||
add(event.kind, event.occurred_at, displayBackendMessage(event.message, event.message), event.event_id);
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
.sort((a, b) => (a.occurred_at || '').localeCompare(b.occurred_at || ''))
|
||||
.slice(-40);
|
||||
}
|
||||
|
||||
export default function FlowDetailPanel({ mission, onClose }: FlowDetailPanelProps) {
|
||||
const [tasks, setTasks] = useState<DebugTask[]>([]);
|
||||
const [runs, setRuns] = useState<DebugRun[]>([]);
|
||||
const [flow, setFlow] = useState<FlowResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [sessionMessages, setSessionMessages] = useState<PlanMessage[]>([]);
|
||||
const [sessionOpen, setSessionOpen] = useState(false);
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const [sessionLoading, setSessionLoading] = useState(false);
|
||||
const [sessionError, setSessionError] = useState<string | null>(null);
|
||||
|
||||
const planId = mission.sourceIds.planId || mission.id;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const refresh = () => {
|
||||
setLoading(true);
|
||||
Promise.allSettled([
|
||||
getDebugTasksForPlan(planId),
|
||||
getDebugPlanFlow(planId),
|
||||
getPlanMessages(planId),
|
||||
]).then(([tasksResult, flowResult, messagesResult]) => {
|
||||
if (cancelled) return;
|
||||
const demoMission = getDemoMissions().find((item) => item.id === planId);
|
||||
const fallbackTasks = demoMission ? buildDemoTasks([demoMission]) : [];
|
||||
const fallbackRuns = fallbackTasks.length > 0 ? buildDemoRuns(fallbackTasks) : [];
|
||||
if (tasksResult.status === 'fulfilled') {
|
||||
setTasks(tasksResult.value.tasks && tasksResult.value.tasks.length > 0 ? tasksResult.value.tasks : fallbackTasks);
|
||||
setRuns(tasksResult.value.runs && tasksResult.value.runs.length > 0 ? tasksResult.value.runs : fallbackRuns);
|
||||
} else {
|
||||
setTasks(fallbackTasks);
|
||||
setRuns(fallbackRuns);
|
||||
}
|
||||
if (flowResult.status === 'fulfilled' && flowResult.value?.events && flowResult.value.events.length > 0) {
|
||||
setFlow(flowResult.value);
|
||||
} else {
|
||||
setFlow(demoMission ? { events: buildDemoFlowEvents(demoMission) } as FlowResponse : null);
|
||||
}
|
||||
if (messagesResult.status === 'fulfilled' && messagesResult.value.messages.length > 0) {
|
||||
setSessionMessages(messagesResult.value.messages);
|
||||
} else if (demoMission) {
|
||||
setSessionMessages(buildDemoPlanMessages(demoMission));
|
||||
} else {
|
||||
setSessionMessages([]);
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
const timer = window.setInterval(refresh, 3000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
}, [planId]);
|
||||
|
||||
useEffect(() => {
|
||||
setSessionMessages([]);
|
||||
setSessionOpen(false);
|
||||
setAdvancedOpen(false);
|
||||
setSessionError(null);
|
||||
}, [planId]);
|
||||
|
||||
const loadSessionMessages = async () => {
|
||||
if (sessionLoading) return;
|
||||
setSessionOpen(true);
|
||||
setSessionLoading(true);
|
||||
setSessionError(null);
|
||||
try {
|
||||
const data = await getPlanMessages(planId);
|
||||
if (data.messages && data.messages.length > 0) {
|
||||
setSessionMessages(data.messages);
|
||||
} else {
|
||||
const demoMission = getDemoMissions().find((item) => item.id === planId);
|
||||
setSessionMessages(demoMission ? buildDemoPlanMessages(demoMission) : []);
|
||||
}
|
||||
} catch (err) {
|
||||
const demoMission = getDemoMissions().find((item) => item.id === planId);
|
||||
if (demoMission) {
|
||||
setSessionMessages(buildDemoPlanMessages(demoMission));
|
||||
setSessionError(null);
|
||||
} else {
|
||||
setSessionMessages([]);
|
||||
setSessionError(err instanceof Error ? err.message : '读取会话失败');
|
||||
}
|
||||
} finally {
|
||||
setSessionLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const runsByTask = useMemo(() => {
|
||||
const grouped = new Map<string, DebugRun[]>();
|
||||
for (const run of runs) {
|
||||
const list = grouped.get(run.task_id) || [];
|
||||
list.push(run);
|
||||
grouped.set(run.task_id, list);
|
||||
}
|
||||
for (const list of grouped.values()) {
|
||||
list.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''));
|
||||
}
|
||||
return grouped;
|
||||
}, [runs]);
|
||||
|
||||
const eventTimeline = useMemo(
|
||||
() => buildOperationalTimeline(mission, tasks, runs, sessionMessages, flow?.events),
|
||||
[flow?.events, mission, runs, sessionMessages, tasks],
|
||||
);
|
||||
|
||||
const businessTimeline = useMemo(
|
||||
() => eventTimeline
|
||||
.filter((event) => event.kind !== 'skill_tool')
|
||||
.slice(-12),
|
||||
[eventTimeline],
|
||||
);
|
||||
|
||||
const approvalSummaries = useMemo(
|
||||
() => approvalSummariesFromRuns(runs),
|
||||
[runs],
|
||||
);
|
||||
|
||||
const resultEvents = useMemo(
|
||||
() => {
|
||||
const facts = eventTimeline.filter((event) => ['artifact', 'task_summary'].includes(event.kind));
|
||||
const taskResults = eventTimeline.filter((event) => event.kind === 'task_done');
|
||||
const runResults = latestByResultSubject(eventTimeline.filter((event) => event.kind === 'run_done'));
|
||||
const terminal = taskResults.length > 0 ? taskResults : runResults;
|
||||
return [...facts, ...terminal]
|
||||
.sort((a, b) => eventTime(a).localeCompare(eventTime(b)))
|
||||
.slice(-6);
|
||||
},
|
||||
[eventTimeline],
|
||||
);
|
||||
|
||||
const latestEventAt = useMemo(
|
||||
() => eventTimeline.reduce<string | null>((latest, event) => {
|
||||
if (!event.occurred_at) return latest;
|
||||
if (!latest || event.occurred_at.localeCompare(latest) > 0) return event.occurred_at;
|
||||
return latest;
|
||||
}, null),
|
||||
[eventTimeline],
|
||||
);
|
||||
const displayUpdatedAt = mission.updatedAt || latestEventAt;
|
||||
|
||||
const openAdvanced = () => {
|
||||
const nextOpen = !advancedOpen;
|
||||
setAdvancedOpen(nextOpen);
|
||||
if (nextOpen && !sessionOpen) {
|
||||
void loadSessionMessages();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="de-drawer" role="dialog" aria-label={mission.title}>
|
||||
<div className="de-drawer-head">
|
||||
<div className="de-drawer-title-group">
|
||||
<span className="de-drawer-eyebrow">委托详情</span>
|
||||
<h3>{mission.title}</h3>
|
||||
</div>
|
||||
<button className="de-btn-secondary" onClick={onClose} aria-label="关闭">✕</button>
|
||||
</div>
|
||||
<div className="de-drawer-status-line">
|
||||
<StatusBadge variant={stepVariant(mission.status)} label={displayStatus(mission.status)} />
|
||||
<span className="de-drawer-meta">{missionPeriodLabel(mission)}</span>
|
||||
<span className="de-drawer-meta">{progressText(mission)}</span>
|
||||
</div>
|
||||
<div className="de-drawer-timeline">
|
||||
<section className="de-detail-section">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>委托概览</h4>
|
||||
</div>
|
||||
<div className="de-detail-kv">
|
||||
<span>业务目标</span><strong>{mission.objective || '未填写'}</strong>
|
||||
<span>周期</span><strong>{missionPeriodLabel(mission)}</strong>
|
||||
<span>当前进度</span><strong>{progressText(mission)}</strong>
|
||||
<span>启动时间</span><strong>{fmtTime(mission.createdAt)}</strong>
|
||||
<span>状态</span><strong>{displayStatus(mission.status)}</strong>
|
||||
<span>更新时间</span><strong>{fmtTime(displayUpdatedAt)}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{approvalSummaries.length > 0 ? (
|
||||
<section className="de-detail-section">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>审批状态</h4>
|
||||
<span className="de-chain-plan-id">{approvalSummaries.length} 项执行期审批</span>
|
||||
</div>
|
||||
<div className="de-event-list">
|
||||
{approvalSummaries.map((approval) => (
|
||||
<div key={`approval-${approval.id}`} className="de-event-row">
|
||||
<strong>{displayStatus(approval.status)}</strong>
|
||||
<span>
|
||||
{approval.note}
|
||||
{' '}
|
||||
{approval.taskId ? `Task ${approval.taskId}` : `Target ${approval.targetKind}:${approval.targetId}`}
|
||||
{approval.runId ? ` / Run ${approval.runId}` : ''}
|
||||
{approval.decidedBy ? ` / ${approval.decidedBy}` : ''}
|
||||
</span>
|
||||
<time>{fmtTime(approval.decidedAt || approval.requestedAt)}</time>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="de-detail-section de-execution-chain">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>业务过程</h4>
|
||||
<span className="de-chain-plan-id">最近更新:{fmtTime(displayUpdatedAt || mission.createdAt)}</span>
|
||||
</div>
|
||||
{loading && businessTimeline.length === 0 ? (
|
||||
<p className="de-empty-text">正在读取业务过程...</p>
|
||||
) : businessTimeline.length === 0 ? (
|
||||
<p className="de-empty-text">暂无业务过程。重新执行本任务后会显示处理进展。</p>
|
||||
) : (
|
||||
<div className="de-chain-list">
|
||||
{businessTimeline.map((event) => (
|
||||
<div key={`chain-${event.event_id}`} className={`de-chain-row de-chain-row--${event.kind} ${isFailureMessage(event.message) ? 'de-chain-row--danger' : ''}`}>
|
||||
<strong>{businessKindLabel(event.kind, event.message)}</strong>
|
||||
<span>{businessTimelineMessage(event.message)}</span>
|
||||
<time>{fmtTime(event.occurred_at)}</time>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="de-detail-section">
|
||||
<h4>结果与产物</h4>
|
||||
{resultEvents.length > 0 ? (
|
||||
<div className="de-event-list">
|
||||
{[...resultEvents].reverse().map((event) => (
|
||||
<div key={`result-${event.event_id}`} className={`de-event-row ${isFailureMessage(event.message) ? 'de-event-row--danger' : ''}`}>
|
||||
<strong>{businessKindLabel(event.kind, event.message)}</strong>
|
||||
<span>{businessTimelineMessage(event.message)}</span>
|
||||
<time>{fmtTime(event.occurred_at)}</time>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="de-empty-text">任务完成后,这里会显示结果摘要和产物入口。</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="de-detail-section">
|
||||
<h4>步骤进展</h4>
|
||||
{loading ? <p className="de-empty-text">正在读取执行情况...</p> : null}
|
||||
{mission.steps.length === 0 ? (
|
||||
<p className="de-empty-text">暂无步骤进展</p>
|
||||
) : (
|
||||
mission.steps.map((step, i) => (
|
||||
<div key={step.id} className="de-step-node">
|
||||
<div className={`de-step-dot de-step-dot--${step.status}`} />
|
||||
{i < mission.steps.length - 1 && <div className="de-step-line" />}
|
||||
<div className="de-step-content">
|
||||
<strong>{step.title}</strong>
|
||||
<span>{displayStatus(step.status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="de-detail-section">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>高级详情</h4>
|
||||
<button className="de-btn-secondary de-btn-sm" onClick={openAdvanced} disabled={sessionLoading}>
|
||||
{advancedOpen ? '收起' : '展开'}
|
||||
</button>
|
||||
</div>
|
||||
{advancedOpen ? (
|
||||
<>
|
||||
<section className="de-detail-section">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>技术链路</h4>
|
||||
<span className="de-chain-plan-id">仅用于排障</span>
|
||||
</div>
|
||||
{eventTimeline.length > 0 ? (
|
||||
<div className="de-event-list">
|
||||
{eventTimeline.slice(-20).reverse().map((event) => (
|
||||
<div key={event.event_id} className={`de-event-row ${isFailureMessage(event.message) ? 'de-event-row--danger' : ''}`}>
|
||||
<strong>{displayEventKind(event.kind)}</strong>
|
||||
<span>{event.message}</span>
|
||||
<time>{fmtTime(event.occurred_at)}</time>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="de-empty-text">暂无事件日志</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="de-detail-section">
|
||||
<div className="de-detail-section-head">
|
||||
<h4>会话记录</h4>
|
||||
<button className="de-btn-secondary de-btn-sm" onClick={loadSessionMessages} disabled={sessionLoading}>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
{sessionError ? (
|
||||
<p className="de-empty-text">会话读取失败:{sessionError}</p>
|
||||
) : sessionLoading ? (
|
||||
<p className="de-empty-text">正在读取会话...</p>
|
||||
) : sessionMessages.length === 0 ? (
|
||||
<p className="de-empty-text">暂无会话消息。</p>
|
||||
) : (
|
||||
<div className="de-session-message-list">
|
||||
{sessionMessages.map((message, index) => (
|
||||
<div key={`${message.role}-${index}`} className={`de-session-message de-session-message--${message.role === 'user' ? 'user' : 'agent'}`}>
|
||||
<div className="de-session-message-role">{messageRoleLabel(message.role)}</div>
|
||||
<p>{displayBackendMessage(message.content, message.content)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="de-detail-section">
|
||||
<h4>任务与执行记录</h4>
|
||||
{tasks.length === 0 ? (
|
||||
<p className="de-empty-text">暂无原始任务记录</p>
|
||||
) : (
|
||||
tasks.map((task) => {
|
||||
const taskRuns = runsByTask.get(task.task_id) || [];
|
||||
const latestRun = taskRuns[0];
|
||||
return (
|
||||
<div key={task.task_id} className="de-task-run-card">
|
||||
<div className="de-task-run-head">
|
||||
<span>{task.title || '子任务'}</span>
|
||||
<StatusBadge variant={stepVariant(task.status)} label={displayStatus(task.status)} />
|
||||
</div>
|
||||
<div className="de-task-run-meta">
|
||||
<span>skill_id:{task.assigned_skill_id || '未分配'}</span>
|
||||
<span>开始:{fmtTime(task.started_at)}</span>
|
||||
<span>结束:{fmtTime(task.finished_at)}</span>
|
||||
</div>
|
||||
<div className="de-task-run-meta">
|
||||
<span>task_id:{task.task_id}</span>
|
||||
<span>执行记录:{taskRuns.length} 次</span>
|
||||
{latestRun ? <span>最近结果:{displayStatus(latestRun.lifecycle)}</span> : null}
|
||||
</div>
|
||||
{isFailed(task.status) && task.error_message ? (
|
||||
<div className="de-task-run-error">
|
||||
<strong>失败原因:</strong>{task.error_message}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<p className="de-empty-text">默认隐藏排障信息;需要排障时可展开。</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
interface GemProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Gem({ className = '' }: GemProps) {
|
||||
return <div className={`de-gem ${className}`} aria-hidden="true" />;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ReactNode, CSSProperties } from 'react';
|
||||
|
||||
interface GlassCardProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export default function GlassCard({ children, className = '', style }: GlassCardProps) {
|
||||
return (
|
||||
<div className={`de-card ${className}`} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||