完成请求推送,但组装mes还有问题

This commit is contained in:
2026-05-04 00:33:29 +08:00
parent 7fc9e10c99
commit 2050a30a52
93 changed files with 9499 additions and 2719 deletions

View File

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

View File

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

View File

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