openvibers/openvibe.live Commits

Chat tools parity: vibes/points/TTS/subscribe on offline chat + popout

OP OpenVibers committed 26 Aug 2026 · 9ef6c8b090 · 4 files, +39 −7

The vibes and channel-points buttons weren't removed — they only ever
existed on the LIVE chat's tools row, so the offline channel chat (and the
popout) looked stripped. Offline chat now carries the full row (TTS,
channel points with balance, send Vibes, subscribe) plus its own rewards
panel; the popout gains subscribe and the points balance. The underlying
helpers stop requiring a live stream: rewards + balances resolve the
channel owner from channel context, and subscribe falls back to the
channel identity.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017AyeSFu7qdLdR64Zv99QNm

Changes

ddiff --git a/public/index.html b/public/index.html
iindex eda25e24..51de4328 100644
---- a/public/index.html
++++ b/public/index.html
@@@ -977,6 +977,28 @@
<button class="emote-picker-btn" onclick="openChannelUploadModal()" title="Add channel emote / sound">
<i class="fa-solid fa-plus"></i>
</button>
++ <button class="chat-tts-toggle tts-active" onclick="toggleChatTTS(this)" title="TTS On (click to mute)">
++ <i class="fa-solid fa-volume-high"></i>
++ </button>
++ <button class="btn btn-openvibe-coins-mini chat-nickels-float" onclick="toggleRewardsPanel()" title="OpenCoins — this channel's points (click for rewards)">
++ <i class="fa-solid fa-coins"></i> <span class="rewards-coin-balance">0</span>
++ </button>
++ <button class="btn btn-openvibe-bucks-mini" onclick="showModal('donate')" title="Send Vibes">
++ <i class="fa-solid fa-dollar-sign"></i>
++ </button>
++ <button class="btn btn-openvibe-bucks-mini" onclick="openSubscribeForCurrentStream()" title="Subscribe to this channel">
++ <i class="fa-solid fa-star"></i>
++ </button>
++ </div>
++ <!-- Rewards Panel (channel points) — offline chat's own instance -->
++ <div class="rewards-panel" style="display:none">
++ <div class="rewards-panel-header">
++ <span><i class="fa-solid fa-coins rewards-cp-icon"></i> <span class="rewards-cp-label">Channel Points</span> Rewards</span>
++ <span class="rewards-coin-bal"><i class="fa-solid fa-coins"></i> <span class="rewards-coin-balance">0</span></span>
++ </div>
++ <div class="rewards-grid ch-rewards-grid">
++ <p class="muted">No rewards configured for this channel</p>
++ </div>
</div>
</div>
</div>
ddiff --git a/public/js/opencoins.js b/public/js/opencoins.js
iindex 8e9e8bc3..2c5361dc 100644
---- a/public/js/opencoins.js
++++ b/public/js/opencoins.js
@@@ -115,9 +115,13 @@ function toggleRewardsPanel() {
}
async function loadRewardsPanel() {
-- if (!currentStreamData) {
++ // Channel points are per-CHANNEL, not per live session — resolve the owner from
++ // the live stream OR the channel context (offline channel page / popout chat).
++ const ownerId = (typeof currentStreamData !== 'undefined' && currentStreamData && currentStreamData.user_id)
++ || (typeof _activeChannelUserId !== 'undefined' && _activeChannelUserId) || null;
++ if (!ownerId) {
document.querySelectorAll('.rewards-grid').forEach(g => {
-- g.innerHTML = '<p class="muted" style="padding:8px">Join a stream to see rewards</p>';
++ g.innerHTML = '<p class="muted" style="padding:8px">Join a channel to see rewards</p>';
});
return;
}
@@@ -125,7 +129,7 @@ async function loadRewardsPanel() {
if (!grids.length) return;
try {
-- const data = await api(`/coins/rewards/${currentStreamData.user_id}`);
++ const data = await api(`/coins/rewards/${ownerId}`);
const rewards = data.rewards || [];
_applyChannelPointsBranding(data.config);
@@@ -152,7 +156,7 @@ async function loadRewardsPanel() {
// Update this channel's points balance in all reward panels
try {
-- const coinData = await api(`/coins/channel-balance?streamerId=${currentStreamData.user_id}`);
++ const coinData = await api(`/coins/channel-balance?streamerId=${ownerId}`);
document.querySelectorAll('.rewards-coin-balance').forEach(el => {
el.textContent = (coinData.balance || 0).toLocaleString();
});
ddiff --git a/public/js/vibes.js b/public/js/vibes.js
iindex 99fbe8d4..8e9e97b6 100644
---- a/public/js/vibes.js
++++ b/public/js/vibes.js
@@@ -171,7 +171,10 @@ function showSubscribeModal(username) {
function openSubscribeForCurrentStream() {
const d = (typeof currentStreamData !== 'undefined' && currentStreamData) || {};
-- const username = d.username || d.user_username || d.streamer_username || d.channel_username || d.owner_username;
++ const username = d.username || d.user_username || d.streamer_username || d.channel_username || d.owner_username
++ // Offline channel page / popout chat — the channel identity lives here instead.
++ || (typeof currentChannelUsername !== 'undefined' && currentChannelUsername)
++ || (typeof chatChannel !== 'undefined' && chatChannel) || null;
if (!username) return toast('No channel selected', 'error');
showSubscribeModal(username);
}
ddiff --git a/public/popout-chat.html b/public/popout-chat.html
iindex 46a643ca..3dc57529 100644
---- a/public/popout-chat.html
++++ b/public/popout-chat.html
@@@ -171,12 +171,15 @@
<button class="chat-tts-toggle tts-active" id="pc-tts-btn" onclick="toggleChatTTS(this)" title="TTS On (click to mute)">
<i class="fa-solid fa-volume-high"></i>
</button>
-- <button class="btn btn-openvibe-coins-mini" onclick="if(typeof toggleRewardsPanel==='function')toggleRewardsPanel()" title="Redeem OpenCoins">
-- <i class="fa-solid fa-coins"></i>
++ <button class="btn btn-openvibe-coins-mini chat-nickels-float" onclick="if(typeof toggleRewardsPanel==='function')toggleRewardsPanel()" title="Channel points (click for rewards)">
++ <i class="fa-solid fa-coins"></i> <span class="rewards-coin-balance">0</span>
</button>
<button class="btn btn-openvibe-bucks-mini" onclick="if(typeof showModal==='function')showModal('donate')" title="Send Vibes">
<i class="fa-solid fa-dollar-sign"></i>
</button>
++ <button class="btn btn-openvibe-bucks-mini" onclick="if(typeof openSubscribeForCurrentStream==='function')openSubscribeForCurrentStream()" title="Subscribe to this channel">
++ <i class="fa-solid fa-star"></i>
++ </button>
</div>
<!-- Rewards Panel (OpenCoins) -->
<div class="rewards-panel" id="ch-rewards-panel" style="display:none">