From db2e37f57b00ceced73684b5824987e5aae0ab78 Mon Sep 17 00:00:00 2001 From: Petr Kadlec Date: Wed, 22 Oct 2025 16:56:19 +0200 Subject: [PATCH 1/4] feat(ui): new ux for run model settings Signed-off-by: Petr Kadlec --- .../examples/request_form_agent.py | 43 +++++++++ apps/beeai-sdk-py/examples/settings_agent.py | 13 ++- apps/beeai-ui/src/app/(auth)/rsc.tsx | 1 + .../RadioSelect/RadioSelect.module.scss} | 0 .../components/RadioSelect/RadioSelect.tsx | 48 ++++++++++ .../form/components/FormActionBar.module.scss | 15 +++ .../modules/form/components/FormActionBar.tsx | 48 ++++++++++ .../modules/form/components/FormRenderer.tsx | 18 ++-- .../runs/components/FormRenderView.tsx | 1 + .../runs/components/ModelProviders.tsx | 84 ---------------- .../src/modules/runs/components/RunInput.tsx | 22 ++++- .../ModelProviders.module.scss | 1 + .../modules/runs/settings/ModelProviders.tsx | 61 ++++++++++++ ...odule.scss => RunDialogButton.module.scss} | 7 +- .../modules/runs/settings/RunDialogButton.tsx | 96 +++++++++++++++++++ .../src/modules/runs/settings/RunModels.tsx | 33 +++++++ .../src/modules/runs/settings/RunSettings.tsx | 62 ++---------- .../settings/SingleSelectSettingsField.tsx | 34 ++----- .../runs/settings/useRunSettingsDialog.ts | 33 ++++--- mise.lock | 71 +++----------- 20 files changed, 440 insertions(+), 251 deletions(-) rename apps/beeai-ui/src/{modules/runs/settings/SingleSelectSettingsField.module.scss => components/RadioSelect/RadioSelect.module.scss} (100%) create mode 100644 apps/beeai-ui/src/components/RadioSelect/RadioSelect.tsx create mode 100644 apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss create mode 100644 apps/beeai-ui/src/modules/form/components/FormActionBar.tsx delete mode 100644 apps/beeai-ui/src/modules/runs/components/ModelProviders.tsx rename apps/beeai-ui/src/modules/runs/{components => settings}/ModelProviders.module.scss (94%) create mode 100644 apps/beeai-ui/src/modules/runs/settings/ModelProviders.tsx rename apps/beeai-ui/src/modules/runs/settings/{RunSettings.module.scss => RunDialogButton.module.scss} (87%) create mode 100644 apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx create mode 100644 apps/beeai-ui/src/modules/runs/settings/RunModels.tsx diff --git a/apps/beeai-sdk-py/examples/request_form_agent.py b/apps/beeai-sdk-py/examples/request_form_agent.py index 86c473217..0c6687f1a 100644 --- a/apps/beeai-sdk-py/examples/request_form_agent.py +++ b/apps/beeai-sdk-py/examples/request_form_agent.py @@ -15,6 +15,19 @@ OptionItem, TextField, ) +from beeai_sdk.a2a.extensions.ui.settings import ( + CheckboxField, + SingleSelectField, + SettingsExtensionServer, + SettingsExtensionSpec, + SettingsRender, +) +from beeai_sdk.a2a.extensions import ( + LLMDemand, + LLMServiceExtensionParams, + LLMServiceExtensionServer, + LLMServiceExtensionSpec, +) from beeai_sdk.server import Server server = Server() @@ -33,6 +46,36 @@ async def request_form_agent( ) ), ], + settings: Annotated[ + SettingsExtensionServer, + SettingsExtensionSpec( + params=SettingsRender( + fields=[ + SingleSelectField( + id="response_style", + label="Response Style", + options=[ + {"label": "Concise", "value": "concise"}, + {"label": "Detailed", "value": "detailed"}, + {"label": "Humorous", "value": "humorous"}, + ], + default_value="concise", + ), + ], + ), + ), + ], + llm: Annotated[ + LLMServiceExtensionServer, + LLMServiceExtensionSpec( + params=LLMServiceExtensionParams( + llm_demands={ + "fast": LLMDemand(suggested=("openai/gpt-4o-mini",)), + "smart": LLMDemand(suggested=("openai/gpt-5",)), + } + ) + ), + ], ): """Request form data""" try: diff --git a/apps/beeai-sdk-py/examples/settings_agent.py b/apps/beeai-sdk-py/examples/settings_agent.py index a76188aaf..42be48659 100644 --- a/apps/beeai-sdk-py/examples/settings_agent.py +++ b/apps/beeai-sdk-py/examples/settings_agent.py @@ -9,6 +9,7 @@ from beeai_sdk.a2a.extensions.ui.settings import ( CheckboxField, + SingleSelectField, CheckboxGroupField, SettingsExtensionServer, SettingsExtensionSpec, @@ -39,7 +40,17 @@ async def settings_agent( default_value=True, ) ], - ) + ), + SingleSelectField( + id="response_style", + label="Response Style", + options=[ + {"label": "Concise", "value": "concise"}, + {"label": "Detailed", "value": "detailed"}, + {"label": "Humorous", "value": "humorous"}, + ], + default_value="concise", + ), ], ), ), diff --git a/apps/beeai-ui/src/app/(auth)/rsc.tsx b/apps/beeai-ui/src/app/(auth)/rsc.tsx index 3dada30b0..5a79db099 100644 --- a/apps/beeai-ui/src/app/(auth)/rsc.tsx +++ b/apps/beeai-ui/src/app/(auth)/rsc.tsx @@ -30,6 +30,7 @@ export async function ensureToken(request: Request) { } const token = await getToken({ req: request, cookieName: AUTH_COOKIE_NAME, secret: process.env.NEXTAUTH_SECRET }); + console.log({ token }); return token; } diff --git a/apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.module.scss b/apps/beeai-ui/src/components/RadioSelect/RadioSelect.module.scss similarity index 100% rename from apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.module.scss rename to apps/beeai-ui/src/components/RadioSelect/RadioSelect.module.scss diff --git a/apps/beeai-ui/src/components/RadioSelect/RadioSelect.tsx b/apps/beeai-ui/src/components/RadioSelect/RadioSelect.tsx new file mode 100644 index 000000000..24bb35318 --- /dev/null +++ b/apps/beeai-ui/src/components/RadioSelect/RadioSelect.tsx @@ -0,0 +1,48 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Checkmark } from '@carbon/icons-react'; +import { RadioButton, RadioButtonGroup } from '@carbon/react'; +import clsx from 'clsx'; +import { useId } from 'react'; + +import classes from './RadioSelect.module.scss'; + +interface Props { + name: string; + label: string; + value?: string; + options: { value: string; label: string }[]; + onChange: (value: string) => void; +} + +export function RadioSelect({ name, label, value, options, onChange }: Props) { + const htmlId = useId(); + + return ( + + {options.map(({ value: optionValue, label }) => { + const isSelected = optionValue === value; + return ( +
+ onChange(optionValue)} + /> + {optionValue === value && } +
+ ); + })} +
+ ); +} diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss new file mode 100644 index 000000000..4b83827d3 --- /dev/null +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss @@ -0,0 +1,15 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +.root { + display: grid; + grid-template-columns: 1fr max-content; + align-items: center; +} + +.settings { + display: flex; + gap: $spacing-03; +} diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx new file mode 100644 index 000000000..26ea57132 --- /dev/null +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx @@ -0,0 +1,48 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Button } from '@carbon/react'; +import { useMergeRefs } from '@floating-ui/react'; + +import { useApp } from '#contexts/App/index.ts'; +import { RunModels } from '#modules/runs/settings/RunModels.tsx'; +import { RunSettings } from '#modules/runs/settings/RunSettings.tsx'; +import { useRunSettingsDialog } from '#modules/runs/settings/useRunSettingsDialog.ts'; + +import classes from './FormActionBar.module.scss'; + +interface Props { + isDisabled?: boolean; + submitLabel: string; + showRunSettings?: boolean; +} + +export function FormActionBar({ isDisabled, submitLabel, showRunSettings }: Props) { + const { + config: { featureFlags }, + } = useApp(); + + const modelsDialog = useRunSettingsDialog(); + const settingsDialog = useRunSettingsDialog(); + const formRefs = useMergeRefs([modelsDialog.refs.setPositionReference, settingsDialog.refs.setPositionReference]); + + return ( +
+ {showRunSettings && ( +
+ + {featureFlags.ModelProviders && } +
+ )} + {!isDisabled && ( +
+ +
+ )} +
+ ); +} diff --git a/apps/beeai-ui/src/modules/form/components/FormRenderer.tsx b/apps/beeai-ui/src/modules/form/components/FormRenderer.tsx index 4483d8ace..cb741d234 100644 --- a/apps/beeai-ui/src/modules/form/components/FormRenderer.tsx +++ b/apps/beeai-ui/src/modules/form/components/FormRenderer.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Button } from '@carbon/react'; import type { FormRender } from 'beeai-sdk'; import { FormProvider, useForm } from 'react-hook-form'; @@ -13,6 +12,7 @@ import { isNotNull } from '#utils/helpers.ts'; import type { RunFormValues } from '../types'; import { getDefaultValues } from '../utils'; +import { FormActionBar } from './FormActionBar'; import { FormFields } from './FormFields'; import classes from './FormRenderer.module.scss'; @@ -21,6 +21,7 @@ interface Props { defaultHeading?: string | null; showHeading?: boolean; isDisabled?: boolean; + showRunSettings?: boolean; onSubmit: (values: RunFormValues) => void; } @@ -28,6 +29,7 @@ export function FormRenderer({ definition, defaultHeading, showHeading: showHeadingProp = true, + showRunSettings, isDisabled, onSubmit, }: Props) { @@ -52,15 +54,11 @@ export function FormRenderer({ - {!isDisabled && ( - <> -
- -
- - )} + diff --git a/apps/beeai-ui/src/modules/runs/components/FormRenderView.tsx b/apps/beeai-ui/src/modules/runs/components/FormRenderView.tsx index 1b19f48b9..869eac777 100644 --- a/apps/beeai-ui/src/modules/runs/components/FormRenderView.tsx +++ b/apps/beeai-ui/src/modules/runs/components/FormRenderView.tsx @@ -27,6 +27,7 @@ export function FormRenderView({ formRender }: Props) { { const form = { request: formRender, diff --git a/apps/beeai-ui/src/modules/runs/components/ModelProviders.tsx b/apps/beeai-ui/src/modules/runs/components/ModelProviders.tsx deleted file mode 100644 index b34ef57d7..000000000 --- a/apps/beeai-ui/src/modules/runs/components/ModelProviders.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright 2025 © BeeAI a Series of LF Projects, LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ModelAlt } from '@carbon/icons-react'; -import { Select, SelectItem } from '@carbon/react'; -import type { ChangeEvent } from 'react'; -import { useId, useMemo } from 'react'; - -import { Tooltip } from '#components/Tooltip/Tooltip.tsx'; - -import { useAgentDemands } from '../contexts/agent-demands'; -import classes from './ModelProviders.module.scss'; - -export function ModelProviders() { - const { - matchedLLMProviders, - selectedLLMProviders, - selectLLMProvider, - matchedEmbeddingProviders, - selectedEmbeddingProviders, - selectEmbeddingProvider, - } = useAgentDemands(); - - const htmlId = useId(); - - const llmProviderList = useMemo( - () => Object.entries(matchedLLMProviders || {}).map(([key, items]) => ({ key, items })), - [matchedLLMProviders], - ); - - const embeddingProviderList = useMemo( - () => Object.entries(matchedEmbeddingProviders || {}).map(([key, items]) => ({ key, items })), - [matchedEmbeddingProviders], - ); - - return ( -
- {llmProviderList.map(({ key, items }) => ( -
- - - - -
- ))} - {embeddingProviderList.map(({ key, items }) => ( -
- - - - -
- ))} -
- ); -} diff --git a/apps/beeai-ui/src/modules/runs/components/RunInput.tsx b/apps/beeai-ui/src/modules/runs/components/RunInput.tsx index 11a6356b0..f17338fca 100644 --- a/apps/beeai-ui/src/modules/runs/components/RunInput.tsx +++ b/apps/beeai-ui/src/modules/runs/components/RunInput.tsx @@ -4,6 +4,7 @@ */ import { InlineLoading } from '@carbon/react'; +import { useMergeRefs } from '@floating-ui/react'; import { useCallback, useRef, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { mergeRefs } from 'react-merge-refs'; @@ -17,10 +18,11 @@ import { dispatchInputEventOnTextarea, submitFormOnEnter } from '#utils/form-uti import { ChatDefaultTools } from '../chat/constants'; import { useAgentRun } from '../contexts/agent-run'; +import { RunModels } from '../settings/RunModels'; import { RunSettings } from '../settings/RunSettings'; +import { useRunSettingsDialog } from '../settings/useRunSettingsDialog'; import type { RunRunFormValues } from '../types'; import { MCPConfig } from './MCPConfig'; -import { ModelProviders } from './ModelProviders'; import { PromptExamples } from './PromptExamples'; import { RunFiles } from './RunFiles'; import classes from './RunInput.module.scss'; @@ -42,6 +44,8 @@ export function RunInput({ promptExamples, onMessageSent }: Props) { config: { featureFlags }, } = useApp(); + const { hasMessages } = useAgentRun(); + const { agent: { ui: { interaction_mode, input_placeholder }, @@ -96,11 +100,21 @@ export function RunInput({ promptExamples, onMessageSent }: Props) { [setValue, dispatchInputEventAndFocus], ); + const modelsDialog = useRunSettingsDialog({ + maxWidth: hasMessages ? 'container' : { widthPx: 482 }, + }); + const settingsDialog = useRunSettingsDialog(); + const formRefs = useMergeRefs([ + hasMessages ? modelsDialog.refs.setPositionReference : null, + settingsDialog.refs.setPositionReference, + formRef, + ]); + return (
{ event.preventDefault(); @@ -132,13 +146,13 @@ export function RunInput({ promptExamples, onMessageSent }: Props) {
- + {!isFileUploadDisabled && } {featureFlags.MCP && } - {featureFlags.ModelProviders && } + {featureFlags.ModelProviders && }
diff --git a/apps/beeai-ui/src/modules/runs/components/ModelProviders.module.scss b/apps/beeai-ui/src/modules/runs/settings/ModelProviders.module.scss similarity index 94% rename from apps/beeai-ui/src/modules/runs/components/ModelProviders.module.scss rename to apps/beeai-ui/src/modules/runs/settings/ModelProviders.module.scss index 4b7ee7638..4a5be4785 100644 --- a/apps/beeai-ui/src/modules/runs/components/ModelProviders.module.scss +++ b/apps/beeai-ui/src/modules/runs/settings/ModelProviders.module.scss @@ -6,6 +6,7 @@ .root { display: flex; flex-direction: column; + row-gap: $spacing-05; } .item { diff --git a/apps/beeai-ui/src/modules/runs/settings/ModelProviders.tsx b/apps/beeai-ui/src/modules/runs/settings/ModelProviders.tsx new file mode 100644 index 000000000..a68e9199c --- /dev/null +++ b/apps/beeai-ui/src/modules/runs/settings/ModelProviders.tsx @@ -0,0 +1,61 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useMemo } from 'react'; + +import { RadioSelect } from '#components/RadioSelect/RadioSelect.tsx'; + +import { useAgentDemands } from '../contexts/agent-demands'; +import classes from './ModelProviders.module.scss'; + +export function ModelProviders() { + const { + matchedLLMProviders, + selectedLLMProviders, + selectLLMProvider, + matchedEmbeddingProviders, + selectedEmbeddingProviders, + selectEmbeddingProvider, + } = useAgentDemands(); + + const llmProviderList = useMemo( + () => Object.entries(matchedLLMProviders || {}).map(([key, items]) => ({ key, items })), + [matchedLLMProviders], + ); + + const embeddingProviderList = useMemo( + () => Object.entries(matchedEmbeddingProviders || {}).map(([key, items]) => ({ key, items })), + [matchedEmbeddingProviders], + ); + + return ( +
+ {llmProviderList.map(({ key, items }) => ( + ({ label: item, value: item }))} + onChange={(value) => { + selectLLMProvider(key, value); + }} + /> + ))} + {embeddingProviderList.map(({ key, items }) => ( + ({ label: item, value: item }))} + onChange={(value) => { + selectEmbeddingProvider(key, value); + }} + /> + ))} +
+ ); +} diff --git a/apps/beeai-ui/src/modules/runs/settings/RunSettings.module.scss b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss similarity index 87% rename from apps/beeai-ui/src/modules/runs/settings/RunSettings.module.scss rename to apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss index 585694440..31e501801 100644 --- a/apps/beeai-ui/src/modules/runs/settings/RunSettings.module.scss +++ b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss @@ -31,9 +31,8 @@ background-color: $background; border-radius: $border-radius; box-shadow: $box-shadow; - padding: $spacing-03; - display: flex; - flex-direction: column; - row-gap: $spacing-06; + padding-inline: $spacing-03; + padding-block: $spacing-05; + inline-size: 100%; max-block-size: rem(294px); } diff --git a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx new file mode 100644 index 000000000..ca0e1901e --- /dev/null +++ b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx @@ -0,0 +1,96 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChevronDown } from '@carbon/icons-react'; +import { Button, IconButton } from '@carbon/react'; +import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; +import clsx from 'clsx'; +import { AnimatePresence, motion } from 'framer-motion'; +import type { ComponentType, PropsWithChildren } from 'react'; + +import { fadeProps } from '#utils/fadeProps.ts'; + +import { useAgentRun } from '../contexts/agent-run'; +import classes from './RunDialogButton.module.scss'; +import type { RunSettingsDialogReturn } from './useRunSettingsDialog'; + +interface Props { + dialog: RunSettingsDialogReturn; + useButtonReference?: boolean; + label: string; + icon: ComponentType; + iconOnly?: boolean; +} + +export function RunDialogButton({ + dialog, + useButtonReference, + children, + label, + icon: Icon, + iconOnly, +}: PropsWithChildren) { + const { hasMessages } = useAgentRun(); + + const { isOpen, refs, floatingStyles, context, getReferenceProps, getFloatingProps } = dialog; + + return ( +
+ {iconOnly ? ( + + + + ) : ( + + )} + + + {isOpen && ( + + +
+ + {children} + +
+
+
+ )} +
+
+ ); +} diff --git a/apps/beeai-ui/src/modules/runs/settings/RunModels.tsx b/apps/beeai-ui/src/modules/runs/settings/RunModels.tsx new file mode 100644 index 000000000..cbe82293d --- /dev/null +++ b/apps/beeai-ui/src/modules/runs/settings/RunModels.tsx @@ -0,0 +1,33 @@ +/** + * Copyright 2025 © BeeAI a Series of LF Projects, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Cube } from '@carbon/icons-react'; +import isEmpty from 'lodash/isEmpty'; + +import { useAgentDemands } from '../contexts/agent-demands'; +import { useAgentRun } from '../contexts/agent-run'; +import { ModelProviders } from './ModelProviders'; +import { RunDialogButton } from './RunDialogButton'; +import type { RunSettingsDialogReturn } from './useRunSettingsDialog'; + +interface Props { + dialog: RunSettingsDialogReturn; + iconOnly?: boolean; +} + +export function RunModels({ dialog, iconOnly = true }: Props) { + const { hasMessages } = useAgentRun(); + const { matchedLLMProviders, matchedEmbeddingProviders } = useAgentDemands(); + + if (isEmpty(matchedLLMProviders) && isEmpty(matchedEmbeddingProviders)) { + return null; + } + + return ( + + + + ); +} diff --git a/apps/beeai-ui/src/modules/runs/settings/RunSettings.tsx b/apps/beeai-ui/src/modules/runs/settings/RunSettings.tsx index 8f6173c76..45db11ba2 100644 --- a/apps/beeai-ui/src/modules/runs/settings/RunSettings.tsx +++ b/apps/beeai-ui/src/modules/runs/settings/RunSettings.tsx @@ -3,70 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Settings } from '@carbon/icons-react'; -import { IconButton } from '@carbon/react'; -import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; -import { AnimatePresence, motion } from 'framer-motion'; -import type { RefObject } from 'react'; - -import { fadeProps } from '#utils/fadeProps.ts'; +import { SettingsAdjust } from '@carbon/icons-react'; import { useAgentRun } from '../contexts/agent-run'; -import classes from './RunSettings.module.scss'; +import { RunDialogButton } from './RunDialogButton'; import { RunSettingsForm } from './RunSettingsForm'; -import { useRunSettingsDialog } from './useRunSettingsDialog'; +import type { RunSettingsDialogReturn } from './useRunSettingsDialog'; interface Props { - containerRef: RefObject; + dialog: RunSettingsDialogReturn; + iconOnly?: boolean; } -export function RunSettings({ containerRef }: Props) { - const { settingsRender, hasMessages } = useAgentRun(); - - const { isOpen, refs, floatingStyles, context, getReferenceProps, getFloatingProps } = useRunSettingsDialog({ - containerRef, - }); +export function RunSettings({ dialog, iconOnly }: Props) { + const { settingsRender } = useAgentRun(); if (!settingsRender?.fields.length) { return null; } return ( -
- - - - - - {isOpen && ( - - -
- - - -
-
-
- )} -
-
+ + + ); } diff --git a/apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.tsx b/apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.tsx index 67ccc6904..48d090cf0 100644 --- a/apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.tsx +++ b/apps/beeai-ui/src/modules/runs/settings/SingleSelectSettingsField.tsx @@ -3,21 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Checkmark } from '@carbon/icons-react'; -import { RadioButton, RadioButtonGroup } from '@carbon/react'; import type { SettingsSingleSelectFieldValue } from 'beeai-sdk'; -import clsx from 'clsx'; -import { useId } from 'react'; import { useController } from 'react-hook-form'; -import classes from './SingleSelectSettingsField.module.scss'; +import { RadioSelect } from '#components/RadioSelect/RadioSelect.tsx'; export function SingleSelectSettingsField({ field, }: { field: { id: string; label: string; options: { value: string; label: string }[] }; }) { - const htmlId = useId(); const { id, label } = field; const { @@ -27,27 +22,12 @@ export function SingleSelectSettingsField({ }); return ( - - {field.options.map(({ value: optionValue, label }) => { - const isSelected = optionValue === value; - return ( -
- onChange(optionValue)} - /> - {optionValue === value && } -
- ); - })} -
+ label={label} + value={value} + options={field.options} + onChange={onChange} + /> ); } diff --git a/apps/beeai-ui/src/modules/runs/settings/useRunSettingsDialog.ts b/apps/beeai-ui/src/modules/runs/settings/useRunSettingsDialog.ts index f3507004b..f907fe07e 100644 --- a/apps/beeai-ui/src/modules/runs/settings/useRunSettingsDialog.ts +++ b/apps/beeai-ui/src/modules/runs/settings/useRunSettingsDialog.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { rem } from '@carbon/layout'; import { autoUpdate, offset, @@ -13,16 +14,15 @@ import { useInteractions, useRole, } from '@floating-ui/react'; -import type { RefObject } from 'react'; import { useState } from 'react'; import { useAgentRun } from '../contexts/agent-run'; interface Props { - containerRef: RefObject; + maxWidth?: 'container' | { widthPx: number }; } -export function useRunSettingsDialog({ containerRef }: Props) { +export function useRunSettingsDialog({ maxWidth = 'container' }: Props = {}) { const { hasMessages } = useAgentRun(); const [isOpen, setIsOpen] = useState(false); @@ -32,16 +32,19 @@ export function useRunSettingsDialog({ containerRef }: Props) { onOpenChange: setIsOpen, whileElementsMounted: autoUpdate, middleware: [ - offset(hasMessages ? OFFSET_CHAT : OFFSET_LANDING), + offset(hasMessages ? OFFSET_CHAT : maxWidth === 'container' ? OFFSET_LANDING_CONTAINER : OFFSET_LANDING), size({ apply({ elements }) { - const container = containerRef.current; + const container = elements.reference; - if (container) { - Object.assign(elements.floating.style, { - maxWidth: `${container.offsetWidth}px`, - }); + if (maxWidth === 'container' && !container) { + return; } + + const widthValue = maxWidth === 'container' ? container.getBoundingClientRect().width : maxWidth.widthPx; + Object.assign(elements.floating.style, { + maxWidth: rem(widthValue), + }); }, }), ], @@ -63,11 +66,17 @@ export function useRunSettingsDialog({ containerRef }: Props) { }; } +export type RunSettingsDialogReturn = ReturnType; + const OFFSET_LANDING = { - mainAxis: 20, + mainAxis: 7, crossAxis: -12, }; +const OFFSET_LANDING_CONTAINER = { + mainAxis: -4, + crossAxis: 0, +}; const OFFSET_CHAT = { - mainAxis: 56, - crossAxis: -12, + mainAxis: 8, + crossAxis: 0, }; diff --git a/mise.lock b/mise.lock index 3b423b096..7d06ba9d4 100644 --- a/mise.lock +++ b/mise.lock @@ -1,93 +1,50 @@ -[[tools."asdf:CrouchingMuppet/asdf-lima"]] +[tools."asdf:CrouchingMuppet/asdf-lima"] version = "1.2.1" backend = "asdf:CrouchingMuppet/asdf-lima" -[[tools.fd]] +[tools.fd] version = "10.3.0" backend = "aqua:sharkdp/fd" -[tools.fd.platforms.macos-arm64] -checksum = "blake3:e7a554aed4fea189f2aa297e82087ceaebb844eac55a2503e5321cce88fbb636" -size = 1337349 -url = "https://github.com/sharkdp/fd/releases/download/v10.3.0/fd-v10.3.0-aarch64-apple-darwin.tar.gz" - -[[tools.gum]] +[tools.gum] version = "0.17.0" backend = "aqua:charmbracelet/gum" -[tools.gum.platforms.macos-arm64] -checksum = "sha256:e2a4b8596efa05821d8c58d0c1afbcd7ad1699ba69c689cc3ff23a4a99c8b237" -size = 4395909 -url = "https://github.com/charmbracelet/gum/releases/download/v0.17.0/gum_0.17.0_Darwin_arm64.tar.gz" - -[[tools.helm]] +[tools.helm] version = "3.19.0" backend = "aqua:helm/helm" -[tools.helm.platforms.macos-arm64] -checksum = "sha256:31513e1193da4eb4ae042eb5f98ef9aca7890cfa136f4707c8d4f70e2115bef6" -size = 17058141 -url = "https://get.helm.sh/helm-v3.19.0-darwin-arm64.tar.gz" - -[[tools.kubeconform]] +[tools.kubeconform] version = "0.7.0" backend = "aqua:yannh/kubeconform" -[tools.kubeconform.platforms.macos-arm64] -checksum = "sha256:b5d32b2cb77f9c781c976b20a85e2d0bc8f9184d5d1cfe665a2f31a19f99eeb9" -size = 7031569 -url = "https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-darwin-arm64.tar.gz" - -[[tools.node]] +[tools.node] version = "22.19.0" backend = "core:node" -[tools.node.platforms.macos-arm64] -checksum = "sha256:c59006db713c770d6ec63ae16cb3edc11f49ee093b5c415d667bb4f436c6526d" -size = 47739366 -url = "https://nodejs.org/dist/v22.19.0/node-v22.19.0-darwin-arm64.tar.gz" - -[[tools."pipx:toml-cli"]] +[tools."pipx:toml-cli"] version = "0.8.2" backend = "pipx:toml-cli" -[[tools.pnpm]] +[tools.pnpm] version = "10.15.1" backend = "aqua:pnpm/pnpm" -[tools.pnpm.platforms.macos-arm64] -checksum = "blake3:502db5ea7eeeccf6307a8af184d2a63acdf88cdb3346d7fb0d55617e807d1c5a" -size = 62649648 -url = "https://github.com/pnpm/pnpm/releases/download/v10.15.1/pnpm-macos-arm64" - -[[tools."ubi:google/addlicense"]] +[tools."ubi:google/addlicense"] version = "1.2.0" backend = "ubi:google/addlicense" -[tools."ubi:google/addlicense".platforms.macos-arm64-addlicense] -checksum = "blake3:29c4b12b602b33cf8b88b2f947741bdbd60516afd72d9585abc95912fbeca47a" - -[[tools."ubi:telepresenceio/telepresence"]] +[tools."ubi:telepresenceio/telepresence"] version = "2.24.1" backend = "ubi:telepresenceio/telepresence" -[tools."ubi:telepresenceio/telepresence".platforms.macos-arm64-telepresence] -checksum = "blake3:1d8fc5495ba38b306e2b7b01da21446312eb6fe2111c83d520a56fa5a0b69cb5" - -[[tools.uv]] +[tools.uv] version = "0.9.5" backend = "aqua:astral-sh/uv" -[tools.uv.platforms.macos-arm64] -checksum = "sha256:dc098ff224d78ed418e121fd374f655949d2c7031a70f6f6604eaf016a130433" -size = 18341942 -url = "https://github.com/astral-sh/uv/releases/download/0.9.5/uv-aarch64-apple-darwin.tar.gz" +[tools.uv.checksums] +"uv-aarch64-apple-darwin.tar.gz" = "sha256:dc098ff224d78ed418e121fd374f655949d2c7031a70f6f6604eaf016a130433" -[[tools.yq]] +[tools.yq] version = "4.47.2" backend = "aqua:mikefarah/yq" - -[tools.yq.platforms.macos-arm64] -checksum = "blake3:28c623201e07c86d5082e88384af61ed40af8b8f71d22758e8b0690d424fc3c2" -size = 10980754 -url = "https://github.com/mikefarah/yq/releases/download/v4.47.2/yq_darwin_arm64" From 296150646a813fb7de90541707248731fa5c70ed Mon Sep 17 00:00:00 2001 From: Petr Kadlec Date: Thu, 23 Oct 2025 10:45:18 +0200 Subject: [PATCH 2/4] fixup! finish run settings dialog Signed-off-by: Petr Kadlec --- .../examples/request_form_agent.py | 43 ------------------- apps/beeai-sdk-py/examples/settings_agent.py | 9 ++-- .../form/components/FormActionBar.module.scss | 1 + .../modules/form/components/FormActionBar.tsx | 4 +- .../src/modules/runs/components/RunInput.tsx | 8 ++-- .../runs/settings/RunDialogButton.module.scss | 17 ++++++++ .../modules/runs/settings/RunDialogButton.tsx | 3 +- .../src/modules/runs/settings/RunModels.tsx | 8 +++- .../runs/settings/useRunSettingsDialog.ts | 42 ++++++++++-------- 9 files changed, 64 insertions(+), 71 deletions(-) diff --git a/apps/beeai-sdk-py/examples/request_form_agent.py b/apps/beeai-sdk-py/examples/request_form_agent.py index 0c6687f1a..86c473217 100644 --- a/apps/beeai-sdk-py/examples/request_form_agent.py +++ b/apps/beeai-sdk-py/examples/request_form_agent.py @@ -15,19 +15,6 @@ OptionItem, TextField, ) -from beeai_sdk.a2a.extensions.ui.settings import ( - CheckboxField, - SingleSelectField, - SettingsExtensionServer, - SettingsExtensionSpec, - SettingsRender, -) -from beeai_sdk.a2a.extensions import ( - LLMDemand, - LLMServiceExtensionParams, - LLMServiceExtensionServer, - LLMServiceExtensionSpec, -) from beeai_sdk.server import Server server = Server() @@ -46,36 +33,6 @@ async def request_form_agent( ) ), ], - settings: Annotated[ - SettingsExtensionServer, - SettingsExtensionSpec( - params=SettingsRender( - fields=[ - SingleSelectField( - id="response_style", - label="Response Style", - options=[ - {"label": "Concise", "value": "concise"}, - {"label": "Detailed", "value": "detailed"}, - {"label": "Humorous", "value": "humorous"}, - ], - default_value="concise", - ), - ], - ), - ), - ], - llm: Annotated[ - LLMServiceExtensionServer, - LLMServiceExtensionSpec( - params=LLMServiceExtensionParams( - llm_demands={ - "fast": LLMDemand(suggested=("openai/gpt-4o-mini",)), - "smart": LLMDemand(suggested=("openai/gpt-5",)), - } - ) - ), - ], ): """Request form data""" try: diff --git a/apps/beeai-sdk-py/examples/settings_agent.py b/apps/beeai-sdk-py/examples/settings_agent.py index 42be48659..55cd499e1 100644 --- a/apps/beeai-sdk-py/examples/settings_agent.py +++ b/apps/beeai-sdk-py/examples/settings_agent.py @@ -9,11 +9,12 @@ from beeai_sdk.a2a.extensions.ui.settings import ( CheckboxField, - SingleSelectField, CheckboxGroupField, + OptionItem, SettingsExtensionServer, SettingsExtensionSpec, SettingsRender, + SingleSelectField, ) from beeai_sdk.a2a.types import RunYield from beeai_sdk.server import Server @@ -45,9 +46,9 @@ async def settings_agent( id="response_style", label="Response Style", options=[ - {"label": "Concise", "value": "concise"}, - {"label": "Detailed", "value": "detailed"}, - {"label": "Humorous", "value": "humorous"}, + OptionItem(value="concise", label="Concise"), + OptionItem(value="detailed", label="Detailed"), + OptionItem(value="humorous", label="Humorous"), ], default_value="concise", ), diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss index 4b83827d3..640c9a324 100644 --- a/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss @@ -12,4 +12,5 @@ .settings { display: flex; gap: $spacing-03; + block-size: rem(40px); } diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx index 26ea57132..85901689a 100644 --- a/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx @@ -24,8 +24,8 @@ export function FormActionBar({ isDisabled, submitLabel, showRunSettings }: Prop config: { featureFlags }, } = useApp(); - const modelsDialog = useRunSettingsDialog(); - const settingsDialog = useRunSettingsDialog(); + const modelsDialog = useRunSettingsDialog({ blockOffset: 8 }); + const settingsDialog = useRunSettingsDialog({ blockOffset: 8 }); const formRefs = useMergeRefs([modelsDialog.refs.setPositionReference, settingsDialog.refs.setPositionReference]); return ( diff --git a/apps/beeai-ui/src/modules/runs/components/RunInput.tsx b/apps/beeai-ui/src/modules/runs/components/RunInput.tsx index f17338fca..de738020a 100644 --- a/apps/beeai-ui/src/modules/runs/components/RunInput.tsx +++ b/apps/beeai-ui/src/modules/runs/components/RunInput.tsx @@ -101,7 +101,7 @@ export function RunInput({ promptExamples, onMessageSent }: Props) { ); const modelsDialog = useRunSettingsDialog({ - maxWidth: hasMessages ? 'container' : { widthPx: 482 }, + maxWidth: hasMessages ? undefined : MODELS_DIALOG_MAX_WIDTH, }); const settingsDialog = useRunSettingsDialog(); const formRefs = useMergeRefs([ @@ -146,13 +146,13 @@ export function RunInput({ promptExamples, onMessageSent }: Props) {
- + {!isFileUploadDisabled && } {featureFlags.MCP && } - {featureFlags.ModelProviders && } + {featureFlags.ModelProviders && }
@@ -182,3 +182,5 @@ export function RunInput({ promptExamples, onMessageSent }: Props) { ); } + +const MODELS_DIALOG_MAX_WIDTH = 482; diff --git a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss index 31e501801..2bdbf8cdd 100644 --- a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss +++ b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.module.scss @@ -5,6 +5,7 @@ .root { display: flex; + align-items: center; :global(.#{$prefix}--btn) { &:hover, &:active, @@ -36,3 +37,19 @@ inline-size: 100%; max-block-size: rem(294px); } + +.button { + padding-inline: $spacing-03; + display: flex; + gap: $spacing-03; + align-items: center; + block-size: rem(28px); +} + +.statusIcon { + transition: transform $duration-fast-01 ease-in-out; + + .isOpen & { + transform: rotate(180deg); + } +} diff --git a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx index ca0e1901e..4f827ff18 100644 --- a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx +++ b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx @@ -53,7 +53,8 @@ export function RunDialogButton({ ) : (
diff --git a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx index 4f827ff18..437b4005f 100644 --- a/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx +++ b/apps/beeai-ui/src/modules/runs/settings/RunDialogButton.tsx @@ -68,12 +68,7 @@ export function RunDialogButton({ {isOpen && ( -
+
Date: Mon, 27 Oct 2025 11:02:20 +0100 Subject: [PATCH 4/4] fixup! review comments Signed-off-by: Petr Kadlec --- .../form/components/FormActionBar.module.scss | 1 - .../modules/form/components/FormActionBar.tsx | 6 ++--- .../form/components/FormRenderer.module.scss | 7 ----- .../modules/form/components/FormRenderer.tsx | 2 +- .../src/modules/runs/components/MCPConfig.tsx | 7 ++++- .../runs/components/RunInput.module.scss | 2 +- .../contexts/agent-run/AgentRunProvider.tsx | 4 +-- .../runs/settings/useRunSettingsDialog.ts | 26 +++++++++++-------- 8 files changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss index 66beb890e..536407541 100644 --- a/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.module.scss @@ -12,7 +12,6 @@ .settings { display: flex; gap: $spacing-03; - block-size: rem(40px); } .buttons { diff --git a/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx index 85901689a..e8b0bc4ef 100644 --- a/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx +++ b/apps/beeai-ui/src/modules/form/components/FormActionBar.tsx @@ -14,12 +14,12 @@ import { useRunSettingsDialog } from '#modules/runs/settings/useRunSettingsDialo import classes from './FormActionBar.module.scss'; interface Props { - isDisabled?: boolean; + showSubmitButton?: boolean; submitLabel: string; showRunSettings?: boolean; } -export function FormActionBar({ isDisabled, submitLabel, showRunSettings }: Props) { +export function FormActionBar({ showSubmitButton = true, submitLabel, showRunSettings }: Props) { const { config: { featureFlags }, } = useApp(); @@ -36,7 +36,7 @@ export function FormActionBar({ isDisabled, submitLabel, showRunSettings }: Prop {featureFlags.ModelProviders && }
)} - {!isDisabled && ( + {showSubmitButton && (