feat: consolidate docs, backend/session infra, and settings updates

This commit is contained in:
Dwindi Ramadhana
2026-05-28 00:58:20 +07:00
parent 2424acf726
commit 44e06eed88
102 changed files with 35423 additions and 11181 deletions

View File

@@ -78,6 +78,7 @@ class WP_Agentic_Writer_Settings_V2 {
wp_enqueue_style( 'wpaw-agentic-variables', WP_AGENTIC_WRITER_URL . 'assets/css/agentic-variables.css', array(), WP_AGENTIC_WRITER_VERSION );
wp_enqueue_style( 'wpaw-agentic-bootstrap-custom', WP_AGENTIC_WRITER_URL . 'assets/css/agentic-bootstrap-custom.css', array( 'bootstrap', 'wpaw-agentic-variables' ), WP_AGENTIC_WRITER_VERSION );
wp_enqueue_style( 'wpaw-agentic-components', WP_AGENTIC_WRITER_URL . 'assets/css/agentic-components.css', array( 'wpaw-agentic-variables' ), WP_AGENTIC_WRITER_VERSION );
wp_enqueue_style( 'wpaw-agentic-workflow', WP_AGENTIC_WRITER_URL . 'assets/css/agentic-workflow.css', array( 'wpaw-agentic-components' ), WP_AGENTIC_WRITER_VERSION );
// Legacy plugin styles
$css_admin_path = WP_AGENTIC_WRITER_DIR . 'assets/css/admin-v2.css';
@@ -101,14 +102,15 @@ class WP_Agentic_Writer_Settings_V2 {
'nonce' => wp_create_nonce( 'wpaw_settings' ),
'models' => $this->get_models_for_select(),
'currentModels' => array(
'planning' => $settings['planning_model'] ?? 'google/gemini-2.0-flash-exp:free',
'writing' => $settings['writing_model'] ?? ( $settings['execution_model'] ?? 'anthropic/claude-3.5-sonnet' ),
'execution' => $settings['writing_model'] ?? ( $settings['execution_model'] ?? 'anthropic/claude-3.5-sonnet' ),
'clarity' => $settings['clarity_model'] ?? 'google/gemini-2.0-flash-exp:free',
'refinement' => $settings['refinement_model'] ?? 'anthropic/claude-3.5-sonnet',
'chat' => $settings['chat_model'] ?? 'google/gemini-2.0-flash-exp:free',
'image' => $settings['image_model'] ?? 'openai/gpt-4o',
'planning' => $settings['planning_model'] ?? WPAW_Model_Registry::get_default_model( 'planning' ),
'writing' => $settings['writing_model'] ?? ( $settings['execution_model'] ?? WPAW_Model_Registry::get_default_model( 'writing' ) ),
'execution' => $settings['writing_model'] ?? ( $settings['execution_model'] ?? WPAW_Model_Registry::get_default_model( 'writing' ) ),
'clarity' => $settings['clarity_model'] ?? WPAW_Model_Registry::get_default_model( 'clarity' ),
'refinement' => $settings['refinement_model'] ?? WPAW_Model_Registry::get_default_model( 'refinement' ),
'chat' => $settings['chat_model'] ?? WPAW_Model_Registry::get_default_model( 'chat' ),
'image' => $settings['image_model'] ?? WPAW_Model_Registry::get_default_model( 'image' ),
),
'presets' => $this->get_model_presets(),
'i18n' => array(
'refreshing' => __( 'Refreshing...', 'wp-agentic-writer' ),
'refreshModels' => __( 'Refresh Models', 'wp-agentic-writer' ),
@@ -122,6 +124,44 @@ class WP_Agentic_Writer_Settings_V2 {
) );
}
/**
* Get curated model presets (centralized source).
*
* These are intentional product decisions for different budget tiers.
* Model IDs may differ from registry defaults to balance cost/quality.
*
* @since 0.2.0
* @return array Curated model presets.
*/
public function get_model_presets() {
return array(
'budget' => array(
'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' => array(
'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' => array(
'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',
),
);
}
/**
* Get models for select dropdowns.
*
@@ -188,26 +228,26 @@ class WP_Agentic_Writer_Settings_V2 {
return array(
'planning' => array(
'recommended' => array(
array( 'id' => 'google/gemini-2.0-flash-exp:free', 'name' => 'Google Gemini 2.0 Flash' ),
array( 'id' => WPAW_Model_Registry::get_default_model( 'planning' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_default_model( 'planning' ) ) ),
),
'all' => array(
array( 'id' => 'google/gemini-2.0-flash-exp:free', 'name' => 'Google Gemini 2.0 Flash' ),
array( 'id' => WPAW_Model_Registry::get_default_model( 'planning' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_default_model( 'planning' ) ) ),
),
),
'execution' => array(
'recommended' => array(
array( 'id' => 'anthropic/claude-3.5-sonnet', 'name' => 'Anthropic Claude 3.5 Sonnet' ),
array( 'id' => WPAW_Model_Registry::get_fallback_model( 'execution' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_fallback_model( 'execution' ) ) ),
),
'all' => array(
array( 'id' => 'anthropic/claude-3.5-sonnet', 'name' => 'Anthropic Claude 3.5 Sonnet' ),
array( 'id' => WPAW_Model_Registry::get_fallback_model( 'execution' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_fallback_model( 'execution' ) ) ),
),
),
'image' => array(
'recommended' => array(
array( 'id' => 'openai/gpt-4o', 'name' => 'OpenAI GPT-4o' ),
array( 'id' => WPAW_Model_Registry::get_default_model( 'image' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_default_model( 'image' ) ) ),
),
'all' => array(
array( 'id' => 'openai/gpt-4o', 'name' => 'OpenAI GPT-4o' ),
array( 'id' => WPAW_Model_Registry::get_default_model( 'image' ), 'name' => WPAW_Model_Registry::get_model_display_name( WPAW_Model_Registry::get_default_model( 'image' ) ) ),
),
),
);
@@ -224,9 +264,9 @@ class WP_Agentic_Writer_Settings_V2 {
// Handle flat model list from OpenRouter
if ( ! empty( $models ) && array_keys( $models ) === range( 0, count( $models ) - 1 ) ) {
$settings = get_option( 'wp_agentic_writer_settings', array() );
$planning_id = $settings['planning_model'] ?? 'google/gemini-2.0-flash-exp:free';
$execution_id = $settings['execution_model'] ?? 'anthropic/claude-3.5-sonnet';
$image_id = $settings['image_model'] ?? 'black-forest-labs/flux-schnell';
$planning_id = $settings['planning_model'] ?? WPAW_Model_Registry::get_default_model( 'planning' );
$execution_id = $settings['execution_model'] ?? WPAW_Model_Registry::get_default_model( 'execution' );
$image_id = $settings['image_model'] ?? WPAW_Model_Registry::get_default_model( 'image' );
$text_models = array();
$image_models = array();
@@ -266,10 +306,10 @@ class WP_Agentic_Writer_Settings_V2 {
}
}
$chat_id = $settings['chat_model'] ?? 'google/gemini-2.0-flash-exp:free';
$clarity_id = $settings['clarity_model'] ?? 'google/gemini-2.0-flash-exp:free';
$refinement_id = $settings['refinement_model'] ?? 'anthropic/claude-3.5-sonnet';
$writing_id = $settings['writing_model'] ?? ( $settings['execution_model'] ?? 'anthropic/claude-3.5-sonnet' );
$chat_id = $settings['chat_model'] ?? WPAW_Model_Registry::get_default_model( 'chat' );
$clarity_id = $settings['clarity_model'] ?? WPAW_Model_Registry::get_default_model( 'clarity' );
$refinement_id = $settings['refinement_model'] ?? WPAW_Model_Registry::get_default_model( 'refinement' );
$writing_id = $settings['writing_model'] ?? ( $settings['execution_model'] ?? WPAW_Model_Registry::get_default_model( 'writing' ) );
// Add currently selected models to text_models if not already present
$current_model_ids = array( $planning_id, $execution_id, $chat_id, $clarity_id, $refinement_id, $writing_id );
@@ -641,81 +681,80 @@ class WP_Agentic_Writer_Settings_V2 {
}
$where_clause = implode( ' AND ', $where );
// Get total count
$total_items = $wpdb->get_var( "SELECT COUNT(*) FROM {$table_name} WHERE {$where_clause}" );
// Get total count of distinct posts
$total_items = $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$table_name} WHERE {$where_clause}" );
$total_pages = ceil( $total_items / $per_page );
// Get all records grouped by post
$all_records = $wpdb->get_results(
"SELECT * FROM {$table_name} WHERE {$where_clause} ORDER BY post_id DESC, created_at DESC"
// Optimized: Get grouped records with aggregation in SQL.
// This pushes grouping and ordering to the database instead of PHP.
$grouped_records_sql = $wpdb->get_results(
$wpdb->prepare(
"SELECT
post_id,
SUM(cost) as total_cost,
COUNT(*) as call_count,
MAX(created_at) as last_call
FROM {$table_name}
WHERE {$where_clause}
GROUP BY post_id
ORDER BY total_cost DESC
LIMIT %d OFFSET %d",
$per_page,
$offset
),
ARRAY_A
);
// Group records by post_id
$grouped_records = array();
foreach ( $all_records as $record ) {
$post_id = $record->post_id;
if ( ! isset( $grouped_records[ $post_id ] ) ) {
$post_title = get_the_title( $post_id );
if ( ! $post_title && $post_id > 0 ) {
$post_title = sprintf( __( '[Removed Post #%d]', 'wp-agentic-writer' ), $post_id );
$post_link = '';
} elseif ( $post_id > 0 ) {
$post_link = get_edit_post_link( $post_id, 'raw' );
} else {
$post_title = __( 'System/Other', 'wp-agentic-writer' );
$post_link = '';
}
// Build grouped records with post details
$formatted_records = array();
$post_ids = array();
$grouped_records[ $post_id ] = array(
'post_id' => $post_id,
'post_title' => $post_title,
'post_link' => $post_link,
'total_cost' => 0,
'call_count' => 0,
'details' => array(),
);
foreach ( $grouped_records_sql as $row ) {
$post_id = (int) $row['post_id'];
$post_ids[] = $post_id;
if ( $post_id > 0 ) {
$post_title = get_the_title( $post_id );
if ( ! $post_title ) {
$post_title = sprintf( __( '[Removed Post #%d]', 'wp-agentic-writer' ), $post_id );
$post_link = '';
} else {
$post_link = get_edit_post_link( $post_id, 'raw' );
}
} else {
$post_title = __( 'System/Other', 'wp-agentic-writer' );
$post_link = '';
}
$grouped_records[ $post_id ]['total_cost'] += (float) $record->cost;
$grouped_records[ $post_id ]['call_count']++;
$grouped_records[ $post_id ]['details'][] = array(
'id' => $record->id,
'created_at' => date_i18n( 'Y-m-d H:i:s', strtotime( $record->created_at ) ),
'model' => $record->model,
'action' => ucfirst( str_replace( '_', ' ', $record->action ) ),
'input_tokens' => number_format( $record->input_tokens ),
'output_tokens' => number_format( $record->output_tokens ),
'cost' => number_format( $record->cost, 4 ),
$formatted_records[] = array(
'post_id' => $post_id,
'post_title' => $post_title,
'post_link' => $post_link,
'total_cost' => number_format( (float) $row['total_cost'], 4 ),
'call_count' => (int) $row['call_count'],
'last_call' => date_i18n( 'Y-m-d H:i:s', strtotime( $row['last_call'] ) ),
'details' => array(), // Lazy-loaded on expand
);
}
// Convert to indexed array and format
$formatted_records = array();
foreach ( $grouped_records as $group ) {
$group['total_cost'] = number_format( $group['total_cost'], 4 );
$formatted_records[] = $group;
}
// Get details for visible posts only (on-demand loading).
// For better performance, we skip details here and let frontend request them.
// The details can be loaded via a separate endpoint when user expands a row.
// Paginate grouped records
$total_items = count( $formatted_records );
$total_pages = ceil( $total_items / $per_page );
$formatted_records = array_slice( $formatted_records, $offset, $per_page );
// Get summary stats
$cost_tracker = WP_Agentic_Writer_Cost_Tracker::get_instance();
$total_all_time = $wpdb->get_var( "SELECT SUM(cost) FROM {$table_name}" );
// Get summary stats (all-time aggregation in SQL)
$total_all_time = $wpdb->get_var( "SELECT COALESCE(SUM(cost), 0) FROM {$table_name}" );
$monthly_total = $cost_tracker->get_monthly_total();
$today_total = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(cost) FROM {$table_name} WHERE DATE(created_at) = %s",
"SELECT COALESCE(SUM(cost), 0) FROM {$table_name} WHERE DATE(created_at) = %s",
current_time( 'Y-m-d' )
)
);
$total_posts = $wpdb->get_var( "SELECT COUNT(DISTINCT post_id) FROM {$table_name} WHERE post_id > 0" );
$avg_per_post = $total_posts > 0 ? $total_all_time / $total_posts : 0;
// Get filter options
$models = $wpdb->get_col( "SELECT DISTINCT model FROM {$table_name} ORDER BY model" );
// Get filter options (distinct values from DB)
$models = $wpdb->get_col( "SELECT DISTINCT model FROM {$table_name} ORDER BY model LIMIT 100" );
$types = $wpdb->get_col( "SELECT DISTINCT action FROM {$table_name} ORDER BY action" );
wp_send_json_success( array(
@@ -884,7 +923,7 @@ class WP_Agentic_Writer_Settings_V2 {
// Test API connection by making a simple request
$response = wp_remote_get(
'https://openrouter.ai/api/v1/models',
'https://openrouter.ai/api/v1/models?output_modalities=all',
array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
@@ -985,13 +1024,13 @@ class WP_Agentic_Writer_Settings_V2 {
$sanitized['brave_search_api_key'] = trim( $input['brave_search_api_key'] );
}
// Sanitize model names (6 models)
$sanitized['chat_model'] = sanitize_text_field( $input['chat_model'] ?? 'google/gemini-2.5-flash' );
$sanitized['clarity_model'] = sanitize_text_field( $input['clarity_model'] ?? 'google/gemini-2.5-flash' );
$sanitized['planning_model'] = sanitize_text_field( $input['planning_model'] ?? 'google/gemini-2.5-flash' );
$sanitized['writing_model'] = sanitize_text_field( $input['writing_model'] ?? 'anthropic/claude-3.5-sonnet' );
$sanitized['refinement_model'] = sanitize_text_field( $input['refinement_model'] ?? 'anthropic/claude-3.5-sonnet' );
$sanitized['image_model'] = sanitize_text_field( $input['image_model'] ?? 'openai/gpt-4o' );
// Sanitize model names (6 models) - using model registry for defaults
$sanitized['chat_model'] = sanitize_text_field( $input['chat_model'] ?? WPAW_Model_Registry::get_default_model( 'chat' ) );
$sanitized['clarity_model'] = sanitize_text_field( $input['clarity_model'] ?? WPAW_Model_Registry::get_default_model( 'clarity' ) );
$sanitized['planning_model'] = sanitize_text_field( $input['planning_model'] ?? WPAW_Model_Registry::get_default_model( 'planning' ) );
$sanitized['writing_model'] = sanitize_text_field( $input['writing_model'] ?? WPAW_Model_Registry::get_default_model( 'writing' ) );
$sanitized['refinement_model'] = sanitize_text_field( $input['refinement_model'] ?? WPAW_Model_Registry::get_default_model( 'refinement' ) );
$sanitized['image_model'] = sanitize_text_field( $input['image_model'] ?? WPAW_Model_Registry::get_default_model( 'image' ) );
// Legacy support: map execution_model to writing_model
if ( isset( $input['execution_model'] ) && ! isset( $input['writing_model'] ) ) {
@@ -1103,15 +1142,15 @@ class WP_Agentic_Writer_Settings_V2 {
* @return array View data.
*/
private function prepare_view_data( $settings ) {
// Extract settings (6 models)
// Extract settings (6 models) using model registry for defaults
$api_key = $settings['openrouter_api_key'] ?? '';
$brave_search_api_key = $settings['brave_search_api_key'] ?? '';
$chat_model = $settings['chat_model'] ?? 'google/gemini-2.5-flash';
$clarity_model = $settings['clarity_model'] ?? 'google/gemini-2.5-flash';
$planning_model = $settings['planning_model'] ?? 'google/gemini-2.5-flash';
$writing_model = $settings['writing_model'] ?? ( $settings['execution_model'] ?? 'anthropic/claude-3.5-sonnet' );
$refinement_model = $settings['refinement_model'] ?? 'anthropic/claude-3.5-sonnet';
$image_model = $settings['image_model'] ?? 'openai/gpt-4o';
$chat_model = $settings['chat_model'] ?? WPAW_Model_Registry::get_default_model( 'chat' );
$clarity_model = $settings['clarity_model'] ?? WPAW_Model_Registry::get_default_model( 'clarity' );
$planning_model = $settings['planning_model'] ?? WPAW_Model_Registry::get_default_model( 'planning' );
$writing_model = $settings['writing_model'] ?? ( $settings['execution_model'] ?? WPAW_Model_Registry::get_default_model( 'writing' ) );
$refinement_model = $settings['refinement_model'] ?? WPAW_Model_Registry::get_default_model( 'refinement' );
$image_model = $settings['image_model'] ?? WPAW_Model_Registry::get_default_model( 'image' );
$web_search_enabled = $settings['web_search_enabled'] ?? false;
$search_engine = $settings['search_engine'] ?? 'auto';
$search_depth = $settings['search_depth'] ?? 'medium';