后端预设列表读取完成
This commit is contained in:
@@ -5,16 +5,64 @@ from ..core.models.chat_history import ChatHistory # 修改导入语句
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# 1. 从本地读取所有的data内容
|
||||
# 从本地读取所有的data内容
|
||||
@router.get("/tool_bar/get_all_role_and_chat")
|
||||
def get_all_role_and_chat_endpoint():
|
||||
# 正确调用函数并返回结果
|
||||
return get_all_role_and_chat()
|
||||
|
||||
# 2. 根据rolename和chatname读取特定聊天记录
|
||||
# 根据rolename和chatname读取特定聊天记录
|
||||
@router.get("/chat_box/get_chat_history")
|
||||
async def get_chat_history_endpoint(role_name: str, chat_name: str):
|
||||
# 实例化工具类
|
||||
reader = ChatHistory.load_from_file(role_name, chat_name)
|
||||
|
||||
return reader.to_chatbox_format()
|
||||
|
||||
|
||||
# 从本地读取所有的预设列表内容
|
||||
@router.get("/presets/list")
|
||||
async def get_presets_list():
|
||||
"""
|
||||
获取所有可用的预设列表
|
||||
返回预设名称列表
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 预设文件存储目录
|
||||
preset_dir = Path("data/preset")
|
||||
|
||||
# 确保目录存在
|
||||
if not preset_dir.exists():
|
||||
return {"presets": []}
|
||||
|
||||
# 获取所有.json文件
|
||||
preset_files = list(preset_dir.glob("*.json"))
|
||||
|
||||
# 提取文件名(不带扩展名)作为预设名称
|
||||
presets = [file.stem for file in preset_files]
|
||||
|
||||
return {"presets": presets}
|
||||
|
||||
|
||||
# 从本地读取特定预设
|
||||
@router.get("/presets/{preset_name}")
|
||||
async def get_preset_content(preset_name: str):
|
||||
"""
|
||||
获取特定预设的完整内容
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
preset_path = Path("data/preset") / f"{preset_name}.json"
|
||||
|
||||
if not preset_path.exists():
|
||||
raise HTTPException(status_code=404, detail="Preset not found")
|
||||
|
||||
with open(preset_path, 'r', encoding='utf-8') as f:
|
||||
preset_data = json.load(f)
|
||||
|
||||
return preset_data
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ class AIDesignSpec(BaseModel):
|
||||
top_a: float = Field(0.0, description="基于平方概率分布的采样")
|
||||
min_p: float = Field(0.0, description="最小概率阈值")
|
||||
repetition_penalty: float = Field(1.0, description="重复惩罚系数(1.0-1.2)")
|
||||
openai_max_context: int = Field(2048, description="上下文窗口大小(Token上限)")
|
||||
openai_max_tokens: int = Field(250, description="单次回复的最大长度")
|
||||
max_context: int = Field(2048, description="上下文窗口大小(Token上限)")
|
||||
max_tokens: int = Field(250, description="单次回复的最大长度")
|
||||
max_context_unlocked: bool = Field(False, description="是否允许超出限制的上下文")
|
||||
names_behavior: int = Field(0, description="名字处理行为(0=默认,1=始终包含,2=仅角色)")
|
||||
send_if_empty: str = Field("", description="用户发送空消息时自动填充的内容")
|
||||
@@ -30,7 +30,7 @@ class AIDesignSpec(BaseModel):
|
||||
scenario_format: str = Field("{{scenario}}", description="场景描述的格式化字符串")
|
||||
personality_format: str = Field("", description="角色性格的格式化字符串")
|
||||
group_nudge_prompt: str = Field("", description="群组聊天中提示AI仅以特定角色回复的提示词")
|
||||
stream_openai: bool = Field(True, description="是否使用流式输出")
|
||||
stream: bool = Field(True, description="是否使用流式输出")
|
||||
assistant_prefill: str = Field("", description="强制AI回复的开头内容")
|
||||
assistant_impersonation: str = Field("", description="模仿模式下强制AI回复的开头内容")
|
||||
use_sysprompt: bool = Field(True, description="是否强制将提示词注入系统层")
|
||||
|
||||
119
frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx
Normal file
119
frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
const usePresetStore = create((set, get) => ({
|
||||
// 预设选择
|
||||
selectedPreset: '',
|
||||
|
||||
// 核心参数
|
||||
parameters: {
|
||||
temperature: 1.0,
|
||||
frequency_penalty: 0.0,
|
||||
presence_penalty: 0.0,
|
||||
top_p: 1.0,
|
||||
top_k: 0,
|
||||
max_context: 1000000,
|
||||
max_tokens: 30000,
|
||||
max_context_unlocked: false,
|
||||
stream_openai: true,
|
||||
seed: -1,
|
||||
n: 1
|
||||
},
|
||||
|
||||
// 可用的预设列表 - 初始为空,将从后端加载
|
||||
presets: [],
|
||||
|
||||
// 是否正在加载预设列表
|
||||
isLoadingPresets: false,
|
||||
|
||||
// 参数设置折叠状态
|
||||
isParametersExpanded: true,
|
||||
|
||||
// 从后端加载预设列表
|
||||
fetchPresets: async () => {
|
||||
set({ isLoadingPresets: true });
|
||||
try {
|
||||
const response = await fetch('/api/presets/list');
|
||||
const data = await response.json();
|
||||
|
||||
// 转换为预设对象数组
|
||||
const presetList = data.presets.map(name => ({
|
||||
id: name,
|
||||
name,
|
||||
parameters: {} // 参数将在选择预设时加载
|
||||
}));
|
||||
|
||||
set({ presets: presetList, isLoadingPresets: false });
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch presets:', error);
|
||||
set({ isLoadingPresets: false });
|
||||
}
|
||||
},
|
||||
|
||||
// 设置选中的预设
|
||||
setSelectedPreset: async (presetId) => {
|
||||
try {
|
||||
// 从后端获取预设的完整内容
|
||||
const response = await fetch(`/api/presets/${presetId}`);
|
||||
const presetData = await response.json();
|
||||
|
||||
// 提取参数并更新状态
|
||||
const parameters = {
|
||||
temperature: presetData.temperature || 1.0,
|
||||
frequency_penalty: presetData.frequency_penalty || 0.0,
|
||||
presence_penalty: presetData.presence_penalty || 0.0,
|
||||
top_p: presetData.top_p || 1.0,
|
||||
top_k: presetData.top_k || 0,
|
||||
max_context: presetData.openai_max_context || 1000000,
|
||||
max_tokens: presetData.openai_max_tokens || 30000,
|
||||
max_context_unlocked: presetData.max_context_unlocked || false,
|
||||
stream_openai: presetData.stream_openai || true,
|
||||
seed: presetData.seed || -1,
|
||||
n: presetData.n || 1
|
||||
};
|
||||
|
||||
set({
|
||||
selectedPreset: presetId,
|
||||
parameters
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load preset:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 更新参数
|
||||
updateParameter: ({ name, value }) => set((state) => ({
|
||||
parameters: { ...state.parameters, [name]: value }
|
||||
})),
|
||||
|
||||
// 添加预设
|
||||
addPreset: (preset) => set((state) => ({
|
||||
presets: [...state.presets, preset]
|
||||
})),
|
||||
|
||||
// 保存当前设置为预设
|
||||
saveCurrentAsPreset: ({ name }) => set((state) => {
|
||||
const newPreset = {
|
||||
id: `preset_${Date.now()}`,
|
||||
name,
|
||||
parameters: { ...state.parameters }
|
||||
};
|
||||
return {
|
||||
presets: [...state.presets, newPreset],
|
||||
selectedPreset: newPreset.id
|
||||
};
|
||||
}),
|
||||
|
||||
// 编辑预设名称
|
||||
editPresetName: (presetId, newName) => set((state) => ({
|
||||
presets: state.presets.map(preset =>
|
||||
preset.id === presetId ? { ...preset, name: newName } : preset
|
||||
)
|
||||
})),
|
||||
|
||||
// 切换参数设置折叠状态
|
||||
toggleParametersExpanded: () => set((state) => ({
|
||||
isParametersExpanded: !state.isParametersExpanded
|
||||
}))
|
||||
}));
|
||||
|
||||
export default usePresetStore;
|
||||
@@ -1,5 +1,5 @@
|
||||
// frontend-react/src/store/index.js
|
||||
export { default as useRoleSelectorStore } from './Slices/RoleSelectorSlice';
|
||||
export { default as useSideBarLeftStore } from './Slices/SideBarLeftSlice';
|
||||
export { default as useSideBarRightStore } from './Slices/SideBarRightSlice';
|
||||
export { default as useSideBarLeftStore } from './Slices/LeftTabsSlices/SideBarLeftSlice';
|
||||
export { default as useSideBarRightStore } from './Slices/RightTabsSlices/SideBarRightSlice';
|
||||
export { default as useChatBoxStore } from './Slices/ChatBoxSlice';
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const DicePanel = () => {
|
||||
return (
|
||||
<div className="dice-panel">
|
||||
<div>骰子区</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DicePanel;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const ImageDisplay = () => {
|
||||
return (
|
||||
<div className="image-display">
|
||||
<div>图片展示区</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageDisplay;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
12
frontend-react/src/components/SideBarRight/tab/Debug.jsx
Normal file
12
frontend-react/src/components/SideBarRight/tab/Debug.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
const Debug = () => {
|
||||
return (
|
||||
<div className="debug-panel">
|
||||
<h2>上下文调试</h2>
|
||||
<p>这是上下文调试面板的占位页面</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Debug;
|
||||
12
frontend-react/src/components/SideBarRight/tab/Dice.jsx
Normal file
12
frontend-react/src/components/SideBarRight/tab/Dice.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
const Dice = () => {
|
||||
return (
|
||||
<div className="dice-panel">
|
||||
<h2>骰子面板</h2>
|
||||
<p>这是骰子面板的占位页面</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dice;
|
||||
12
frontend-react/src/components/SideBarRight/tab/Macros.jsx
Normal file
12
frontend-react/src/components/SideBarRight/tab/Macros.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
const Macros = () => {
|
||||
return (
|
||||
<div className="macros-panel">
|
||||
<h2>快捷宏</h2>
|
||||
<p>这是快捷宏面板的占位页面</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Macros;
|
||||
12
frontend-react/src/components/SideBarRight/tab/Table.jsx
Normal file
12
frontend-react/src/components/SideBarRight/tab/Table.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
const Table = () => {
|
||||
return (
|
||||
<div className="table-panel">
|
||||
<h2>动态表格</h2>
|
||||
<p>这是动态表格面板的占位页面</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
Reference in New Issue
Block a user