Просмотр исходного кода

✨ 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.
Apple\Apple 9 месяцев назад
Родитель
Сommit
fbb189ecd7

+ 31 - 13
web/src/components/playground/ImageUrlInput.js

@@ -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
-          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={imageUrls.length >= 5}
-        />
+        <div className="flex items-center gap-2">
+          <Switch
+            checked={imageEnabled}
+            onChange={onImageEnabledChange}
+            checkedText="启用"
+            uncheckedText="停用"
+            size="small"
+            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>
         ))}
         ))}

+ 2 - 0
web/src/components/playground/SettingsPanel.js

@@ -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)}
         />
         />
 
 
         {/* 参数控制组件 */}
         {/* 参数控制组件 */}

+ 1 - 0
web/src/components/playground/configStorage.js

@@ -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,

+ 8 - 2
web/src/pages/Playground/Playground.js

@@ -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') => {