chore(publication): close pre-integration release blockers

This commit is contained in:
Alexandre Teixeira
2026-10-05 01:37:49 +01:00
parent 3d3aee2093
commit dab660543b
77 changed files with 4074 additions and 83103 deletions
+3 -3
View File
@@ -2083,15 +2083,15 @@ export function _expandModelRow(row, modelData) {
const _HWFIT_ENGINE_GLYPHS = {
'': '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="6" x2="20" y2="6"></line><line x1="4" y1="12" x2="20" y2="12"></line><line x1="4" y1="18" x2="20" y2="18"></line><circle cx="8" cy="6" r="2" fill="currentColor" stroke="none"></circle><circle cx="16" cy="12" r="2" fill="currentColor" stroke="none"></circle><circle cx="10" cy="18" r="2" fill="currentColor" stroke="none"></circle></svg>',
vllm: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4l7 16 7-16"></path><path d="M14 4l4 9 3-9"></path></svg>',
sglang: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;"></span>',
sglang: '',
mlx: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18V6l4 7 4-7v12"></path><path d="M16 6v12"></path><path d="M20 6v12"></path></svg>',
llamacpp: '<svg width="14" height="14" viewBox="0 0 600 600" fill="none" aria-hidden="true"><path d="M600 392L504.249 558L504.137 557.929C487.252 584.069 458.193 600 426.864 600H120L240 392H600Z" fill="currentColor"></path><path d="M240 392H0L199.602 46.0254C216.032 17.5463 246.411 0 279.29 0H466.154L240 392Z" fill="currentColor"></path></svg>',
ollama: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;"></span>',
ollama: '',
diffusers: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M5 19l2-2M17 7l2-2"></path></svg>',
};
function _hwfitEngineGlyph(value) {
return _HWFIT_ENGINE_GLYPHS[value] || _HWFIT_ENGINE_GLYPHS[''];
return _HWFIT_ENGINE_GLYPHS[value] ?? _HWFIT_ENGINE_GLYPHS[''];
}
const _HWFIT_USECASE_GLYPHS = {
+2 -2
View File
@@ -1167,12 +1167,12 @@ async function _fetchDependencies() {
// icon (the name alone is fine for librosa, hf_transfer, etc.).
const _DEP_GLYPHS = {
vllm: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4l7 16 7-16"/><path d="M14 4l4 9 3-9"/></svg>',
sglang: '<span aria-hidden="true" style="display:block;width:13px;height:13px;background:currentColor;-webkit-mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;"></span>',
sglang: '',
mlx_lm: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18V6l4 7 4-7v12"/><path d="M16 6v12"/><path d="M20 6v12"/></svg>',
mflux: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>',
boogu_image_mlx: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 17c2.5-4 4.5-4 7 0"/><circle cx="9" cy="9" r="1"/><circle cx="15" cy="9" r="1"/></svg>',
llama_cpp: '<svg width="13" height="13" viewBox="0 0 600 600" fill="none" aria-hidden="true"><path d="M600 392L504.249 558L504.137 557.929C487.252 584.069 458.193 600 426.864 600H120L240 392H600Z" fill="currentColor"/><path d="M240 392H0L199.602 46.0254C216.032 17.5463 246.411 0 279.29 0H466.154L240 392Z" fill="currentColor"/></svg>',
ollama: '<img src="/static/icons/ollama-mark-crop.png" alt="" aria-hidden="true" width="13" height="13" style="display:block;width:13px;height:13px;object-fit:contain;" />',
ollama: '',
diffusers: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M5 19l2-2M17 7l2-2"/></svg>',
krea_diffusers: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 19V5"/><path d="M4 12h4"/><path d="M12 5l-7 7 7 7"/><path d="M14 19l3-14 3 14"/><path d="M15.3 13h3.4"/></svg>',
sam_mask: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7c3-3 13-3 16 0"/><path d="M4 17c3 3 13 3 16 0"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3"/></svg>',
+4 -4
View File
@@ -2210,11 +2210,11 @@ function _rerenderCachedModels() {
// custom picker so the dropdown lists "[V] vLLM", "[⚡] SGLang", etc.
const _BACKEND_GLYPHS = {
vllm: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4l7 16 7-16"/><path d="M14 4l4 9 3-9"/></svg>',
sglang: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;mask:url(/static/icons/sglang-mark.png) center/contain no-repeat;"></span>',
sglang: '',
mlx: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18V6l4 7 4-7v12"/><path d="M16 6v12"/><path d="M20 6v12"/></svg>',
mlx_image: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9" r="1.5"/><path d="M21 15l-5-5L5 20"/><path d="M17.5 4v4M15.5 6h4"/></svg>',
llamacpp: '<svg width="14" height="14" viewBox="0 0 600 600" fill="none" aria-hidden="true"><path d="M600 392L504.249 558L504.137 557.929C487.252 584.069 458.193 600 426.864 600H120L240 392H600Z" fill="currentColor"/><path d="M240 392H0L199.602 46.0254C216.032 17.5463 246.411 0 279.29 0H466.154L240 392Z" fill="currentColor"/></svg>',
ollama: '<span aria-hidden="true" style="display:block;width:14px;height:14px;background:currentColor;-webkit-mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;mask:url(/static/icons/ollama-mark-crop.png) center/contain no-repeat;"></span>',
ollama: '',
diffusers: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M5 19l2-2M17 7l2-2"/></svg>',
};
@@ -2234,7 +2234,7 @@ function _rerenderCachedModels() {
const opt = _backendSource?.querySelector(`option[value="${CSS.escape(v)}"]`);
const label = opt ? opt.textContent : v;
if (_backendBtnLabel) _backendBtnLabel.textContent = label;
if (_backendBtnIconSlot) _backendBtnIconSlot.innerHTML = _BACKEND_GLYPHS[v] || _BACKEND_GLYPHS.vllm;
if (_backendBtnIconSlot) _backendBtnIconSlot.innerHTML = _BACKEND_GLYPHS[v] ?? _BACKEND_GLYPHS.vllm;
}
function _renderBackendMenu() {
@@ -2242,7 +2242,7 @@ function _rerenderCachedModels() {
const items = Array.from(_backendSource.options).map(o => ({ value: o.value, label: o.textContent }));
_backendMenu.innerHTML = items.map(it => `
<button type="button" role="option" class="hwfit-backend-item" data-value="${it.value}" style="all:unset;display:flex;align-items:center;gap:8px;width:100%;padding:6px 9px;border-radius:5px;font-size:12px;cursor:pointer;color:var(--fg);box-sizing:border-box;">
<span class="hwfit-backend-item-icon" style="display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;color:var(--accent, var(--red));flex-shrink:0;">${_BACKEND_GLYPHS[it.value] || _BACKEND_GLYPHS.vllm}</span>
<span class="hwfit-backend-item-icon" style="display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;color:var(--accent, var(--red));flex-shrink:0;">${_BACKEND_GLYPHS[it.value] ?? _BACKEND_GLYPHS.vllm}</span>
<span class="hwfit-backend-item-label" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${it.label}</span>
</button>
`).join('');
+56 -43
View File
@@ -15060,20 +15060,6 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
return _docxReady;
}
let _html2pdfReady = null;
function ensureHtml2Pdf() {
if (_html2pdfReady) return _html2pdfReady;
if (window.html2pdf) return (_html2pdfReady = Promise.resolve());
_html2pdfReady = new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = '/static/lib/html2pdf.bundle.min.js';
s.onload = resolve;
s.onerror = () => reject(new Error('Failed to load PDF library'));
document.head.appendChild(s);
});
return _html2pdfReady;
}
function _getExportBaseName() {
const doc = docs.get(activeDocId);
const title = (doc && doc.title) || 'document';
@@ -15282,11 +15268,11 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
// white paper preview; PDF is converted from its extracted text into an
// editable DOCX in the browser.
// PDF-backed documents already have a lossless filled-PDF export above.
// Running the generic html2pdf path would rebuild the extracted text and
// Running the generic browser print path would rebuild the extracted text and
// destroy the original page layout, images, and form structure.
if (!isForm) {
if (!isForm && (_isDocxLang(lang) || typeof window.print === 'function')) {
options.push({
label: _isDocxLang(lang) ? 'Convert to PDF' : 'Print as PDF',
label: _isDocxLang(lang) ? 'Convert to PDF' : 'Print / save PDF',
fn: _isDocxLang(lang) ? () => convertOriginalDocument('pdf') : exportAsPdf,
});
}
@@ -15449,15 +15435,13 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
if (!activeDocId) return;
const textarea = document.getElementById('doc-editor-textarea');
if (!textarea) return;
try {
await ensureHtml2Pdf();
} catch (e) {
if (uiModule) uiModule.showError('Failed to load PDF library');
if (typeof window.print !== 'function') {
if (uiModule) uiModule.showError('Browser printing is unavailable.');
return;
}
const lang = document.getElementById('doc-language-select')?.value || '';
const text = textarea.value || '';
// Render content as HTML for PDF
// Render content as HTML for the browser print dialog.
let html;
if (_isRichTextLang(lang)) {
html = text;
@@ -15470,28 +15454,57 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
html = '<pre style="white-space:pre-wrap;font-size:11px;font-family:monospace;color:#000;background:#fff;">' +
text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</pre>';
}
const container = document.createElement('div');
container.style.cssText = 'padding:20px;font-family:sans-serif;font-size:12px;color:#000;background:#fff;line-height:1.6;';
container.innerHTML = html;
if (_isRichTextLang(lang)) {
const style = document.createElement('style');
style.textContent = _richTextExportCss();
container.prepend(style);
// Use a sandboxed same-origin frame: rich text can render, but scripts,
// event handlers and embedded plugins cannot execute in the print view.
document.getElementById('doc-browser-print-frame')?.remove();
const frame = document.createElement('iframe');
frame.id = 'doc-browser-print-frame';
frame.title = 'Document print view';
frame.setAttribute('sandbox', 'allow-same-origin allow-modals');
frame.style.cssText = 'position:fixed;left:-10000px;top:0;width:800px;height:600px;border:0;';
document.body.appendChild(frame);
try {
const printDoc = frame.contentDocument;
printDoc.open();
printDoc.write('<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>');
printDoc.close();
printDoc.title = _getExportBaseName();
const container = printDoc.createElement('div');
container.style.cssText = 'padding:20px;font-family:sans-serif;font-size:12px;color:#000;background:#fff;line-height:1.6;';
container.innerHTML = html;
if (_isRichTextLang(lang)) {
const style = printDoc.createElement('style');
style.textContent = _richTextExportCss();
container.prepend(style);
}
// Typeset deferred math before copying it into the print document.
await markdownModule.renderMath(container);
const styles = [];
document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
if (!link.href.includes('/katex/')) return;
const copy = printDoc.createElement('link');
copy.rel = 'stylesheet';
copy.href = link.href;
styles.push(new Promise((resolve, reject) => {
copy.onload = resolve;
copy.onerror = () => reject(new Error('Math styles could not be loaded'));
}));
printDoc.head.appendChild(copy);
});
const printStyle = printDoc.createElement('style');
printStyle.textContent = '@page { margin: 10mm; } body { margin: 0; } img { max-width: 100%; } pre { white-space: pre-wrap; overflow-wrap: anywhere; }';
printDoc.head.appendChild(printStyle);
printDoc.body.appendChild(printDoc.importNode(container, true));
await Promise.all(styles);
await printDoc.fonts.ready;
await Promise.all(Array.from(printDoc.images, img => img.decode().catch(() => {})));
frame.contentWindow.addEventListener('afterprint', () => frame.remove(), { once: true });
frame.contentWindow.focus();
frame.contentWindow.print();
} catch (error) {
frame.remove();
if (uiModule) uiModule.showError('Could not open browser print: ' + (error.message || error));
}
// This container is detached, so the document-scoped flush mdToHtml
// schedules never sees it. Typeset the deferred math before html2pdf
// rasterises, or the PDF gets raw formula source. renderMath() returns
// immediately, without loading KaTeX, when there is nothing pending.
await markdownModule.renderMath(container);
const baseName = _getExportBaseName();
window.html2pdf().set({
margin: 10,
filename: baseName + '.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: { scale: 2 },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
}).from(container).save();
if (uiModule) uiModule.showToast('Exporting PDF...');
}
function _docxHexColor(value) {
+17 -8
View File
@@ -40,13 +40,17 @@ const LS_KEY = 'odysseus-theme';
const CUSTOM_THEMES_KEY = 'odysseus-custom-themes';
const FONT_MAP = {
gohu: "'GohuFont', monospace",
mono: "'Fira Code', monospace",
sans: "system-ui, -apple-system, 'Segoe UI', sans-serif",
serif: "Georgia, 'Times New Roman', serif",
opendyslexic: "'OpenDyslexic', sans-serif",
};
const DEFAULT_FONT = 'gohu';
const DEFAULT_FONT = 'mono';
// Compatibility for persisted themes that selected the omitted custom face.
function normalizeFont(font) {
return !font || font === 'gohu' || font === 'GohuFont' ? DEFAULT_FONT : font;
}
const DEFAULT_DENSITY = 'comfortable';
const MAX_CUSTOM_THEMES = 8;
@@ -108,7 +112,11 @@ const THEME_DEFAULT_FROSTED = {
// ── Custom theme persistence ──
function _loadCustomThemes() {
return Storage.getJSON(CUSTOM_THEMES_KEY, {});
const themes = Storage.getJSON(CUSTOM_THEMES_KEY, {});
for (const theme of Object.values(themes)) {
if (theme && theme.font) theme.font = normalizeFont(theme.font);
}
return themes;
}
function _saveCustomThemes(obj) {
Storage.setJSON(CUSTOM_THEMES_KEY, obj);
@@ -121,7 +129,7 @@ export function saveCustomTheme(name, colors, opts) {
}
const entry = { ...colors };
if (opts) {
if (opts.font) entry.font = opts.font;
if (opts.font) entry.font = normalizeFont(opts.font);
if (opts.density) entry.density = opts.density;
if (opts.bgPattern) entry.bgPattern = opts.bgPattern;
if (opts.bgEffectColor) entry.bgEffectColor = opts.bgEffectColor;
@@ -400,7 +408,7 @@ function _injectFontFace(familyName, variants) {
}
export function applyFontDensity(font, density) {
const f = font || DEFAULT_FONT;
const f = normalizeFont(font);
const d = density || DEFAULT_DENSITY;
let family = FONT_MAP[f];
if (!family && _customFonts[f]) {
@@ -506,13 +514,14 @@ export function getSaved() {
obj.name = replacement[obj.name];
obj.colors = { ...THEMES[obj.name] };
}
if (obj && obj.font) obj.font = normalizeFont(obj.font);
return obj;
}
export function save(name, colors, opts) {
const obj = { name, colors };
if (opts) {
if (opts.font && opts.font !== DEFAULT_FONT) obj.font = opts.font;
if (normalizeFont(opts.font) !== DEFAULT_FONT) obj.font = normalizeFont(opts.font);
if (opts.density && opts.density !== DEFAULT_DENSITY) obj.density = opts.density;
if (opts.bgPattern && opts.bgPattern !== 'none') obj.bgPattern = opts.bgPattern;
if (opts.bgEffectColor) obj.bgEffectColor = opts.bgEffectColor;
@@ -757,7 +766,7 @@ export function initThemeUI() {
sw.classList.add('active');
syncPickers(colors);
const ct = sw.dataset.custom ? customThemes[name] : null;
const f = ct && ct.font ? ct.font : DEFAULT_FONT;
const f = normalizeFont(ct && ct.font);
const d = ct && ct.density ? ct.density : DEFAULT_DENSITY;
const p = ct && ct.bgPattern ? ct.bgPattern : (THEME_DEFAULT_PATTERN[name] || 'none');
const ec = ct && ct.bgEffectColor ? ct.bgEffectColor : (THEME_DEFAULT_EFFECT_COLOR[name] || '');
@@ -1146,7 +1155,7 @@ export function initThemeUI() {
syncResetButtons();
// Font, density, background pattern controls
const _initFont = (saved && saved.font) || DEFAULT_FONT;
const _initFont = normalizeFont(saved && saved.font);
const _initDensity = (saved && saved.density) || DEFAULT_DENSITY;
const _initPattern = (saved && saved.bgPattern) || (saved && THEME_DEFAULT_PATTERN[saved.name]) || 'none';
const _initEffectColor = (saved && saved.bgEffectColor) || (saved && THEME_DEFAULT_EFFECT_COLOR[saved.name]) || '';