Replace all link dialog terminology with resolver terminology throughout the codebase, including module names, CSS classes, IDs, JS variables, SVG gradient IDs, and log module names. Remove the old web/linker/ tree and introduce the new web/resolver/ tree with parallel structure. Also replace core/linker.py with core/resolver.py.
180 lines
6.6 KiB
JavaScript
180 lines
6.6 KiB
JavaScript
import { app } from "../../../../../scripts/app.js";
|
||
import { api } from "../../../../../scripts/api.js";
|
||
import { $el } from "../../../../../scripts/ui.js";
|
||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||
export const renderFormatMethods = {
|
||
/**
|
||
* Get a colored confidence badge HTML
|
||
* @param {number} confidence - Confidence percentage (0-100)
|
||
* @returns {string} HTML for the badge
|
||
*/
|
||
getConfidenceBadge(confidence) {
|
||
let badgeClass;
|
||
if (confidence >= 95) {
|
||
badgeClass = 'mr-badge-high';
|
||
} else if (confidence >= 70) {
|
||
badgeClass = 'mr-badge-medium';
|
||
} else {
|
||
badgeClass = 'mr-badge-low';
|
||
}
|
||
return `<span class="mr-badge ${badgeClass}">${confidence}%</span>`;
|
||
},
|
||
|
||
getStatusBadge(label, variant = 'neutral') {
|
||
return `<span class="mr-badge mr-badge-${variant}">${label}</span>`;
|
||
},
|
||
|
||
/**
|
||
* Format a filename with smart truncation
|
||
* @param {string} path - Full path or filename
|
||
* @param {number} maxLength - Maximum display length
|
||
* @returns {object} { display: truncated name, full: full name }
|
||
*/
|
||
formatFilename(path, maxLength = 50) {
|
||
if (!path) return { display: 'Unknown', full: 'Unknown' };
|
||
|
||
// Extract just the filename from path
|
||
const filename = path.split(/[\/\\]/).pop() || path;
|
||
|
||
if (filename.length <= maxLength) {
|
||
return { display: filename, full: filename };
|
||
}
|
||
|
||
// Smart truncation: keep extension visible
|
||
const lastDot = filename.lastIndexOf('.');
|
||
const ext = lastDot > 0 ? filename.slice(lastDot) : '';
|
||
const name = lastDot > 0 ? filename.slice(0, lastDot) : filename;
|
||
|
||
// Calculate how much of the name we can show
|
||
const availableLength = maxLength - ext.length - 3; // 3 for "..."
|
||
if (availableLength < 8) {
|
||
// Too short, just truncate at the end
|
||
return { display: filename.slice(0, maxLength - 3) + '...', full: filename };
|
||
}
|
||
|
||
// Truncate middle of name
|
||
const frontLength = Math.ceil(availableLength / 2);
|
||
const backLength = Math.floor(availableLength / 2);
|
||
const truncated = name.slice(0, frontLength) + '...' + name.slice(-backLength) + ext;
|
||
|
||
return { display: truncated, full: filename };
|
||
},
|
||
|
||
/**
|
||
* Format a path showing directory context
|
||
* @param {string} path - Full relative path
|
||
* @param {number} maxLength - Maximum display length
|
||
* @returns {object} { display: formatted path, full: full path }
|
||
*/
|
||
formatPath(path, maxLength = 60) {
|
||
if (!path) return { display: 'Unknown', full: 'Unknown' };
|
||
|
||
if (path.length <= maxLength) {
|
||
return { display: path, full: path };
|
||
}
|
||
|
||
// Try to show meaningful parts: first dir + filename
|
||
const parts = path.split(/[\/\\]/);
|
||
const filename = parts.pop() || '';
|
||
const firstDir = parts[0] || '';
|
||
|
||
if (parts.length === 0) {
|
||
// Just a filename
|
||
return this.formatFilename(path, maxLength);
|
||
}
|
||
|
||
// Show first directory + ... + filename
|
||
const formatted = firstDir + '\\...' + (filename.length > 40 ? this.formatFilename(filename, 40).display : filename);
|
||
|
||
if (formatted.length <= maxLength) {
|
||
return { display: formatted, full: path };
|
||
}
|
||
|
||
// Still too long, just truncate
|
||
return { display: path.slice(0, maxLength - 3) + '...', full: path };
|
||
},
|
||
|
||
/**
|
||
* Render a status message with icon
|
||
* @param {string} message - Message text
|
||
* @param {string} type - 'error' | 'success' | 'info' | 'warning'
|
||
* @returns {string} HTML for status message
|
||
*/
|
||
renderStatusMessage(message, type = 'info') {
|
||
const icons = {
|
||
error: '⚠',
|
||
success: '✓',
|
||
info: 'ℹ',
|
||
warning: '⚡'
|
||
};
|
||
const icon = icons[type] || icons.info;
|
||
|
||
return `
|
||
<div class="mr-status mr-status-${type}">
|
||
<span class="mr-status-icon">${icon}</span>
|
||
<span>${message}</span>
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
/**
|
||
* Render a progress bar
|
||
* @param {number} percent - Progress percentage (0-100)
|
||
* @param {string} leftText - Text on the left
|
||
* @param {string} rightText - Text on the right
|
||
* @returns {string} HTML for progress bar
|
||
*/
|
||
renderProgressBar(percent, leftText = '', rightText = '') {
|
||
return `
|
||
<div class="mr-progress-container">
|
||
<div class="mr-progress-bar">
|
||
<div class="mr-progress-fill" style="width: ${percent}%"></div>
|
||
</div>
|
||
<div class="mr-progress-text">
|
||
<span>${leftText}</span>
|
||
<span>${rightText}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
renderAnalysisProgress(progress = {}) {
|
||
const current = Number(progress.current) || 0;
|
||
const total = Number(progress.total) || 0;
|
||
const percent = total > 0 ? Math.max(0, Math.min(100, Math.round((current / total) * 100))) : 0;
|
||
const message = progress.message || 'Analyzing workflow...';
|
||
const modelName = progress.model_name ? this.escapeHtml(String(progress.model_name)) : '';
|
||
const detail = total > 0 ? `${current} / ${total}` : 'Preparing...';
|
||
|
||
return `
|
||
<div class="mr-download-section">
|
||
<div class="mr-status-inline">
|
||
${this.getStatusBadge('Analyzing', 'info')}
|
||
<span class="mr-download-info">${message}</span>
|
||
</div>
|
||
${this.renderProgressBar(percent, detail, `${percent}%`)}
|
||
${modelName ? `<div class="mr-download-info">${modelName}</div>` : ''}
|
||
</div>
|
||
`;
|
||
},
|
||
|
||
async pollAnalysisProgress(analysisId, token) {
|
||
while (this._analysisProgressToken === token) {
|
||
try {
|
||
const response = await api.fetchApi(`/model_resolver/analyze-progress/${analysisId}`);
|
||
if (response.ok && this.contentElement && this._analysisProgressToken === token) {
|
||
const progress = await response.json();
|
||
this.contentElement.innerHTML = this.renderAnalysisProgress(progress);
|
||
if (progress.status === 'completed' || progress.status === 'error') {
|
||
return;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('Model Resolver: analysis progress polling failed', error);
|
||
}
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 250));
|
||
}
|
||
}
|
||
};
|