From 4e943506e85e2530f5522e2db0a12a5da30bfc0a Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 10:06:54 -0700 Subject: [PATCH 1/6] 1166: Do not close modal on apply expression --- .../CalculatedFieldOptions.tsx | 5 ++- .../ExpressionAssistantModal.test.tsx | 20 ++++++------ .../ExpressionAssistantModal.tsx | 32 +++++++++++++------ packages/components/src/theme/chat.scss | 18 +++++++++++ 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx index 1b04cdf8c5..cd736dca7b 100644 --- a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx +++ b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx @@ -163,10 +163,9 @@ export const CalculatedFieldOptions: FC = memo(prop setError(undefined); setParsedType(undefined); validateExpression(analysis, true); - close(); incrementClientSideMetricCount(EXPR_ASST_METRIC_FEATURE_AREA, 'applyExpression'); }, - [close, inputId, onChange, validateExpression] + [inputId, onChange, validateExpression] ); const onOpenAssistant = useCallback(() => { @@ -297,8 +296,8 @@ export const CalculatedFieldOptions: FC = memo(prop fieldError={field.valueExpression ? error : undefined} fieldExpression={field.valueExpression} getDomainFields={getDomainFields} + onApplyExpression={handleApplyExpression} onCancel={close} - onComplete={handleApplyExpression} /> )} diff --git a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.test.tsx b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.test.tsx index 35a26e3661..21d6fa0adb 100644 --- a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.test.tsx +++ b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.test.tsx @@ -308,11 +308,11 @@ describe('ExpressionAssistantModal', () => { }); describe('renderSegment / SqlExpression', () => { - test('expression segments render an Apply Expression action that calls onComplete with the SQL', () => { + test('expression segments render an Apply Expression action that calls onApplyExpression with the SQL', () => { // Arrange - const onComplete = jest.fn(); + const onApplyExpression = jest.fn(); renderWithAppContext( - , + , makeApiContext() ); // Render the segment ourselves into a container so we can interact with it @@ -322,17 +322,17 @@ describe('ExpressionAssistantModal', () => { const { unmount } = render(
{node}
); fireEvent.click(screen.getByRole('button', { name: /apply expression/i })); - // Assert - the SQL is shown and clicking Apply forwards the expression to onComplete + // Assert - the SQL is shown, and clicking Apply forwards the expression to onApplyExpression expect(screen.getByText('SELECT 1')).toBeInTheDocument(); - expect(onComplete).toHaveBeenCalledTimes(1); - expect(onComplete).toHaveBeenCalledWith('SELECT 1'); + expect(onApplyExpression).toHaveBeenCalledTimes(1); + expect(onApplyExpression).toHaveBeenCalledWith('SELECT 1'); unmount(); }); test('sql segments render read-only without an Apply action', () => { // Arrange renderWithAppContext( - , + , makeApiContext() ); const node = chatModalProps.renderSegment({ type: 'sql', sql: 'SELECT 2' }, 0); @@ -345,15 +345,15 @@ describe('ExpressionAssistantModal', () => { expect(screen.queryByRole('button', { name: /apply expression/i })).not.toBeInTheDocument(); }); - test('expression segment without onComplete still renders read-only', () => { - // Arrange - omit onComplete + test('expression segment without onApplyExpression still renders read-only', () => { + // Arrange - omit onApplyExpression renderWithAppContext(, makeApiContext()); const node = chatModalProps.renderSegment({ type: 'expression', sql: 'SELECT 3' }, 0); // Act render(
{node}
); - // Assert - SQL still renders, but there is no Apply action when no onComplete is supplied + // Assert - SQL still renders, but there is no Apply action when no onApplyExpression is supplied expect(screen.getByText('SELECT 3')).toBeInTheDocument(); expect(screen.queryByRole('button', { name: /apply expression/i })).not.toBeInTheDocument(); }); diff --git a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx index a32dde317e..3d63f7561b 100644 --- a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx +++ b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx @@ -1,4 +1,5 @@ import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import classNames from 'classnames'; import { useAppContext } from '../../AppContext'; import { generateId } from '../../util/utils'; import { ChatModal, RenderSegment } from '../mcp/ChatModal'; @@ -26,21 +27,34 @@ function createChatMessage(message: Partial): ChatMessage { } interface SqlSnippetProps { - onApply?: (sql: string) => void; + onApplyExpression?: (sql: string) => void; readOnly?: boolean; sql: string; } -const SqlExpression: FC = memo(({ onApply, readOnly, sql }) => { - const handleApply = useCallback(() => onApply?.(sql), [onApply, sql]); +const SqlExpression: FC = memo(({ onApplyExpression, readOnly, sql }) => { + const [animating, setAnimating] = useState(false); + const handleApply = useCallback(() => { + setAnimating(true); + onApplyExpression(sql); + }, [onApplyExpression, sql]); + + const onAnimationEnd = useCallback(() => { + setAnimating(false); + }, []); + return (
                 {sql}
             
- {!readOnly && onApply && ( + {!readOnly && onApplyExpression && ( )}
@@ -52,8 +66,8 @@ export interface ExpressionAssistantModalProps { fieldError?: string; fieldExpression?: string; getDomainFields: GetDomainFields; + onApplyExpression?: (analysis: string) => void; onCancel: () => void; - onComplete?: (analysis: string) => void; } function useExpressionAssistance( @@ -187,7 +201,7 @@ function useExpressionAssistance( } export const ExpressionAssistantModal: FC = memo(props => { - const { fieldError, fieldExpression, getDomainFields, onCancel, onComplete } = props; + const { fieldError, fieldExpression, getDomainFields, onApplyExpression, onCancel } = props; const { domainFields, systemFields } = useMemo(() => { const { domainFields, systemFields } = getDomainFields(); return { domainFields: domainFields.toArray(), systemFields }; @@ -202,14 +216,14 @@ export const ExpressionAssistantModal: FC = memo( const renderSegment = useCallback( (segment, index) => { if (segment.type === 'expression' && segment.sql) { - return ; + return ; } if (segment.type === 'sql' && segment.sql) { return ; } return undefined; }, - [onComplete] + [onApplyExpression] ); return ( diff --git a/packages/components/src/theme/chat.scss b/packages/components/src/theme/chat.scss index 22ea843341..0a94c3b1f5 100644 --- a/packages/components/src/theme/chat.scss +++ b/packages/components/src/theme/chat.scss @@ -124,3 +124,21 @@ margin-top: 12px; } } + +@keyframes customBounce { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.4); + color: $brand-success; + } + 100% { + transform: scale(1); + } +} + +.bounce-effect { + display: inline-block; + animation: customBounce 0.6s ease-in-out; +} From 8709d40cc12d34b7f0835246181c473e821b7408 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 10:36:03 -0700 Subject: [PATCH 2/6] 1170: Refill prompt on interrupt --- .../components/mcp/ChatModal.test.tsx | 45 +++++++++++++++++++ .../src/internal/components/mcp/ChatModal.tsx | 14 +++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/components/src/internal/components/mcp/ChatModal.test.tsx b/packages/components/src/internal/components/mcp/ChatModal.test.tsx index c9ac51ee6b..5a1bac426e 100644 --- a/packages/components/src/internal/components/mcp/ChatModal.test.tsx +++ b/packages/components/src/internal/components/mcp/ChatModal.test.tsx @@ -221,6 +221,51 @@ describe('ChatModal', () => { }); }); + describe('interrupt behavior', () => { + test('stop refills the prompt with the last sent value when the input is empty', () => { + // Arrange + const sendPrompt = jest.fn().mockResolvedValue(undefined); + const onInterrupt = jest.fn(); + const { rerender } = render(); + const textarea = getTextarea(); + + // Act - send a prompt, then simulate pending state, then click stop + fireEvent.change(textarea, { target: { value: 'first prompt' } }); + fireEvent.keyDown(textarea, { key: 'Enter' }); + expect(sendPrompt).toHaveBeenCalledWith('first prompt'); + expect(getTextarea()).toHaveValue(''); + + rerender(); + fireEvent.click(document.querySelector('.prompt-button') as HTMLButtonElement); + + // Assert - interrupt fired and the empty input is repopulated with the last sent value + expect(onInterrupt).toHaveBeenCalledWith(true); + rerender(); + expect(getTextarea()).toHaveValue('first prompt'); + }); + + test('stop preserves the current input when the user has typed something else', () => { + // Arrange + const sendPrompt = jest.fn().mockResolvedValue(undefined); + const onInterrupt = jest.fn(); + const { rerender } = render(); + const textarea = getTextarea(); + + // Act - send a prompt, transition to pending, then type a new prompt before clicking stop + fireEvent.change(textarea, { target: { value: 'first prompt' } }); + fireEvent.keyDown(textarea, { key: 'Enter' }); + + rerender(); + fireEvent.change(getTextarea(), { target: { value: 'new draft' } }); + fireEvent.click(document.querySelector('.prompt-button') as HTMLButtonElement); + + // Assert - interrupt fired and the user's in-progress text is not overwritten + expect(onInterrupt).toHaveBeenCalledWith(true); + rerender(); + expect(getTextarea()).toHaveValue('new draft'); + }); + }); + describe('cancel behavior', () => { test('End Chat calls onInterrupt(false) then onCancel', () => { // Arrange diff --git a/packages/components/src/internal/components/mcp/ChatModal.tsx b/packages/components/src/internal/components/mcp/ChatModal.tsx index 5b2cf913a4..973f0f1895 100644 --- a/packages/components/src/internal/components/mcp/ChatModal.tsx +++ b/packages/components/src/internal/components/mcp/ChatModal.tsx @@ -60,6 +60,7 @@ export interface ChatModalProps { export const ChatModal: FC = memo(props => { const { isPending, messages, onCancel, onInterrupt, renderSegment, sendPrompt, title } = props; const [prompt, setPrompt] = useState(''); + const lastSentPromptRef = useRef(''); const textAreaRef = useRef(null); const historyRef = useRef(null); const timer = useTimeout(); @@ -104,11 +105,22 @@ export const ChatModal: FC = memo(props => { const handleInterrupt = useCallback(() => { onInterrupt(true); - }, [onInterrupt]); + setPrompt(current => (current === '' ? lastSentPromptRef.current : current)); + + // Place the cursor back where it was in the prompt + timer.set(() => { + const el = textAreaRef.current; + if (!el) return; + el.focus(); + const end = el.value.length; + el.setSelectionRange(end, end); + }); + }, [onInterrupt, timer]); const handleSend = useCallback(() => { const trimmed = prompt.trim(); if (!trimmed || isPending) return; + lastSentPromptRef.current = trimmed; setPrompt(''); sendPrompt(trimmed); }, [prompt, isPending, sendPrompt]); From 30be03893cab9a5d91f8909f90a383fdf146b67d Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 14:38:07 -0700 Subject: [PATCH 3/6] 1165: display data type for applicable expressions --- .../CalculatedFieldOptions.tsx | 2 +- .../ExpressionAssistantModal.test.tsx | 32 +++++++++--- .../ExpressionAssistantModal.tsx | 52 ++++++++++++------- .../components/domainproperties/actions.ts | 16 +++++- .../src/internal/components/mcp/models.ts | 1 + packages/components/src/theme/chat.scss | 4 ++ 6 files changed, 78 insertions(+), 29 deletions(-) diff --git a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx index cd736dca7b..b8f908aab2 100644 --- a/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx +++ b/packages/components/src/internal/components/domainproperties/CalculatedFieldOptions.tsx @@ -292,9 +292,9 @@ export const CalculatedFieldOptions: FC = memo(prop {show && ( ({ domainFields: List(fields), @@ -56,6 +62,7 @@ function getDomainFields(fields = DEFAULT_FIELDS) { function defaultProps(overrides?: Partial): ExpressionAssistantModalProps { return { + field: CALC_FIELD, getDomainFields: getDomainFields(), onCancel: jest.fn(), ...overrides, @@ -86,7 +93,10 @@ describe('ExpressionAssistantModal', () => { const expressionAssistant = jest.fn(); // Act - renderWithAppContext(, makeApiContext(expressionAssistant)); + renderWithAppContext( + , + makeApiContext(expressionAssistant) + ); // Assert - one assistant intro message with the NEW prompt text and no SQL segment const messages = chatModalProps.messages as ChatMessage[]; @@ -99,10 +109,7 @@ describe('ExpressionAssistantModal', () => { test('shows the CHANGE intro with a SQL segment when fieldExpression is provided', () => { // Arrange / Act - renderWithAppContext( - , - makeApiContext() - ); + renderWithAppContext(, makeApiContext()); // Assert - intro begins with the CHANGE prompt and includes a sql segment containing the existing expression const intro = (chatModalProps.messages as ChatMessage[])[0]; @@ -120,7 +127,11 @@ describe('ExpressionAssistantModal', () => { // Act renderWithAppContext( - , + , makeApiContext(expressionAssistant) ); @@ -188,12 +199,17 @@ describe('ExpressionAssistantModal', () => { test('passes columnMap and PHI columns derived from the provided domain fields', async () => { // Arrange const fields = [ + CALC_FIELD, makeField('plain', 'http://www.w3.org/2001/XMLSchema#string'), makeField('secret', 'http://www.w3.org/2001/XMLSchema#string', 'Restricted'), ]; const expressionAssistant = jest.fn().mockResolvedValue({ conversationId: 'c', success: true, text: 'ok' }); renderWithAppContext( - , + , makeApiContext(expressionAssistant) ); diff --git a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx index 3d63f7561b..7ec4db8b04 100644 --- a/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx +++ b/packages/components/src/internal/components/domainproperties/ExpressionAssistantModal.tsx @@ -7,7 +7,7 @@ import { ChatMessage, ChatRole, ChatSegment } from '../mcp/models'; import { DomainField, GetDomainFields, SystemField } from './models'; import { useRequestHandler } from '../../util/RequestHandler'; import { incrementClientSideMetricCount } from '../../actions'; -import { getColumnTypeMap, getPHIColumnNames } from './CalculatedFieldOptions'; +import { getColumnTypeMap, getPHIColumnNames, typeToDisplay } from './CalculatedFieldOptions'; import { ExpressionAssistOptions } from './actions'; import { resolveErrorMessage } from '../../util/messaging'; @@ -27,12 +27,13 @@ function createChatMessage(message: Partial): ChatMessage { } interface SqlSnippetProps { + jdbcType?: string; onApplyExpression?: (sql: string) => void; readOnly?: boolean; sql: string; } -const SqlExpression: FC = memo(({ onApplyExpression, readOnly, sql }) => { +const SqlExpression: FC = memo(({ onApplyExpression, jdbcType, readOnly, sql }) => { const [animating, setAnimating] = useState(false); const handleApply = useCallback(() => { setAnimating(true); @@ -49,13 +50,20 @@ const SqlExpression: FC = memo(({ onApplyExpression, readOnly, {sql} {!readOnly && onApplyExpression && ( - + <> + + {jdbcType && ( + + The calculated data type is {typeToDisplay(jdbcType).toLowerCase()} + + )} + )} ); @@ -63,8 +71,8 @@ const SqlExpression: FC = memo(({ onApplyExpression, readOnly, SqlExpression.displayName = 'SqlExpression'; export interface ExpressionAssistantModalProps { + field: DomainField; fieldError?: string; - fieldExpression?: string; getDomainFields: GetDomainFields; onApplyExpression?: (analysis: string) => void; onCancel: () => void; @@ -73,7 +81,7 @@ export interface ExpressionAssistantModalProps { function useExpressionAssistance( domainFields: DomainField[], systemFields: SystemField[], - fieldExpression?: string, + field: DomainField, fieldError?: string ) { const [conversationId, setConversationId] = useState(); @@ -82,9 +90,9 @@ function useExpressionAssistance( let segments: ChatSegment[] | undefined; if (fieldError) { text = VALIDATE_INTRO; - } else if (fieldExpression) { + } else if (field.valueExpression) { text = CHANGE_INTRO; - segments = [{ type: 'sql', sql: fieldExpression }]; + segments = [{ type: 'sql', sql: field.valueExpression }]; } else { text = NEW_INTRO; } @@ -136,8 +144,9 @@ function useExpressionAssistance( if (conversationId === undefined) { options.domainFields = combinedFields; + options.field = field; options.fieldError = fieldError; - options.fieldExpression = fieldExpression; + options.fieldExpression = field.valueExpression; } const response = await api.domain.expressionAssistant(options); @@ -174,8 +183,8 @@ function useExpressionAssistance( columnMap, combinedFields, conversationId, + field, fieldError, - fieldExpression, phiColumns, pushMessage, requestHandler, @@ -201,7 +210,7 @@ function useExpressionAssistance( } export const ExpressionAssistantModal: FC = memo(props => { - const { fieldError, fieldExpression, getDomainFields, onApplyExpression, onCancel } = props; + const { field, fieldError, getDomainFields, onApplyExpression, onCancel } = props; const { domainFields, systemFields } = useMemo(() => { const { domainFields, systemFields } = getDomainFields(); return { domainFields: domainFields.toArray(), systemFields }; @@ -209,14 +218,21 @@ export const ExpressionAssistantModal: FC = memo( const { isPending, messages, onInterrupt, sendPrompt } = useExpressionAssistance( domainFields, systemFields, - fieldExpression, + field, fieldError ); const renderSegment = useCallback( (segment, index) => { if (segment.type === 'expression' && segment.sql) { - return ; + return ( + + ); } if (segment.type === 'sql' && segment.sql) { return ; diff --git a/packages/components/src/internal/components/domainproperties/actions.ts b/packages/components/src/internal/components/domainproperties/actions.ts index b3efac29d7..4546a71ee2 100644 --- a/packages/components/src/internal/components/domainproperties/actions.ts +++ b/packages/components/src/internal/components/domainproperties/actions.ts @@ -1532,6 +1532,7 @@ export interface ExpressionAssistOptions { containerPath?: string; conversationId?: string; domainFields?: (DomainField | SystemField)[]; + field?: DomainField; fieldError?: string; fieldExpression?: string; phiColumns?: string[]; @@ -1554,11 +1555,22 @@ export interface ExpressionAssistResponse { } export function expressionAssistant(options: ExpressionAssistOptions): Promise { - const { containerPath, requestHandler, ...jsonData } = options; + const { containerPath, domainFields, field, requestHandler, ...jsonData } = options; + + const serializedField = field ? DomainField.serialize(field) : undefined; + if (serializedField) { + // Do not pass the value expression for the current field as that is supplied separately + delete serializedField.valueExpression; + } + return request({ url: ActionURL.buildURL('query', 'expressionAssistantAgent.api', containerPath), method: 'POST', - jsonData, + jsonData: { + ...jsonData, + domainFields: domainFields?.map(f => (f instanceof DomainField ? DomainField.serialize(f) : f)), + field: serializedField, + }, errorLogMsg: 'Failed to assist with expression', requestHandler, }); diff --git a/packages/components/src/internal/components/mcp/models.ts b/packages/components/src/internal/components/mcp/models.ts index 9558fedc02..76a77894e6 100644 --- a/packages/components/src/internal/components/mcp/models.ts +++ b/packages/components/src/internal/components/mcp/models.ts @@ -9,6 +9,7 @@ export enum ChatRole { // (e.g., an applicable SQL expression). export interface ChatSegment { html?: string; + jdbcType?: string; sql?: string; text?: string; type: string; diff --git a/packages/components/src/theme/chat.scss b/packages/components/src/theme/chat.scss index 0a94c3b1f5..05138882a4 100644 --- a/packages/components/src/theme/chat.scss +++ b/packages/components/src/theme/chat.scss @@ -66,6 +66,10 @@ margin-bottom: 8px; } + .assistant-expression__type { + float: right; + } + .error-response { color: $brand-danger; } From 349dfd3c46d7a7f3cc74bf8d3a51ddc3d8dc4b11 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Wed, 27 May 2026 14:38:38 -0700 Subject: [PATCH 4/6] 7.39.2-fb-chat-asst.0 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index b39e1db101..193d570ffc 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.39.1", + "version": "7.39.2-fb-chat-asst.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.39.1", + "version": "7.39.2-fb-chat-asst.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 19c7ba1c93..a94c0a4529 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.39.1", + "version": "7.39.2-fb-chat-asst.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From b52e3d233d3c87c0c9004ded921448c5abd4ea59 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 28 May 2026 15:25:09 -0700 Subject: [PATCH 5/6] Prepare release notes --- packages/components/releaseNotes/components.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index dd13b772d3..4aeffd5135 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,8 +1,16 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages -### version TBD -*Released*: TBD +### version 7.40.0 +*Released*: 28 May 2026 +- Calculated Column Assistant + - Keep the modal open after clicking apply expression + - Send the current field to server to distinguish from a field set + - Display column type for validated expressions + - Refill prompt when request interrupted + +### version 7.39.0 +*Released*: 27 May 2026 - Misc. accessibility improvements - Auto-link to study input field labels - Remove tabIndex value from `DomainRow` From b734a883ca3c1e49906532121fb2e830ae8fd788 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 28 May 2026 15:27:11 -0700 Subject: [PATCH 6/6] 7.40.0 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 193d570ffc..4960217ef5 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.39.2-fb-chat-asst.0", + "version": "7.40.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.39.2-fb-chat-asst.0", + "version": "7.40.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index a94c0a4529..44b6b4b2c1 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.39.2-fb-chat-asst.0", + "version": "7.40.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [