完成请求推送,但组装mes还有问题
This commit is contained in:
@@ -330,3 +330,160 @@
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==================== 设置面板专用样式 ==================== */
|
||||
|
||||
.settings-panel .panel-body {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.settings-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.settings-tabs .tab-button {
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.settings-tabs .tab-button:hover {
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.settings-tabs .tab-button.active {
|
||||
color: white;
|
||||
border-bottom-color: var(--color-accent);
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid #4caf50;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
border: 1px solid #f44336;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 10px 20px;
|
||||
background: var(--color-accent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--color-accent-hover, #0056b3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 设置面板输入框 */
|
||||
.settings-input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.settings-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.settings-input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* 设置面板代码块 */
|
||||
.settings-code {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
color: var(--color-accent);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 设置面板文件列表 */
|
||||
.settings-file-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
list-style: none;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.settings-file-list li {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
/* 设置面板提示文字 */
|
||||
.settings-hint {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* 设置面板预览框 */
|
||||
.preview-box {
|
||||
padding: 12px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,16 @@ const Toolbar = () => {
|
||||
const { activeMap, fetchProfile } = useApiConfigStore();
|
||||
const [coreModel, setCoreModel] = useState('未设置');
|
||||
const [assistModel, setAssistModel] = useState('未设置');
|
||||
|
||||
// 系统设置状态
|
||||
const [settings, setSettings] = useState({
|
||||
thinkingTagPrefix: '<thinking>',
|
||||
thinkingTagSuffix: '</thinking>',
|
||||
currentPresetName: null
|
||||
});
|
||||
const [settingsLoading, setSettingsLoading] = useState(false);
|
||||
const [settingsMessage, setSettingsMessage] = useState({ type: '', text: '' });
|
||||
const [settingsActiveTab, setSettingsActiveTab] = useState('display'); // 默认显示分页
|
||||
|
||||
// 点击外部关闭面板 - 使用 useCallback 优化
|
||||
const handleClickOutside = React.useCallback((event) => {
|
||||
@@ -43,6 +53,68 @@ const Toolbar = () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [handleClickOutside]);
|
||||
|
||||
// 加载系统设置
|
||||
useEffect(() => {
|
||||
if (activePanel === 'settings') {
|
||||
loadSettings();
|
||||
}
|
||||
}, [activePanel]);
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setSettingsLoading(true);
|
||||
const response = await fetch('/api/regex/settings');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setSettings(data.settings);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载系统设置失败:', error);
|
||||
showSettingsMessage('error', '加载设置失败');
|
||||
} finally {
|
||||
setSettingsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveSettings = async () => {
|
||||
try {
|
||||
setSettingsLoading(true);
|
||||
const response = await fetch('/api/regex/settings', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showSettingsMessage('success', '设置已保存');
|
||||
} else {
|
||||
showSettingsMessage('error', '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存设置失败:', error);
|
||||
showSettingsMessage('error', '保存失败');
|
||||
} finally {
|
||||
setSettingsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const showSettingsMessage = (type, text) => {
|
||||
setSettingsMessage({ type, text });
|
||||
setTimeout(() => setSettingsMessage({ type: '', text: '' }), 3000);
|
||||
};
|
||||
|
||||
const handleSettingsInputChange = (field, value) => {
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
[field]: value
|
||||
}));
|
||||
};
|
||||
|
||||
// ✅ 简化:直接调用 store 方法
|
||||
const handleSidebarModeChange = (mode) => {
|
||||
@@ -170,55 +242,231 @@ const Toolbar = () => {
|
||||
{/* 设置面板 - 使用条件渲染优化 */}
|
||||
{activePanel === 'settings' && (
|
||||
<div className="panel-overlay" ref={panelRef}>
|
||||
<div className="panel-content">
|
||||
<div className="panel-content settings-panel">
|
||||
<div className="panel-header">
|
||||
<h3>系统设置</h3>
|
||||
<button className="close-panel-button" onClick={handleClosePanel} title="关闭">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
{/* 左侧栏模式 */}
|
||||
<div className="setting-section">
|
||||
<h4>左侧栏模式</h4>
|
||||
<div className="setting-options">
|
||||
<label className={`setting-option ${sidebarMode === 'fixed' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="fixed"
|
||||
checked={sidebarMode === 'fixed'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)} // ✅ 使用 store 方法
|
||||
/>
|
||||
<span>固定</span>
|
||||
</label>
|
||||
<label className={`setting-option ${sidebarMode === 'smart' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="smart"
|
||||
checked={sidebarMode === 'smart'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)} // ✅ 使用 store 方法
|
||||
/>
|
||||
<span>智能</span>
|
||||
</label>
|
||||
<label className={`setting-option ${sidebarMode === 'expanded' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="expanded"
|
||||
checked={sidebarMode === 'expanded'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)} // ✅ 使用 store 方法
|
||||
/>
|
||||
<span>扩展</span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="setting-description">
|
||||
{sidebarMode === 'fixed' && '左侧栏保持默认宽度,不响应交互'}
|
||||
{sidebarMode === 'smart' && '鼠标悬停时自动展开,移开后收起'}
|
||||
{sidebarMode === 'expanded' && '左侧栏保持最大宽度'}
|
||||
</p>
|
||||
|
||||
{/* 消息提示 */}
|
||||
{settingsMessage.text && (
|
||||
<div className={`message ${settingsMessage.type}`} style={{ margin: '10px', padding: '8px', borderRadius: '4px' }}>
|
||||
{settingsMessage.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分页标签 */}
|
||||
<div className="settings-tabs">
|
||||
<button
|
||||
className={`tab-button ${settingsActiveTab === 'display' ? 'active' : ''}`}
|
||||
onClick={() => setSettingsActiveTab('display')}
|
||||
>
|
||||
显示
|
||||
</button>
|
||||
<button
|
||||
className={`tab-button ${settingsActiveTab === 'formatting' ? 'active' : ''}`}
|
||||
onClick={() => setSettingsActiveTab('formatting')}
|
||||
>
|
||||
格式化
|
||||
</button>
|
||||
<button
|
||||
className={`tab-button ${settingsActiveTab === 'user' ? 'active' : ''}`}
|
||||
onClick={() => setSettingsActiveTab('user')}
|
||||
>
|
||||
用户
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="panel-body" style={{ padding: '16px' }}>
|
||||
{settingsLoading && <div className="loading">加载中...</div>}
|
||||
|
||||
{/* 显示设置 */}
|
||||
{settingsActiveTab === 'display' && (
|
||||
<div className="settings-section">
|
||||
<div className="setting-section">
|
||||
<h4>左侧栏模式</h4>
|
||||
<div className="setting-options">
|
||||
<label className={`setting-option ${sidebarMode === 'fixed' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="fixed"
|
||||
checked={sidebarMode === 'fixed'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)}
|
||||
/>
|
||||
<span>固定</span>
|
||||
</label>
|
||||
<label className={`setting-option ${sidebarMode === 'smart' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="smart"
|
||||
checked={sidebarMode === 'smart'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)}
|
||||
/>
|
||||
<span>智能</span>
|
||||
</label>
|
||||
<label className={`setting-option ${sidebarMode === 'expanded' ? 'active' : ''}`}>
|
||||
<input
|
||||
type="radio"
|
||||
name="sidebarMode"
|
||||
value="expanded"
|
||||
checked={sidebarMode === 'expanded'}
|
||||
onChange={(e) => handleSidebarModeChange(e.target.value)}
|
||||
/>
|
||||
<span>扩展</span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="setting-description">
|
||||
{sidebarMode === 'fixed' && '左侧栏保持默认宽度,不响应交互'}
|
||||
{sidebarMode === 'smart' && '鼠标悬停时自动展开,移开后收起'}
|
||||
{sidebarMode === 'expanded' && '左侧栏保持最大宽度'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 格式化设置 */}
|
||||
{settingsActiveTab === 'formatting' && (
|
||||
<div className="settings-section">
|
||||
<div className="setting-section">
|
||||
<h4>思考标签配置</h4>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500', color: 'var(--color-text-primary)' }}>前缀</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.thinkingTagPrefix}
|
||||
onChange={(e) => handleSettingsInputChange('thinkingTagPrefix', e.target.value)}
|
||||
placeholder="<thinking>"
|
||||
className="settings-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500', color: 'var(--color-text-primary)' }}>后缀</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.thinkingTagSuffix}
|
||||
onChange={(e) => handleSettingsInputChange('thinkingTagSuffix', e.target.value)}
|
||||
placeholder="</thinking>"
|
||||
className="settings-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="preview-box">
|
||||
<div className="original" style={{ marginBottom: '12px' }}>
|
||||
<strong style={{ display: 'block', marginBottom: '4px', fontSize: '12px', color: 'var(--color-text-secondary)' }}>原始内容:</strong>
|
||||
<p style={{ margin: 0, padding: '8px', background: 'rgba(0, 0, 0, 0.2)', borderRadius: '3px', fontFamily: 'Courier New, monospace', fontSize: '12px', color: 'var(--color-text-primary)' }}>
|
||||
这是回复 {settings.thinkingTagPrefix}思考过程{settings.thinkingTagSuffix} 继续
|
||||
</p>
|
||||
</div>
|
||||
<div className="processed">
|
||||
<strong style={{ display: 'block', marginBottom: '4px', fontSize: '12px', color: 'var(--color-text-secondary)' }}>处理后:</strong>
|
||||
<p style={{ margin: 0, padding: '8px', background: 'rgba(0, 0, 0, 0.2)', borderRadius: '3px', fontFamily: 'Courier New, monospace', fontSize: '12px', color: 'var(--color-text-primary)' }}>
|
||||
这是回复 继续
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="setting-section" style={{ marginTop: '20px', paddingTop: '20px', borderTop: '1px solid var(--border-color)' }}>
|
||||
<h4>正则规则管理</h4>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<code className="settings-code">
|
||||
data/regex/
|
||||
</code>
|
||||
<ul className="settings-file-list">
|
||||
<li>📁 global/ - 全局规则</li>
|
||||
<li>📁 characters/ - 角色卡规则</li>
|
||||
<li>📁 presets/ - 预设规则</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', fontWeight: '500', color: 'var(--color-text-primary)' }}>导入规则文件</label>
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/regex/import', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showSettingsMessage('success', data.message);
|
||||
} else {
|
||||
showSettingsMessage('error', '导入失败');
|
||||
}
|
||||
} catch (error) {
|
||||
showSettingsMessage('error', '导入失败');
|
||||
}
|
||||
}}
|
||||
className="settings-input"
|
||||
/>
|
||||
<small className="settings-hint">
|
||||
支持 SillyTavern 格式的规则文件
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={() => window.open('/api/regex/export/global', '_blank')}
|
||||
>
|
||||
导出全局规则
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={saveSettings}
|
||||
disabled={settingsLoading}
|
||||
>
|
||||
{settingsLoading ? '保存中...' : '保存设置'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 用户设置 */}
|
||||
{settingsActiveTab === 'user' && (
|
||||
<div className="settings-section">
|
||||
<div className="setting-section">
|
||||
<h4>当前预设</h4>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.currentPresetName || ''}
|
||||
onChange={(e) => handleSettingsInputChange('currentPresetName', e.target.value || null)}
|
||||
placeholder="未选择"
|
||||
className="settings-input"
|
||||
/>
|
||||
<small className="settings-hint">
|
||||
影响全局正则规则的启用
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<button
|
||||
className="btn-primary"
|
||||
onClick={saveSettings}
|
||||
disabled={settingsLoading}
|
||||
>
|
||||
{settingsLoading ? '保存中...' : '保存设置'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import useAppLayoutStore from '../../../../Store/AppLayoutSlice';
|
||||
import './ThemeToggle.css';
|
||||
|
||||
const themes = [
|
||||
@@ -50,19 +51,16 @@ const themes = [
|
||||
|
||||
const ThemeToggle = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [currentTheme, setCurrentTheme] = useState(() => {
|
||||
return localStorage.getItem('colorTheme') || 'dark';
|
||||
});
|
||||
const panelRef = useRef(null);
|
||||
|
||||
// ✅ 从 Zustand store 获取主题状态和方法
|
||||
const { colorTheme, setColorTheme } = useAppLayoutStore();
|
||||
|
||||
// ✅ 当 store 中的主题变化时,同步到 DOM
|
||||
useEffect(() => {
|
||||
// 应用主题到 document
|
||||
document.documentElement.setAttribute('data-theme', currentTheme);
|
||||
document.documentElement.setAttribute('data-color-theme', currentTheme);
|
||||
// 保存到 localStorage
|
||||
localStorage.setItem('theme', currentTheme);
|
||||
localStorage.setItem('colorTheme', currentTheme);
|
||||
}, [currentTheme]);
|
||||
document.documentElement.setAttribute('data-theme', colorTheme);
|
||||
document.documentElement.setAttribute('data-color-theme', colorTheme);
|
||||
}, [colorTheme]);
|
||||
|
||||
// 点击外部关闭面板
|
||||
useEffect(() => {
|
||||
@@ -82,11 +80,11 @@ const ThemeToggle = () => {
|
||||
}, [isOpen]);
|
||||
|
||||
const handleThemeSelect = (themeId) => {
|
||||
setCurrentTheme(themeId);
|
||||
setColorTheme(themeId); // ✅ 使用 store 的方法
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const currentThemeData = themes.find(t => t.id === currentTheme) || themes[2];
|
||||
const currentThemeData = themes.find(t => t.id === colorTheme) || themes[2];
|
||||
|
||||
return (
|
||||
<div className="theme-toggle-wrapper" ref={panelRef}>
|
||||
@@ -108,7 +106,7 @@ const ThemeToggle = () => {
|
||||
{themes.map(theme => (
|
||||
<button
|
||||
key={theme.id}
|
||||
className={`theme-item ${currentTheme === theme.id ? 'active' : ''}`}
|
||||
className={`theme-item ${colorTheme === theme.id ? 'active' : ''}`}
|
||||
onClick={() => handleThemeSelect(theme.id)}
|
||||
>
|
||||
<div className="theme-item-icon">{theme.icon}</div>
|
||||
|
||||
Reference in New Issue
Block a user