From dd65dc02d00a8cb8173173b679197c0e8b7acc6b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 7 May 2026 13:25:51 +0000 Subject: [PATCH] feat: add image generation as an ephemeral tool toggle in chat menu Image generation was only reachable by explicitly adding a DALL-E/Flux/Gemini tool to an agent. Add it as a first-class toggle in the chat input tools dropdown alongside web search, code interpreter, file search, etc., so users can enable it directly in any chat. The toggle maps to the gemini_image_gen backend tool when enabled. - data-provider: AgentCapabilities.image_gen, defaultAgentCapabilities, LocalStorageKeys.LAST_IMAGE_GEN_TOGGLE_, TEphemeralAgent.image_gen - packages/api/agents: load.ts and added.ts push 'gemini_image_gen' when the ephemeral image_gen flag is set - client: useAgentCapabilities exposes imageGenEnabled, BadgeRowContext exposes the imageGen toggle, ToolsDropdown adds the menu entry, ImageGen.tsx renders the active/pinned badge https://claude.ai/code/session_014RgsxcUvbQBuNa1dA9vt61 --- client/src/Providers/BadgeRowContext.tsx | 21 +++++++ client/src/components/Chat/Input/BadgeRow.tsx | 2 + client/src/components/Chat/Input/ImageGen.tsx | 26 +++++++++ .../components/Chat/Input/ToolsDropdown.tsx | 58 ++++++++++++++++++- .../src/hooks/Agents/useAgentCapabilities.ts | 7 +++ packages/api/src/agents/added.ts | 5 ++ packages/api/src/agents/load.ts | 3 + packages/data-provider/src/config.ts | 4 ++ packages/data-provider/src/types.ts | 1 + 9 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 client/src/components/Chat/Input/ImageGen.tsx diff --git a/client/src/Providers/BadgeRowContext.tsx b/client/src/Providers/BadgeRowContext.tsx index 025532f0c6a..a74ac0e7c01 100644 --- a/client/src/Providers/BadgeRowContext.tsx +++ b/client/src/Providers/BadgeRowContext.tsx @@ -18,6 +18,7 @@ interface BadgeRowContextType { agentsConfig?: TAgentsEndpoint | null; skills: ReturnType; webSearch: ReturnType; + imageGen: ReturnType; artifacts: ReturnType; fileSearch: ReturnType; codeInterpreter: ReturnType; @@ -98,12 +99,14 @@ export default function BadgeRowProvider({ const codeToggleKey = `${LocalStorageKeys.LAST_CODE_TOGGLE_}${storageSuffix}`; const webSearchToggleKey = `${LocalStorageKeys.LAST_WEB_SEARCH_TOGGLE_}${storageSuffix}`; const fileSearchToggleKey = `${LocalStorageKeys.LAST_FILE_SEARCH_TOGGLE_}${storageSuffix}`; + const imageGenToggleKey = `${LocalStorageKeys.LAST_IMAGE_GEN_TOGGLE_}${storageSuffix}`; const artifactsToggleKey = `${LocalStorageKeys.LAST_ARTIFACTS_TOGGLE_}${storageSuffix}`; const skillsToggleKey = `${LocalStorageKeys.LAST_SKILLS_TOGGLE_}${storageSuffix}`; const codeToggleValue = getTimestampedValue(codeToggleKey); const webSearchToggleValue = getTimestampedValue(webSearchToggleKey); const fileSearchToggleValue = getTimestampedValue(fileSearchToggleKey); + const imageGenToggleValue = getTimestampedValue(imageGenToggleKey); const artifactsToggleValue = getTimestampedValue(artifactsToggleKey); const skillsToggleValue = getTimestampedValue(skillsToggleKey); @@ -133,6 +136,14 @@ export default function BadgeRowProvider({ } } + if (imageGenToggleValue !== null) { + try { + initialValues[AgentCapabilities.image_gen] = JSON.parse(imageGenToggleValue); + } catch (e) { + console.error('Failed to parse image gen toggle value:', e); + } + } + if (artifactsToggleValue !== null) { try { initialValues[AgentCapabilities.artifacts] = JSON.parse(artifactsToggleValue); @@ -232,6 +243,15 @@ export default function BadgeRowProvider({ isAuthenticated: true, }); + /** ImageGen hook — toggle maps to gemini_image_gen tool server-side */ + const imageGen = useToolToggle({ + conversationId, + storageContextKey, + toolKey: AgentCapabilities.image_gen, + localStorageKey: LocalStorageKeys.LAST_IMAGE_GEN_TOGGLE_, + isAuthenticated: true, + }); + /** Artifacts hook - using a custom key since it's not a Tool but a capability */ const artifacts = useToolToggle({ conversationId, @@ -255,6 +275,7 @@ export default function BadgeRowProvider({ const value: BadgeRowContextType = { skills, webSearch, + imageGen, artifacts, fileSearch, agentsConfig, diff --git a/client/src/components/Chat/Input/BadgeRow.tsx b/client/src/components/Chat/Input/BadgeRow.tsx index 7b7140c9fb9..5b52008bb84 100644 --- a/client/src/components/Chat/Input/BadgeRow.tsx +++ b/client/src/components/Chat/Input/BadgeRow.tsx @@ -19,6 +19,7 @@ import { useChatBadges } from '~/hooks'; import ToolDialogs from './ToolDialogs'; import FileSearch from './FileSearch'; import Artifacts from './Artifacts'; +import ImageGen from './ImageGen'; import MCPSelect from './MCPSelect'; import WebSearch from './WebSearch'; import Skills from './Skills'; @@ -374,6 +375,7 @@ function BadgeRow({ + diff --git a/client/src/components/Chat/Input/ImageGen.tsx b/client/src/components/Chat/Input/ImageGen.tsx new file mode 100644 index 00000000000..bd14e93a4c9 --- /dev/null +++ b/client/src/components/Chat/Input/ImageGen.tsx @@ -0,0 +1,26 @@ +import React, { memo } from 'react'; +import { Image } from 'lucide-react'; +import { CheckboxButton } from '@librechat/client'; +import { useLocalize } from '~/hooks'; +import { useBadgeRowContext } from '~/Providers'; + +function ImageGen() { + const localize = useLocalize(); + const context = useBadgeRowContext(); + const { toggleState: imageGen, debouncedChange, isPinned } = context?.imageGen ?? {}; + + return ( + (imageGen || isPinned) && ( +