diff --git a/backend/api/route.py b/backend/api/route.py index d19ee1d..8d8a633 100644 --- a/backend/api/route.py +++ b/backend/api/route.py @@ -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 + diff --git a/backend/core/models/PromptList.py b/backend/core/models/PromptList.py index 8080d64..4d4bd8f 100644 --- a/backend/core/models/PromptList.py +++ b/backend/core/models/PromptList.py @@ -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="是否强制将提示词注入系统层") diff --git a/frontend-react/src/Store/Slices/LeftTabsSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlice.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx new file mode 100644 index 0000000..15ba181 --- /dev/null +++ b/frontend-react/src/Store/Slices/LeftTabsSlices/PresetSlice.jsx @@ -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; diff --git a/frontend-react/src/Store/Slices/SideBarLeftSlice.jsx b/frontend-react/src/Store/Slices/LeftTabsSlices/SideBarLeftSlice.jsx similarity index 100% rename from frontend-react/src/Store/Slices/SideBarLeftSlice.jsx rename to frontend-react/src/Store/Slices/LeftTabsSlices/SideBarLeftSlice.jsx diff --git a/frontend-react/src/Store/Slices/SideBarRightSlice.jsx b/frontend-react/src/Store/Slices/RightTabsSlices/SideBarRightSlice.jsx similarity index 100% rename from frontend-react/src/Store/Slices/SideBarRightSlice.jsx rename to frontend-react/src/Store/Slices/RightTabsSlices/SideBarRightSlice.jsx diff --git a/frontend-react/src/Store/indexStore.jsx b/frontend-react/src/Store/indexStore.jsx index 08e14dd..29412f8 100644 --- a/frontend-react/src/Store/indexStore.jsx +++ b/frontend-react/src/Store/indexStore.jsx @@ -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'; \ No newline at end of file diff --git a/frontend-react/src/components/DicePanel/DicePanel.jsx b/frontend-react/src/components/DicePanel/DicePanel.jsx deleted file mode 100644 index c232e78..0000000 --- a/frontend-react/src/components/DicePanel/DicePanel.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const DicePanel = () => { - return ( -
-
骰子区
-
- ); -}; - -export default DicePanel; diff --git a/frontend-react/src/components/ImageDisplay/ImageDisplay.jsx b/frontend-react/src/components/ImageDisplay/ImageDisplay.jsx deleted file mode 100644 index ab88d3c..0000000 --- a/frontend-react/src/components/ImageDisplay/ImageDisplay.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const ImageDisplay = () => { - return ( -
-
图片展示区
-
- ); -}; - -export default ImageDisplay; diff --git a/frontend-react/src/components/PresetPanel/PresetPanel.css b/frontend-react/src/components/PresetPanel/PresetPanel.css deleted file mode 100644 index 58f50e5..0000000 --- a/frontend-react/src/components/PresetPanel/PresetPanel.css +++ /dev/null @@ -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; -} diff --git a/frontend-react/src/components/PresetPanel/PresetPanel.jsx b/frontend-react/src/components/PresetPanel/PresetPanel.jsx deleted file mode 100644 index 10c068b..0000000 --- a/frontend-react/src/components/PresetPanel/PresetPanel.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import './PresetPanel.css'; - -const PresetPanel = () => { - return ( -
- {/* 顶部:预设选择与输入 */} -
- {/* 下拉框 */} - - {/* 输入框组(待定) */} -
- {/* inputs here */} -
-
- - {/* 下方:大槽位区域 */} -
- {/* 槽位列表 */} -
槽位 1
-
槽位 2
- {/* ... */} -
-
- ); -}; - -export default PresetPanel; diff --git a/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx b/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx index f2cfaa2..f950b4b 100644 --- a/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx +++ b/frontend-react/src/components/SideBarLeft/SideBarLeft.jsx @@ -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'; diff --git a/frontend-react/src/components/SideBarLeft/tab/Presets.jsx b/frontend-react/src/components/SideBarLeft/tab/Presets.jsx index 5b033fb..d44b5b4 100644 --- a/frontend-react/src/components/SideBarLeft/tab/Presets.jsx +++ b/frontend-react/src/components/SideBarLeft/tab/Presets.jsx @@ -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 ( -
-

预设配置

- {/* 在这里实现预设配置的具体内容 */} +
+ {/* 工具提示 */} + {tooltip.visible && ( +
+ {tooltip.content} +
+ )} + + {/* 顶部:预设选择与操作 */} +
+ {/* 预设选择下拉框 */} +
+ + +
+ + {/* 操作按钮 */} +
+ + + + +
+
+ + {/* 保存预设对话框 */} + {showSaveDialog && ( +
+ setNewPresetName(e.target.value)} + placeholder="预设名称" + /> +
+ + +
+
+ )} + + {/* 编辑预设对话框 */} + {showEditDialog && ( +
+ setEditPresetName(e.target.value)} + placeholder="预设名称" + /> +
+ + +
+
+ )} + + {/* 导入预设对话框 */} + {showImportDialog && ( +
+