完善自动化任务广场目标选择

自动化任务目标改为从广场发布资源获取,不再按当前空间筛选。

任务中心允许任务归档空间与发布目标空间不同,并在执行时透传组件、技能和模型参数。
This commit is contained in:
baiyanyun
2026-07-13 08:59:32 +08:00
parent f0a2986cf3
commit f8ee4023ff
5 changed files with 2423 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ import com.xspaceagi.agent.core.adapter.dto.TryReqDto;
import com.xspaceagi.agent.core.adapter.dto.WorkflowExecuteRequestDto;
import com.xspaceagi.agent.core.adapter.dto.config.ModelConfigDto;
import com.xspaceagi.agent.core.adapter.dto.config.workflow.WorkflowConfigDto;
import com.xspaceagi.agent.core.adapter.repository.entity.AgentComponentConfig;
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
import com.xspaceagi.agent.core.infra.component.agent.dto.AgentExecuteResult;
import com.xspaceagi.system.application.dto.SendNotifyMessageDto;
@@ -84,7 +85,7 @@ public class TaskCenterApplicationServiceImpl implements TaskExecuteService {
}
private void executeWorkflowTask(MonoSink<Boolean> sink, ScheduleTaskDto scheduleTask) {
WorkflowConfigDto workflowConfigDto = workflowApplicationService.queryPublishedWorkflowConfig(Long.parseLong(scheduleTask.getTargetId()), scheduleTask.getSpaceId(), true);
WorkflowConfigDto workflowConfigDto = workflowApplicationService.queryPublishedWorkflowConfig(Long.parseLong(scheduleTask.getTargetId()), null, true);
if (workflowConfigDto == null) {
sink.error(BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.agentWorkflowOffline));
return;
@@ -133,13 +134,22 @@ public class TaskCenterApplicationServiceImpl implements TaskExecuteService {
}
});
}
mergeSelectedComponents(selectedComponents, params.get("selectedComponents"));
TryReqDto tryReqDto = new TryReqDto();
tryReqDto.setDebug(false);
tryReqDto.setConversationId(conversation.getId());
tryReqDto.setMessage(params.get("message").toString());
tryReqDto.setSelectedComponents(selectedComponents);
List<Long> skillIds = toLongList(params.get("skillIds"));
if (!skillIds.isEmpty()) {
tryReqDto.setSkillIds(skillIds);
}
Long modelId = toLong(params.get("modelId"));
if (modelId != null) {
tryReqDto.setModelId(modelId);
}
try {
if (YesOrNoEnum.Y.getKey().equals(conversation.getAgent().getAllowOtherModel())) {
if (tryReqDto.getModelId() == null && YesOrNoEnum.Y.getKey().equals(conversation.getAgent().getAllowOtherModel())) {
List<ModelConfigDto> modelConfigDtos = agentApplicationService.queryUserCanSelectModelListForAgent(conversation.getUserId(), conversation.getAgentId());
if (!CollectionUtils.isEmpty(modelConfigDtos)) {
tryReqDto.setModelId(modelConfigDtos.get(0).getId());
@@ -154,6 +164,94 @@ public class TaskCenterApplicationServiceImpl implements TaskExecuteService {
executeAgentTask0(sink, requestContext, tryReqDto, scheduleTask, 0);
}
private void mergeSelectedComponents(List<TryReqDto.SelectedComponentDto> selectedComponents, Object rawSelectedComponents) {
if (!(rawSelectedComponents instanceof Collection<?> rawList)) {
return;
}
Set<String> existing = new HashSet<>();
for (TryReqDto.SelectedComponentDto item : selectedComponents) {
if (item.getId() != null && item.getType() != null) {
existing.add(item.getType().name() + ":" + item.getId());
}
}
for (Object rawItem : rawList) {
TryReqDto.SelectedComponentDto selectedComponentDto = parseSelectedComponent(rawItem);
if (selectedComponentDto == null || selectedComponentDto.getId() == null || selectedComponentDto.getType() == null) {
continue;
}
String key = selectedComponentDto.getType().name() + ":" + selectedComponentDto.getId();
if (existing.add(key)) {
selectedComponents.add(selectedComponentDto);
}
}
}
private TryReqDto.SelectedComponentDto parseSelectedComponent(Object rawItem) {
if (rawItem instanceof TryReqDto.SelectedComponentDto selectedComponentDto) {
return selectedComponentDto.getId() != null && selectedComponentDto.getType() != null ? selectedComponentDto : null;
}
if (!(rawItem instanceof Map<?, ?> map)) {
return null;
}
Long id = toLong(map.get("id"));
AgentComponentConfig.Type type = toComponentType(map.get("type"));
if (id == null || type == null) {
return null;
}
TryReqDto.SelectedComponentDto selectedComponentDto = new TryReqDto.SelectedComponentDto();
selectedComponentDto.setId(id);
selectedComponentDto.setType(type);
return selectedComponentDto;
}
private AgentComponentConfig.Type toComponentType(Object value) {
if (value instanceof AgentComponentConfig.Type type) {
return type;
}
if (value == null) {
return null;
}
String typeName = value.toString();
for (AgentComponentConfig.Type type : AgentComponentConfig.Type.values()) {
if (type.name().equalsIgnoreCase(typeName)) {
return type;
}
}
return null;
}
private List<Long> toLongList(Object value) {
if (!(value instanceof Collection<?> collection)) {
return Collections.emptyList();
}
List<Long> result = new ArrayList<>();
for (Object item : collection) {
Long parsed = toLong(item);
if (parsed != null) {
result.add(parsed);
}
}
return result;
}
private Long toLong(Object value) {
if (value instanceof Number number) {
return number.longValue();
}
if (value == null) {
return null;
}
String text = value.toString();
if (text.isBlank()) {
return null;
}
try {
return Long.parseLong(text);
} catch (NumberFormatException e) {
return null;
}
}
private void executeAgentTask0(MonoSink<Boolean> sink, RequestContext<Object> requestContext, TryReqDto tryReqDto, ScheduleTaskDto scheduleTask, int times) {
RequestContext.set(requestContext);
AtomicBoolean retry = new AtomicBoolean(false);

View File

@@ -78,9 +78,6 @@ public class TaskCenterController {
if (publishedDto == null) {
return ReqResult.error("Target object does not exist or has been removed");
}
if (!publishedDto.getSpaceId().equals(scheduleTaskAddDto.getSpaceId())) {
return ReqResult.error("Target object space ID does not match task space ID");
}
ScheduleTaskDto scheduleTaskDto = new ScheduleTaskDto();
BeanUtils.copyProperties(scheduleTaskAddDto, scheduleTaskDto);
scheduleTaskDto.setTaskId(UUID.randomUUID().toString().replace("-", ""));

View File

@@ -0,0 +1,432 @@
import { message } from "antd";
import {
AUTH_KEYS,
DEFAULT_API_TIMEOUT,
DEFAULT_SERVER_HOST,
} from "@shared/constants";
import { getDomainTokenKey } from "@shared/utils/domain";
import { normalizeServerHost } from "./auth";
import type { AiManualComponent } from "./aiChat";
const SUCCESS_CODE = "0000";
interface ApiResponse<T> {
code: string;
message?: string;
data: T;
success?: boolean;
}
interface RequestOptions {
method?: "GET" | "POST";
data?: unknown;
showError?: boolean;
}
export interface AutomationPageResult<T> {
records: T[];
total: number;
size?: number;
current?: number;
pages?: number;
}
export interface AutomationSpace {
id: number;
name?: string;
spaceName?: string;
icon?: string;
}
export interface AutomationCronItem {
cron?: string;
desc?: string;
}
export interface AutomationCronGroup {
typeName: string;
items: AutomationCronItem[];
}
export type AutomationTargetType = "Agent" | "Workflow";
export interface AutomationTarget {
id: number;
targetId: number;
targetType: AutomationTargetType;
name: string;
description?: string;
icon?: string;
category?: string;
spaceId?: number;
}
export interface AutomationArgDetail {
key?: string | number;
name?: string;
displayName?: string;
description?: string;
dataType?: string;
inputType?: string;
require?: boolean;
required?: boolean;
enable?: boolean;
bindValue?: string | number | boolean | null;
children?: AutomationArgDetail[];
subArgs?: AutomationArgDetail[];
[key: string]: unknown;
}
export interface AutomationAgentDetail extends AutomationTarget {
variables?: AutomationArgDetail[];
manualComponents?: AiManualComponent[];
}
export interface AutomationWorkflowDetail extends AutomationTarget {
inputArgs?: AutomationArgDetail[];
outputArgs?: AutomationArgDetail[];
}
export type AutomationTaskStatus =
| "CREATE"
| "EXECUTING"
| "CONTINUE"
| "OVERFLOW_MAX_EXEC_TIMES"
| "COMPLETE"
| "FAIL"
| "CANCEL"
| string;
export interface AutomationTask {
id: number;
spaceId: number;
targetName?: string;
targetIcon?: string;
targetType: AutomationTargetType;
targetId: string;
taskName: string;
taskId?: string;
cron?: string | null;
params?: Record<string, unknown>;
status?: AutomationTaskStatus;
latestExecTime?: string | null;
lockTime?: string | null;
execTimes?: number;
maxExecTimes?: number | null;
keepConversation?: 0 | 1;
error?: string;
modified?: string;
created?: string;
creator?: {
userId?: number;
userName?: string;
avatar?: string;
};
}
export interface AutomationSelectedComponent {
id: number;
type: string;
}
export interface AutomationTaskCreateParams {
id?: number;
spaceId: number;
targetType: AutomationTargetType;
targetId: string;
taskName: string;
cron: string | null;
lockTime?: number | null;
maxExecTimes?: number | null;
keepConversation?: 0 | 1;
params: {
message?: string;
variables?: Record<string, unknown>;
selectedComponents?: AutomationSelectedComponent[];
skillIds?: number[];
modelId?: number | null;
[key: string]: unknown;
};
}
export interface AutomationRunLog {
id: string;
requestId?: string;
spaceId?: number;
userId?: number;
userName?: string;
targetType?: string;
targetName?: string;
targetId?: string;
input?: string;
output?: string;
status?: string;
elapsedTime?: number;
created?: string;
requestTime?: string;
startTime?: string;
endTime?: string;
[key: string]: unknown;
}
export interface AutomationTemplate {
id: string;
name: string;
description: string;
icon: string;
prompt: string;
cronHint?: "daily" | "weekly" | "once";
}
async function getBaseUrl(): Promise<string> {
const config = (await window.electronAPI?.settings.get(
AUTH_KEYS.STEP1_CONFIG,
)) as { serverHost?: string } | null;
return normalizeServerHost(config?.serverHost || DEFAULT_SERVER_HOST);
}
async function getAuthToken(domain: string): Promise<string | null> {
const oneShotToken = (await window.electronAPI?.settings.get(
AUTH_KEYS.AUTH_TOKEN,
)) as string | null;
if (oneShotToken) return oneShotToken;
const domainTokenKey = getDomainTokenKey(domain);
return (await window.electronAPI?.settings.get(domainTokenKey)) as
| string
| null;
}
function normalizeAssetUrl(
baseUrl: string,
value?: string,
): string | undefined {
if (!value) return value;
if (/^(https?:)?\/\//i.test(value) || value.startsWith("data:")) {
return value;
}
try {
return new URL(value, baseUrl).toString();
} catch {
return value;
}
}
async function automationRequest<T>(
path: string,
options: RequestOptions = {},
): Promise<T> {
const baseUrl = await getBaseUrl();
const token = await getAuthToken(baseUrl);
const response = await fetch(`${baseUrl}${path}`, {
method: options.method || "GET",
credentials: "include",
cache: "no-store",
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
signal: AbortSignal.timeout(DEFAULT_API_TIMEOUT),
...(options.data !== undefined
? { body: JSON.stringify(options.data) }
: {}),
});
if (!response.ok) {
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
if (options.showError !== false) message.error(error.message);
throw error;
}
const result = (await response.json()) as ApiResponse<T>;
if (result.code !== SUCCESS_CODE) {
const errorMessage = result.message || "请求失败";
if (options.showError !== false) message.error(errorMessage);
throw new Error(errorMessage);
}
return result.data;
}
function normalizeTarget<T extends AutomationTarget>(
baseUrl: string,
item: T,
targetType: AutomationTargetType,
): T {
return {
...item,
targetType,
targetId: item.targetId || item.id,
icon: normalizeAssetUrl(baseUrl, item.icon),
};
}
export async function fetchAutomationSpaces(): Promise<AutomationSpace[]> {
return automationRequest<AutomationSpace[]>("/api/space/list", {
method: "GET",
});
}
export async function fetchAutomationTasks(
spaceId: number,
): Promise<AutomationTask[]> {
const baseUrl = await getBaseUrl();
const tasks = await automationRequest<AutomationTask[]>(
`/api/task/list/${spaceId}`,
{ method: "POST" },
);
return (tasks || []).map((task) => ({
...task,
targetIcon: normalizeAssetUrl(baseUrl, task.targetIcon),
}));
}
export async function createAutomationTask(
params: AutomationTaskCreateParams,
): Promise<AutomationTask> {
return automationRequest<AutomationTask>("/api/task/create", {
method: "POST",
data: params,
});
}
export async function updateAutomationTask(
params: AutomationTaskCreateParams & { id: number },
): Promise<void> {
await automationRequest<null>("/api/task/update", {
method: "POST",
data: params,
});
}
export async function executeAutomationTask(id: number): Promise<void> {
await automationRequest<null>(`/api/task/execute/${id}`, {
method: "POST",
});
}
export async function enableAutomationTask(id: number): Promise<void> {
await automationRequest<null>(`/api/task/enable/${id}`, {
method: "POST",
});
}
export async function disableAutomationTask(id: number): Promise<void> {
await automationRequest<null>(`/api/task/cancel/${id}`, {
method: "POST",
});
}
export async function deleteAutomationTask(id: number): Promise<void> {
await automationRequest<null>(`/api/task/delete/${id}`, {
method: "POST",
});
}
export async function fetchAutomationCronList(): Promise<
AutomationCronGroup[]
> {
return automationRequest<AutomationCronGroup[]>("/api/task/cron/list", {
method: "GET",
});
}
export async function fetchAutomationTargets(params: {
targetType: AutomationTargetType;
kw?: string;
page?: number;
pageSize?: number;
}): Promise<AutomationPageResult<AutomationTarget>> {
const baseUrl = await getBaseUrl();
const path =
params.targetType === "Agent"
? "/api/published/agent/list"
: "/api/published/workflow/list";
const page = await automationRequest<AutomationPageResult<AutomationTarget>>(
path,
{
method: "POST",
data: {
page: params.page || 1,
pageSize: params.pageSize || 50,
kw: params.kw || undefined,
...(params.targetType === "Agent"
? { targetType: "Agent", targetSubType: "ChatBot" }
: {}),
},
showError: false,
},
);
return {
...page,
records: (page.records || []).map((item) =>
normalizeTarget(baseUrl, item, params.targetType),
),
};
}
export async function fetchAutomationAgentDetail(
agentId: number,
): Promise<AutomationAgentDetail> {
const baseUrl = await getBaseUrl();
const detail = await automationRequest<AutomationAgentDetail>(
`/api/published/agent/${agentId}`,
{
method: "GET",
},
);
return normalizeTarget(baseUrl, detail, "Agent");
}
export async function fetchAutomationWorkflowDetail(
workflowId: number,
): Promise<AutomationWorkflowDetail> {
const baseUrl = await getBaseUrl();
const detail = await automationRequest<AutomationWorkflowDetail>(
`/api/published/workflow/${workflowId}`,
{
method: "GET",
},
);
return normalizeTarget(baseUrl, detail, "Workflow");
}
export async function fetchAutomationRunLogs(params: {
targetType: string;
targetId: string | number;
current?: number;
pageSize?: number;
}): Promise<AutomationPageResult<AutomationRunLog>> {
return automationRequest<AutomationPageResult<AutomationRunLog>>(
"/api/requestLogs/list",
{
method: "POST",
data: {
current: params.current || 1,
pageSize: params.pageSize || 10,
queryFilter: {
targetType: params.targetType,
targetId: String(params.targetId),
from: "task_center",
},
},
showError: false,
},
);
}
export async function fetchAutomationRunLogDetail(
id: string,
): Promise<AutomationRunLog> {
return automationRequest<AutomationRunLog>("/api/requestLogs/detail", {
method: "POST",
data: { id },
showError: false,
});
}

View File

@@ -0,0 +1,445 @@
.page {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
background: #fbfaf8;
color: #1f211f;
overflow: hidden;
}
.header {
flex: 0 0 auto;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding: 36px 32px 24px;
}
.header h1 {
margin: 0;
color: #171817;
font-size: 30px;
line-height: 38px;
font-weight: 800;
}
.header p {
margin: 14px 0 0;
color: #2f312f;
font-size: 16px;
line-height: 24px;
}
.headerActions {
display: flex;
align-items: center;
gap: 12px;
white-space: nowrap;
}
.headerActions :global(.ant-btn) {
height: 48px;
border-radius: 10px;
font-weight: 700;
}
.content {
flex: 1;
min-height: 0;
padding: 0 32px 36px;
overflow: auto;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 28px;
}
.sectionTitle {
display: flex;
align-items: center;
gap: 12px;
margin: 0 0 24px;
}
.sectionTitle h2 {
margin: 0;
color: #686b66;
font-size: 18px;
line-height: 26px;
font-weight: 800;
}
.sectionTitle span {
color: #9b9e98;
font-size: 13px;
}
.emptyState {
min-height: 320px;
display: flex;
align-items: center;
justify-content: center;
}
.taskList {
display: flex;
flex-direction: column;
gap: 8px;
}
.taskItem {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto 36px 36px;
align-items: center;
gap: 14px;
min-height: 58px;
padding: 8px 0;
}
.statusDot {
width: 9px;
height: 9px;
margin-left: 4px;
border-radius: 50%;
background: #777a74;
}
.running {
background: #1677ff;
}
.waiting {
background: #74776f;
}
.success {
background: #00a870;
}
.failed {
background: #d9363e;
}
.ended {
background: #c7c9c4;
}
.taskMain {
min-width: 0;
}
.taskTitleRow {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.taskTitleRow strong {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #20211f;
font-size: 17px;
line-height: 24px;
font-weight: 800;
}
.taskTitleRow span,
.taskMetaRow span {
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #70736d;
font-size: 13px;
}
.taskTitleRow span {
padding: 2px 8px;
border: 1px solid #e4e1da;
border-radius: 8px;
background: #ffffff;
}
.taskMetaRow {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 6px;
}
.taskMetaRow span {
padding: 3px 8px;
border: 1px solid #e8e5de;
border-radius: 8px;
background: #f7f6f3;
}
.taskNext {
color: #656862;
font-size: 15px;
white-space: nowrap;
}
.templateGrid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px;
}
.templateCard {
min-height: 108px;
display: grid;
grid-template-columns: 56px minmax(0, 1fr);
align-items: center;
gap: 18px;
padding: 20px 24px;
border: 1px solid #e6e3dc;
border-radius: 12px;
background: #ffffff;
color: inherit;
text-align: left;
cursor: pointer;
transition:
border-color 0.18s ease,
box-shadow 0.18s ease,
transform 0.18s ease;
}
.templateCard:hover {
border-color: #d8d3c8;
box-shadow: 0 10px 24px rgba(31, 33, 31, 0.07);
transform: translateY(-1px);
}
.templateIcon {
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
color: #73766f;
font-size: 30px;
}
.templateCard strong {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #171817;
font-size: 20px;
line-height: 28px;
font-weight: 800;
}
.templateCard small {
display: -webkit-box;
margin-top: 6px;
overflow: hidden;
color: #5f625d;
font-size: 15px;
line-height: 22px;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.taskModal :global(.ant-modal-content) {
border-radius: 18px;
padding: 34px 40px 30px;
}
.taskModal :global(.ant-modal-header) {
margin-bottom: 28px;
}
.taskModal :global(.ant-modal-title) {
color: #1c1d1b;
font-size: 28px;
line-height: 36px;
font-weight: 800;
}
.taskModal :global(.ant-modal-footer) {
margin-top: 28px;
}
.taskModal :global(.ant-modal-footer .ant-btn) {
min-width: 144px;
height: 48px;
border-radius: 10px;
font-size: 16px;
font-weight: 800;
}
.taskModal :global(.ant-modal-footer .ant-btn-primary) {
background: #111111;
border-color: #111111;
}
.taskForm {
max-height: min(70vh, 800px);
padding-right: 10px;
overflow: auto;
}
.taskForm :global(.ant-form-item-label > label) {
color: #686b66;
font-size: 16px;
font-weight: 800;
}
.taskForm :global(.ant-input),
.taskForm :global(.ant-input-affix-wrapper),
.taskForm :global(.ant-select-selector),
.taskForm :global(.ant-picker) {
border-radius: 10px !important;
}
.inlineFields {
display: grid;
grid-template-columns: minmax(220px, 0.7fr) minmax(280px, 1.3fr);
gap: 18px;
}
.promptInput {
min-height: 210px !important;
border-color: #9a9c98 !important;
font-size: 15px;
}
.promptTools {
display: flex;
align-items: center;
gap: 10px;
margin: -10px 0 22px;
flex-wrap: wrap;
}
.toolSelect {
min-width: 180px;
}
.toolSelectWide {
min-width: 280px;
flex: 1;
}
.variablePanel {
margin: 8px 0 20px;
padding: 18px;
border: 1px solid #e8e4dc;
border-radius: 12px;
background: #ffffff;
}
.formSubTitle {
margin-bottom: 12px;
color: #323431;
font-size: 15px;
font-weight: 800;
}
.frequencyBlock {
margin-top: 4px;
}
.fullWidth {
width: 100%;
}
.logList {
display: flex;
flex-direction: column;
gap: 10px;
}
.logItem {
display: flex;
flex-direction: column;
gap: 5px;
padding: 14px 16px;
border: 1px solid #e7e3dc;
border-radius: 10px;
background: #ffffff;
color: inherit;
text-align: left;
cursor: pointer;
}
.logItem strong {
color: #20211f;
font-size: 14px;
}
.logItem span,
.logItem small {
overflow: hidden;
color: #777b74;
text-overflow: ellipsis;
white-space: nowrap;
}
.logDetail {
margin-top: 22px;
padding-top: 18px;
border-top: 1px solid #ece8e0;
}
.logDetail h3 {
margin: 0 0 12px;
}
.logDetail pre {
max-height: 360px;
margin: 0;
padding: 14px;
overflow: auto;
border-radius: 10px;
background: #f6f5f2;
color: #30322f;
font-size: 12px;
line-height: 1.55;
}
@media (max-width: 1180px) {
.templateGrid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 860px) {
.header {
flex-direction: column;
}
.headerActions {
flex-wrap: wrap;
}
.toolbar,
.sectionTitle {
align-items: flex-start;
flex-direction: column;
}
.taskItem {
grid-template-columns: 18px minmax(0, 1fr) 36px 36px;
}
.taskNext {
grid-column: 2 / -1;
}
.templateGrid,
.inlineFields {
grid-template-columns: 1fr;
}
}