feat: consolidate docs, backend/session infra, and settings updates
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user