后端预设列表读取完成

This commit is contained in:
2026-04-04 12:36:27 +08:00
parent 0ae53c4b81
commit 1abfaeda9d
20 changed files with 1050 additions and 130 deletions

View File

@@ -1,11 +0,0 @@
import React from 'react';
const DicePanel = () => {
return (
<div className="dice-panel">
<div>骰子区</div>
</div>
);
};
export default DicePanel;

View File

@@ -1,11 +0,0 @@
import React from 'react';
const ImageDisplay = () => {
return (
<div className="image-display">
<div>图片展示区</div>
</div>
);
};
export default ImageDisplay;

View File

@@ -1,57 +0,0 @@
/* ==================== 顶部工具栏区域 ==================== */
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50px;
background-color: #fff;
border-bottom: 1px solid #ddd;
padding: 0;
display: flex;
align-items: center;
justify-content: flex-end;
z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: height 0.3s ease;
}
.toolbar.expanded {
height: auto;
max-height: 300px;
overflow-y: auto;
padding-bottom: 10px;
}
.toolbar-content {
display: none;
width: 100%;
padding: 10px 20px;
}
.toolbar.expanded .toolbar-content {
display: block;
}
.toolbar-toggle-btn {
position: absolute;
right: 0;
top: 0;
height: 50px;
width: 50px;
background: none;
border: none;
border-left: 1px solid #ddd;
cursor: pointer;
font-size: 0.8rem;
color: #666;
display: flex;
align-items: center;
justify-content: center;
z-index: 101;
}
.toolbar-toggle-btn:hover {
background-color: #f5f5f5;
}

View File

@@ -1,30 +0,0 @@
import React from 'react';
import './PresetPanel.css';
const PresetPanel = () => {
return (
<div className="preset-panel">
{/* 顶部:预设选择与输入 */}
<div className="preset-header">
{/* 下拉框 */}
<select className="preset-select">
<option>选择预设...</option>
</select>
{/* 输入框组(待定) */}
<div className="preset-inputs">
{/* inputs here */}
</div>
</div>
{/* 下方:大槽位区域 */}
<div className="preset-slots">
{/* 槽位列表 */}
<div className="slot-item">槽位 1</div>
<div className="slot-item">槽位 2</div>
{/* ... */}
</div>
</div>
);
};
export default PresetPanel;

View File

@@ -2,7 +2,7 @@
import React from 'react';
import './SideBarLeft.css';
import { useSideBarLeftStore } from '../../Store/indexStore';
import useSideBarRightStore from '../../Store/Slices/SideBarLeftSlice';
import useSideBarRightStore from '../../Store/Slices/LeftTabsSlices/SideBarLeftSlice';
import Gallery from './tab/Gallery';
import ApiConfig from './tab/ApiConfig';
import Presets from './tab/Presets';

View File

@@ -1,13 +1,523 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import usePresetStore from '../../../Store/Slices/LeftTabsSlices/PresetSlice';
import '../tabcss/Presets.css';
const Presets = () => {
const PresetPanel = () => {
const {
selectedPreset,
parameters,
presets,
isLoadingPresets,
setSelectedPreset,
updateParameter,
saveCurrentAsPreset,
editPresetName: updatePresetName,
isParametersExpanded,
toggleParametersExpanded,
fetchPresets
} = usePresetStore();
const [showSaveDialog, setShowSaveDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false);
const [showImportDialog, setShowImportDialog] = useState(false);
const [newPresetName, setNewPresetName] = useState('');
const [editPresetId, setEditPresetId] = useState('');
const [editPresetName, setEditPresetName] = useState('');
const [importPresetData, setImportPresetData] = useState('');
const [tooltip, setTooltip] = useState({ visible: false, content: '', x: 0, y: 0 });
// 参数描述映射
const parameterDescriptions = {
temperature: "生成温度,控制随机性(0-2)",
frequency_penalty: "频率惩罚降低重复token概率",
presence_penalty: "存在惩罚,鼓励谈论新话题",
top_p: "核采样,控制词汇选择范围",
top_k: "随机采样范围从概率最高的K个词中选择",
max_context: "上下文窗口大小(Token上限)",
max_tokens: "单次回复的最大长度",
max_context_unlocked: "是否允许超出限制的上下文",
stream_openai: "是否使用流式输出",
seed: "随机种子(-1为随机)",
n: "生成回复的数量"
};
// 显示工具提示
const showTooltip = (event, content) => {
setTooltip({
visible: true,
content,
x: event.clientX,
y: event.clientY
});
};
// 隐藏工具提示
const hideTooltip = () => {
setTooltip({ ...tooltip, visible: false });
};
// 处理参数更新
const handleParameterChange = (name, value) => {
// 根据参数类型转换值
let convertedValue = value;
if (name === 'temperature' || name === 'frequency_penalty' || name === 'presence_penalty' || name === 'top_p') {
convertedValue = parseFloat(value);
} else if (name === 'top_k' || name === 'max_context' || name === 'max_tokens' || name === 'seed' || name === 'n') {
convertedValue = parseInt(value, 10);
} else if (name === 'max_context_unlocked' || name === 'stream_openai') {
convertedValue = value;
}
updateParameter({ name, value: convertedValue });
};
useEffect(() => {
fetchPresets();
}, [fetchPresets]);
// 保存当前设置为预设
const handleSavePreset = () => {
if (newPresetName.trim()) {
saveCurrentAsPreset({ name: newPresetName });
setNewPresetName('');
setShowSaveDialog(false);
}
};
// 编辑预设名称
const handleEditPreset = () => {
if (editPresetId && editPresetName.trim()) {
updatePresetName(editPresetId, editPresetName);
setEditPresetId('');
setEditPresetName('');
setShowEditDialog(false);
}
};
// 导入预设
const handleImportPreset = () => {
try {
const importedPreset = JSON.parse(importPresetData);
if (importedPreset.name && importedPreset.parameters) {
saveCurrentAsPreset({ name: importedPreset.name });
// 这里需要更新参数
Object.keys(importedPreset.parameters).forEach(key => {
updateParameter({ name: key, value: importedPreset.parameters[key] });
});
setImportPresetData('');
setShowImportDialog(false);
}
} catch (error) {
console.error('导入预设失败:', error);
}
};
// 导出预设
const handleExportPreset = () => {
if (selectedPreset) {
const preset = presets.find(p => p.id === selectedPreset);
if (preset) {
const dataStr = JSON.stringify(preset, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `${preset.name}.json`;
link.click();
URL.revokeObjectURL(url);
}
}
};
return (
<div className="presets-content">
<h2>预设配置</h2>
{/* 在这里实现预设配置的具体内容 */}
<div className="preset-panel">
{/* 工具提示 */}
{tooltip.visible && (
<div
className="tooltip"
style={{ left: `${tooltip.x}px`, top: `${tooltip.y}px` }}
>
{tooltip.content}
</div>
)}
{/* 顶部:预设选择与操作 */}
<div className="preset-header">
{/* 预设选择下拉框 */}
<div className="preset-select-container">
<label
className="preset-label"
onMouseEnter={(e) => showTooltip(e, "选择预设配置")}
onMouseLeave={hideTooltip}
>
预设:
</label>
<select
className="preset-select"
value={selectedPreset}
onChange={(e) => setSelectedPreset(e.target.value)}
disabled={isLoadingPresets}
>
<option value="">{isLoadingPresets ? "加载中..." : "选择预设..."}</option>
{presets.map(preset => (
<option key={preset.id} value={preset.id}>{preset.name}</option>
))}
</select>
</div>
{/* 操作按钮 */}
<div className="preset-actions">
<button
className="preset-action-btn"
onClick={() => setShowSaveDialog(true)}
onMouseEnter={(e) => showTooltip(e, "保存当前设置为新预设")}
onMouseLeave={hideTooltip}
>
💾
</button>
<button
className="preset-action-btn"
onClick={() => {
if (selectedPreset) {
const preset = presets.find(p => p.id === selectedPreset);
if (preset) {
setEditPresetId(selectedPreset);
setEditPresetName(preset.name);
setShowEditDialog(true);
}
}
}}
onMouseEnter={(e) => showTooltip(e, "编辑当前预设")}
onMouseLeave={hideTooltip}
>
</button>
<button
className="preset-action-btn"
onClick={() => setShowImportDialog(true)}
onMouseEnter={(e) => showTooltip(e, "导入预设")}
onMouseLeave={hideTooltip}
>
📥
</button>
<button
className="preset-action-btn"
onClick={handleExportPreset}
onMouseEnter={(e) => showTooltip(e, "导出当前预设")}
onMouseLeave={hideTooltip}
>
📤
</button>
</div>
</div>
{/* 保存预设对话框 */}
{showSaveDialog && (
<div className="preset-save-dialog">
<input
type="text"
value={newPresetName}
onChange={(e) => setNewPresetName(e.target.value)}
placeholder="预设名称"
/>
<div className="dialog-buttons">
<button onClick={handleSavePreset}>保存</button>
<button onClick={() => setShowSaveDialog(false)}>取消</button>
</div>
</div>
)}
{/* 编辑预设对话框 */}
{showEditDialog && (
<div className="preset-edit-dialog">
<input
type="text"
value={editPresetName}
onChange={(e) => setEditPresetName(e.target.value)}
placeholder="预设名称"
/>
<div className="dialog-buttons">
<button onClick={handleEditPreset}>保存</button>
<button onClick={() => setShowEditDialog(false)}>取消</button>
</div>
</div>
)}
{/* 导入预设对话框 */}
{showImportDialog && (
<div className="preset-import-dialog">
<textarea
value={importPresetData}
onChange={(e) => setImportPresetData(e.target.value)}
placeholder="粘贴预设JSON数据"
rows="5"
/>
<div className="dialog-buttons">
<button onClick={handleImportPreset}>导入</button>
<button onClick={() => setShowImportDialog(false)}>取消</button>
</div>
</div>
)}
{/* 参数设置区域 */}
<div className="preset-parameters-container">
<div
className="parameters-header"
onClick={toggleParametersExpanded}
>
<span>参数设置</span>
<span className={`expand-icon ${isParametersExpanded ? 'expanded' : ''}`}></span>
</div>
{isParametersExpanded && (
<div className="preset-parameters">
{/* 温度滑块 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.temperature)}
onMouseLeave={hideTooltip}
>
Temperature
</label>
<input
type="range"
min="0"
max="2"
step="0.1"
value={parameters.temperature}
onChange={(e) => handleParameterChange('temperature', e.target.value)}
className="parameter-slider"
/>
<input
type="number"
min="0"
max="2"
step="0.1"
value={parameters.temperature}
onChange={(e) => handleParameterChange('temperature', e.target.value)}
className="parameter-number"
/>
</div>
{/* 频率惩罚滑块 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.frequency_penalty)}
onMouseLeave={hideTooltip}
>
Frequency Penalty
</label>
<input
type="range"
min="-2"
max="2"
step="0.1"
value={parameters.frequency_penalty}
onChange={(e) => handleParameterChange('frequency_penalty', e.target.value)}
className="parameter-slider"
/>
<input
type="number"
min="-2"
max="2"
step="0.1"
value={parameters.frequency_penalty}
onChange={(e) => handleParameterChange('frequency_penalty', e.target.value)}
className="parameter-number"
/>
</div>
{/* 存在惩罚滑块 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.presence_penalty)}
onMouseLeave={hideTooltip}
>
Presence Penalty
</label>
<input
type="range"
min="-2"
max="2"
step="0.1"
value={parameters.presence_penalty}
onChange={(e) => handleParameterChange('presence_penalty', e.target.value)}
className="parameter-slider"
/>
<input
type="number"
min="-2"
max="2"
step="0.1"
value={parameters.presence_penalty}
onChange={(e) => handleParameterChange('presence_penalty', e.target.value)}
className="parameter-number"
/>
</div>
{/* Top P 滑块 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.top_p)}
onMouseLeave={hideTooltip}
>
Top P
</label>
<input
type="range"
min="0"
max="1"
step="0.05"
value={parameters.top_p}
onChange={(e) => handleParameterChange('top_p', e.target.value)}
className="parameter-slider"
/>
<input
type="number"
min="0"
max="1"
step="0.05"
value={parameters.top_p}
onChange={(e) => handleParameterChange('top_p', e.target.value)}
className="parameter-number"
/>
</div>
{/* Top K 输入框 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.top_k)}
onMouseLeave={hideTooltip}
>
Top K
</label>
<input
type="number"
min="0"
value={parameters.top_k}
onChange={(e) => handleParameterChange('top_k', e.target.value)}
className="parameter-input"
/>
</div>
{/* 最大上下文输入框 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.max_context)}
onMouseLeave={hideTooltip}
>
Max Context
</label>
<input
type="number"
min="1"
max="10000000"
defaultValue="1000000"
value={parameters.max_context}
onChange={(e) => handleParameterChange('max_context', e.target.value)}
className="parameter-input"
/>
</div>
{/* 最大Token输入框 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.max_tokens)}
onMouseLeave={hideTooltip}
>
Max Tokens
</label>
<input
type="number"
min="1"
max="100000"
defaultValue="30000"
value={parameters.max_tokens}
onChange={(e) => handleParameterChange('max_tokens', e.target.value)}
className="parameter-input"
/>
</div>
{/* 随机种子输入框 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.seed)}
onMouseLeave={hideTooltip}
>
Seed
</label>
<input
type="number"
value={parameters.seed}
onChange={(e) => handleParameterChange('seed', e.target.value)}
className="parameter-input"
/>
</div>
{/* 生成数量输入框 */}
<div className="parameter-row">
<label
className="parameter-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.n)}
onMouseLeave={hideTooltip}
>
N (生成数量)
</label>
<input
type="number"
min="1"
value={parameters.n}
onChange={(e) => handleParameterChange('n', e.target.value)}
className="parameter-input"
/>
</div>
{/* 开关选项 */}
<div className="parameter-toggles">
<div className="toggle-row">
<label
className="toggle-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.max_context_unlocked)}
onMouseLeave={hideTooltip}
>
Max Context Unlocked
</label>
<input
type="checkbox"
checked={parameters.max_context_unlocked}
onChange={(e) => handleParameterChange('max_context_unlocked', e.target.checked)}
className="toggle-checkbox"
/>
</div>
<div className="toggle-row">
<label
className="toggle-label"
onMouseEnter={(e) => showTooltip(e, parameterDescriptions.stream_openai)}
onMouseLeave={hideTooltip}
>
Stream Output
</label>
<input
type="checkbox"
checked={parameters.stream_openai}
onChange={(e) => handleParameterChange('stream_openai', e.target.checked)}
className="toggle-checkbox"
/>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default Presets;
export default PresetPanel;

View File

@@ -0,0 +1,302 @@
/* ==================== 预设页区域 ==================== */
.preset-panel {
display: flex;
flex-direction: column;
padding: 12px;
background-color: #f9f9f9;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
height: 100%;
overflow-y: auto;
}
.preset-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #eaeaea;
}
.preset-select-container {
display: flex;
align-items: center;
flex: 1;
margin-right: 8px;
}
.preset-label {
margin-right: 8px;
font-size: 14px;
font-weight: 600;
color: #333;
cursor: help;
}
.preset-select {
flex: 1;
padding: 6px 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: white;
font-size: 14px;
color: #333;
transition: border-color 0.2s;
}
.preset-select:focus {
outline: none;
border-color: #4a6cf7;
}
.preset-actions {
display: flex;
gap: 4px;
}
.preset-action-btn {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
padding: 6px;
border-radius: 4px;
transition: background-color 0.2s;
color: #555;
}
.preset-action-btn:hover {
background-color: #e8e8e8;
color: #333;
}
.preset-save-dialog,
.preset-edit-dialog,
.preset-import-dialog {
display: flex;
flex-direction: column;
padding: 12px;
background-color: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
}
.preset-save-dialog input,
.preset-edit-dialog input {
padding: 8px;
margin-bottom: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.2s;
}
.preset-save-dialog input:focus,
.preset-edit-dialog input:focus {
outline: none;
border-color: #4a6cf7;
}
.preset-import-dialog textarea {
padding: 8px;
margin-bottom: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
resize: vertical;
font-family: monospace;
font-size: 13px;
min-height: 100px;
transition: border-color 0.2s;
}
.preset-import-dialog textarea:focus {
outline: none;
border-color: #4a6cf7;
}
.dialog-buttons {
display: flex;
justify-content: flex-end;
}
.dialog-buttons button {
margin-left: 8px;
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.dialog-buttons button:first-child {
background-color: #4a6cf7;
color: white;
}
.dialog-buttons button:first-child:hover {
background-color: #3a5ce5;
}
.dialog-buttons button:last-child {
background-color: #f0f0f0;
color: #333;
}
.dialog-buttons button:last-child:hover {
background-color: #e0e0e0;
}
.preset-parameters-container {
border: 1px solid #eaeaea;
border-radius: 6px;
overflow: hidden;
}
.parameters-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #f5f5f5;
cursor: pointer;
font-size: 14px;
font-weight: 600;
color: #333;
transition: background-color 0.2s;
}
.parameters-header:hover {
background-color: #eaeaea;
}
.expand-icon {
transition: transform 0.3s ease;
font-size: 12px;
}
.expand-icon.expanded {
transform: rotate(180deg);
}
.preset-parameters {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
}
.parameter-row {
display: flex;
align-items: center;
gap: 8px;
}
.parameter-label {
width: 140px;
font-size: 13px;
font-weight: 500;
color: #555;
cursor: help;
}
.parameter-slider {
flex: 1;
height: 4px;
border-radius: 2px;
background: #e0e0e0;
outline: none;
-webkit-appearance: none;
}
.parameter-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #4a6cf7;
cursor: pointer;
}
.parameter-slider::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: #4a6cf7;
cursor: pointer;
border: none;
}
.parameter-number {
width: 60px;
padding: 4px 6px;
border: 1px solid #e0e0e0;
border-radius: 4px;
text-align: center;
font-size: 13px;
transition: border-color 0.2s;
}
.parameter-number:focus {
outline: none;
border-color: #4a6cf7;
}
.parameter-input {
flex: 1;
padding: 6px 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
font-size: 13px;
transition: border-color 0.2s;
}
.parameter-input:focus {
outline: none;
border-color: #4a6cf7;
}
.parameter-toggles {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.toggle-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.toggle-label {
font-size: 13px;
font-weight: 500;
color: #555;
cursor: help;
}
.toggle-checkbox {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #4a6cf7;
}
.tooltip {
position: fixed;
padding: 6px 10px;
background-color: #333;
color: white;
border-radius: 4px;
font-size: 12px;
max-width: 250px;
z-index: 1000;
pointer-events: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
line-height: 1.4;
}

View File

@@ -69,7 +69,6 @@
border-bottom: 1px solid #f0f0f0;
}
.tab-content:last-child {
border-bottom: none;
}
@@ -99,4 +98,3 @@
.tab-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}

View File

@@ -1,16 +1,20 @@
import React, { useEffect } from 'react';
import './SideBarRight.css';
import DicePanel from '../DicePanel/DicePanel';
import ImageDisplay from '../ImageDisplay/ImageDisplay';
import useSideBarRightStore from '../../Store/Slices/SideBarRightSlice';
import Dice from './tab/Dice';
import Debug from './tab/Debug';
import Macros from './tab/Macros';
import Table from './tab/Table';
import useSideBarRightStore from '../../Store/Slices/RightTabsSlices/SideBarRightSlice';
const SideBarRight = () => {
const { selectedTabs, allTabs, handleTabClick, setTabComponent } = useSideBarRightStore();
// 设置标签组件
useEffect(() => {
setTabComponent('dice', DicePanel);
// 可以在这里设置其他标签的组件
setTabComponent('dice', Dice);
setTabComponent('debug', Debug);
setTabComponent('macros', Macros);
setTabComponent('table', Table);
}, [setTabComponent]);
return (

View File

@@ -0,0 +1,12 @@
import React from 'react';
const Debug = () => {
return (
<div className="debug-panel">
<h2>上下文调试</h2>
<p>这是上下文调试面板的占位页面</p>
</div>
);
};
export default Debug;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const Dice = () => {
return (
<div className="dice-panel">
<h2>骰子面板</h2>
<p>这是骰子面板的占位页面</p>
</div>
);
};
export default Dice;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const Macros = () => {
return (
<div className="macros-panel">
<h2>快捷宏</h2>
<p>这是快捷宏面板的占位页面</p>
</div>
);
};
export default Macros;

View File

@@ -0,0 +1,12 @@
import React from 'react';
const Table = () => {
return (
<div className="table-panel">
<h2>动态表格</h2>
<p>这是动态表格面板的占位页面</p>
</div>
);
};
export default Table;