mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🧩 style: Agent Side Panel Layout and Consistency Fixes (#12676)
* style: Update padding in ActionsPanel, ModelPanel, and AdvancedPanel for improved layout * Adjusted padding in ActionsPanel, ModelPanel, and AdvancedPanel components to enhance visual consistency and layout. * Changed `py-4` to `pt-2` in the main container of each panel to reduce vertical spacing and improve overall design aesthetics. * style: Update text size in MCPTool component for improved accessibility * Changed text size in the MCPTool component to `text-sm` for better readability and consistency across the UI. * This adjustment enhances the user experience by ensuring that text is appropriately sized for various display settings. * style: Enhance layout and accessibility in ActionsInput, ActionsPanel, and AgentPanel components * Updated the layout in ActionsInput to improve flex properties and ensure better responsiveness. * Refined the structure of ActionsPanel for a more consistent visual hierarchy and added accessibility features. * Adjusted the AgentPanel form layout for improved usability and streamlined component integration. * Changed text size in Dropdown component to `text-sm` for better readability across the UI. * style: Refactor layout in ActionsInput component for improved responsiveness * Updated the layout of the ActionsInput component to enhance flex properties and ensure better responsiveness. * Adjusted the textarea styling for improved usability and consistency in design. * Removed commented-out code related to example functionality to clean up the component structure. * refactor: Simplify single line code detection in MarkdownComponents * Introduced a new utility function `isSingleLineCode` to streamline the logic for determining if code is a single line. * Updated references in the `MarkdownCode` and `MarkdownCodeNoExecution` components to use the new utility function for improved readability and maintainability. * Enhanced the `processChildren` function in the Markdown editor to handle non-code elements more effectively.
This commit is contained in:
parent
edd4c6d60c
commit
dd26a2fda5
9 changed files with 155 additions and 152 deletions
|
|
@ -17,6 +17,16 @@ type TCodeProps = {
|
|||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const isSingleLineCode = (children: React.ReactNode): boolean => {
|
||||
if (typeof children === 'string') {
|
||||
return !children.includes('\n');
|
||||
}
|
||||
if (Array.isArray(children)) {
|
||||
return children.every((child) => typeof child === 'string' && !child.includes('\n'));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const code: React.ElementType = memo(function MarkdownCode({
|
||||
className,
|
||||
children,
|
||||
|
|
@ -29,7 +39,7 @@ export const code: React.ElementType = memo(function MarkdownCode({
|
|||
const lang = match && match[1];
|
||||
const isMath = lang === 'math';
|
||||
const isMermaid = lang === 'mermaid';
|
||||
const isSingleLine = typeof children === 'string' && children.split('\n').length === 1;
|
||||
const isSingleLine = isSingleLineCode(children);
|
||||
|
||||
const { getNextIndex, resetCounter } = useCodeBlockContext();
|
||||
const blockIndex = useRef(getNextIndex(isMath || isMermaid || isSingleLine)).current;
|
||||
|
|
@ -78,7 +88,7 @@ export const codeNoExecution: React.ElementType = memo(function MarkdownCodeNoEx
|
|||
} else if (lang === 'mermaid') {
|
||||
const content = typeof children === 'string' ? children : String(children);
|
||||
return <Mermaid>{content}</Mermaid>;
|
||||
} else if (typeof children === 'string' && children.split('\n').length === 1) {
|
||||
} else if (isSingleLineCode(children)) {
|
||||
return (
|
||||
<code onDoubleClick={handleDoubleClick} className={className}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ const processChildren = (children: React.ReactNode): React.ReactNode => {
|
|||
|
||||
if (React.isValidElement(children)) {
|
||||
const element = children as React.ReactElement<{ children?: React.ReactNode }>;
|
||||
if (typeof element.type !== 'string' || element.type === 'code') {
|
||||
return children;
|
||||
}
|
||||
if (element.props.children) {
|
||||
return React.cloneElement(element, {
|
||||
...element.props,
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ export default function ActionsInput({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="">
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="mb-1 flex flex-wrap items-center justify-between gap-4">
|
||||
<label
|
||||
htmlFor="schemaInput"
|
||||
|
|
@ -210,31 +210,17 @@ export default function ActionsInput({
|
|||
>
|
||||
{localize('com_ui_schema')}
|
||||
</label>
|
||||
{/* TODO: Implement examples functionality
|
||||
<div className="flex items-center gap-2">
|
||||
<select
|
||||
onChange={(e) => logger.log('actions', 'selecting example action', e.target.value)}
|
||||
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
|
||||
>
|
||||
<option value="label">{localize('com_ui_examples')}</option>
|
||||
<option value="0">Weather (JSON)</option>
|
||||
<option value="1">Pet Store (YAML)</option>
|
||||
<option value="2">Blank Template</option>
|
||||
</select>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
<div className="border-token-border-medium bg-token-surface-primary hover:border-token-border-hover mb-4 w-full overflow-hidden rounded-lg border ring-0">
|
||||
<div className="relative">
|
||||
<div className="border-token-border-medium bg-token-surface-primary hover:border-token-border-hover mb-4 flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border ring-0">
|
||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
||||
<textarea
|
||||
id="schemaInput"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
spellCheck="false"
|
||||
placeholder={localize('com_ui_enter_openapi_schema')}
|
||||
className="text-token-text-primary block h-96 w-full bg-transparent p-2 font-mono text-xs outline-none focus:ring-1 focus:ring-border-light"
|
||||
className="text-token-text-primary block min-h-[12rem] flex-1 resize-y bg-transparent p-2 font-mono text-xs outline-none focus:ring-1 focus:ring-border-light"
|
||||
/>
|
||||
{/* TODO: format input button */}
|
||||
</div>
|
||||
{validationResult && validationResult.message !== 'OpenAPI spec is valid.' && (
|
||||
<div className="border-token-border-light border-t p-2 text-red-500">
|
||||
|
|
@ -270,11 +256,11 @@ export default function ActionsInput({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="mt-auto flex items-center justify-end pt-2">
|
||||
<button
|
||||
disabled={!functions || !functions.length}
|
||||
onClick={saveAction}
|
||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||
className="focus:shadow-outline flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||
type="button"
|
||||
>
|
||||
{getButtonContent()}
|
||||
|
|
|
|||
|
|
@ -85,76 +85,81 @@ export default function ActionsPanel() {
|
|||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form className="h-full grow overflow-hidden">
|
||||
<div className="h-full overflow-auto px-2 pb-12 text-sm">
|
||||
<div className="relative flex flex-col items-center px-16 py-6 text-center">
|
||||
<div className="absolute left-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-neutral relative"
|
||||
onClick={() => {
|
||||
setActivePanel(Panel.builder);
|
||||
setAction(undefined);
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<ChevronLeft />
|
||||
<div className="h-full overflow-auto px-2 text-sm">
|
||||
<div className="flex min-h-full flex-col pb-3">
|
||||
<div>
|
||||
<div className="relative flex flex-col items-center px-16 pt-2 text-center">
|
||||
<div className="absolute left-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-neutral relative"
|
||||
onClick={() => {
|
||||
setActivePanel(Panel.builder);
|
||||
setAction(undefined);
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
<ChevronLeft />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!!action && (
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<div className="absolute right-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isEphemeralAgent(agent_id) || !action.action_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
||||
>
|
||||
<TrashIcon className="text-red-500" />
|
||||
</button>
|
||||
</div>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_action')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_action_confirm')}
|
||||
</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => {
|
||||
if (isEphemeralAgent(agent_id)) {
|
||||
return showToast({
|
||||
message: localize('com_agents_no_agent_id_error'),
|
||||
status: 'error',
|
||||
});
|
||||
{!!action && (
|
||||
<OGDialog>
|
||||
<OGDialogTrigger asChild>
|
||||
<div className="absolute right-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isEphemeralAgent(agent_id) || !action.action_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
||||
>
|
||||
<TrashIcon className="text-red-500" />
|
||||
</button>
|
||||
</div>
|
||||
</OGDialogTrigger>
|
||||
<OGDialogTemplate
|
||||
showCloseButton={false}
|
||||
title={localize('com_ui_delete_action')}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">
|
||||
{localize('com_ui_delete_action_confirm')}
|
||||
</Label>
|
||||
}
|
||||
deleteAgentAction.mutate({
|
||||
action_id: action.action_id,
|
||||
agent_id: agent_id || '',
|
||||
});
|
||||
},
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
)}
|
||||
selection={{
|
||||
selectHandler: () => {
|
||||
if (isEphemeralAgent(agent_id)) {
|
||||
return showToast({
|
||||
message: localize('com_agents_no_agent_id_error'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
deleteAgentAction.mutate({
|
||||
action_id: action.action_id,
|
||||
agent_id: agent_id || '',
|
||||
});
|
||||
},
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</OGDialog>
|
||||
)}
|
||||
|
||||
<div className="text-xl font-medium">{(action ? 'Edit' : 'Add') + ' ' + 'actions'}</div>
|
||||
<div className="text-xs text-text-secondary">
|
||||
{localize('com_assistants_actions_info')}
|
||||
<div className="text-xl font-medium">
|
||||
{(action ? 'Edit' : 'Add') + ' ' + 'actions'}
|
||||
</div>
|
||||
<div className="text-xs text-text-secondary">
|
||||
{localize('com_assistants_actions_info')}
|
||||
</div>
|
||||
</div>
|
||||
<ActionsAuth />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<ActionsInput action={action} agent_id={agent_id} setAction={setAction} />
|
||||
</div>
|
||||
{/* <div className="text-sm text-text-secondary">
|
||||
<a href="https://help.openai.com/en/articles/8554397-creating-a-gpt" target="_blank" rel="noreferrer" className="font-medium">Learn more.</a>
|
||||
</div> */}
|
||||
</div>
|
||||
<ActionsAuth />
|
||||
<ActionsInput action={action} agent_id={agent_id} setAction={setAction} />
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export default function AdvancedPanel() {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="scrollbar-gutter-stable h-full min-h-[40vh] overflow-auto pb-12 text-sm">
|
||||
<div className="advanced-panel relative flex flex-col items-center px-16 py-4 text-center">
|
||||
<div className="mb-1 flex w-full flex-col gap-2 text-sm">
|
||||
<div className="advanced-panel relative flex flex-col items-center px-16 pt-2 text-center">
|
||||
<div className="absolute left-0 top-4">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -41,7 +41,7 @@ export default function AdvancedPanel() {
|
|||
</div>
|
||||
<div className="mb-2 mt-2 text-xl font-medium">{localize('com_ui_advanced_settings')}</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 px-2">
|
||||
<div className="flex flex-col gap-4 px-2 pb-2">
|
||||
<MaxAgentSteps />
|
||||
<Controller
|
||||
name="edges"
|
||||
|
|
|
|||
|
|
@ -480,71 +480,70 @@ export default function AgentPanel() {
|
|||
<FormProvider {...methods}>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="scrollbar-gutter-stable h-auto w-full flex-shrink-0 px-3 pb-3"
|
||||
className="scrollbar-gutter-stable flex flex-1 flex-col px-3 pb-3"
|
||||
aria-label="Agent configuration form"
|
||||
>
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
<div className="w-full">
|
||||
<AgentSelect
|
||||
createMutation={create}
|
||||
agentQuery={agentQuery}
|
||||
setCurrentAgentId={setCurrentAgentId}
|
||||
// The following is required to force re-render the component when the form's agent ID changes
|
||||
// Also maintains ComboBox Focus for Accessibility
|
||||
selectedAgentId={agentQuery.isInitialLoading ? null : (current_agent_id ?? null)}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<div className="flex w-full flex-wrap gap-2">
|
||||
<div className="w-full">
|
||||
<AgentSelect
|
||||
createMutation={create}
|
||||
agentQuery={agentQuery}
|
||||
setCurrentAgentId={setCurrentAgentId}
|
||||
selectedAgentId={agentQuery.isInitialLoading ? null : (current_agent_id ?? null)}
|
||||
/>
|
||||
</div>
|
||||
{agent_id && (
|
||||
<div className="flex w-full gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full justify-center"
|
||||
onClick={() => {
|
||||
reset(getDefaultAgentFormValues());
|
||||
setCurrentAgentId(undefined);
|
||||
}}
|
||||
disabled={agentQuery.isInitialLoading}
|
||||
aria-label={localize('com_ui_create_new_agent')}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" aria-hidden="true" />
|
||||
{localize('com_ui_create_new_agent')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="submit"
|
||||
disabled={isEphemeralAgent(agent_id) || agentQuery.isInitialLoading}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelectAgent();
|
||||
}}
|
||||
aria-label={localize('com_ui_select_agent')}
|
||||
>
|
||||
{localize('com_ui_select')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Create + Select Button */}
|
||||
{agent_id && (
|
||||
<div className="flex w-full gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full justify-center"
|
||||
onClick={() => {
|
||||
reset(getDefaultAgentFormValues());
|
||||
setCurrentAgentId(undefined);
|
||||
}}
|
||||
disabled={agentQuery.isInitialLoading}
|
||||
aria-label={localize('com_ui_create_new_agent')}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" aria-hidden="true" />
|
||||
{localize('com_ui_create_new_agent')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="submit"
|
||||
disabled={isEphemeralAgent(agent_id) || agentQuery.isInitialLoading}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleSelectAgent();
|
||||
}}
|
||||
aria-label={localize('com_ui_select_agent')}
|
||||
>
|
||||
{localize('com_ui_select')}
|
||||
</Button>
|
||||
{agentQuery.isInitialLoading && <AgentPanelSkeleton />}
|
||||
{!canEditAgent && !agentQuery.isInitialLoading && (
|
||||
<div className="flex h-[30vh] w-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-token-text-primary m-2 text-xl font-semibold">
|
||||
{localize('com_agents_not_available')}
|
||||
</h2>
|
||||
<p className="text-token-text-secondary">{localize('com_agents_no_access')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && (
|
||||
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
|
||||
<AgentConfig />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (
|
||||
<AdvancedPanel />
|
||||
)}
|
||||
</div>
|
||||
{agentQuery.isInitialLoading && <AgentPanelSkeleton />}
|
||||
{!canEditAgent && !agentQuery.isInitialLoading && (
|
||||
<div className="flex h-[30vh] w-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-token-text-primary m-2 text-xl font-semibold">
|
||||
{localize('com_agents_not_available')}
|
||||
</h2>
|
||||
<p className="text-token-text-secondary">{localize('com_agents_no_access')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.model && (
|
||||
<ModelPanel models={models} providers={providers} setActivePanel={setActivePanel} />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.builder && (
|
||||
<AgentConfig />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && activePanel === Panel.advanced && (
|
||||
<AdvancedPanel />
|
||||
)}
|
||||
{canEditAgent && !agentQuery.isInitialLoading && (
|
||||
<AgentFooter
|
||||
createMutation={create}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export default function MCPTool({ serverInfo }: { serverInfo?: MCPServerInfo })
|
|||
<Accordion type="single" value={accordionValue} onValueChange={setAccordionValue} collapsible>
|
||||
<AccordionItem value={currentServerName} className="group relative w-full border-none">
|
||||
<div
|
||||
className="relative flex w-full items-center gap-1 rounded-lg p-1 hover:bg-surface-primary-alt"
|
||||
className="relative flex w-full items-center gap-1 rounded-lg p-1 text-sm hover:bg-surface-primary-alt"
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ export default function ModelPanel({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="mb-1 flex h-full min-h-[50vh] w-full flex-col gap-2 text-sm">
|
||||
<div className="model-panel relative flex flex-col items-center px-16 py-4 text-center">
|
||||
<div className="mb-1 flex w-full flex-col gap-2 text-sm">
|
||||
<div className="model-panel relative flex flex-col items-center px-16 pt-2 text-center">
|
||||
<div className="absolute left-0 top-4">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ const Dropdown: React.FC<DropdownProps> = ({
|
|||
portal={portal}
|
||||
store={selectProps}
|
||||
className={cn(
|
||||
'popover-ui z-40',
|
||||
'popover-ui z-40 text-sm',
|
||||
sizeClasses,
|
||||
className,
|
||||
'max-h-[80vh] overflow-y-auto',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue