mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-18 10:17:26 +00:00
- Add SSEViewer component for interactive SSE message inspection * Display SSE data stream with collapsible panels * Show parsed JSON with syntax highlighting * Display key information badges (content, tokens, finish reason) * Support copy individual or all SSE messages * Show error messages with detailed information - Support Ctrl+V to paste images in chat input * Enable image paste in CustomInputRender component * Auto-detect and add pasted images to image list * Show toast notifications for paste results - Add complete i18n support for 6 languages * Chinese (zh): Complete translations * English (en): Complete translations * Japanese (ja): Add 28 new translations * French (fr): Add 28 new translations * Russian (ru): Add 28 new translations * Vietnamese (vi): Add 32 new translations - Update .gitignore to exclude data directory
143 lines
4.8 KiB
JavaScript
143 lines
4.8 KiB
JavaScript
/*
|
|
Copyright (C) 2025 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { Input, Typography, Button, Switch } from '@douyinfe/semi-ui';
|
|
import { IconFile } from '@douyinfe/semi-icons';
|
|
import { FileText, Plus, X, Image } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
const ImageUrlInput = ({
|
|
imageUrls,
|
|
imageEnabled,
|
|
onImageUrlsChange,
|
|
onImageEnabledChange,
|
|
disabled = false,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const handleAddImageUrl = () => {
|
|
const newUrls = [...imageUrls, ''];
|
|
onImageUrlsChange(newUrls);
|
|
};
|
|
|
|
const handleUpdateImageUrl = (index, value) => {
|
|
const newUrls = [...imageUrls];
|
|
newUrls[index] = value;
|
|
onImageUrlsChange(newUrls);
|
|
};
|
|
|
|
const handleRemoveImageUrl = (index) => {
|
|
const newUrls = imageUrls.filter((_, i) => i !== index);
|
|
onImageUrlsChange(newUrls);
|
|
};
|
|
|
|
return (
|
|
<div className={disabled ? 'opacity-50' : ''}>
|
|
<div className='flex items-center justify-between mb-2'>
|
|
<div className='flex items-center gap-2'>
|
|
<Image
|
|
size={16}
|
|
className={
|
|
imageEnabled && !disabled ? 'text-blue-500' : 'text-gray-400'
|
|
}
|
|
/>
|
|
<Typography.Text strong className='text-sm'>
|
|
{t('图片地址')}
|
|
</Typography.Text>
|
|
{disabled && (
|
|
<Typography.Text className='text-xs text-orange-600'>
|
|
({t('已在自定义模式中忽略')})
|
|
</Typography.Text>
|
|
)}
|
|
</div>
|
|
<div className='flex items-center gap-2'>
|
|
<Switch
|
|
checked={imageEnabled}
|
|
onChange={onImageEnabledChange}
|
|
checkedText={t('启用')}
|
|
uncheckedText={t('停用')}
|
|
size='small'
|
|
className='flex-shrink-0'
|
|
disabled={disabled}
|
|
/>
|
|
<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 || disabled}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{!imageEnabled ? (
|
|
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
|
{disabled
|
|
? t('图片功能在自定义请求体模式下不可用')
|
|
: t('启用后可添加图片URL进行多模态对话')}
|
|
</Typography.Text>
|
|
) : imageUrls.length === 0 ? (
|
|
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
|
{disabled
|
|
? t('图片功能在自定义请求体模式下不可用')
|
|
: t('点击 + 按钮添加图片URL进行多模态对话')}
|
|
</Typography.Text>
|
|
) : (
|
|
<Typography.Text className='text-xs text-gray-500 mb-2 block'>
|
|
{t('已添加')} {imageUrls.length} {t('张图片')}
|
|
{disabled ? ` (${t('自定义模式下不可用')})` : ''}
|
|
</Typography.Text>
|
|
)}
|
|
|
|
<div
|
|
className={`space-y-2 max-h-32 overflow-y-auto image-list-scroll ${!imageEnabled || disabled ? 'opacity-50' : ''}`}
|
|
>
|
|
{imageUrls.map((url, index) => (
|
|
<div key={index} className='flex items-center gap-2'>
|
|
<div className='flex-1'>
|
|
<Input
|
|
placeholder={`https://example.com/image${index + 1}.jpg`}
|
|
value={url}
|
|
onChange={(value) => handleUpdateImageUrl(index, value)}
|
|
className='!rounded-lg'
|
|
size='small'
|
|
prefix={<IconFile size='small' />}
|
|
disabled={!imageEnabled || disabled}
|
|
/>
|
|
</div>
|
|
<Button
|
|
icon={<X size={12} />}
|
|
size='small'
|
|
theme='borderless'
|
|
type='danger'
|
|
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'
|
|
disabled={!imageEnabled || disabled}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ImageUrlInput;
|