| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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; |
| |
|