Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -377,10 +377,33 @@
.filter((option) => option !== null);
}, [options]);

const fromAiExpression = useMemo(
() => (description ? `=fromAi('${name}', '${description}')` : `=fromAi('${name}')`),
[description, name]
);
const fromAiExpression = useMemo(() => {
const mapEntries: string[] = [];

if (description) {
const escapedDescription = description.replace(/'/g, "''");

Check warning on line 384 in client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts

View check run for this annotation

SonarQubeCloud / [client] SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=bytechef_client&issues=AZ2MuOz7J7QFf8fr-hDm&open=AZ2MuOz7J7QFf8fr-hDm&pullRequest=4758

mapEntries.push(`'description': '${escapedDescription}'`);
}

if (defaultValue !== '' && defaultValue !== null && defaultValue !== undefined) {
const escapedDefault = String(defaultValue).replace(/'/g, "''");

Check warning on line 390 in client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts

View check run for this annotation

SonarQubeCloud / [client] SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=bytechef_client&issues=AZ2MuOz7J7QFf8fr-hDn&open=AZ2MuOz7J7QFf8fr-hDn&pullRequest=4758

mapEntries.push(`'defaultValue': '${escapedDefault}'`);
}

if (formattedOptions != null && formattedOptions.length > 0) {
const optionValues = formattedOptions
.map((option) => `'${String(option?.value ?? '').replace(/'/g, "''")}'`)

Check warning on line 397 in client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts

View check run for this annotation

SonarQubeCloud / [client] SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=bytechef_client&issues=AZ2MuOz7J7QFf8fr-hDo&open=AZ2MuOz7J7QFf8fr-hDo&pullRequest=4758
.join(', ');

mapEntries.push(`'options': {${optionValues}}`);
}

mapEntries.push(`'required': ${required}`);

return `=fromAi('${name}', '${type}', {${mapEntries.join(', ')}})`;
}, [defaultValue, description, formattedOptions, name, required, type]);

const isValidControlType = useMemo(
() => controlType && INPUT_PROPERTY_CONTROL_TYPES.includes(controlType),
Expand Down Expand Up @@ -1045,16 +1068,10 @@

if (fromAi) {
if (editorRef.current) {
const escapedDescription = description?.replace(/'/g, "''");

const fromAi = escapedDescription
? `=fromAi('${property.name}', '${escapedDescription}')`
: `=fromAi('${property.name}')`;

editorRef.current.commands.setContent(fromAi);
editorRef.current.commands.setContent(fromAiExpression);
editorRef.current.setEditable(false);

value = fromAi;
value = fromAiExpression;
}
} else {
if (editorRef.current) {
Expand All @@ -1079,9 +1096,8 @@
},
[
custom,
description,
fromAiExpression,
path,
property.name,
propertyParameterValue,
setFocusedInput,
type,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the ByteChef Enterprise license (the "Enterprise License");
* you may not use this file except in compliance with the Enterprise License.
*/

package com.bytechef.ee.platform.component.remote.client.facade;

import com.bytechef.platform.component.ComponentConnection;
import com.bytechef.platform.component.facade.ConnectionDefinitionFacade;
import org.springframework.stereotype.Component;

/**
* @version ee
*
* @author Ivica Cardic
*/
@Component
public class RemoteConnectionDefinitionFacadeClient implements ConnectionDefinitionFacade {

@Override
public ComponentConnection executeConnectionRefresh(Long connectionId) {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the ByteChef Enterprise license (the "Enterprise License");
* you may not use this file except in compliance with the Enterprise License.
*/

package com.bytechef.ee.platform.scheduler.remote.client;

import com.bytechef.ee.remote.client.LoadBalancedRestClient;
import com.bytechef.platform.scheduler.ConnectionRefreshScheduler;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Instant;
import org.springframework.stereotype.Component;

/**
* @version ee
*
* @author Ivica Cardic
*/
@Component
public class RemoteConnectionRefreshSchedulerClient implements ConnectionRefreshScheduler {

private static final String CONNECTION_REFRESH_SCHEDULER = "/remote/connection-refresh-scheduler";
private static final String SCHEDULER_APP = "scheduler-app";

private final LoadBalancedRestClient loadBalancedRestClient;

@SuppressFBWarnings("EI")
public RemoteConnectionRefreshSchedulerClient(LoadBalancedRestClient loadBalancedRestClient) {
this.loadBalancedRestClient = loadBalancedRestClient;
}

@Override
public void cancelConnectionRefresh(Long connectionId) {
loadBalancedRestClient.post(
uriBuilder -> uriBuilder
.host(SCHEDULER_APP)
.path(CONNECTION_REFRESH_SCHEDULER + "/cancel-connection-refresh")
.build(),
connectionId);
}

@Override
public void scheduleConnectionRefresh(Long connectionId, Instant expiry) {
loadBalancedRestClient.post(
uriBuilder -> uriBuilder
.host(SCHEDULER_APP)
.path(CONNECTION_REFRESH_SCHEDULER + "/schedule-connection-refresh")
.build(),
new ScheduleConnectionRefreshRequest(connectionId, expiry));
}

private record ScheduleConnectionRefreshRequest(Long connectionId, Instant expiry) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the ByteChef Enterprise license (the "Enterprise License");
* you may not use this file except in compliance with the Enterprise License.
*/

package com.bytechef.ee.platform.workflow.execution.remote.client.facade;

import com.bytechef.component.definition.Authorization.AuthorizationType;
import com.bytechef.ee.remote.client.LoadBalancedRestClient;
import com.bytechef.platform.workflow.execution.facade.ConnectionLifecycleFacade;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Map;
import org.springframework.stereotype.Component;

/**
* @version ee
*
* @author Ivica Cardic
*/
@Component
public class RemoteConnectionLifecycleFacadeClient implements ConnectionLifecycleFacade {

private static final String CONNECTION_LIFECYCLE_FACADE = "/remote/connection-lifecycle-facade";
private static final String EXECUTION_APP = "execution-app";

private final LoadBalancedRestClient loadBalancedRestClient;

@SuppressFBWarnings("EI")
public RemoteConnectionLifecycleFacadeClient(LoadBalancedRestClient loadBalancedRestClient) {
this.loadBalancedRestClient = loadBalancedRestClient;
}

@Override
public void scheduleConnectionRefresh(
Long connectionId, Map<String, ?> parameters, AuthorizationType authorizationType) {

loadBalancedRestClient.post(
uriBuilder -> uriBuilder
.host(EXECUTION_APP)
.path(CONNECTION_LIFECYCLE_FACADE + "/schedule-connection-refresh")
.build(),
new ScheduleConnectionRefreshRequest(connectionId, parameters, authorizationType));
}

@Override
public void deleteScheduledConnectionRefresh(Long connectionId, AuthorizationType authorizationType) {
loadBalancedRestClient.post(
uriBuilder -> uriBuilder
.host(EXECUTION_APP)
.path(CONNECTION_LIFECYCLE_FACADE + "/delete-scheduled-connection-refresh")
.build(),
new DeleteScheduledConnectionRefreshRequest(connectionId, authorizationType));
}

@SuppressFBWarnings("EI")
private record ScheduleConnectionRefreshRequest(
Long connectionId, Map<String, ?> parameters, AuthorizationType authorizationType) {
}

private record DeleteScheduledConnectionRefreshRequest(Long connectionId, AuthorizationType authorizationType) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.bytechef.ai.tool;

import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.expression.AccessException;
Expand All @@ -29,17 +31,17 @@
public class FromAi implements MethodExecutor {

private static final Set<String> VALID_TYPES = Set.of(
"STRING", "NUMBER", "INTEGER", "BOOLEAN", "ARRAY", "OBJECT");
"STRING", "NUMBER", "INTEGER", "BOOLEAN", "ARRAY", "OBJECT", "DATE", "TIME", "DATE_TIME");

@Override
public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
if (arguments.length == 0) {
throw new IllegalArgumentException("fromAi requires at least a name argument.");
}

if (arguments.length > 4) {
if (arguments.length > 5) {
throw new IllegalArgumentException(
"fromAi accepts at most 4 arguments (name, description, type, default).");
"fromAi accepts at most 5 arguments (name, type, description, default, options).");
}

Object nameArgument = arguments[0];
Expand All @@ -50,22 +52,10 @@ public TypedValue execute(EvaluationContext context, Object target, Object... ar

String name = string.trim();

String description = null;

if (arguments.length > 1) {
Object descriptionArgument = arguments[1];

if (descriptionArgument != null && !(descriptionArgument instanceof String)) {
throw new IllegalArgumentException("fromAi description argument must be a String or null.");
}

description = (String) descriptionArgument;
}

String type = "STRING";

if (arguments.length > 2) {
Object typeArgument = arguments[2];
if (arguments.length > 1) {
Object typeArgument = arguments[1];

if (typeArgument != null && !(typeArgument instanceof String)) {
throw new IllegalArgumentException("fromAi type argument must be a String or null.");
Expand All @@ -87,8 +77,49 @@ public TypedValue execute(EvaluationContext context, Object target, Object... ar
}
}

Object defaultValue = arguments.length > 3 ? arguments[3] : null;
String description = null;
Object defaultValue = null;
List<Object> options = null;
boolean required = false;

if (arguments.length > 2) {
Object thirdArgument = arguments[2];

if (thirdArgument instanceof Map<?, ?> paramMap) {
Object descArg = paramMap.get("description");

if (descArg != null && !(descArg instanceof String)) {
throw new IllegalArgumentException("fromAi 'description' in map must be a String or null.");
}

description = (String) descArg;
defaultValue = paramMap.get("defaultValue");

Object optionsArg = paramMap.get("options");

if (optionsArg != null && !(optionsArg instanceof List)) {
throw new IllegalArgumentException("fromAi 'options' in map must be a List or null.");
}

if (optionsArg != null) {
@SuppressWarnings("unchecked")
List<Object> castedOptions = (List<Object>) optionsArg;

options = castedOptions;
}

Object requiredArg = paramMap.get("required");

if (requiredArg != null && !(requiredArg instanceof Boolean)) {
throw new IllegalArgumentException("fromAi 'required' in map must be a boolean.");
}

if (requiredArg != null) {
required = (Boolean) requiredArg;
}
}
}

return new TypedValue(new FromAiResult(name, description, type, defaultValue));
return new TypedValue(new FromAiResult(name, type, description, defaultValue, options, required));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.bytechef.ai.tool;

import java.util.List;
import java.util.Objects;

/**
Expand All @@ -29,24 +30,13 @@
* @param defaultValue fallback value when the AI model does not provide one (nullable)
* @author Ivica Cardic
*/
public record FromAiResult(String name, String description, String type, Object defaultValue) {

private static final String DEFAULT_TYPE = "STRING";
public record FromAiResult(
String name, String type, String description, Object defaultValue, List<Object> options, boolean required) {

public FromAiResult {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(type, "type must not be null");
}

public FromAiResult(String name) {
this(name, null, DEFAULT_TYPE, null);
}

public FromAiResult(String name, String description) {
this(name, description, DEFAULT_TYPE, null);
}

public FromAiResult(String name, String description, String type) {
this(name, description, type, null);
options = options == null ? null : List.copyOf(options);
}
}
Loading
Loading