refactor: Cleanup git state - commit all staged changes
Major refactoring cleanup: - Add new controller architecture (class-controller-*.php) - Add new settings-v2 UI (views/settings-v2/) - Add new CSS architecture (agentic-sidebar.css, tokens) - Add esbuild build pipeline (scripts/build.js, package.json) - Add composer dependencies (vendor/) - Add frontend src directory (assets/js/src/index.jsx) - Add documentation files - Remove old/obsolete files (class-settings.php, old CSS) This commits all pending changes from previous refactoring efforts.
This commit is contained in:
377
includes/class-custom-search-api.php
Normal file
377
includes/class-custom-search-api.php
Normal file
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
/**
|
||||
* Custom Search API Integration
|
||||
*
|
||||
* Handles fetching web search results using generic adapters (9Router, Brave, Tavily, Serper)
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
*/
|
||||
|
||||
if (!defined("ABSPATH")) {
|
||||
exit();
|
||||
}
|
||||
|
||||
class WP_Agentic_Writer_Custom_Search_API
|
||||
{
|
||||
/**
|
||||
* Get singleton instance.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @return WP_Agentic_Writer_Custom_Search_API
|
||||
*/
|
||||
public static function get_instance()
|
||||
{
|
||||
static $instance = null;
|
||||
|
||||
if (null === $instance) {
|
||||
$instance = new self();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a web search based on configured search engine.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @param string $query Required. The user's search query.
|
||||
* @param int $count Optional. Number of results to return. Default 3.
|
||||
* @return array|WP_Error Array of formatted search results, or WP_Error on failure.
|
||||
*/
|
||||
public function search($query, $count = 3)
|
||||
{
|
||||
$settings = get_option("wp_agentic_writer_settings", []);
|
||||
$api_key = $settings["brave_search_api_key"] ?? "";
|
||||
$engine = $settings["search_engine"] ?? "9router";
|
||||
$base_url = $settings["custom_search_url"] ?? "";
|
||||
|
||||
// Auto fallback to 9Router if not openrouter/auto
|
||||
if ($engine === "auto" || $engine === "openrouter") {
|
||||
$engine = "9router";
|
||||
}
|
||||
|
||||
if (empty($api_key)) {
|
||||
return new WP_Error(
|
||||
"search_api_key_missing",
|
||||
__(
|
||||
"Search API Key is missing. Please configure it in WP Agentic Writer settings under Tools.",
|
||||
"wp-agentic-writer",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Check cache first to prevent burning API limits
|
||||
$cache_key =
|
||||
"wpaw_custom_search_" . md5($engine . "_" . $query . "_" . $count);
|
||||
$cached_results = get_transient($cache_key);
|
||||
if (false !== $cached_results) {
|
||||
return $cached_results;
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// ADAPTER ROUTING
|
||||
// -------------------------------------------------------------
|
||||
switch ($engine) {
|
||||
case "brave":
|
||||
$results = $this->search_brave(
|
||||
$query,
|
||||
$count,
|
||||
$api_key,
|
||||
$base_url,
|
||||
);
|
||||
break;
|
||||
case "tavily":
|
||||
$results = $this->search_tavily(
|
||||
$query,
|
||||
$count,
|
||||
$api_key,
|
||||
$base_url,
|
||||
);
|
||||
break;
|
||||
case "serper":
|
||||
$results = $this->search_serper(
|
||||
$query,
|
||||
$count,
|
||||
$api_key,
|
||||
$base_url,
|
||||
);
|
||||
break;
|
||||
case "9router":
|
||||
default:
|
||||
$results = $this->search_9router(
|
||||
$query,
|
||||
$count,
|
||||
$api_key,
|
||||
$base_url,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_wp_error($results)) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
if (empty($results)) {
|
||||
return []; // No results found
|
||||
}
|
||||
|
||||
// Cache results for 1 hour to prevent redundant API calls
|
||||
set_transient($cache_key, $results, HOUR_IN_SECONDS);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver: 9Router Proxy (Generic OpenAI-style search endpoint)
|
||||
*/
|
||||
private function search_9router($query, $count, $api_key, $base_url)
|
||||
{
|
||||
$url = !empty($base_url)
|
||||
? $base_url
|
||||
: "http://localhost:20128/v1/search";
|
||||
|
||||
$body = [
|
||||
"model" => "gemini", // generic
|
||||
"query" => $query,
|
||||
"search_type" => "web",
|
||||
"max_results" => absint($count),
|
||||
];
|
||||
|
||||
$response = wp_remote_post($url, [
|
||||
"headers" => [
|
||||
"Content-Type" => "application/json",
|
||||
"Authorization" => "Bearer " . $api_key,
|
||||
],
|
||||
"body" => wp_json_encode($body),
|
||||
"timeout" => 15,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$http_code = wp_remote_retrieve_response_code($response);
|
||||
$body_response = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
if (200 !== $http_code) {
|
||||
return new WP_Error(
|
||||
"search_error",
|
||||
"9Router Search Error: " .
|
||||
($body_response["error"]["message"] ?? "Unknown"),
|
||||
);
|
||||
}
|
||||
|
||||
$formatted = [];
|
||||
if (
|
||||
!empty($body_response["data"]) &&
|
||||
is_array($body_response["data"])
|
||||
) {
|
||||
foreach ($body_response["data"] as $result) {
|
||||
$formatted[] = [
|
||||
"title" => $result["title"] ?? "",
|
||||
"url" => $result["url"] ?? "",
|
||||
"description" =>
|
||||
$result["content"] ?? ($result["description"] ?? ""),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver: Tavily API
|
||||
*/
|
||||
private function search_tavily($query, $count, $api_key, $base_url)
|
||||
{
|
||||
$url = !empty($base_url) ? $base_url : "https://api.tavily.com/search";
|
||||
|
||||
$body = [
|
||||
"api_key" => $api_key,
|
||||
"query" => $query,
|
||||
"search_depth" => "basic",
|
||||
"max_results" => absint($count),
|
||||
];
|
||||
|
||||
$response = wp_remote_post($url, [
|
||||
"headers" => ["Content-Type" => "application/json"],
|
||||
"body" => wp_json_encode($body),
|
||||
"timeout" => 15,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$http_code = wp_remote_retrieve_response_code($response);
|
||||
$body_response = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
if (200 !== $http_code) {
|
||||
return new WP_Error(
|
||||
"search_error",
|
||||
"Tavily Search Error: " .
|
||||
($body_response["detail"] ?? "Unknown"),
|
||||
);
|
||||
}
|
||||
|
||||
$formatted = [];
|
||||
if (
|
||||
!empty($body_response["results"]) &&
|
||||
is_array($body_response["results"])
|
||||
) {
|
||||
foreach ($body_response["results"] as $result) {
|
||||
$formatted[] = [
|
||||
"title" => $result["title"] ?? "",
|
||||
"url" => $result["url"] ?? "",
|
||||
"description" => $result["content"] ?? "",
|
||||
];
|
||||
}
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver: Serper.dev
|
||||
*/
|
||||
private function search_serper($query, $count, $api_key, $base_url)
|
||||
{
|
||||
$url = !empty($base_url)
|
||||
? $base_url
|
||||
: "https://google.serper.dev/search";
|
||||
|
||||
$body = [
|
||||
"q" => $query,
|
||||
"num" => absint($count),
|
||||
];
|
||||
|
||||
$response = wp_remote_post($url, [
|
||||
"headers" => [
|
||||
"Content-Type" => "application/json",
|
||||
"X-API-KEY" => $api_key,
|
||||
],
|
||||
"body" => wp_json_encode($body),
|
||||
"timeout" => 15,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$http_code = wp_remote_retrieve_response_code($response);
|
||||
$body_response = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
if (200 !== $http_code) {
|
||||
return new WP_Error(
|
||||
"search_error",
|
||||
"Serper Search Error: " .
|
||||
($body_response["message"] ?? "Unknown"),
|
||||
);
|
||||
}
|
||||
|
||||
$formatted = [];
|
||||
if (
|
||||
!empty($body_response["organic"]) &&
|
||||
is_array($body_response["organic"])
|
||||
) {
|
||||
foreach ($body_response["organic"] as $result) {
|
||||
$formatted[] = [
|
||||
"title" => $result["title"] ?? "",
|
||||
"url" => $result["link"] ?? "",
|
||||
"description" => $result["snippet"] ?? "",
|
||||
];
|
||||
}
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Driver: Brave Search API
|
||||
*/
|
||||
private function search_brave($query, $count, $api_key, $base_url)
|
||||
{
|
||||
$base = !empty($base_url)
|
||||
? $base_url
|
||||
: "https://api.search.brave.com/res/v1/web/search";
|
||||
$url = add_query_arg(
|
||||
[
|
||||
"q" => urlencode($query),
|
||||
"count" => absint($count),
|
||||
"text_decorations" => 0,
|
||||
"spellcheck" => 1,
|
||||
],
|
||||
$base,
|
||||
);
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Accept-Encoding" => "gzip",
|
||||
"X-Subscription-Token" => $api_key,
|
||||
],
|
||||
"timeout" => 15,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$http_code = wp_remote_retrieve_response_code($response);
|
||||
$body_response = json_decode(wp_remote_retrieve_body($response), true);
|
||||
|
||||
if (200 !== $http_code) {
|
||||
return new WP_Error(
|
||||
"search_error",
|
||||
"Brave Search Error: " .
|
||||
($body_response["message"] ?? "Unknown"),
|
||||
);
|
||||
}
|
||||
|
||||
$formatted = [];
|
||||
if (
|
||||
!empty($body_response["web"]["results"]) &&
|
||||
is_array($body_response["web"]["results"])
|
||||
) {
|
||||
foreach ($body_response["web"]["results"] as $result) {
|
||||
$formatted[] = [
|
||||
"title" => $result["title"] ?? "",
|
||||
"url" => $result["url"] ?? "",
|
||||
"description" => $result["description"] ?? "",
|
||||
];
|
||||
}
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats search results into a markdown context block for LLM System Prompt injection.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @param array $results Search results array.
|
||||
* @param string $query Original query.
|
||||
* @return string Formatted markdown context string.
|
||||
*/
|
||||
public function format_results_for_llm($results, $query)
|
||||
{
|
||||
if (empty($results) || is_wp_error($results)) {
|
||||
return "No reliable web search results found for: {$query}";
|
||||
}
|
||||
|
||||
$markdown = "## LIVE WEB SEARCH CONTEXT\n";
|
||||
$markdown .= "> You successfully searched the internet for: \"{$query}\"\n";
|
||||
$markdown .=
|
||||
"> Please incorporate the following real-time data into your answer:\n\n";
|
||||
|
||||
$counter = 1;
|
||||
foreach ($results as $item) {
|
||||
$markdown .= "{$counter}. **{$item["title"]}**\n";
|
||||
$markdown .= " URL: {$item["url"]}\n";
|
||||
$markdown .= " Summary: {$item["description"]}\n\n";
|
||||
$counter++;
|
||||
}
|
||||
|
||||
$markdown .= "---------------------------\n";
|
||||
|
||||
return $markdown;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user