From 3561ccb6cae83fc5b1fa495dd50ea3d1acee7bac Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Fri, 8 May 2026 13:41:07 -0700 Subject: [PATCH 01/38] Initial ChatModal --- packages/components/src/internal/actions.ts | 6 + packages/components/src/internal/app/utils.ts | 4 + .../CalculatedFieldOptions.tsx | 120 +++++++++++------- .../ExpressionAssistantModal.tsx | 27 ++++ .../src/internal/components/mcp/ChatModal.tsx | 90 +++++++++++++ .../src/internal/query/APIWrapper.ts | 4 + .../src/theme/domainproperties.scss | 96 ++++++++++++++ 7 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx create mode 100644 packages/components/src/internal/components/mcp/ChatModal.tsx diff --git a/packages/components/src/internal/actions.ts b/packages/components/src/internal/actions.ts index 77a10200dd..fd64682f0a 100644 --- a/packages/components/src/internal/actions.ts +++ b/packages/components/src/internal/actions.ts @@ -784,3 +784,9 @@ export function renameGridView( }); }); } + +export function expressionAssist(prompt: string): Promise { + return new Promise((resolve, reject) => { + console.log('Expression assisting...'); + }); +} diff --git a/packages/components/src/internal/app/utils.ts b/packages/components/src/internal/app/utils.ts index ab948a1f6f..9fdb8287dc 100644 --- a/packages/components/src/internal/app/utils.ts +++ b/packages/components/src/internal/app/utils.ts @@ -390,6 +390,10 @@ export function isAdvancedWorkflowEnabled(moduleContext?: ModuleContext): boolea ); } +export function isAIAssistanceEnabled(): boolean { + return getServerContext()['mcpReady'] === true; +} + export function isDataChangeCommentRequirementFeatureEnabled(moduleContext?: ModuleContext): boolean { return isFeatureEnabled(ProductFeature.DataChangeCommentRequirement, moduleContext); } diff --git a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx index 7c6fa8d4bb..3a9744cbfb 100644 --- a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx +++ b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx @@ -14,6 +14,10 @@ import { SectionHeading } from './SectionHeading'; import { isFieldFullyLocked, isFieldPartiallyLocked } from './propertiesUtil'; import { CALCULATED_TYPE, MULTI_CHOICE_TYPE, PropDescType } from './PropDescType'; import { parseCalculatedColumn } from './actions'; +import { SVGIcon } from '../base/SVGIcon'; +import { isAIAssistanceEnabled } from '../../app/utils'; +import { useModalState } from '../../hooks'; +import { ExpressionAssistantModal } from './ExpressionAssistantModal'; // export for jest testing export const typeToDisplay = (type: string): string => { @@ -90,10 +94,19 @@ export const CalculatedFieldOptions: FC = memo(props => { const [loading, setLoading] = useState(!field.isNew()); const [error, setError] = useState(undefined); const [parsedType, setParsedType] = useState(undefined); + const { close, open, show } = useModalState(); + const assistanceEnabled = isAIAssistanceEnabled(); const isNew = useMemo(() => field.isNew(), [field]); + const { headingId, inputId } = useMemo( + () => ({ + headingId: `expression-label-${domainIndex}-${index}`, + inputId: createFormInputId(DOMAIN_FIELD_VALUE_EXPRESSION, domainIndex, index), + }), + [domainIndex, index] + ); - const handleChange = useCallback( - (evt: any): void => { + const handleChange = useCallback>( + evt => { onChange(evt.target.id, evt.target.value); setError(undefined); setParsedType(undefined); @@ -136,24 +149,18 @@ export const CalculatedFieldOptions: FC = memo(props => { [domainIndex, field.name, getDomainFields, index, onChange] ); - const handleBlur = useCallback( - (evt: any): void => { - const value = evt.target.value; - validateExpression(value, true); + const handleBlur = useCallback>( + evt => { + validateExpression(evt.target.value, true); }, [validateExpression] ); - useEffect( - () => { - if (!isNew) { - validateExpression(field.valueExpression, false); - } - }, - [ - /* on mount only */ - ] - ); + useEffect(() => { + if (!isNew) { + validateExpression(field.valueExpression, false); + } + }, []); //eslint-disable-line react-hooks/exhaustive-deps -- on mount only return (
= memo(props => { })} >
-
+
+
+