Add TTS dual-button toggles
This commit is contained in:
@@ -121,6 +121,43 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-toggles {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-toggle:hover {
|
||||||
|
border-color: var(--border-light);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-toggle input[type="checkbox"] {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
accent-color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-toggle span { display: none; }
|
||||||
|
.header-toggle { padding: 6px 8px; }
|
||||||
|
}
|
||||||
|
|
||||||
.header-badge {
|
.header-badge {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1205,6 +1242,16 @@ select.input { cursor: pointer; }
|
|||||||
<div id="badge_trial" class="header-badge active"><i class="fa-solid fa-circle"></i><span>试用</span></div>
|
<div id="badge_trial" class="header-badge active"><i class="fa-solid fa-circle"></i><span>试用</span></div>
|
||||||
<div id="badge_auth" class="header-badge"><i class="fa-solid fa-circle"></i><span>鉴权</span></div>
|
<div id="badge_auth" class="header-badge"><i class="fa-solid fa-circle"></i><span>鉴权</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="header-toggles">
|
||||||
|
<label class="header-toggle" title="消息楼层内显示播放器">
|
||||||
|
<input type="checkbox" id="showFloorButton" checked>
|
||||||
|
<span>楼层</span>
|
||||||
|
</label>
|
||||||
|
<label class="header-toggle" title="屏幕固定位置显示播放器">
|
||||||
|
<input type="checkbox" id="showFloatingButton">
|
||||||
|
<span>悬浮</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="header-spacer"></div>
|
<div class="header-spacer"></div>
|
||||||
<button id="tts_close" class="header-close">✕</button>
|
<button id="tts_close" class="header-close">✕</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2152,6 +2199,9 @@ function fillForm(cfg) {
|
|||||||
$('cacheMaxEntries').value = cfg.volc?.cacheMaxEntries ?? 200;
|
$('cacheMaxEntries').value = cfg.volc?.cacheMaxEntries ?? 200;
|
||||||
$('cacheMaxMB').value = cfg.volc?.cacheMaxMB ?? 200;
|
$('cacheMaxMB').value = cfg.volc?.cacheMaxMB ?? 200;
|
||||||
applyCacheStats(cfg.cacheStats || {});
|
applyCacheStats(cfg.cacheStats || {});
|
||||||
|
|
||||||
|
$('showFloorButton').checked = cfg.showFloorButton !== false;
|
||||||
|
$('showFloatingButton').checked = cfg.showFloatingButton === true;
|
||||||
|
|
||||||
updateApiStatus();
|
updateApiStatus();
|
||||||
renderMyVoiceList();
|
renderMyVoiceList();
|
||||||
@@ -2249,6 +2299,8 @@ window.addEventListener('message', ev => {
|
|||||||
handleSaveResult(false);
|
handleSaveResult(false);
|
||||||
post('xb-tts:toast', { type: 'error', message: payload?.message || '保存失败' });
|
post('xb-tts:toast', { type: 'error', message: payload?.message || '保存失败' });
|
||||||
break;
|
break;
|
||||||
|
case 'xb-tts:button-mode-saved':
|
||||||
|
break;
|
||||||
case 'xb-tts:test-done':
|
case 'xb-tts:test-done':
|
||||||
['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, 'playing', '播放中...'));
|
['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, 'playing', '播放中...'));
|
||||||
setTimeout(() => ['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, '', '')), 3000);
|
setTimeout(() => ['testMyStatus', 'testTrialStatus', 'testAuthStatus'].forEach(id => setTestStatus(id, '', '')), 3000);
|
||||||
@@ -2278,6 +2330,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
$$('.nav-item, .mobile-nav-item').forEach(item => item.addEventListener('click', () => switchView(item.dataset.view)));
|
$$('.nav-item, .mobile-nav-item').forEach(item => item.addEventListener('click', () => switchView(item.dataset.view)));
|
||||||
$('tts_close').addEventListener('click', () => post('xb-tts:close'));
|
$('tts_close').addEventListener('click', () => post('xb-tts:close'));
|
||||||
|
$('showFloorButton').addEventListener('change', () => {
|
||||||
|
post('xb-tts:save-button-mode', {
|
||||||
|
showFloorButton: $('showFloorButton').checked,
|
||||||
|
showFloatingButton: $('showFloatingButton').checked
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('showFloatingButton').addEventListener('change', () => {
|
||||||
|
post('xb-tts:save-button-mode', {
|
||||||
|
showFloorButton: $('showFloorButton').checked,
|
||||||
|
showFloatingButton: $('showFloatingButton').checked
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('toggleKey').addEventListener('click', () => {
|
$('toggleKey').addEventListener('click', () => {
|
||||||
const input = $('accessKey');
|
const input = $('accessKey');
|
||||||
@@ -2404,4 +2468,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -14,9 +14,14 @@ import {
|
|||||||
removeAllTtsPanels,
|
removeAllTtsPanels,
|
||||||
initTtsPanelStyles,
|
initTtsPanelStyles,
|
||||||
setPanelConfigHandlers,
|
setPanelConfigHandlers,
|
||||||
|
clearPanelConfigHandlers,
|
||||||
updateAutoSpeakAll,
|
updateAutoSpeakAll,
|
||||||
updateSpeedAll,
|
updateSpeedAll,
|
||||||
updateVoiceAll
|
updateVoiceAll,
|
||||||
|
initFloatingPanel,
|
||||||
|
destroyFloatingPanel,
|
||||||
|
resetFloatingState,
|
||||||
|
updateButtonVisibility,
|
||||||
} from "./tts-panel.js";
|
} from "./tts-panel.js";
|
||||||
import { getCacheEntry, setCacheEntry, getCacheStats, clearExpiredCache, clearAllCache, pruneCache } from './tts-cache.js';
|
import { getCacheEntry, setCacheEntry, getCacheStats, clearExpiredCache, clearAllCache, pruneCache } from './tts-cache.js';
|
||||||
import { speakMessageFree, clearAllFreeQueues, clearFreeQueueForMessage } from './tts-free-provider.js';
|
import { speakMessageFree, clearAllFreeQueues, clearFreeQueueForMessage } from './tts-free-provider.js';
|
||||||
@@ -890,6 +895,7 @@ function onChatChanged() {
|
|||||||
if (player) player.clear();
|
if (player) player.clear();
|
||||||
messageStateMap.clear();
|
messageStateMap.clear();
|
||||||
removeAllTtsPanels();
|
removeAllTtsPanels();
|
||||||
|
resetFloatingState();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
renderExistingMessageUIs();
|
renderExistingMessageUIs();
|
||||||
@@ -929,6 +935,8 @@ async function loadConfig() {
|
|||||||
config.skipRanges = Array.isArray(config.skipRanges) ? config.skipRanges : [];
|
config.skipRanges = Array.isArray(config.skipRanges) ? config.skipRanges : [];
|
||||||
config.readRanges = Array.isArray(config.readRanges) ? config.readRanges : [];
|
config.readRanges = Array.isArray(config.readRanges) ? config.readRanges : [];
|
||||||
config.readRangesEnabled = config.readRangesEnabled === true;
|
config.readRangesEnabled = config.readRangesEnabled === true;
|
||||||
|
config.showFloorButton = config.showFloorButton !== false;
|
||||||
|
config.showFloatingButton = config.showFloatingButton === true;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -942,6 +950,8 @@ async function saveConfig(updates) {
|
|||||||
await TtsStorage.set('readRangesEnabled', config.readRangesEnabled === true);
|
await TtsStorage.set('readRangesEnabled', config.readRangesEnabled === true);
|
||||||
await TtsStorage.set('skipTags', config.skipTags);
|
await TtsStorage.set('skipTags', config.skipTags);
|
||||||
await TtsStorage.set('skipCodeBlocks', config.skipCodeBlocks);
|
await TtsStorage.set('skipCodeBlocks', config.skipCodeBlocks);
|
||||||
|
await TtsStorage.set('showFloorButton', config.showFloorButton);
|
||||||
|
await TtsStorage.set('showFloatingButton', config.showFloatingButton);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await TtsStorage.saveNow({ silent: false });
|
return await TtsStorage.saveNow({ silent: false });
|
||||||
@@ -1051,6 +1061,20 @@ async function handleIframeMessage(ev) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'xb-tts:save-button-mode': {
|
||||||
|
const { showFloorButton, showFloatingButton } = payload;
|
||||||
|
config.showFloorButton = showFloorButton;
|
||||||
|
config.showFloatingButton = showFloatingButton;
|
||||||
|
const ok = await saveConfig({ showFloorButton, showFloatingButton });
|
||||||
|
if (ok) {
|
||||||
|
updateButtonVisibility(showFloorButton, showFloatingButton);
|
||||||
|
if (showFloorButton) {
|
||||||
|
renderExistingMessageUIs();
|
||||||
|
}
|
||||||
|
postToIframe(iframe, { type: 'xb-tts:button-mode-saved' });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'xb-tts:toast':
|
case 'xb-tts:toast':
|
||||||
if (payload.type === 'error') toastr.error(payload.message);
|
if (payload.type === 'error') toastr.error(payload.message);
|
||||||
else if (payload.type === 'success') toastr.success(payload.message);
|
else if (payload.type === 'success') toastr.success(payload.message);
|
||||||
@@ -1160,8 +1184,19 @@ export async function initTts() {
|
|||||||
state.error = '';
|
state.error = '';
|
||||||
updateTtsPanel(messageId, state);
|
updateTtsPanel(messageId, state);
|
||||||
},
|
},
|
||||||
|
getLastAIMessageId: () => {
|
||||||
|
const context = getContext();
|
||||||
|
const chat = context.chat || [];
|
||||||
|
for (let i = chat.length - 1; i >= 0; i--) {
|
||||||
|
if (chat[i] && !chat[i].is_user) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
speakMessage: (messageId) => handleMessagePlayClick(messageId),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initFloatingPanel();
|
||||||
|
|
||||||
player.onStateChange = (state, item, info) => {
|
player.onStateChange = (state, item, info) => {
|
||||||
if (!isModuleEnabled()) return;
|
if (!isModuleEnabled()) return;
|
||||||
const messageId = item?.messageId;
|
const messageId = item?.messageId;
|
||||||
@@ -1322,10 +1357,9 @@ export function cleanupTts() {
|
|||||||
|
|
||||||
closeSettings();
|
closeSettings();
|
||||||
removeAllTtsPanels();
|
removeAllTtsPanels();
|
||||||
|
destroyFloatingPanel();
|
||||||
|
|
||||||
try {
|
clearPanelConfigHandlers();
|
||||||
import('./tts-panel.js').then(m => m.clearPanelConfigHandlers?.());
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
messageStateMap.clear();
|
messageStateMap.clear();
|
||||||
cacheCounters.hits = 0;
|
cacheCounters.hits = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user