mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 09:37:28 +00:00
✨ feat(chat-input): gate prompt optimize by image output capability (#13992)
This commit is contained in:
parent
18042b7d31
commit
eb99190f9f
9 changed files with 61 additions and 8 deletions
|
|
@ -14,6 +14,7 @@ interface AgentBuilderConversationProps {
|
|||
agentId: string;
|
||||
}
|
||||
const actions: ActionKeys[] = ['model'];
|
||||
const rightActions: ActionKeys[] = [];
|
||||
|
||||
/**
|
||||
* Agent Builder Conversation Component
|
||||
|
|
@ -32,7 +33,7 @@ const AgentBuilderConversation = memo<AgentBuilderConversationProps>(({ agentId
|
|||
<Flexbox flex={1} style={{ overflow: 'hidden' }}>
|
||||
<ChatList welcome={<AgentBuilderWelcome />} />
|
||||
</Flexbox>
|
||||
<ChatInput leftActions={actions} showRuntimeConfig={false} />
|
||||
<ChatInput leftActions={actions} rightActions={rightActions} showRuntimeConfig={false} />
|
||||
</Flexbox>
|
||||
</DragUploadZone>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,13 +12,19 @@ const PromptTransform = memo(() => {
|
|||
const onPromptChange = useCallback(
|
||||
(prompt: string) => {
|
||||
if (!editor) return;
|
||||
editor.setDocument('markdown', prompt);
|
||||
// `keepHistory` prevents setDocument from wiping the undo/redo stacks.
|
||||
editor.setDocument('markdown', prompt, { keepHistory: true });
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
// Image mode expands vague inputs; text mode forbids expansion.
|
||||
return (
|
||||
<PromptTransformAction mode={'text'} prompt={markdownContent} onPromptChange={onPromptChange} />
|
||||
<PromptTransformAction
|
||||
mode={'image'}
|
||||
prompt={markdownContent}
|
||||
onPromptChange={onPromptChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ const ChatInput = memo<ChatInputProps>(
|
|||
allowExpand,
|
||||
leftActions = [],
|
||||
leftContent,
|
||||
rightActions = ['promptTransform'],
|
||||
rightActions = [],
|
||||
children,
|
||||
extraActionItems,
|
||||
mentionItems,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,12 @@ export const usePromptTransform = ({ mode, prompt, onPromptChange }: UsePromptTr
|
|||
const isRewriteActionEnabled = rewriteConfig?.enabled ?? false;
|
||||
|
||||
const getConfigByAction = useCallback(
|
||||
(action: PromptTransformAction) =>
|
||||
action === 'rewrite' ? (rewriteConfig ?? {}) : (translateConfig ?? {}),
|
||||
(action: PromptTransformAction) => {
|
||||
// Strip config-only fields (enabled, customPrompt); strict upstreams reject unknown OpenAI params.
|
||||
const config = action === 'rewrite' ? rewriteConfig : translateConfig;
|
||||
if (!config) return {};
|
||||
return { model: config.model, provider: config.provider };
|
||||
},
|
||||
[rewriteConfig, translateConfig],
|
||||
);
|
||||
|
||||
|
|
|
|||
10
src/hooks/useModelSupportImageOutput.ts
Normal file
10
src/hooks/useModelSupportImageOutput.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { useAiInfraStore } from '@/store/aiInfra';
|
||||
import { aiModelSelectors } from '@/store/aiInfra/selectors';
|
||||
|
||||
export const useModelSupportImageOutput = (id?: string, provider?: string) => {
|
||||
return useAiInfraStore((s) => {
|
||||
if (!id || !provider) return false;
|
||||
|
||||
return aiModelSelectors.isModelSupportImageOutput(id, provider)(s);
|
||||
});
|
||||
};
|
||||
|
|
@ -4,13 +4,17 @@ import { memo, useMemo } from 'react';
|
|||
|
||||
import { type ActionKeys } from '@/features/ChatInput';
|
||||
import { ChatInput } from '@/features/Conversation';
|
||||
import { useModelSupportImageOutput } from '@/hooks/useModelSupportImageOutput';
|
||||
import { useAgentStore } from '@/store/agent';
|
||||
import { agentSelectors } from '@/store/agent/selectors';
|
||||
import { useChatStore } from '@/store/chat';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
||||
|
||||
import { useSendMenuItems } from './useSendMenuItems';
|
||||
|
||||
const rightActions: ActionKeys[] = ['promptTransform'];
|
||||
const emptyRightActions: ActionKeys[] = [];
|
||||
const promptTransformRightActions: ActionKeys[] = ['promptTransform'];
|
||||
|
||||
/**
|
||||
* MainChatInput
|
||||
|
|
@ -24,6 +28,11 @@ const MainChatInput = memo(() => {
|
|||
const isDevMode = useUserStore((s) => userGeneralSettingsSelectors.config(s).isDevMode);
|
||||
const sendMenuItems = useSendMenuItems();
|
||||
|
||||
const model = useAgentStore(agentSelectors.currentAgentModel);
|
||||
const provider = useAgentStore(agentSelectors.currentAgentModelProvider);
|
||||
const supportsImageOutput = useModelSupportImageOutput(model, provider);
|
||||
const rightActions = supportsImageOutput ? promptTransformRightActions : emptyRightActions;
|
||||
|
||||
const leftActions: ActionKeys[] = useMemo(
|
||||
() => [
|
||||
'model',
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ interface AgentBuilderConversationProps {
|
|||
agentId: string;
|
||||
}
|
||||
const actions: ActionKeys[] = ['model'];
|
||||
const rightActions: ActionKeys[] = [];
|
||||
|
||||
/**
|
||||
* Agent Builder Conversation Component
|
||||
|
|
@ -23,7 +24,7 @@ const AgentBuilderConversation = memo<AgentBuilderConversationProps>(({ agentId
|
|||
<Flexbox flex={1} style={{ overflow: 'hidden' }}>
|
||||
<ChatList welcome={<AgentBuilderWelcome mode="group" />} />
|
||||
</Flexbox>
|
||||
<ChatInput leftActions={actions} showRuntimeConfig={false} />
|
||||
<ChatInput leftActions={actions} rightActions={rightActions} showRuntimeConfig={false} />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ describe('aiModelSelectors', () => {
|
|||
functionCall: true,
|
||||
vision: true,
|
||||
reasoning: true,
|
||||
imageOutput: true,
|
||||
},
|
||||
contextWindowTokens: 4000,
|
||||
settings: {
|
||||
|
|
@ -210,6 +211,20 @@ describe('aiModelSelectors', () => {
|
|||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should check image output support', () => {
|
||||
expect(aiModelSelectors.isModelSupportImageOutput('model1', 'provider1')(mockState)).toBe(
|
||||
true,
|
||||
);
|
||||
// Missing ability defaults to false via `|| false` coercion.
|
||||
expect(aiModelSelectors.isModelSupportImageOutput('model4', 'provider2')(mockState)).toBe(
|
||||
false,
|
||||
);
|
||||
// Unknown model returns false instead of throwing.
|
||||
expect(aiModelSelectors.isModelSupportImageOutput('missing', 'provider1')(mockState)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('context window checks', () => {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ const isModelSupportVideo = (id: string, provider: string) => (s: AIProviderStor
|
|||
return model?.abilities?.video;
|
||||
};
|
||||
|
||||
const isModelSupportImageOutput = (id: string, provider: string) => (s: AIProviderStoreState) => {
|
||||
const model = getEnabledModelById(id, provider)(s);
|
||||
|
||||
return model?.abilities?.imageOutput || false;
|
||||
};
|
||||
|
||||
const isModelSupportReasoning = (id: string, provider: string) => (s: AIProviderStoreState) => {
|
||||
const model = getEnabledModelById(id, provider)(s);
|
||||
|
||||
|
|
@ -155,6 +161,7 @@ export const aiModelSelectors = {
|
|||
isModelHasExtendParams,
|
||||
isModelLoading,
|
||||
isModelSupportFiles,
|
||||
isModelSupportImageOutput,
|
||||
isModelSupportReasoning,
|
||||
isModelSupportToolUse,
|
||||
isModelSupportVideo,
|
||||
|
|
|
|||
Loading…
Reference in a new issue