mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-08 14:56:03 +00:00
fix(web): show forced sandbox scope + make disabled tooltip tap-friendly
When a SaaS deployment pins every pipeline to a fixed sandbox scope via system.limitation.force_box_session_id_template, the Sandbox Scope selector was correctly locked but still displayed the pipeline's stored value (e.g. the per-chat default), misrepresenting the scope that the runtime actually enforces on every exec. Coerce the displayed/saved value to the forced template so the locked selector truthfully shows the active scope (e.g. Global). Also fix the disabled_tooltip being invisible on touch devices: hover-only Radix tooltips never open without a pointer, so the explanation of why the field is locked could not be read on mobile. Wrap the info icon so a tap toggles the tooltip while desktop hover still works.
This commit is contained in:
@@ -198,6 +198,35 @@ function WebhookUrlField({
|
||||
);
|
||||
}
|
||||
|
||||
// Hover-only Radix tooltips never open on touch devices (no pointer hover),
|
||||
// so the ``disabled_tooltip`` explaining why a field is locked was invisible on
|
||||
// mobile. This wrapper makes the info icon also toggle the tooltip on tap while
|
||||
// keeping hover behavior on desktop.
|
||||
function DisabledTooltipIcon({ text }: { text: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip open={open} onOpenChange={setOpen}>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={text}
|
||||
className="inline-flex shrink-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen((v) => !v);
|
||||
}}
|
||||
>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help shrink-0" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">{text}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DynamicFormComponent({
|
||||
itemConfigList,
|
||||
onSubmit,
|
||||
@@ -550,18 +579,7 @@ export default function DynamicFormComponent({
|
||||
? extractI18nObject(config.disabled_tooltip)
|
||||
: '';
|
||||
const renderDisabledTooltipIcon = () =>
|
||||
disabledTooltip ? (
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help shrink-0" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
{disabledTooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : null;
|
||||
disabledTooltip ? <DisabledTooltipIcon text={disabledTooltip} /> : null;
|
||||
|
||||
// Webhook URL fields are display-only; render outside of form binding
|
||||
if (config.type === 'webhook-url') {
|
||||
|
||||
@@ -427,8 +427,9 @@ export default function PipelineFormComponent({
|
||||
// 1. Box sandbox is unavailable, or
|
||||
// 2. the deployment pins all pipelines to a fixed scope via
|
||||
// ``system.limitation.force_box_session_id_template`` (SaaS).
|
||||
const boxScopeForced =
|
||||
!!systemInfo.limitation?.force_box_session_id_template;
|
||||
const forcedBoxTemplate =
|
||||
systemInfo.limitation?.force_box_session_id_template || '';
|
||||
const boxScopeForced = !!forcedBoxTemplate;
|
||||
const stageSystemContext =
|
||||
stage.name === 'local-agent'
|
||||
? {
|
||||
@@ -437,6 +438,24 @@ export default function PipelineFormComponent({
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// When the deployment pins every pipeline to a fixed sandbox scope (SaaS
|
||||
// ``force_box_session_id_template``), the Sandbox Scope selector is locked.
|
||||
// The runtime already overrides the scope on every exec, but the stored
|
||||
// pipeline value can be anything (e.g. the per-chat default), which would
|
||||
// make the locked selector display a scope that is NOT the one actually in
|
||||
// effect. Coerce the displayed/saved value to the forced template so the UI
|
||||
// truthfully reflects runtime behavior.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const stageInitialValues: Record<string, any> =
|
||||
(form.watch(formName) as Record<string, any>)?.[stage.name] || {};
|
||||
const effectiveInitialValues =
|
||||
stage.name === 'local-agent' && boxScopeForced
|
||||
? {
|
||||
...stageInitialValues,
|
||||
'box-session-id-template': forcedBoxTemplate,
|
||||
}
|
||||
: stageInitialValues;
|
||||
|
||||
return (
|
||||
<Card key={stage.name}>
|
||||
<CardHeader>
|
||||
@@ -450,10 +469,7 @@ export default function PipelineFormComponent({
|
||||
<CardContent className="space-y-6">
|
||||
<DynamicFormComponent
|
||||
itemConfigList={stage.config}
|
||||
initialValues={
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(form.watch(formName) as Record<string, any>)?.[stage.name] || {}
|
||||
}
|
||||
initialValues={effectiveInitialValues}
|
||||
onSubmit={(values) => {
|
||||
handleDynamicFormEmit(formName, stage.name, values);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user