Compare commits
3 Commits
v0.9.5-bet
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b40bc291ba | ||
|
|
1216c80118 | ||
|
|
eda493856a |
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## v0.9.6
|
||||||
|
|
||||||
|
### Changes since v0.9.5
|
||||||
|
|
||||||
|
- feat(ext): add session inject button and auto-inject toggle in sidepanel Configuration tab
|
||||||
|
- feat(ext): replace interactive Chat tab with live read-only Studio feed mirror
|
||||||
|
- chore(ext): bump extension to v0.9.6
|
||||||
|
|
||||||
## v0.9.5
|
## v0.9.5
|
||||||
|
|
||||||
### Changes since v0.9.4
|
### Changes since v0.9.4
|
||||||
|
|||||||
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "muyue-extension",
|
"name": "muyue-extension",
|
||||||
"version": "0.1.0",
|
"version": "0.9.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "muyue-extension",
|
"name": "muyue-extension",
|
||||||
"version": "0.1.0",
|
"version": "0.9.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wxt": "^0.20"
|
"wxt": "^0.20"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "muyue-extension",
|
"name": "muyue-extension",
|
||||||
"version": "0.9.0",
|
"version": "0.9.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { dispatch } from '../lib/page-rpc';
|
|||||||
export default defineContentScript({
|
export default defineContentScript({
|
||||||
matches: ['http://*/*', 'https://*/*'],
|
matches: ['http://*/*', 'https://*/*'],
|
||||||
runAt: 'document_idle',
|
runAt: 'document_idle',
|
||||||
main() {
|
async main() {
|
||||||
|
const autoInjectResult = await chrome.storage.local.get('muyue_auto_inject');
|
||||||
|
if (!autoInjectResult.muyue_auto_inject) return;
|
||||||
if (window.__muyueExtension) return;
|
if (window.__muyueExtension) return;
|
||||||
window.__muyueExtension = true;
|
window.__muyueExtension = true;
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<span>Muyue</span> extension v0.9.0
|
<span>Muyue</span> extension v0.9.6
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,26 @@
|
|||||||
<a id="btn-dashboard" href="#" class="btn btn-primary" target="_blank">
|
<a id="btn-dashboard" href="#" class="btn btn-primary" target="_blank">
|
||||||
Open Dashboard
|
Open Dashboard
|
||||||
</a>
|
</a>
|
||||||
|
<button id="btn-inject-session" class="btn">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
|
||||||
|
</svg>
|
||||||
|
Injecter le script de session
|
||||||
|
</button>
|
||||||
|
<div class="inject-status" id="inject-status" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-section">
|
||||||
|
<div class="toggle-row">
|
||||||
|
<span class="toggle-label">Auto-injection</span>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="toggle-auto-inject" />
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-desc">
|
||||||
|
Injecte automatiquement le script de session dans chaque page visitée
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
@@ -59,28 +79,16 @@
|
|||||||
<span>Server offline</span>
|
<span>Server offline</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="chat-area" class="studio-feed-layout" style="display:none">
|
<div id="chat-area" class="studio-feed-layout" style="display:none">
|
||||||
<div id="chat-feed" class="studio-feed"></div>
|
<div class="chat-live-header">
|
||||||
<div class="studio-input-area">
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="var(--accent)"><circle cx="12" cy="12" r="5"/></svg>
|
||||||
<div class="studio-input-row">
|
<span>Live — Studio</span>
|
||||||
<textarea id="chat-input" placeholder="Envoyer un message…" rows="1"></textarea>
|
|
||||||
<button id="chat-send" class="studio-send-btn">
|
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button id="chat-stop" class="studio-stop-btn" style="display:none">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="studio-input-hint">/clear /help</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="chat-feed" class="studio-feed"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<span>Muyue</span> extension v0.9.0
|
<span>Muyue</span> extension v0.9.6
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import '../../styles/panel.css';
|
import '../../styles/panel.css';
|
||||||
import { getServerUrl, setServerUrl, fetchSessions, checkServerHealth } from '../../lib/config';
|
import { getServerUrl, setServerUrl, fetchSessions, checkServerHealth } from '../../lib/config';
|
||||||
import { getChatHistory, sendChat, clearChat } from '../../lib/api';
|
import { getChatHistory } from '../../lib/api';
|
||||||
|
|
||||||
const $ = (s) => document.querySelector(s);
|
const $ = (s) => document.querySelector(s);
|
||||||
const $$ = (s) => document.querySelectorAll(s);
|
const $$ = (s) => document.querySelectorAll(s);
|
||||||
@@ -15,16 +15,13 @@ const $btnSaveUrl = $('#btn-save-url');
|
|||||||
const $chatOffline = $('#chat-offline');
|
const $chatOffline = $('#chat-offline');
|
||||||
const $chatArea = $('#chat-area');
|
const $chatArea = $('#chat-area');
|
||||||
const $chatFeed = $('#chat-feed');
|
const $chatFeed = $('#chat-feed');
|
||||||
const $chatStreaming = $('#chat-streaming');
|
const $btnInject = $('#btn-inject-session');
|
||||||
const $chatInput = $('#chat-input');
|
const $injectStatus = $('#inject-status');
|
||||||
const $chatSend = $('#chat-send');
|
const $toggleAutoInject = $('#toggle-auto-inject');
|
||||||
const $chatStop = $('#chat-stop');
|
|
||||||
|
|
||||||
let serverOnline = false;
|
let serverOnline = false;
|
||||||
let messages = [];
|
let lastMessageCount = -1;
|
||||||
let loading = false;
|
let pollInterval = null;
|
||||||
let abortController = null;
|
|
||||||
let currentStreamingEl = null;
|
|
||||||
|
|
||||||
function dot(color) {
|
function dot(color) {
|
||||||
return `<span class="dot dot-${color}"></span>`;
|
return `<span class="dot dot-${color}"></span>`;
|
||||||
@@ -54,6 +51,10 @@ function renderSessions(sessions) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
function formatText(text) {
|
function formatText(text) {
|
||||||
let html = text
|
let html = text
|
||||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
@@ -95,10 +96,6 @@ function renderContent(text) {
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
|
||||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMessageEl(msg) {
|
function createMessageEl(msg) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = `chat-msg ${msg.role}`;
|
el.className = `chat-msg ${msg.role}`;
|
||||||
@@ -188,14 +185,6 @@ function createMessageEl(msg) {
|
|||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMessages() {
|
|
||||||
$chatFeed.innerHTML = '';
|
|
||||||
messages.forEach((msg) => {
|
|
||||||
$chatFeed.appendChild(createMessageEl(msg));
|
|
||||||
});
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
$chatFeed.scrollTop = $chatFeed.scrollHeight;
|
$chatFeed.scrollTop = $chatFeed.scrollHeight;
|
||||||
@@ -205,6 +194,9 @@ function scrollToBottom() {
|
|||||||
function switchTab(tabName) {
|
function switchTab(tabName) {
|
||||||
$$('.tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === tabName));
|
$$('.tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === tabName));
|
||||||
$$('.tab-content').forEach((s) => s.classList.toggle('active', s.id === `tab-${tabName}`));
|
$$('.tab-content').forEach((s) => s.classList.toggle('active', s.id === `tab-${tabName}`));
|
||||||
|
if (tabName === 'chat' && serverOnline) {
|
||||||
|
pollStudio();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateChatVisibility() {
|
function updateChatVisibility() {
|
||||||
@@ -217,187 +209,82 @@ function updateChatVisibility() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadChatHistory() {
|
async function pollStudio() {
|
||||||
try {
|
try {
|
||||||
const data = await getChatHistory();
|
const data = await getChatHistory();
|
||||||
if (data.messages && data.messages.length > 0) {
|
const msgs = data.messages || [];
|
||||||
messages = data.messages;
|
if (msgs.length !== lastMessageCount) {
|
||||||
} else {
|
lastMessageCount = msgs.length;
|
||||||
messages = [{ id: 'welcome', role: 'system', content: 'Ready. Type a message to start.' }];
|
$chatFeed.innerHTML = '';
|
||||||
|
msgs.forEach((msg) => {
|
||||||
|
$chatFeed.appendChild(createMessageEl(msg));
|
||||||
|
});
|
||||||
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
renderMessages();
|
} catch {}
|
||||||
} catch {
|
|
||||||
messages = [{ id: 'welcome', role: 'system', content: 'Ready. Type a message to start.' }];
|
|
||||||
renderMessages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSend() {
|
function injectSessionScript() {
|
||||||
const text = $chatInput.value.trim();
|
$injectStatus.style.display = 'block';
|
||||||
if (!text || loading) return;
|
$injectStatus.className = 'inject-status';
|
||||||
|
$injectStatus.textContent = 'Injection en cours…';
|
||||||
|
|
||||||
if (text === '/clear') {
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||||
try { await clearChat(); } catch {}
|
if (!tabs || !tabs[0]) {
|
||||||
messages = [{ id: 'clear-' + Date.now(), role: 'system', content: 'Conversation cleared.' }];
|
$injectStatus.textContent = 'Erreur: aucun onglet actif';
|
||||||
renderMessages();
|
$injectStatus.classList.add('inject-error');
|
||||||
$chatInput.value = '';
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$chatInput.value = '';
|
|
||||||
$chatInput.style.height = 'auto';
|
|
||||||
|
|
||||||
const userMsg = { id: Date.now().toString(), role: 'user', content: text };
|
|
||||||
messages.push(userMsg);
|
|
||||||
$chatFeed.appendChild(createMessageEl(userMsg));
|
|
||||||
scrollToBottom();
|
|
||||||
|
|
||||||
loading = true;
|
|
||||||
$chatSend.style.display = 'none';
|
|
||||||
$chatStop.style.display = 'flex';
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
abortController = controller;
|
|
||||||
|
|
||||||
let segments = [];
|
|
||||||
let thinking = '';
|
|
||||||
let textStartIdx = 0;
|
|
||||||
let streamText = '';
|
|
||||||
|
|
||||||
const updateLastText = (text) => {
|
|
||||||
if (!text) return;
|
|
||||||
const last = segments.length > 0 ? segments[segments.length - 1] : null;
|
|
||||||
if (last && last.type === 'text') {
|
|
||||||
last.content = text;
|
|
||||||
} else {
|
|
||||||
segments.push({ type: 'text', content: text });
|
|
||||||
}
|
}
|
||||||
};
|
const tabId = tabs[0].id;
|
||||||
|
|
||||||
currentStreamingEl = document.createElement('div');
|
chrome.scripting.executeScript({
|
||||||
currentStreamingEl.className = 'chat-msg assistant streaming';
|
target: { tabId },
|
||||||
$chatFeed.appendChild(currentStreamingEl);
|
func: () => {
|
||||||
scrollToBottom();
|
if (window.__muyueExtension) {
|
||||||
|
return 'already_injected';
|
||||||
try {
|
}
|
||||||
const finalContent = await sendChat(text, true, (partial, event) => {
|
window.__muyueExtension = true;
|
||||||
if (event && (event.thinking !== undefined || event.thinking_start || event.thinking_end)) {
|
return 'fresh_inject';
|
||||||
if (event.thinking !== undefined) thinking += event.thinking;
|
},
|
||||||
|
}, (results) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
$injectStatus.textContent = 'Erreur: ' + chrome.runtime.lastError.message;
|
||||||
|
$injectStatus.classList.add('inject-error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event && event.tool_call) {
|
|
||||||
updateLastText(partial.slice(textStartIdx));
|
const status = results?.[0]?.result;
|
||||||
textStartIdx = partial.length;
|
|
||||||
segments.push({ type: 'tool', call: event.tool_call, result: null });
|
if (status === 'already_injected') {
|
||||||
} else if (event && event.tool_result) {
|
$injectStatus.textContent = '✓ Script déjà injecté dans cette page';
|
||||||
const segIdx = segments.findIndex((s) => s.type === 'tool' && s.call && s.call.tool_call_id === event.tool_result.tool_call_id);
|
$injectStatus.classList.add('inject-success');
|
||||||
if (segIdx >= 0) segments[segIdx].result = event.tool_result;
|
|
||||||
} else {
|
} else {
|
||||||
updateLastText(partial.slice(textStartIdx));
|
chrome.tabs.reload(tabId, {}, () => {
|
||||||
}
|
$injectStatus.textContent = '✓ Page rechargée avec le script de session';
|
||||||
streamText = partial;
|
$injectStatus.classList.add('inject-success');
|
||||||
|
});
|
||||||
const allText = segments.filter((s) => s.type === 'text').map((s) => s.content).join('');
|
|
||||||
const toolSegs = segments.filter((s) => s.type === 'tool');
|
|
||||||
|
|
||||||
let html = '';
|
|
||||||
if (thinking) {
|
|
||||||
html += `<div class="chat-thinking"><span class="chat-thinking-icon">⏱</span> Thinking…</div>`;
|
|
||||||
}
|
|
||||||
segments.forEach((seg) => {
|
|
||||||
if (seg.type === 'text' && seg.content) {
|
|
||||||
const c = seg.content.replace(/<think[^>]*>[\s\S]*?<\/think>/gi, '');
|
|
||||||
if (c) html += `<div class="chat-content">${formatText(c)}</div>`;
|
|
||||||
}
|
|
||||||
if (seg.type === 'tool') {
|
|
||||||
const name = seg.call?.name || 'tool';
|
|
||||||
const icon = { terminal: '⌨', crush_run: '⚡', read_file: '📄', web_fetch: '🌐' }[name] || '🔧';
|
|
||||||
const done = seg.result;
|
|
||||||
const isErr = done && done.is_error;
|
|
||||||
const preview = (() => {
|
|
||||||
try {
|
|
||||||
const args = typeof seg.call.args === 'string' ? JSON.parse(seg.call.args) : seg.call.args;
|
|
||||||
return args.command || args.task || args.path || JSON.stringify(args).slice(0, 60);
|
|
||||||
} catch { return ''; }
|
|
||||||
})();
|
|
||||||
html += `<div class="chat-tool ${done ? 'done' : 'running'} ${isErr ? 'error' : ''}"><div class="chat-tool-header"><span class="chat-tool-icon">${icon}</span><span>${name}</span>${done ? `<span class="chat-tool-status ${isErr ? 'err' : 'ok'}">${isErr ? '✗' : '✓'}</span>` : '<span class="chat-dots"><span></span><span></span><span></span></span>'}</div>${preview ? `<div class="chat-tool-args">${escapeHtml(preview)}</div>` : ''}</div>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!html) {
|
|
||||||
html = '<span class="chat-dots"><span></span><span></span><span></span></span>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentStreamingEl.innerHTML = `
|
setTimeout(() => {
|
||||||
<div class="chat-avatar ai">◆</div>
|
$injectStatus.style.display = 'none';
|
||||||
<div class="chat-body">
|
}, 3000);
|
||||||
<div class="chat-header"><span class="chat-badge" style="color:#FF9100;border-color:#FF9100">GEN</span></div>
|
});
|
||||||
${html}
|
});
|
||||||
<span class="chat-cursor"></span>
|
}
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
scrollToBottom();
|
|
||||||
}, controller.signal);
|
|
||||||
|
|
||||||
if (currentStreamingEl && currentStreamingEl.parentNode) {
|
async function loadAutoInjectSetting() {
|
||||||
currentStreamingEl.remove();
|
const result = await chrome.storage.local.get('muyue_auto_inject');
|
||||||
}
|
$toggleAutoInject.checked = !!result.muyue_auto_inject;
|
||||||
|
}
|
||||||
|
|
||||||
const allText = segments.filter((s) => s.type === 'text').map((s) => s.content).join('');
|
async function saveAutoInjectSetting(enabled) {
|
||||||
const toolSegs = segments.filter((s) => s.type === 'tool');
|
await chrome.storage.local.set({ muyue_auto_inject: enabled });
|
||||||
const aiMsg = {
|
|
||||||
id: (Date.now() + 1).toString(),
|
|
||||||
role: 'assistant',
|
|
||||||
content: toolSegs.length > 0 ? JSON.stringify({
|
|
||||||
segments: segments.map((s) => s.type === 'text'
|
|
||||||
? { type: 'text', content: s.content }
|
|
||||||
: { type: 'tool', call: s.call, result: s.result ? { content: s.result.content || '', is_error: s.result.is_error || false, tool_call_id: s.call?.tool_call_id } : null }),
|
|
||||||
content: allText,
|
|
||||||
}) : (allText || finalContent),
|
|
||||||
};
|
|
||||||
messages.push(aiMsg);
|
|
||||||
$chatFeed.appendChild(createMessageEl(aiMsg));
|
|
||||||
scrollToBottom();
|
|
||||||
} catch (err) {
|
|
||||||
if (currentStreamingEl && currentStreamingEl.parentNode) {
|
|
||||||
currentStreamingEl.remove();
|
|
||||||
}
|
|
||||||
if (err.name !== 'AbortError') {
|
|
||||||
const errMsg = { id: (Date.now() + 1).toString(), role: 'system', content: `Error: ${err.message}` };
|
|
||||||
messages.push(errMsg);
|
|
||||||
$chatFeed.appendChild(createMessageEl(errMsg));
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
abortController = null;
|
|
||||||
currentStreamingEl = null;
|
|
||||||
$chatSend.style.display = 'flex';
|
|
||||||
$chatStop.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$$('.tab').forEach((tab) => {
|
$$('.tab').forEach((tab) => {
|
||||||
tab.addEventListener('click', () => switchTab(tab.dataset.tab));
|
tab.addEventListener('click', () => switchTab(tab.dataset.tab));
|
||||||
});
|
});
|
||||||
|
|
||||||
$chatInput.addEventListener('input', () => {
|
|
||||||
$chatInput.style.height = 'auto';
|
|
||||||
$chatInput.style.height = Math.min($chatInput.scrollHeight, 100) + 'px';
|
|
||||||
});
|
|
||||||
|
|
||||||
$chatInput.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSend();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$chatSend.addEventListener('click', handleSend);
|
|
||||||
$chatStop.addEventListener('click', () => {
|
|
||||||
if (abortController) abortController.abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
$chatFeed.addEventListener('click', (e) => {
|
$chatFeed.addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('.chat-copy-btn');
|
const btn = e.target.closest('.chat-copy-btn');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
@@ -408,6 +295,12 @@ $chatFeed.addEventListener('click', (e) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$btnInject.addEventListener('click', injectSessionScript);
|
||||||
|
|
||||||
|
$toggleAutoInject.addEventListener('change', () => {
|
||||||
|
saveAutoInjectSetting($toggleAutoInject.checked);
|
||||||
|
});
|
||||||
|
|
||||||
$btnSaveUrl.addEventListener('click', async () => {
|
$btnSaveUrl.addEventListener('click', async () => {
|
||||||
const url = $serverUrl.value.trim().replace(/\/$/, '');
|
const url = $serverUrl.value.trim().replace(/\/$/, '');
|
||||||
if (url) {
|
if (url) {
|
||||||
@@ -442,6 +335,11 @@ async function refresh() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadAutoInjectSetting();
|
||||||
refresh();
|
refresh();
|
||||||
loadChatHistory();
|
|
||||||
setInterval(refresh, 10000);
|
if (pollInterval) clearInterval(pollInterval);
|
||||||
|
pollInterval = setInterval(() => {
|
||||||
|
refresh();
|
||||||
|
if (serverOnline) pollStudio();
|
||||||
|
}, 3000);
|
||||||
|
|||||||
@@ -704,3 +704,138 @@ header h1 { font-size: 16px; font-weight: 600; letter-spacing: -0.3px; color: va
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Inject Session ── */
|
||||||
|
#btn-inject-session {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-color: var(--accent-dim);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-inject-session:hover {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-inject-session svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inject-status {
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
animation: fadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inject-status.inject-success {
|
||||||
|
color: var(--success);
|
||||||
|
background: rgba(0, 230, 118, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inject-status.inject-error {
|
||||||
|
color: var(--error);
|
||||||
|
background: rgba(255, 23, 68, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Toggle Switch ── */
|
||||||
|
.toggle-section {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-desc {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
margin-top: 6px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 22px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background: var(--text-tertiary);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .toggle-slider {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:checked + .toggle-slider::before {
|
||||||
|
background: var(--accent);
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Live Chat Header ── */
|
||||||
|
.chat-live-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-live-header svg {
|
||||||
|
animation: pulse-live 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-live {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
|||||||
'notifications',
|
'notifications',
|
||||||
'alarms',
|
'alarms',
|
||||||
],
|
],
|
||||||
host_permissions: ['http://127.0.0.1:*/*', 'http://localhost:*/*'],
|
host_permissions: ['<all_urls>'],
|
||||||
action: {
|
action: {
|
||||||
default_icon: {
|
default_icon: {
|
||||||
16: 'icon/16.png',
|
16: 'icon/16.png',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
Name = "muyue"
|
Name = "muyue"
|
||||||
Version = "0.9.5"
|
Version = "0.9.6"
|
||||||
Author = "La Légion de Muyue"
|
Author = "La Légion de Muyue"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user