refactor: move monolithic link dialog methods into domain-based modules
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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 = 'ml-badge-high';
|
||||
} else if (confidence >= 70) {
|
||||
badgeClass = 'ml-badge-medium';
|
||||
} else {
|
||||
badgeClass = 'ml-badge-low';
|
||||
}
|
||||
return `<span class="ml-badge ${badgeClass}">${confidence}%</span>`;
|
||||
},
|
||||
|
||||
getStatusBadge(label, variant = 'neutral') {
|
||||
return `<span class="ml-badge ml-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="ml-status ml-status-${type}">
|
||||
<span class="ml-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="ml-progress-container">
|
||||
<div class="ml-progress-bar">
|
||||
<div class="ml-progress-fill" style="width: ${percent}%"></div>
|
||||
</div>
|
||||
<div class="ml-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="ml-download-section">
|
||||
<div class="ml-status-inline">
|
||||
${this.getStatusBadge('Analyzing', 'info')}
|
||||
<span class="ml-download-info">${message}</span>
|
||||
</div>
|
||||
${this.renderProgressBar(percent, detail, `${percent}%`)}
|
||||
${modelName ? `<div class="ml-download-info">${modelName}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
async pollAnalysisProgress(analysisId, token) {
|
||||
while (this._analysisProgressToken === token) {
|
||||
try {
|
||||
const response = await api.fetchApi(`/model_linker/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 Linker: analysis progress polling failed', error);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user