feat: consolidate docs, backend/session infra, and settings updates
This commit is contained in:
@@ -6,6 +6,15 @@
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
// Debug logging utility
|
||||
const isDebug = typeof wpAgenticWriter !== 'undefined' && wpAgenticWriter.debug;
|
||||
const wpawLog = {
|
||||
log: (...args) => { if (isDebug) console.log('[WPAW]', ...args); },
|
||||
error: (...args) => console.error('[WPAW]', ...args),
|
||||
info: (...args) => { if (isDebug) console.info('[WPAW]', ...args); },
|
||||
warn: (...args) => { if (isDebug) console.warn('[WPAW]', ...args); },
|
||||
};
|
||||
|
||||
// Global state
|
||||
const state = {
|
||||
models: {},
|
||||
@@ -20,71 +29,48 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Preset configurations
|
||||
const presets = {
|
||||
budget: {
|
||||
chat: 'google/gemini-2.5-flash',
|
||||
clarity: 'google/gemini-2.5-flash',
|
||||
planning: 'google/gemini-2.5-flash',
|
||||
writing: 'mistralai/mistral-small-creative',
|
||||
refinement: 'google/gemini-2.5-flash',
|
||||
image: 'openai/gpt-4o'
|
||||
},
|
||||
balanced: {
|
||||
chat: 'google/gemini-2.5-flash',
|
||||
clarity: 'google/gemini-2.5-flash',
|
||||
planning: 'google/gemini-2.5-flash',
|
||||
writing: 'anthropic/claude-3.5-sonnet',
|
||||
refinement: 'anthropic/claude-3.5-sonnet',
|
||||
image: 'openai/gpt-4o'
|
||||
},
|
||||
premium: {
|
||||
chat: 'google/gemini-3-flash-preview',
|
||||
clarity: 'anthropic/claude-sonnet-4',
|
||||
planning: 'google/gemini-3-flash-preview',
|
||||
writing: 'openai/gpt-4.1',
|
||||
refinement: 'openai/gpt-4.1',
|
||||
image: 'openai/gpt-4o'
|
||||
}
|
||||
};
|
||||
// Preset configurations (sourced from PHP for single-source-of-truth).
|
||||
// These presets represent intentional product decisions for different budget tiers.
|
||||
// Model IDs may differ from registry defaults to balance cost/quality per tier.
|
||||
const presets = wpawSettingsV2?.presets || {};
|
||||
|
||||
// Debug function to check models
|
||||
window.wpawDebugModels = function () {
|
||||
console.log('=== WPAW Models Debug ===');
|
||||
console.log('Total model categories:', Object.keys(state.models).length);
|
||||
wpawLog.log('=== WPAW Models Debug ===');
|
||||
wpawLog.log('Total model categories:', Object.keys(state.models).length);
|
||||
|
||||
Object.keys(state.models).forEach(category => {
|
||||
const models = state.models[category]?.all || [];
|
||||
console.log(`\n${category.toUpperCase()}: ${models.length} models`);
|
||||
wpawLog.log(`\n${category.toUpperCase()}: ${models.length} models`);
|
||||
|
||||
// Check for specific models
|
||||
const checkIds = ['deepseek/deepseek-chat-v3-0324', 'anthropic/claude-3.5-sonnet'];
|
||||
checkIds.forEach(id => {
|
||||
const found = models.find(m => m.id === id);
|
||||
if (found) {
|
||||
console.log(` ✓ FOUND: ${id} => ${found.name}`);
|
||||
wpawLog.log(` ✓ FOUND: ${id} => ${found.name}`);
|
||||
} else {
|
||||
console.log(` ✗ NOT FOUND: ${id}`);
|
||||
wpawLog.log(` ✗ NOT FOUND: ${id}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Show models with raw is_free and pricing data
|
||||
if (category === 'image') {
|
||||
console.log(` ALL image models (raw data from PHP):`);
|
||||
wpawLog.log(` ALL image models (raw data from PHP):`);
|
||||
models.forEach(m => {
|
||||
console.log(` - ${m.id} | is_free=${m.is_free} | pricing=`, m.pricing);
|
||||
wpawLog.log(` - ${m.id} | is_free=${m.is_free} | pricing=`, m.pricing);
|
||||
});
|
||||
} else {
|
||||
// Show first 10 models with is_free status
|
||||
console.log(` First 10 models (raw data from PHP):`);
|
||||
wpawLog.log(` First 10 models (raw data from PHP):`);
|
||||
models.slice(0, 10).forEach(m => {
|
||||
console.log(` - ${m.id} | is_free=${m.is_free} | pricing=`, m.pricing);
|
||||
wpawLog.log(` - ${m.id} | is_free=${m.is_free} | pricing=`, m.pricing);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX debug call
|
||||
console.log('\n=== Fetching from server ===');
|
||||
wpawLog.log('\n=== Fetching from server ===');
|
||||
$.ajax({
|
||||
url: wpawSettingsV2.ajaxUrl,
|
||||
type: 'POST',
|
||||
@@ -94,17 +80,17 @@
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
console.log('Server response:', response.data);
|
||||
console.log('Total models from API:', response.data.total_models);
|
||||
console.log('Found models:', response.data.found_models);
|
||||
console.log('Missing models:', response.data.missing_models);
|
||||
console.log('Sample models:', response.data.sample_models);
|
||||
wpawLog.log('Server response:', response.data);
|
||||
wpawLog.log('Total models from API:', response.data.total_models);
|
||||
wpawLog.log('Found models:', response.data.found_models);
|
||||
wpawLog.log('Missing models:', response.data.missing_models);
|
||||
wpawLog.log('Sample models:', response.data.sample_models);
|
||||
} else {
|
||||
console.error('Error:', response.data.message);
|
||||
wpawLog.error('Error:', response.data.message);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('AJAX error:', error);
|
||||
wpawLog.error('AJAX error:', error);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -127,7 +113,7 @@
|
||||
});
|
||||
|
||||
// Log debug info
|
||||
console.log('WPAW Settings V2 loaded. Run wpawDebugModels() to debug model issues.');
|
||||
wpawLog.log('WPAW Settings V2 loaded. Run wpawDebugModels() to debug model issues.');
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -170,7 +156,7 @@
|
||||
const newOption = new Option(modelData.name || currentValue, currentValue, true, true);
|
||||
$select.append(newOption).trigger('change');
|
||||
} else {
|
||||
console.warn('Model not found in list:', currentValue);
|
||||
wpawLog.warn('Model not found in list:', currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,17 +373,17 @@
|
||||
* Initialize cost log functionality
|
||||
*/
|
||||
function initCostLog() {
|
||||
console.log('Initializing cost log...');
|
||||
wpawLog.log('Initializing cost log...');
|
||||
|
||||
// Load on tab show
|
||||
$('#cost-log-tab').on('shown.bs.tab', function () {
|
||||
console.log('Cost log tab shown, loading data...');
|
||||
wpawLog.log('Cost log tab shown, loading data...');
|
||||
loadCostLogData();
|
||||
});
|
||||
|
||||
// Auto-load if cost-log tab is active on page load
|
||||
if ($('#cost-log-tab').hasClass('active')) {
|
||||
console.log('Cost log tab is active on load, loading data...');
|
||||
wpawLog.log('Cost log tab is active on load, loading data...');
|
||||
loadCostLogData();
|
||||
}
|
||||
|
||||
@@ -449,12 +435,12 @@
|
||||
* Load cost log data via AJAX
|
||||
*/
|
||||
function loadCostLogData() {
|
||||
console.log('loadCostLogData called');
|
||||
console.log('wpawSettingsV2:', wpawSettingsV2);
|
||||
console.log('State:', state);
|
||||
wpawLog.log('loadCostLogData called');
|
||||
wpawLog.log('wpawSettingsV2:', wpawSettingsV2);
|
||||
wpawLog.log('State:', state);
|
||||
|
||||
const $tbody = $('#wpaw-cost-log-tbody');
|
||||
console.log('Table tbody found:', $tbody.length);
|
||||
wpawLog.log('Table tbody found:', $tbody.length);
|
||||
|
||||
$tbody.html(`
|
||||
<tr>
|
||||
@@ -478,14 +464,14 @@
|
||||
filter_date_to: state.filters.dateTo
|
||||
};
|
||||
|
||||
console.log('AJAX request data:', ajaxData);
|
||||
wpawLog.log('AJAX request data:', ajaxData);
|
||||
|
||||
$.ajax({
|
||||
url: wpawSettingsV2.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: ajaxData,
|
||||
success: function (response) {
|
||||
console.log('Cost log response:', response);
|
||||
wpawLog.log('Cost log response:', response);
|
||||
if (response.success) {
|
||||
renderCostLogTable(response.data);
|
||||
updateCostLogStats(response.data.stats);
|
||||
@@ -493,14 +479,14 @@
|
||||
renderPagination(response.data);
|
||||
} else {
|
||||
const errorMsg = response.data?.message || 'Error loading data';
|
||||
console.error('Cost log error:', errorMsg);
|
||||
wpawLog.error('Cost log error:', errorMsg);
|
||||
$tbody.html('<tr><td colspan="7" class="text-center py-4 text-danger">' + escapeHtml(errorMsg) + '</td></tr>');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('Cost log AJAX error:', status, error);
|
||||
console.error('XHR:', xhr);
|
||||
console.error('Response text:', xhr.responseText);
|
||||
wpawLog.error('Cost log AJAX error:', status, error);
|
||||
wpawLog.error('XHR:', xhr);
|
||||
wpawLog.error('Response text:', xhr.responseText);
|
||||
$tbody.html('<tr><td colspan="7" class="text-center py-4 text-danger">Failed to load cost log. Check browser console for details.</td></tr>');
|
||||
}
|
||||
});
|
||||
@@ -1022,4 +1008,133 @@
|
||||
initSelect2();
|
||||
}
|
||||
|
||||
/**
|
||||
* Workflow Pipeline Status Display
|
||||
* Updates the 5-step workflow visualization based on backend status
|
||||
*/
|
||||
function initWorkflowDisplay() {
|
||||
// Status mapping from backend to step index
|
||||
// Backend statuses: starting, planning, plan_complete, writing, writing_section, refinement, checking, complete
|
||||
const statusToStep = {
|
||||
'starting': 1, // Context
|
||||
'planning': 2, // Planning
|
||||
'plan_complete': 2, // Planning (done)
|
||||
'writing': 3, // Writing
|
||||
'writing_section': 3, // Writing
|
||||
'refinement': 4, // Refinement
|
||||
'refining': 4, // Refinement
|
||||
'checking': 4, // Refinement
|
||||
'complete': 5, // Done
|
||||
'done': 5, // Done
|
||||
};
|
||||
|
||||
// Status messages mapping
|
||||
const statusMessages = {
|
||||
'starting': 'Loading context and analyzing post...',
|
||||
'planning': 'Creating article outline...',
|
||||
'plan_complete': 'Outline ready, starting to write...',
|
||||
'writing': 'Generating article content...',
|
||||
'writing_section': 'Writing section content...',
|
||||
'refinement': 'Polishing and optimizing content...',
|
||||
'refining': 'Applying refinements...',
|
||||
'checking': 'Checking quality and consistency...',
|
||||
'complete': 'Article finished successfully!',
|
||||
'done': 'All done!',
|
||||
};
|
||||
|
||||
/**
|
||||
* Update workflow display based on status
|
||||
* @param {string} status - Backend status string
|
||||
* @param {string} message - Optional custom message
|
||||
*/
|
||||
window.updateWorkflowStatus = function(status, message) {
|
||||
const stepIndex = statusToStep[status] || 0;
|
||||
const $workflow = $('#wpaw-workflow-display');
|
||||
|
||||
if (!$workflow.length) return;
|
||||
|
||||
const $steps = $workflow.find('.wpaw-step');
|
||||
const $connectors = $workflow.find('.wpaw-step-connector');
|
||||
const $statusText = $('#wpaw-workflow-status');
|
||||
const $messageEl = $('#wpaw-workflow-message');
|
||||
|
||||
// Reset all steps
|
||||
$steps.removeClass('active completed pending error');
|
||||
$connectors.removeClass('active completed');
|
||||
|
||||
// Update steps based on current status
|
||||
$steps.each(function(index) {
|
||||
const $step = $(this);
|
||||
const stepNum = index + 1;
|
||||
|
||||
if (stepNum < stepIndex) {
|
||||
// Completed steps
|
||||
$step.addClass('completed');
|
||||
if ($connectors[index]) {
|
||||
$($connectors[index]).addClass('completed');
|
||||
}
|
||||
} else if (stepNum === stepIndex) {
|
||||
// Active step
|
||||
$step.addClass('active');
|
||||
if ($connectors[index]) {
|
||||
$($connectors[index]).addClass('active');
|
||||
}
|
||||
} else {
|
||||
// Pending steps
|
||||
$step.addClass('pending');
|
||||
}
|
||||
});
|
||||
|
||||
// Update status text
|
||||
const statusText = stepIndex > 0 ? `Step ${stepIndex} of 5` : 'Idle';
|
||||
$statusText.text(statusText);
|
||||
|
||||
// Show message if provided
|
||||
if (message || statusMessages[status]) {
|
||||
const displayMessage = message || statusMessages[status];
|
||||
$messageEl.text(displayMessage).show();
|
||||
|
||||
// Add appropriate class
|
||||
$messageEl.removeClass('success error');
|
||||
if (status === 'complete' || status === 'done') {
|
||||
$messageEl.addClass('success');
|
||||
} else if (status === 'error') {
|
||||
$messageEl.addClass('error');
|
||||
}
|
||||
} else {
|
||||
$messageEl.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// Demo function for testing - cycles through all steps
|
||||
window.demoWorkflow = function() {
|
||||
const statuses = ['starting', 'planning', 'plan_complete', 'writing', 'refinement', 'complete'];
|
||||
let index = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
updateWorkflowStatus(statuses[index]);
|
||||
index++;
|
||||
|
||||
if (index >= statuses.length) {
|
||||
clearInterval(interval);
|
||||
setTimeout(() => {
|
||||
// Reset to idle
|
||||
$('#wpaw-workflow-status').text('Idle');
|
||||
$('#wpaw-workflow-message').hide();
|
||||
$('.wpaw-step').removeClass('active completed').addClass('pending');
|
||||
$('.wpaw-step-connector').removeClass('active completed');
|
||||
}, 2000);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// Initialize with idle state
|
||||
updateWorkflowStatus('idle');
|
||||
}
|
||||
|
||||
// Initialize workflow display on page load
|
||||
$(document).ready(function() {
|
||||
initWorkflowDisplay();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
||||
215
assets/js/sidebar-utils.js
Normal file
215
assets/js/sidebar-utils.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* WP Agentic Writer - Utility Functions
|
||||
*
|
||||
* Pure utility functions with no React dependencies
|
||||
* These are shared utilities that can be used by any script
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
*/
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
const escapeHtml = (value) => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
};
|
||||
|
||||
// Normalize message content (convert objects/arrays to string)
|
||||
const normalizeMessageContent = (content) => {
|
||||
if (typeof content === 'string' || typeof content === 'number') {
|
||||
return String(content);
|
||||
}
|
||||
return JSON.stringify(content);
|
||||
};
|
||||
|
||||
// Truncate text with ellipsis
|
||||
const truncateText = (text, maxLength = 40) => {
|
||||
if (!text || text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
||||
// Convert markdown to HTML (full renderer)
|
||||
const markdownToHtml = (markdown, markdownit, DOMPurify) => {
|
||||
const raw = normalizeMessageContent(markdown);
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let rendered = '';
|
||||
if (markdownit && DOMPurify) {
|
||||
const renderer = markdownit({
|
||||
html: false,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
});
|
||||
|
||||
if (window.markdownitTaskLists) {
|
||||
renderer.use(window.markdownitTaskLists, { enabled: true, label: true, labelAfter: true });
|
||||
}
|
||||
|
||||
rendered = renderer.render(raw);
|
||||
|
||||
if (DOMPurify.sanitize) {
|
||||
rendered = DOMPurify.sanitize(rendered, {
|
||||
ADD_TAGS: ['input'],
|
||||
ADD_ATTR: ['type', 'checked', 'disabled'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
// Extract code blocks from HTML
|
||||
const extractCodeBlocks = (html) => {
|
||||
const codeBlocks = [];
|
||||
const preRegex = /<pre[^>]*><code(?:\s+class="language-([^"]*)")?>([\s\S]*?)<\/code><\/pre>/g;
|
||||
let match;
|
||||
|
||||
while ((match = preRegex.exec(html)) !== null) {
|
||||
const lang = match[1] || '';
|
||||
const code = match[2]
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'");
|
||||
codeBlocks.push({ lang, code });
|
||||
}
|
||||
|
||||
return codeBlocks;
|
||||
};
|
||||
|
||||
// Debounce function
|
||||
const debounce = (func, wait) => {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
// Parse outline plan from AI response
|
||||
const parseOutlinePlan = (content) => {
|
||||
const sections = [];
|
||||
const lines = content.split('\n');
|
||||
|
||||
let currentSection = null;
|
||||
let currentSubsection = null;
|
||||
|
||||
lines.forEach((line) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
// H2 section (## Title)
|
||||
const h2Match = trimmed.match(/^##\s+(.+)$/);
|
||||
if (h2Match) {
|
||||
if (currentSection) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
currentSection = {
|
||||
id: 'section-' + (sections.length + 1),
|
||||
title: h2Match[1].trim(),
|
||||
subsections: [],
|
||||
};
|
||||
currentSubsection = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// H3 subsection (### Title)
|
||||
const h3Match = trimmed.match(/^###\s+(.+)$/);
|
||||
if (h3Match && currentSection) {
|
||||
currentSubsection = {
|
||||
id: 'subsection-' + (currentSection.subsections.length + 1),
|
||||
title: h3Match[1].trim(),
|
||||
content: '',
|
||||
};
|
||||
currentSection.subsections.push(currentSubsection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Content line
|
||||
if (currentSection) {
|
||||
if (currentSubsection) {
|
||||
currentSubsection.content += (currentSubsection.content ? '\n' : '') + trimmed;
|
||||
} else {
|
||||
if (!currentSection.content) {
|
||||
currentSection.content = trimmed;
|
||||
} else {
|
||||
currentSection.content += '\n' + trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSection) {
|
||||
sections.push(currentSection);
|
||||
}
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
// Parse FAQ schema from AI response
|
||||
const parseFaqSchema = (content) => {
|
||||
const faqs = [];
|
||||
const faqBlocks = content.split(/\n\s*#{1,2}\s*Q[^\n]*\n/);
|
||||
|
||||
faqBlocks.slice(1).forEach((block) => {
|
||||
const lines = block.trim().split('\n');
|
||||
if (lines.length >= 2) {
|
||||
const question = lines[0].replace(/^[#*]+\s*/, '').trim();
|
||||
const answer = lines.slice(1).join('\n').trim();
|
||||
if (question && answer) {
|
||||
faqs.push({ question, answer });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return faqs;
|
||||
};
|
||||
|
||||
// Extract block preview from content
|
||||
const extractBlockPreview = (block) => {
|
||||
if (!block) return '';
|
||||
|
||||
const content = block.innerHTML || block.content || '';
|
||||
const text = content.replace(/<[^>]+>/g, '').trim();
|
||||
return truncateText(text, 100);
|
||||
};
|
||||
|
||||
// To text value helper
|
||||
const toTextValue = (value) => {
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return String(value);
|
||||
if (Array.isArray(value)) return value.map(toTextValue).join(', ');
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Export for use in other modules
|
||||
if (typeof window !== 'undefined') {
|
||||
window.WPAWUtils = {
|
||||
escapeHtml,
|
||||
normalizeMessageContent,
|
||||
truncateText,
|
||||
markdownToHtml,
|
||||
extractCodeBlocks,
|
||||
debounce,
|
||||
parseOutlinePlan,
|
||||
parseFaqSchema,
|
||||
extractBlockPreview,
|
||||
toTextValue,
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user