sidebar = $sidebar; } /** * Handle block refine request. * * @since 0.1.0 * @param WP_REST_Request $request REST request. * @return WP_REST_Response|WP_Error|void Response or streaming. */ public function handle_block_refine($request) { $params = $request->get_json_params(); $block_id = $params["blockId"] ?? ""; $block_type = $params["blockType"] ?? ""; $block_content = $params["blockContent"] ?? ""; $refinement_request = $params["refinementRequest"] ?? ""; $article_context = $params["articleContext"] ?? []; $post_id = $params["postId"] ?? 0; $stream = $params["stream"] ?? false; $chat_history = $params["chatHistory"] ?? []; if (empty($block_content) || empty($refinement_request)) { return new WP_Error( "missing_data", __( "Block content and refinement request are required.", "wp-agentic-writer", ), ["status" => 400], ); } // Check post permission BEFORE reading post data. if ($post_id > 0 && !$this->sidebar->check_post_permission($post_id)) { return new WP_Error( "forbidden", __( "You do not have permission to edit this post.", "wp-agentic-writer", ), ["status" => 403], ); } // Only read post config after permission check. $post_config = $this->sidebar->resolve_post_config_from_request( $params, $post_id, ); // If streaming is requested, use streaming response. if ($stream) { return $this->sidebar->stream_block_refine( $block_id, $block_type, $block_content, $refinement_request, $article_context, $post_id, $post_config, ); } $provider_result = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( "refinement", ); $provider = $provider_result->provider; // Build context from article structure. $context_str = "\n\nArticle Context:\n"; $context_str .= "Title: " . ($article_context["title"] ?? "Unknown") . "\n"; if (!empty($article_context["previousBlock"])) { $context_str .= "Previous section: " . $article_context["previousBlock"]["heading"] . "\n"; } $context_str .= "Current block type: " . $block_type . "\n"; $context_str .= "Current content:\n" . $block_content . "\n"; if (!empty($article_context["nextBlock"])) { $context_str .= "Next section: " . $article_context["nextBlock"]["heading"] . "\n"; } // Add chat history context if available $chat_history_context = ""; if (!empty($chat_history) && is_array($chat_history)) { $chat_history_context = "\n\n--- ORIGINAL CONVERSATION ---\n"; foreach ($chat_history as $msg) { $role = isset($msg["role"]) ? ucfirst($msg["role"]) : "Unknown"; $content = isset($msg["content"]) ? $msg["content"] : ""; if ( !empty($content) && "system" !== strtolower($msg["role"] ?? "") ) { $chat_history_context .= "{$role}: {$content}\n\n"; } } $chat_history_context .= "--- END CONVERSATION ---\n"; $chat_history_context .= "This shows the original discussion that led to this article."; } // Add plan context if available $plan_context = ""; $plan = get_post_meta($post_id, "_wpaw_plan", true); if (!empty($plan) && is_array($plan)) { $plan_context = "\n\nOriginal Article Outline:\n"; if (!empty($plan["title"])) { $plan_context .= "Title: {$plan["title"]}\n"; } if (!empty($plan["sections"]) && is_array($plan["sections"])) { foreach ($plan["sections"] as $section) { $heading = $section["heading"] ?? ($section["title"] ?? ""); if (!empty($heading)) { $plan_context .= "- {$heading}\n"; } } } } $system_prompt = "You are an expert editor helping refine a specific section of an article. {$context_str} {$plan_context} {$chat_history_context} USER REQUEST: {$refinement_request} TASK: Refine the current section content considering: 1. How it fits into the overall article flow 2. Consistency with surrounding sections 3. The original intent from the conversation and outline 4. The user's specific refinement request 5. Maintaining the original key information Provide the refined content in Markdown format. Keep the same block type (paragraph, heading, list, etc.)."; $messages = [ [ "role" => "system", "content" => $system_prompt, ], [ "role" => "user", "content" => "Please refine this content.", ], ]; $response = $provider->chat( $messages, ["temperature" => 0.7], "execution", ); if (is_wp_error($response)) { return new WP_Error( "refinement_error", $response->get_error_message(), ["status" => 500], ); } // Parse refined content as Gutenberg blocks. $blocks = WP_Agentic_Writer_Markdown_Parser::parse( $response["content"], ); // MEMANTO: Remember block refinement. do_action( "wpaw_memanto_block_refined", $post_id, $block_id, $refinement_request, ); // Track cost (always track for debugging). $this->sidebar->track_ai_cost( $post_id, $response["model"] ?? "", "block_refinement", $response["input_tokens"] ?? 0, $response["output_tokens"] ?? 0, $response["cost"] ?? 0, $provider_result, "", "success", ); return new WP_REST_Response( [ "blocks" => $blocks, "blockId" => $block_id, "cost" => $response["cost"] ?? 0, "provider_metadata" => $this->sidebar->build_provider_metadata( $provider_result, $response["model"] ?? "", ), ], 200, ); } /** * Handle regenerate block request. * * @since 0.1.0 * @param WP_REST_Request $request REST request. * @return WP_REST_Response|WP_Error Response. */ public function handle_regenerate_block($request) { // Check rate limit. $rate_limit = WPAW_Rate_Limiter::check("refine"); if (is_wp_error($rate_limit)) { return $rate_limit; } $params = $request->get_json_params(); $block_content = $params["blockContent"] ?? ""; $context = $params["context"] ?? ""; $post_id = $params["postId"] ?? 0; if (empty($block_content)) { return new WP_Error( "no_content", __("Block content is required.", "wp-agentic-writer"), ["status" => 400], ); } // Check post permission if post_id is provided. if ($post_id > 0 && !$this->sidebar->check_post_permission($post_id)) { return new WP_Error( "forbidden", __( "You do not have permission to edit this post.", "wp-agentic-writer", ), ["status" => 403], ); } // Get provider for writing task. $provider_result = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( "writing", ); $provider = $provider_result->provider; $messages = [ [ "role" => "system", "content" => "You are an expert technical writer. Rewrite the provided content to improve it while maintaining the same meaning and key information.", ], [ "role" => "user", "content" => "Context: {$context}\n\nOriginal content:\n\n{$block_content}\n\nPlease rewrite this content.", ], ]; $response = $provider->chat( $messages, ["temperature" => 0.8], "execution", ); if (is_wp_error($response)) { // Track failed attempt for observability. $this->sidebar->track_ai_cost( $post_id, WPAW_Model_Registry::get_default_model("writing"), "regeneration", 0, 0, 0, $provider_result, "", "error", ); return new WP_Error( "regeneration_error", $response->get_error_message(), ["status" => 500], ); } // Track cost (always track for debugging). $this->sidebar->track_ai_cost( $post_id, $response["model"] ?? "", "regeneration", $response["input_tokens"] ?? 0, $response["output_tokens"] ?? 0, $response["cost"] ?? 0, $provider_result, "", "success", ); return new WP_REST_Response( [ "content" => $response["content"], "cost" => $response["cost"] ?? 0, "provider_metadata" => $this->sidebar->build_provider_metadata( $provider_result, $response["model"] ?? "", ), ], 200, ); } /** * Handle refine from chat request. * * @since 0.1.0 * @param WP_REST_Request $request REST request. * @return void Streams response to client. */ public function handle_refine_from_chat($request) { $params = $request->get_json_params(); $message = $params["topic"] ?? ""; $selected_block = $params["selectedBlockClientId"] ?? ""; $post_id = $params["postId"] ?? 0; $session_id = $this->sidebar->resolve_or_create_session_id( $params["sessionId"] ?? "", $post_id, ); $blocks_to_refine = $params["blocksToRefine"] ?? []; $all_blocks = $params["allBlocks"] ?? []; $diff_plan = !empty($params["diffPlan"]); $selective_refine = !empty($params["selectiveRefine"]); $audit_context = isset($params["auditContext"]) && is_array($params["auditContext"]) ? $params["auditContext"] : []; if (empty($blocks_to_refine) || !is_array($blocks_to_refine)) { return new WP_Error( "no_blocks_mentioned", __( "No valid blocks found to refine. Try mentioning blocks like @this, @previous, or specific blocks like @paragraph-1", "wp-agentic-writer", ), ["status" => 400], ); } // Check post permission BEFORE reading post data. if ($post_id > 0 && !$this->sidebar->check_post_permission($post_id)) { return new WP_Error( "forbidden", __( "You do not have permission to edit this post.", "wp-agentic-writer", ), ["status" => 403], ); } // Only read post config after permission check. $post_config = $this->sidebar->resolve_post_config_from_request( $params, $post_id, ); // Stream refinement for each mentioned block $this->sidebar->stream_refinement_from_chat( $blocks_to_refine, $message, $selected_block, $post_id, $all_blocks, $diff_plan, $post_config, $session_id, $selective_refine, $audit_context, ); // Return early to avoid REST API trying to send headers after streaming exit(); } }