✨ feat: add image upload toggle with auto-disable after sending
Add a toggle switch to enable/disable image uploads in the playground, with automatic disabling after message sending to prevent accidental image inclusion in subsequent messages. Changes: - Add `imageEnabled` field to default configuration with false as default - Enhance ImageUrlInput component with enable/disable toggle switch - Update UI to show disabled state with opacity and color changes - Modify message sending logic to only include images when enabled - Implement auto-disable functionality after message is sent - Update SettingsPanel to pass through new imageEnabled props - Maintain backward compatibility with existing configurations User Experience: - Images are disabled by default for privacy and intentional usage - Users must explicitly enable image uploads before adding URLs - After sending a message with images, the feature auto-disables - Clear visual feedback shows current enabled/disabled state - Manual control allows users to re-enable when needed This improves user control over multimodal conversations and prevents unintentional image sharing in follow-up messages.
This commit is contained in:
@@ -3,15 +3,17 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Typography,
|
Typography,
|
||||||
Button,
|
Button,
|
||||||
|
Switch,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import { IconFile } from '@douyinfe/semi-icons';
|
import { IconFile } from '@douyinfe/semi-icons';
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Plus,
|
Plus,
|
||||||
X,
|
X,
|
||||||
|
Image,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
const ImageUrlInput = ({ imageUrls, imageEnabled, onImageUrlsChange, onImageEnabledChange }) => {
|
||||||
const handleAddImageUrl = () => {
|
const handleAddImageUrl = () => {
|
||||||
const newUrls = [...imageUrls, ''];
|
const newUrls = [...imageUrls, ''];
|
||||||
onImageUrlsChange(newUrls);
|
onImageUrlsChange(newUrls);
|
||||||
@@ -32,7 +34,7 @@ const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileText size={16} className="text-gray-500" />
|
<Image size={16} className={imageEnabled ? "text-blue-500" : "text-gray-400"} />
|
||||||
<Typography.Text strong className="text-sm">
|
<Typography.Text strong className="text-sm">
|
||||||
图片地址
|
图片地址
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
@@ -40,18 +42,32 @@ const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
|||||||
(多模态对话)
|
(多模态对话)
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div className="flex items-center gap-2">
|
||||||
icon={<Plus size={14} />}
|
<Switch
|
||||||
size="small"
|
checked={imageEnabled}
|
||||||
theme="solid"
|
onChange={onImageEnabledChange}
|
||||||
type="primary"
|
checkedText="启用"
|
||||||
onClick={handleAddImageUrl}
|
uncheckedText="停用"
|
||||||
className="!rounded-full !w-4 !h-4 !p-0 !min-w-0"
|
size="small"
|
||||||
disabled={imageUrls.length >= 5}
|
className="flex-shrink-0"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<Plus size={14} />}
|
||||||
|
size="small"
|
||||||
|
theme="solid"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleAddImageUrl}
|
||||||
|
className="!rounded-full !w-4 !h-4 !p-0 !min-w-0"
|
||||||
|
disabled={!imageEnabled || imageUrls.length >= 5}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{imageUrls.length === 0 ? (
|
{!imageEnabled ? (
|
||||||
|
<Typography.Text className="text-xs text-gray-500 mb-2 block">
|
||||||
|
图片发送已停用,启用后可添加图片URL进行多模态对话
|
||||||
|
</Typography.Text>
|
||||||
|
) : imageUrls.length === 0 ? (
|
||||||
<Typography.Text className="text-xs text-gray-500 mb-2 block">
|
<Typography.Text className="text-xs text-gray-500 mb-2 block">
|
||||||
点击 + 按钮添加图片URL,支持最多5张图片
|
点击 + 按钮添加图片URL,支持最多5张图片
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
@@ -61,7 +77,7 @@ const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
<div className={`space-y-2 max-h-32 overflow-y-auto ${!imageEnabled ? 'opacity-50' : ''}`}>
|
||||||
{imageUrls.map((url, index) => (
|
{imageUrls.map((url, index) => (
|
||||||
<div key={index} className="flex items-center gap-2">
|
<div key={index} className="flex items-center gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -72,6 +88,7 @@ const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
|||||||
className="!rounded-lg"
|
className="!rounded-lg"
|
||||||
size="small"
|
size="small"
|
||||||
prefix={<IconFile size='small' />}
|
prefix={<IconFile size='small' />}
|
||||||
|
disabled={!imageEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -81,6 +98,7 @@ const ImageUrlInput = ({ imageUrls, onImageUrlsChange }) => {
|
|||||||
type="danger"
|
type="danger"
|
||||||
onClick={() => handleRemoveImageUrl(index)}
|
onClick={() => handleRemoveImageUrl(index)}
|
||||||
className="!rounded-full !w-6 !h-6 !p-0 !min-w-0 !text-red-500 hover:!bg-red-50 flex-shrink-0"
|
className="!rounded-full !w-6 !h-6 !p-0 !min-w-0 !text-red-500 hover:!bg-red-50 flex-shrink-0"
|
||||||
|
disabled={!imageEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -125,7 +125,9 @@ const SettingsPanel = ({
|
|||||||
{/* 图片URL输入 */}
|
{/* 图片URL输入 */}
|
||||||
<ImageUrlInput
|
<ImageUrlInput
|
||||||
imageUrls={inputs.imageUrls}
|
imageUrls={inputs.imageUrls}
|
||||||
|
imageEnabled={inputs.imageEnabled}
|
||||||
onImageUrlsChange={(urls) => onInputChange('imageUrls', urls)}
|
onImageUrlsChange={(urls) => onInputChange('imageUrls', urls)}
|
||||||
|
onImageEnabledChange={(enabled) => onInputChange('imageEnabled', enabled)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 参数控制组件 */}
|
{/* 参数控制组件 */}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const DEFAULT_CONFIG = {
|
|||||||
seed: null,
|
seed: null,
|
||||||
stream: true,
|
stream: true,
|
||||||
imageUrls: [],
|
imageUrls: [],
|
||||||
|
imageEnabled: false,
|
||||||
},
|
},
|
||||||
parameterEnabled: {
|
parameterEnabled: {
|
||||||
max_tokens: true,
|
max_tokens: true,
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ const Playground = () => {
|
|||||||
let messageContent;
|
let messageContent;
|
||||||
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
|
const validImageUrls = inputs.imageUrls.filter(url => url.trim() !== '');
|
||||||
|
|
||||||
if (validImageUrls.length > 0) {
|
if (inputs.imageEnabled && validImageUrls.length > 0) {
|
||||||
messageContent = [
|
messageContent = [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -643,6 +643,12 @@ const Playground = () => {
|
|||||||
handleNonStreamRequest(payload);
|
handleNonStreamRequest(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputs.imageEnabled) {
|
||||||
|
setTimeout(() => {
|
||||||
|
handleInputChange('imageEnabled', false);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
newMessage.push({
|
newMessage.push({
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: '',
|
content: '',
|
||||||
@@ -655,7 +661,7 @@ const Playground = () => {
|
|||||||
return newMessage;
|
return newMessage;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[getSystemMessage, inputs, setMessage, parameterEnabled],
|
[getSystemMessage, inputs, setMessage, parameterEnabled, handleInputChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const completeMessage = useCallback((status = 'complete') => {
|
const completeMessage = useCallback((status = 'complete') => {
|
||||||
|
|||||||
Reference in New Issue
Block a user