mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-16 14:57:15 +00:00
fix(pipelines): show the actual sandbox scope restriction (#2527)
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
@@ -46,30 +46,10 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { systemInfo } from '@/app/infra/http';
|
||||
import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs';
|
||||
|
||||
/**
|
||||
* Resolve the value referenced by a `show_if.field` string.
|
||||
*
|
||||
* Fields prefixed with `__system.` are looked up in the caller-supplied
|
||||
* `systemContext` dictionary (e.g. `__system.is_wizard` → `systemContext.is_wizard`).
|
||||
* All other field names are resolved from the live form values first, then
|
||||
* fall back to `externalDependentValues`.
|
||||
*/
|
||||
function resolveShowIfValue(
|
||||
field: string,
|
||||
watchedValues: Record<string, unknown>,
|
||||
externalDependentValues?: Record<string, unknown>,
|
||||
systemContext?: Record<string, unknown>,
|
||||
): unknown {
|
||||
if (field.startsWith(SYSTEM_FIELD_PREFIX)) {
|
||||
const key = field.slice(SYSTEM_FIELD_PREFIX.length);
|
||||
return systemContext?.[key];
|
||||
}
|
||||
if (watchedValues[field] !== undefined) {
|
||||
return watchedValues[field];
|
||||
}
|
||||
return externalDependentValues?.[field];
|
||||
}
|
||||
import {
|
||||
resolveDisabledState,
|
||||
resolveShowIfValue,
|
||||
} from './DynamicFormConditions';
|
||||
|
||||
type DynamicFormValueSpec = Pick<
|
||||
IDynamicFormItemSchema,
|
||||
@@ -675,40 +655,19 @@ export default function DynamicFormComponent({
|
||||
}
|
||||
}
|
||||
|
||||
// ``disable_if`` mirrors ``show_if``'s evaluator but instead of
|
||||
// hiding the field, leaves it visible and inert. Use it when the
|
||||
// operator needs to see that the field exists yet cannot edit it
|
||||
// under the current runtime state (e.g. sandbox-bound fields when
|
||||
// Box is disabled).
|
||||
let isDisabledByCondition = false;
|
||||
if (config.disable_if) {
|
||||
const dependValue = resolveShowIfValue(
|
||||
config.disable_if.field,
|
||||
// Keep locked fields visible and resolve only the applicable reason.
|
||||
const { isDisabledByCondition, disabledTooltip: tooltip } =
|
||||
resolveDisabledState(
|
||||
config,
|
||||
watchedValues as Record<string, unknown>,
|
||||
externalDependentValues,
|
||||
systemContext,
|
||||
);
|
||||
const cond = config.disable_if;
|
||||
if (cond.operator === 'eq' && dependValue === cond.value) {
|
||||
isDisabledByCondition = true;
|
||||
} else if (cond.operator === 'neq' && dependValue !== cond.value) {
|
||||
isDisabledByCondition = true;
|
||||
} else if (
|
||||
cond.operator === 'in' &&
|
||||
Array.isArray(cond.value) &&
|
||||
cond.value.includes(dependValue)
|
||||
) {
|
||||
isDisabledByCondition = true;
|
||||
}
|
||||
}
|
||||
|
||||
// All fields are disabled when editing (creation_settings are
|
||||
// immutable) or when ``disable_if`` matches.
|
||||
const isFieldDisabled = !!isEditing || isDisabledByCondition;
|
||||
const disabledTooltip =
|
||||
isDisabledByCondition && config.disabled_tooltip
|
||||
? extractI18nObject(config.disabled_tooltip)
|
||||
: '';
|
||||
const disabledTooltip = tooltip ? extractI18nObject(tooltip) : '';
|
||||
const renderDisabledTooltipIcon = () =>
|
||||
disabledTooltip ? (
|
||||
<DisabledTooltipIcon text={disabledTooltip} />
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
SYSTEM_FIELD_PREFIX,
|
||||
type IDynamicFormItemSchema,
|
||||
type IShowIfCondition,
|
||||
} from '@/app/infra/entities/form/dynamic';
|
||||
|
||||
/** System references use caller context; other fields prefer live form values. */
|
||||
export function resolveShowIfValue(
|
||||
field: string,
|
||||
watchedValues: Record<string, unknown>,
|
||||
externalDependentValues?: Record<string, unknown>,
|
||||
systemContext?: Record<string, unknown>,
|
||||
): unknown {
|
||||
if (field.startsWith(SYSTEM_FIELD_PREFIX)) {
|
||||
return systemContext?.[field.slice(SYSTEM_FIELD_PREFIX.length)];
|
||||
}
|
||||
if (watchedValues[field] !== undefined) {
|
||||
return watchedValues[field];
|
||||
}
|
||||
return externalDependentValues?.[field];
|
||||
}
|
||||
|
||||
export function matchesFormCondition(
|
||||
condition: IShowIfCondition,
|
||||
watchedValues: Record<string, unknown>,
|
||||
externalDependentValues?: Record<string, unknown>,
|
||||
systemContext?: Record<string, unknown>,
|
||||
): boolean {
|
||||
const value = resolveShowIfValue(
|
||||
condition.field,
|
||||
watchedValues,
|
||||
externalDependentValues,
|
||||
systemContext,
|
||||
);
|
||||
switch (condition.operator) {
|
||||
case 'eq':
|
||||
return value === condition.value;
|
||||
case 'neq':
|
||||
return value !== condition.value;
|
||||
case 'in':
|
||||
return Array.isArray(condition.value) && condition.value.includes(value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDisabledState(
|
||||
config: Pick<
|
||||
IDynamicFormItemSchema,
|
||||
'disable_if' | 'disabled_tooltip' | 'disabled_tooltip_overrides'
|
||||
>,
|
||||
watchedValues: Record<string, unknown>,
|
||||
externalDependentValues?: Record<string, unknown>,
|
||||
systemContext?: Record<string, unknown>,
|
||||
) {
|
||||
const matches = (condition: IShowIfCondition) =>
|
||||
matchesFormCondition(
|
||||
condition,
|
||||
watchedValues,
|
||||
externalDependentValues,
|
||||
systemContext,
|
||||
);
|
||||
const isDisabledByCondition =
|
||||
!!config.disable_if && matches(config.disable_if);
|
||||
const disabledTooltip = isDisabledByCondition
|
||||
? (config.disabled_tooltip_overrides?.find((override) =>
|
||||
matches(override.when),
|
||||
)?.tooltip ?? config.disabled_tooltip)
|
||||
: undefined;
|
||||
return { isDisabledByCondition, disabledTooltip };
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Unavailability takes priority over the deployment's scope restriction. */
|
||||
export function getBoxScopeContext(
|
||||
boxAvailable: boolean,
|
||||
forcedTemplate?: string,
|
||||
) {
|
||||
forcedTemplate = forcedTemplate?.trim();
|
||||
return {
|
||||
box_available: boxAvailable,
|
||||
box_scope_editable: boxAvailable && !forcedTemplate,
|
||||
// Only expose forced-scope reasons when the sandbox is available.
|
||||
box_scope_forced: boxAvailable && !!forcedTemplate,
|
||||
box_scope_forced_global: boxAvailable && forcedTemplate === '{global}',
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
|
||||
import N8nAuthFormComponent from '@/app/home/components/dynamic-form/N8nAuthFormComponent';
|
||||
import { useBoxStatus } from '@/app/infra/hooks/useBoxStatus';
|
||||
import { getBoxScopeContext } from './BoxScopeContext';
|
||||
import { systemInfo } from '@/app/infra/http';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -425,13 +426,12 @@ export default function PipelineFormComponent({
|
||||
// 2. the deployment pins all pipelines to a fixed scope via
|
||||
// ``system.limitation.force_box_session_id_template`` (SaaS).
|
||||
const forcedBoxTemplate =
|
||||
systemInfo.limitation?.force_box_session_id_template || '';
|
||||
systemInfo.limitation?.force_box_session_id_template?.trim() || '';
|
||||
const boxScopeForced = !!forcedBoxTemplate;
|
||||
const isLocalAgentStage = formName === 'ai' && stage.name === 'local-agent';
|
||||
const stageSystemContext = isLocalAgentStage
|
||||
? {
|
||||
box_available: boxAvailable,
|
||||
box_scope_editable: boxAvailable && !boxScopeForced,
|
||||
...getBoxScopeContext(boxAvailable, forcedBoxTemplate),
|
||||
pipeline_id: pipelineId,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
@@ -39,6 +39,13 @@ export interface IDynamicFormItemSchema {
|
||||
disable_if?: IShowIfCondition;
|
||||
/** Tooltip shown next to the field label when ``disable_if`` is active. */
|
||||
disabled_tooltip?: I18nObject;
|
||||
/** Optional overrides evaluated in order when ``disable_if`` matches.
|
||||
* The first matching ``when`` wins; otherwise use ``disabled_tooltip``.
|
||||
* Conditions use the same operators and value lookup as ``disable_if``. */
|
||||
disabled_tooltip_overrides?: {
|
||||
when: IShowIfCondition;
|
||||
tooltip: I18nObject;
|
||||
}[];
|
||||
|
||||
/** when type is PLUGIN_SELECTOR, the scopes is the scopes of components(plugin contains), the default is all */
|
||||
scopes?: string[];
|
||||
|
||||
Reference in New Issue
Block a user