fix: mobile menu — no icons, hard-truncated titles within the drawer width, ⋮ always visible without horizontal scroll; translation uses the reliable plain-text path
This commit is contained in:
parent
ec4f1905de
commit
094de9ebd1
3 changed files with 27 additions and 35 deletions
|
|
@ -355,3 +355,12 @@ body.assistant-workspace .assistant-learning-view { min-height: 0; height: 100vh
|
||||||
.assistant-history .card { border:none; box-shadow:none; background:none; padding:0; }
|
.assistant-history .card { border:none; box-shadow:none; background:none; padding:0; }
|
||||||
.assistant-history .card-header { padding:2px 4px; }
|
.assistant-history .card-header { padding:2px 4px; }
|
||||||
.assistant-history .card-header h3 { font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; }
|
.assistant-history .card-header h3 { font-size:11px; font-weight:700; color:var(--g500); text-transform:uppercase; letter-spacing:.04em; }
|
||||||
|
/* Mobile drawer rows: no icons, hard-truncated titles, ⋮ always visible */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.assistant-saved-chat-icon { display:none; }
|
||||||
|
.assistant-saved-chat { position:relative; overflow:hidden; max-width:100%; padding-right:36px; }
|
||||||
|
.assistant-saved-chat-title { flex:1 1 auto; min-width:0; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||||
|
.assistant-saved-chat-menu { position:absolute; right:6px; top:50%; transform:translateY(-50%); opacity:1; pointer-events:auto; }
|
||||||
|
.assistant-saved-chat-meta { display:none; }
|
||||||
|
.assistant-goback-rail i, .assistant-create-image i, .assistant-new-chat i { display:none; }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1617,20 +1617,15 @@ import {
|
||||||
document.querySelectorAll('[data-assistant-translate-pop]').forEach(function(pop) { pop.remove(); });
|
document.querySelectorAll('[data-assistant-translate-pop]').forEach(function(pop) { pop.remove(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyHtmlForTranslation(html) {
|
function plainTextForTranslation(markdown) {
|
||||||
if (typeof DOMParser === 'undefined') {
|
return String(markdown || '')
|
||||||
// Older harnesses/browsers: send the raw markdown as text instead.
|
.replace(/^#{1,6}\s+/gm, '')
|
||||||
return String(html || '').replace(/<[^>]*>/g, '');
|
.replace(/[*_~`]{1,3}/g, '')
|
||||||
}
|
.replace(/^\s*[-*+]\s+/gm, '• ')
|
||||||
var doc = new DOMParser().parseFromString(String(html || ''), 'text/html');
|
.replace(/^\s*\d+\.\s+/gm, '')
|
||||||
doc.body.querySelectorAll('.katex, .assistant-cite, .assistant-code-copy, .assistant-table-actions, .assistant-msg-actions, mjx-container, script, style').forEach(function(el) {
|
.replace(/^\s*[|>]\s*/gm, '')
|
||||||
el.replaceWith(doc.createTextNode(el.textContent || ''));
|
.replace(/\|/g, ' ')
|
||||||
});
|
.trim();
|
||||||
doc.body.querySelectorAll('.assistant-table-scroll').forEach(function(el) {
|
|
||||||
var table = el.querySelector('table');
|
|
||||||
if (table) el.replaceWith(table);
|
|
||||||
});
|
|
||||||
return doc.body.innerHTML;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestMessageTranslation(row, target, provider) {
|
function requestMessageTranslation(row, target, provider) {
|
||||||
|
|
@ -1640,34 +1635,22 @@ import {
|
||||||
var imageCards = Array.prototype.slice.call(bubble.querySelectorAll('.assistant-image-card'));
|
var imageCards = Array.prototype.slice.call(bubble.querySelectorAll('.assistant-image-card'));
|
||||||
imageCards.forEach(function(card) { card.remove(); });
|
imageCards.forEach(function(card) { card.remove(); });
|
||||||
if (!bubble.assistantOriginalHtml) bubble.assistantOriginalHtml = bubble.innerHTML;
|
if (!bubble.assistantOriginalHtml) bubble.assistantOriginalHtml = bubble.innerHTML;
|
||||||
// Translate simplified rendered HTML so headings, bold, lists and tables
|
// Reliable path: translate plain text (no markdown syntax to mangle) and
|
||||||
// survive; LibreTranslate keeps those tags in html mode. Citations render
|
// show it as clean paragraphs; the original keeps its full formatting.
|
||||||
// as plain numbers inside their chips.
|
|
||||||
var raw = bubble.assistantRawContent !== undefined ? bubble.assistantRawContent : String(bubble.textContent || '').trim();
|
var raw = bubble.assistantRawContent !== undefined ? bubble.assistantRawContent : String(bubble.textContent || '').trim();
|
||||||
if (!String(raw).trim()) return;
|
if (!String(raw).trim()) return;
|
||||||
var simpleHtml = simplifyHtmlForTranslation(renderMarkdown(raw, [], {}));
|
var plain = plainTextForTranslation(raw);
|
||||||
translateAssistantMessage(simpleHtml, target, provider, 'html')
|
if (!String(plain).trim()) return;
|
||||||
|
translateAssistantMessage(plain, target, provider, 'text')
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data.success) throw new Error(data.error || 'Translation failed');
|
if (!data.success) throw new Error(data.error || 'Translation failed');
|
||||||
bubble.innerHTML = sanitize(String(data.translated || '')) +
|
bubble.innerHTML = '<p class="assistant-translated-text">' + escapeHtml(String(data.translated || '')) + '</p>' +
|
||||||
'<button type="button" class="btn-sm btn-ghost" data-assistant-msg-show-original><i class="fas fa-undo"></i> Show original</button>';
|
'<button type="button" class="btn-sm btn-ghost" data-assistant-msg-show-original><i class="fas fa-undo"></i> Show original</button>';
|
||||||
imageCards.forEach(function(card) { bubble.appendChild(card); }); // same node — its status polling continues
|
imageCards.forEach(function(card) { bubble.appendChild(card); }); // same node — its status polling continues
|
||||||
renderEmbeddedBlocks(bubble);
|
renderEmbeddedBlocks(bubble);
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
// Html mode can be rejected by some LibreTranslate builds; fall back
|
if (typeof showToast === 'function') showToast(err && err.message ? err.message : 'Translation failed', 'error');
|
||||||
// to plain text and re-render, so formatting is never fully lost.
|
|
||||||
translateAssistantMessage(raw, target, provider, 'text')
|
|
||||||
.then(function(data2) {
|
|
||||||
if (!data2.success) throw new Error(data2.error || 'Translation failed');
|
|
||||||
bubble.innerHTML = renderMarkdown(String(data2.translated || ''), [], {}) +
|
|
||||||
'<button type="button" class="btn-sm btn-ghost" data-assistant-msg-show-original><i class="fas fa-undo"></i> Show original</button>';
|
|
||||||
imageCards.forEach(function(card) { bubble.appendChild(card); });
|
|
||||||
renderEmbeddedBlocks(bubble);
|
|
||||||
})
|
|
||||||
.catch(function(err2) {
|
|
||||||
if (typeof showToast === 'function') showToast(err2 && err2.message ? err2.message : 'Translation failed', 'error');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ test('per-message translate picker offers a provider choice and leaves the raw t
|
||||||
const sent = JSON.parse(translateCalls[0].options.body);
|
const sent = JSON.parse(translateCalls[0].options.body);
|
||||||
assert.equal(sent.target, 'es');
|
assert.equal(sent.target, 'es');
|
||||||
assert.equal(sent.provider, 'libretranslate');
|
assert.equal(sent.provider, 'libretranslate');
|
||||||
assert.equal(sent.format, 'html');
|
assert.equal(sent.format, 'text');
|
||||||
assert.match(sent.message, /Chest pain in a four year old\./, 'simplified rendered HTML carries the formatting');
|
assert.equal(sent.message, 'Chest pain in a four year old.');
|
||||||
const bubble = row.querySelector('.assistant-bubble');
|
const bubble = row.querySelector('.assistant-bubble');
|
||||||
assert.match(bubble.textContent, /Traducción de "Chest pain in a four year old/);
|
assert.match(bubble.textContent, /Traducción de "Chest pain in a four year old/);
|
||||||
assert.equal(c.messages[0].content, 'Chest pain in a four year old.', 'canonical transcript unchanged');
|
assert.equal(c.messages[0].content, 'Chest pain in a four year old.', 'canonical transcript unchanged');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue