| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import React, { useRef } from 'react'; |
| import { Button, Typography, Toast, Modal, Dropdown } from '@douyinfe/semi-ui'; |
| import { Download, Upload, RotateCcw, Settings2 } from 'lucide-react'; |
| import { useTranslation } from 'react-i18next'; |
| import { |
| exportConfig, |
| importConfig, |
| clearConfig, |
| hasStoredConfig, |
| getConfigTimestamp, |
| } from './configStorage'; |
|
|
| const ConfigManager = ({ |
| currentConfig, |
| onConfigImport, |
| onConfigReset, |
| styleState, |
| messages, |
| }) => { |
| const { t } = useTranslation(); |
| const fileInputRef = useRef(null); |
|
|
| const handleExport = () => { |
| try { |
| |
| const configWithTimestamp = { |
| ...currentConfig, |
| timestamp: new Date().toISOString(), |
| }; |
| localStorage.setItem( |
| 'playground_config', |
| JSON.stringify(configWithTimestamp), |
| ); |
|
|
| exportConfig(currentConfig, messages); |
| Toast.success({ |
| content: t('配置已导出到下载文件夹'), |
| duration: 3, |
| }); |
| } catch (error) { |
| Toast.error({ |
| content: t('导出配置失败: ') + error.message, |
| duration: 3, |
| }); |
| } |
| }; |
|
|
| const handleImportClick = () => { |
| fileInputRef.current?.click(); |
| }; |
|
|
| const handleFileChange = async (event) => { |
| const file = event.target.files[0]; |
| if (!file) return; |
|
|
| try { |
| const importedConfig = await importConfig(file); |
|
|
| Modal.confirm({ |
| title: t('确认导入配置'), |
| content: t('导入的配置将覆盖当前设置,是否继续?'), |
| okText: t('确定导入'), |
| cancelText: t('取消'), |
| onOk: () => { |
| onConfigImport(importedConfig); |
| Toast.success({ |
| content: t('配置导入成功'), |
| duration: 3, |
| }); |
| }, |
| }); |
| } catch (error) { |
| Toast.error({ |
| content: t('导入配置失败: ') + error.message, |
| duration: 3, |
| }); |
| } finally { |
| |
| event.target.value = ''; |
| } |
| }; |
|
|
| const handleReset = () => { |
| Modal.confirm({ |
| title: t('重置配置'), |
| content: t( |
| '将清除所有保存的配置并恢复默认设置,此操作不可撤销。是否继续?', |
| ), |
| okText: t('确定重置'), |
| cancelText: t('取消'), |
| okButtonProps: { |
| type: 'danger', |
| }, |
| onOk: () => { |
| |
| Modal.confirm({ |
| title: t('重置选项'), |
| content: t( |
| '是否同时重置对话消息?选择"是"将清空所有对话记录并恢复默认示例;选择"否"将保留当前对话记录。', |
| ), |
| okText: t('同时重置消息'), |
| cancelText: t('仅重置配置'), |
| okButtonProps: { |
| type: 'danger', |
| }, |
| onOk: () => { |
| clearConfig(); |
| onConfigReset({ resetMessages: true }); |
| Toast.success({ |
| content: t('配置和消息已全部重置'), |
| duration: 3, |
| }); |
| }, |
| onCancel: () => { |
| clearConfig(); |
| onConfigReset({ resetMessages: false }); |
| Toast.success({ |
| content: t('配置已重置,对话消息已保留'), |
| duration: 3, |
| }); |
| }, |
| }); |
| }, |
| }); |
| }; |
|
|
| const getConfigStatus = () => { |
| if (hasStoredConfig()) { |
| const timestamp = getConfigTimestamp(); |
| if (timestamp) { |
| const date = new Date(timestamp); |
| return t('上次保存: ') + date.toLocaleString(); |
| } |
| return t('已有保存的配置'); |
| } |
| return t('暂无保存的配置'); |
| }; |
|
|
| const dropdownItems = [ |
| { |
| node: 'item', |
| name: 'export', |
| onClick: handleExport, |
| children: ( |
| <div className='flex items-center gap-2'> |
| <Download size={14} /> |
| {t('导出配置')} |
| </div> |
| ), |
| }, |
| { |
| node: 'item', |
| name: 'import', |
| onClick: handleImportClick, |
| children: ( |
| <div className='flex items-center gap-2'> |
| <Upload size={14} /> |
| {t('导入配置')} |
| </div> |
| ), |
| }, |
| { |
| node: 'divider', |
| }, |
| { |
| node: 'item', |
| name: 'reset', |
| onClick: handleReset, |
| children: ( |
| <div className='flex items-center gap-2 text-red-600'> |
| <RotateCcw size={14} /> |
| {t('重置配置')} |
| </div> |
| ), |
| }, |
| ]; |
|
|
| if (styleState.isMobile) { |
| |
| return ( |
| <> |
| <Dropdown |
| trigger='click' |
| position='bottomLeft' |
| showTick |
| menu={dropdownItems} |
| > |
| <Button |
| icon={<Settings2 size={14} />} |
| theme='borderless' |
| type='tertiary' |
| size='small' |
| className='!rounded-lg !text-gray-600 hover:!text-blue-600 hover:!bg-blue-50' |
| /> |
| </Dropdown> |
| |
| <input |
| ref={fileInputRef} |
| type='file' |
| accept='.json' |
| onChange={handleFileChange} |
| style={{ display: 'none' }} |
| /> |
| </> |
| ); |
| } |
|
|
| |
| return ( |
| <div className='space-y-3'> |
| {/* 配置状态信息和重置按钮 */} |
| <div className='flex items-center justify-between'> |
| <Typography.Text className='text-xs text-gray-500'> |
| {getConfigStatus()} |
| </Typography.Text> |
| <Button |
| icon={<RotateCcw size={12} />} |
| size='small' |
| theme='borderless' |
| type='danger' |
| onClick={handleReset} |
| className='!rounded-full !text-xs !px-2' |
| /> |
| </div> |
| |
| {/* 导出和导入按钮 */} |
| <div className='flex gap-2'> |
| <Button |
| icon={<Download size={12} />} |
| size='small' |
| theme='solid' |
| type='primary' |
| onClick={handleExport} |
| className='!rounded-lg flex-1 !text-xs !h-7' |
| > |
| {t('导出')} |
| </Button> |
| |
| <Button |
| icon={<Upload size={12} />} |
| size='small' |
| theme='outline' |
| type='primary' |
| onClick={handleImportClick} |
| className='!rounded-lg flex-1 !text-xs !h-7' |
| > |
| {t('导入')} |
| </Button> |
| </div> |
| |
| <input |
| ref={fileInputRef} |
| type='file' |
| accept='.json' |
| onChange={handleFileChange} |
| style={{ display: 'none' }} |
| /> |
| </div> |
| ); |
| }; |
|
|
| export default ConfigManager; |
|
|