From cb3482e89871bbff6f1a5c9db8402572d9fa9aa1 Mon Sep 17 00:00:00 2001 From: Barani05072002 Date: Fri, 10 Oct 2025 11:34:19 +0530 Subject: [PATCH 1/4] ci: fix pnpm publish checks (#2572) Allow publishing to npm registry from release tags This is caused by our current release process as we create release first, which then trigger publishing --- .github/actions/pnpm-node-install/action.yaml | 1 + .github/workflows/publish-libs.yaml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/actions/pnpm-node-install/action.yaml b/.github/actions/pnpm-node-install/action.yaml index b8c432e147..9e0efd274e 100644 --- a/.github/actions/pnpm-node-install/action.yaml +++ b/.github/actions/pnpm-node-install/action.yaml @@ -18,6 +18,7 @@ runs: uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} + registry-url: 'https://registry.npmjs.org' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - name: Install JS dependencies diff --git a/.github/workflows/publish-libs.yaml b/.github/workflows/publish-libs.yaml index 32653c253a..54da6dbf4f 100644 --- a/.github/workflows/publish-libs.yaml +++ b/.github/workflows/publish-libs.yaml @@ -48,4 +48,7 @@ jobs: run: pnpm build:libs - name: Publish packages to npm - run: pnpm publish --recursive ${{ inputs.dry_run && '--dry-run --no-git-checks' || '' }} # --no-git-checks allows testing from non-main branches \ No newline at end of file + # --no-git-checks allows testing from non-main branches and publishing from release tags + run: pnpm publish --recursive --no-git-checks ${{ inputs.dry_run && '--dry-run' || '' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_REACT_CLIENT }} \ No newline at end of file From 59be57679f45df4ef801ec462985663fe1ede3b5 Mon Sep 17 00:00:00 2001 From: Barani05072002 Date: Fri, 10 Oct 2025 12:37:00 +0530 Subject: [PATCH 2/4] now user can invoke chat settings using '@' keyword --- .../components/chat/MessageComposer/Input.tsx | 329 +++++++++++++++++- .../components/chat/MessageComposer/index.tsx | 56 ++- 2 files changed, 365 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/chat/MessageComposer/Input.tsx b/frontend/src/components/chat/MessageComposer/Input.tsx index 33f4a5d53f..c50f417150 100644 --- a/frontend/src/components/chat/MessageComposer/Input.tsx +++ b/frontend/src/components/chat/MessageComposer/Input.tsx @@ -8,7 +8,7 @@ import React, { } from 'react'; import { useRecoilValue } from 'recoil'; -import { ICommand, commandsState } from '@chainlit/react-client'; +import { ICommand, commandsState, useChatData } from '@chainlit/react-client'; import AutoResizeTextarea from '@/components/AutoResizeTextarea'; import Icon from '@/components/Icon'; @@ -28,6 +28,8 @@ interface Props { placeholder?: string; selectedCommand?: ICommand; setSelectedCommand: (command: ICommand | undefined) => void; + selectedSetting?: any; + setSelectedSetting: (setting: any | undefined) => void; onChange: (value: string) => void; onPaste?: (event: any) => void; onEnter?: () => void; @@ -46,6 +48,7 @@ const Input = forwardRef( autoFocus, selectedCommand, setSelectedCommand, + setSelectedSetting, onChange, onEnter, onPaste @@ -53,9 +56,16 @@ const Input = forwardRef( ref ) => { const commands = useRecoilValue(commandsState); + const { chatSettingsInputs } = useChatData(); const [isComposing, setIsComposing] = useState(false); const [showCommands, setShowCommands] = useState(false); + const [showSettings, setShowSettings] = useState(false); + const [showSettingValues, setShowSettingValues] = useState(false); const [commandInput, setCommandInput] = useState(''); + const [settingInput, setSettingInput] = useState(''); + const [settingValueInput, setSettingValueInput] = useState(''); + const [tempSelectedSetting, setTempSelectedSetting] = + useState(undefined); const [value, setValue] = useState(''); const textareaRef = useRef(null); @@ -69,11 +79,45 @@ const Input = forwardRef( return indexA - indexB; }); + const normalizedSettingInput = settingInput.toLowerCase().slice(1); + + const filteredSettings = chatSettingsInputs + .filter((setting: any) => + setting.id.toLowerCase().includes(normalizedSettingInput) + ) + .sort((a: any, b: any) => { + const indexA = a.id.toLowerCase().indexOf(normalizedSettingInput); + const indexB = b.id.toLowerCase().indexOf(normalizedSettingInput); + return indexA - indexB; + }); + + const normalizedSettingValueInput = settingValueInput + .toLowerCase() + .slice(1); + + const filteredSettingValues = tempSelectedSetting?.items + ? tempSelectedSetting.items + .filter( + (item: any) => + item.label.toLowerCase().includes(normalizedSettingValueInput) || + item.value.toLowerCase().includes(normalizedSettingValueInput) + ) + .sort((a: any, b: any) => { + const indexA = a.label + .toLowerCase() + .indexOf(normalizedSettingValueInput); + const indexB = b.label + .toLowerCase() + .indexOf(normalizedSettingValueInput); + return indexA - indexB; + }) + : []; + const { - selectedIndex, - handleMouseMove, - handleMouseLeave, - handleKeyDown: navigationKeyDown + selectedIndex: commandSelectedIndex, + handleMouseMove: commandHandleMouseMove, + handleMouseLeave: commandHandleMouseLeave, + handleKeyDown: commandNavigationKeyDown } = useCommandNavigation({ items: filteredCommands, isOpen: showCommands, @@ -86,13 +130,54 @@ const Input = forwardRef( } }); + const { + selectedIndex: settingSelectedIndex, + handleMouseMove: settingHandleMouseMove, + handleMouseLeave: settingHandleMouseLeave, + handleKeyDown: settingNavigationKeyDown + } = useCommandNavigation({ + items: filteredSettings, + isOpen: showSettings, + onSelect: (setting) => { + handleSettingSelect(setting); + }, + onClose: () => { + setShowSettings(false); + setSettingInput(''); + } + }); + + const { + selectedIndex: settingValueSelectedIndex, + handleMouseMove: settingValueHandleMouseMove, + handleMouseLeave: settingValueHandleMouseLeave, + handleKeyDown: settingValueNavigationKeyDown + } = useCommandNavigation({ + items: filteredSettingValues, + isOpen: showSettingValues, + onSelect: (valueItem) => { + handleSettingValueSelect(valueItem); + }, + onClose: () => { + setShowSettingValues(false); + setSettingValueInput(''); + setTempSelectedSetting(undefined); + } + }); + const reset = () => { setValue(''); if (!selectedCommand?.persistent) { setSelectedCommand(undefined); } + setSelectedSetting(undefined); + setTempSelectedSetting(undefined); setCommandInput(''); + setSettingInput(''); + setSettingValueInput(''); setShowCommands(false); + setShowSettings(false); + setShowSettingValues(false); onChange(''); }; @@ -113,12 +198,54 @@ const Input = forwardRef( // Command detection for dropdown const words = newValue.split(' '); - if (words.length === 1 && words[0].startsWith('/')) { + const firstWord = words[0]; + + if (words.length === 1 && firstWord.startsWith('/')) { setShowCommands(true); - setCommandInput(words[0]); + setShowSettings(false); + setShowSettingValues(false); + setCommandInput(firstWord); + setSettingInput(''); + setSettingValueInput(''); + } else if (words.length === 1 && firstWord.startsWith('@')) { + // Check if it's @setting/value pattern + const parts = firstWord.split('/'); + + if (parts.length === 1) { + // Just @setting - show settings dropdown + setShowSettings(true); + setShowCommands(false); + setShowSettingValues(false); + setSettingInput(firstWord); + setCommandInput(''); + setSettingValueInput(''); + setTempSelectedSetting(undefined); + } else if (parts.length === 2) { + // @setting/value - show values dropdown + const settingPart = parts[0]; + const valuePart = parts[1]; + const setting = chatSettingsInputs.find( + (s: any) => + s.id.toLowerCase() === settingPart.slice(1).toLowerCase() + ); + + if (setting && setting.items && setting.items.length > 0) { + setTempSelectedSetting(setting); + setShowSettingValues(true); + setShowSettings(false); + setShowCommands(false); + setSettingInput(''); + setCommandInput(''); + setSettingValueInput('/' + valuePart); + } + } } else { setShowCommands(false); + setShowSettings(false); + setShowSettingValues(false); setCommandInput(''); + setSettingInput(''); + setSettingValueInput(''); } }; @@ -139,23 +266,104 @@ const Input = forwardRef( }, 0); }; + const handleSettingSelect = (setting: any) => { + setShowSettings(false); + + // Check if setting has values/items to select from + if (setting.items && setting.items.length > 0) { + // Setting has values - show value selection by adding / + setTempSelectedSetting(setting); + const newValue = `@${setting.id}/`; + setValue(newValue); + onChange(newValue); + setSettingInput(''); + setSettingValueInput('/'); + setShowSettingValues(true); + + // Focus back on textarea + setTimeout(() => { + textareaRef.current?.focus(); + }, 0); + } else { + // Setting has no values - use default/initial value + setSelectedSetting({ + ...setting, + selectedValue: setting.initial + }); + + // Remove the setting text from the input + const newValue = value.replace(settingInput, '').trimStart(); + setValue(newValue); + onChange(newValue); + + setSettingInput(''); + + // Focus back on textarea + setTimeout(() => { + textareaRef.current?.focus(); + }, 0); + } + }; + + const handleSettingValueSelect = (valueItem: any) => { + if (!tempSelectedSetting) return; + + setShowSettingValues(false); + setSelectedSetting({ + ...tempSelectedSetting, + selectedValue: valueItem.value, + selectedLabel: valueItem.label + }); + + // Remove the @setting/value text from the input + const newValue = value.replace(/@[^/]+\/[^\s]*/, '').trimStart(); + setValue(newValue); + onChange(newValue); + + setSettingValueInput(''); + setTempSelectedSetting(undefined); + + // Focus back on textarea + setTimeout(() => { + textareaRef.current?.focus(); + }, 0); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { // Handle command selection - check this FIRST before other key handling if (showCommands && filteredCommands.length > 0) { - navigationKeyDown(e); + commandNavigationKeyDown(e); // If the navigation handled the key, don't process further if (e.defaultPrevented) { return; } } - // Handle regular enter only if command menu is not showing + // Handle setting selection + if (showSettings && filteredSettings.length > 0) { + settingNavigationKeyDown(e); + if (e.defaultPrevented) { + return; + } + } + + // Handle setting value selection + if (showSettingValues && filteredSettingValues.length > 0) { + settingValueNavigationKeyDown(e); + if (e.defaultPrevented) { + return; + } + } + + // Handle regular enter only if no menu is showing if ( e.key === 'Enter' && !e.shiftKey && onEnter && !isComposing && - !showCommands + !showCommands && + !showSettings && + !showSettingValues ) { e.preventDefault(); onEnter(); @@ -185,7 +393,7 @@ const Input = forwardRef( {showCommands && filteredCommands.length > 0 && (
@@ -194,8 +402,8 @@ const Input = forwardRef( handleMouseMove(index)} + isSelected={index === commandSelectedIndex} + onMouseMove={() => commandHandleMouseMove(index)} onSelect={() => handleCommandSelect(command)} className="command-item space-x-2" > @@ -203,7 +411,7 @@ const Input = forwardRef( name={command.icon} className={cn( '!size-5 text-muted-foreground transition-transform duration-150', - index === selectedIndex && 'scale-110' + index === commandSelectedIndex && 'scale-110' )} />
@@ -219,6 +427,99 @@ const Input = forwardRef(
)} + + {showSettings && filteredSettings.length > 0 && ( +
+ + + + {filteredSettings.map((setting: any, index: number) => ( + settingHandleMouseMove(index)} + onSelect={() => handleSettingSelect(setting)} + className="command-item space-x-2" + > + +
+
+ {setting.label || setting.id} +
+
+ {setting.description || + setting.tooltip || + 'Chat setting'} + {setting.items && setting.items.length > 0 && ( + + ({setting.items.length} values) + + )} +
+
+
+ ))} +
+
+
+
+ )} + + {showSettingValues && filteredSettingValues.length > 0 && ( +
+ + + +
+ Select value for:{' '} + + {tempSelectedSetting?.label || tempSelectedSetting?.id} + +
+ {filteredSettingValues.map((item: any, index: number) => ( + settingValueHandleMouseMove(index)} + onSelect={() => handleSettingValueSelect(item)} + className="command-item space-x-2" + > + +
+
{item.label}
+ {item.label !== item.value && ( +
+ {item.value} +
+ )} +
+
+ ))} +
+
+
+
+ )}
); } diff --git a/frontend/src/components/chat/MessageComposer/index.tsx b/frontend/src/components/chat/MessageComposer/index.tsx index 9d796bc693..a9cf1e1229 100644 --- a/frontend/src/components/chat/MessageComposer/index.tsx +++ b/frontend/src/components/chat/MessageComposer/index.tsx @@ -49,12 +49,13 @@ export default function MessageComposer({ const [selectedCommand, setSelectedCommand] = useRecoilState( persistentCommandState ); + const [selectedSetting, setSelectedSetting] = useState(undefined); const setChatSettingsOpen = useSetRecoilState(chatSettingsOpenState); const [attachments, setAttachments] = useRecoilState(attachmentsState); const { t } = useTranslation(); const { user } = useAuth(); - const { sendMessage, replyMessage } = useChatInteract(); + const { sendMessage, replyMessage, updateChatSettings } = useChatInteract(); const { askUser, chatSettingsInputs, disabled: _disabled } = useChatData(); const disabled = _disabled || !!attachments.find((a) => !a.uploaded); @@ -84,8 +85,20 @@ export default function MessageComposer({ async ( msg: string, attachments?: IAttachment[], - selectedCommand?: string + selectedCommand?: string, + selectedSetting?: any ) => { + // Apply chat setting if selected + if (selectedSetting) { + const settingValue = { + [selectedSetting.id]: + selectedSetting.selectedValue !== undefined + ? selectedSetting.selectedValue + : selectedSetting.initial + }; + updateChatSettings(settingValue); + } + const message: IStep = { threadId: '', command: selectedCommand, @@ -106,7 +119,7 @@ export default function MessageComposer({ } sendMessage(message, fileReferences); }, - [user, sendMessage, autoScrollRef] + [user, sendMessage, autoScrollRef, updateChatSettings] ); const onReply = useCallback( @@ -132,7 +145,10 @@ export default function MessageComposer({ const submit = useCallback(() => { if ( disabled || - (value.trim() === '' && attachments.length === 0 && !selectedCommand) + (value.trim() === '' && + attachments.length === 0 && + !selectedCommand && + !selectedSetting) ) { return; } @@ -140,10 +156,11 @@ export default function MessageComposer({ if (askUser) { onReply(value); } else { - onSubmit(value, attachments, selectedCommand?.id); + onSubmit(value, attachments, selectedCommand?.id, selectedSetting); } setAttachments([]); + setSelectedSetting(undefined); setValue(''); // Clear the value state inputRef.current?.reset(); }, [ @@ -152,6 +169,7 @@ export default function MessageComposer({ askUser, attachments, selectedCommand, + selectedSetting, setAttachments, onSubmit, onReply @@ -167,12 +185,35 @@ export default function MessageComposer({ ) : null} + {selectedSetting && ( +
+ + + @{selectedSetting.label || selectedSetting.id} + {selectedSetting.selectedValue !== undefined && + selectedSetting.selectedLabel && ( + + /{selectedSetting.selectedLabel} + + )} + + + +
+ )} From 80e5774c8550134512591ab30f7e1cade37b5348 Mon Sep 17 00:00:00 2001 From: Barani05072002 Date: Sat, 11 Oct 2025 15:24:31 +0530 Subject: [PATCH 3/4] Now when I entered @ only showing the Option type Chat settings --- frontend/src/components/chat/MessageComposer/Input.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/chat/MessageComposer/Input.tsx b/frontend/src/components/chat/MessageComposer/Input.tsx index c50f417150..d228929cfc 100644 --- a/frontend/src/components/chat/MessageComposer/Input.tsx +++ b/frontend/src/components/chat/MessageComposer/Input.tsx @@ -82,9 +82,13 @@ const Input = forwardRef( const normalizedSettingInput = settingInput.toLowerCase().slice(1); const filteredSettings = chatSettingsInputs - .filter((setting: any) => - setting.id.toLowerCase().includes(normalizedSettingInput) - ) + .filter((setting: any) => { + // Only show settings that have items/values (like Select) + const hasValues = setting.items && setting.items.length > 0; + const matchesSearch = setting.id.toLowerCase().includes(normalizedSettingInput); + + return hasValues && matchesSearch; + }) .sort((a: any, b: any) => { const indexA = a.id.toLowerCase().indexOf(normalizedSettingInput); const indexB = b.id.toLowerCase().indexOf(normalizedSettingInput); From 8b3ec4481d98b49833467aadc40ff824984b1f12 Mon Sep 17 00:00:00 2001 From: Barani05072002 Date: Sat, 18 Oct 2025 10:43:13 +0530 Subject: [PATCH 4/4] When using single select Chat_settings you can directly access the chatsettings using @ sign --- .../components/chat/MessageComposer/Input.tsx | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/chat/MessageComposer/Input.tsx b/frontend/src/components/chat/MessageComposer/Input.tsx index d228929cfc..a850b1510c 100644 --- a/frontend/src/components/chat/MessageComposer/Input.tsx +++ b/frontend/src/components/chat/MessageComposer/Input.tsx @@ -85,8 +85,10 @@ const Input = forwardRef( .filter((setting: any) => { // Only show settings that have items/values (like Select) const hasValues = setting.items && setting.items.length > 0; - const matchesSearch = setting.id.toLowerCase().includes(normalizedSettingInput); - + const matchesSearch = setting.id + .toLowerCase() + .includes(normalizedSettingInput); + return hasValues && matchesSearch; }) .sort((a: any, b: any) => { @@ -216,17 +218,34 @@ const Input = forwardRef( const parts = firstWord.split('/'); if (parts.length === 1) { - // Just @setting - show settings dropdown - setShowSettings(true); - setShowCommands(false); - setShowSettingValues(false); - setSettingInput(firstWord); - setCommandInput(''); - setSettingValueInput(''); - setTempSelectedSetting(undefined); + // Just @setting - check how many settings have values + const settingsWithValues = chatSettingsInputs.filter( + (setting: any) => setting.items && setting.items.length > 0 + ); + + if (settingsWithValues.length === 1) { + // Only one setting with values - skip to showing values directly + const singleSetting = settingsWithValues[0]; + setTempSelectedSetting(singleSetting); + setShowSettingValues(true); + setShowSettings(false); + setShowCommands(false); + setSettingInput(''); + setCommandInput(''); + setSettingValueInput(firstWord); + } else { + // Multiple settings - show settings dropdown + setShowSettings(true); + setShowCommands(false); + setShowSettingValues(false); + setSettingInput(firstWord); + setCommandInput(''); + setSettingValueInput(''); + setTempSelectedSetting(undefined); + } } else if (parts.length === 2) { // @setting/value - show values dropdown - const settingPart = parts[0]; + const settingPart = parts[0]; const valuePart = parts[1]; const setting = chatSettingsInputs.find( (s: any) => @@ -319,10 +338,8 @@ const Input = forwardRef( selectedLabel: valueItem.label }); - // Remove the @setting/value text from the input - const newValue = value.replace(/@[^/]+\/[^\s]*/, '').trimStart(); - setValue(newValue); - onChange(newValue); + setValue(''); + onChange(''); setSettingValueInput(''); setTempSelectedSetting(undefined);