new version
This commit is contained in:
parent
2e01e34278
commit
436e0e57c5
@ -1,45 +1,50 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
$actions = [];
|
||||||
|
class Action {
|
||||||
|
private $function_name;
|
||||||
|
private $no_of_args;
|
||||||
|
private $args_type;
|
||||||
|
|
||||||
|
public function setFunctionName(string $s) { $this->function_name = $s; return $this; }
|
||||||
|
public function setArgumentsTypes(array $a) { $this->args_type = $a; $this->no_of_args = count($a); return $this; }
|
||||||
|
public function getExpectedNoOfArguments() { return $this->no_of_args; }
|
||||||
|
public function getFunctionName() { return $this->function_name; }
|
||||||
|
}
|
||||||
|
function registerAction(Action $a) { global $actions; $actions[$a->getFunctionName()] = $a; return true;}
|
||||||
|
// Register all actions available
|
||||||
|
require_once __DIR__."/actions/index.php";
|
||||||
|
|
||||||
function ResolveActions(string $text) {
|
function ResolveActions(string $text) {
|
||||||
|
global $actions;
|
||||||
|
|
||||||
|
$function_name_regex_format = "@([a-zA-Z_][a-zA-Z0-9_]+)";
|
||||||
|
$arguments_regex_format = "([\w:\/\.-]+)";
|
||||||
|
|
||||||
|
|
||||||
if (preg_match("/@rss_reader ([\w:\/\.]+)/",$text,$matches)) {
|
if (preg_match("/$function_name_regex_format( |$)/",$text,$matches)) {
|
||||||
$url = $matches[1];
|
$function_name = $matches[1];
|
||||||
|
|
||||||
// Initialize cURL
|
if ( !isset($actions[$function_name]) ||
|
||||||
$ch = curl_init();
|
!function_exists($function_name )) return "[[ Action $function_name not available. ]]";
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_URL => $url,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_TIMEOUT => 10,
|
|
||||||
CURLOPT_USERAGENT => "PHP cURL RSS Reader"
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
|
|
||||||
if (curl_errno($ch)) die("cURL error: " . curl_error($ch));
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
// Parse as XML
|
|
||||||
$xml = @simplexml_load_string($response);
|
|
||||||
|
|
||||||
if (!$xml) die("Failed to parse XML.");
|
|
||||||
|
|
||||||
// Find first <title>
|
|
||||||
$firstTitle = '';
|
|
||||||
if (isset($xml->channel->item[0]->title)) {
|
|
||||||
// RSS 2.0 style
|
|
||||||
$firstTitle = (string)$xml->channel->item[0]->title;
|
|
||||||
} elseif (isset($xml->entry[0]->title)) {
|
|
||||||
// Atom style
|
|
||||||
$firstTitle = (string)$xml->entry[0]->title;
|
|
||||||
} elseif (isset($xml->title)) {
|
|
||||||
// fallback
|
|
||||||
$firstTitle = (string)$xml->title;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$text = str_replace($matches[0],$firstTitle,$text);
|
$action = $actions[$function_name];
|
||||||
|
|
||||||
|
// Fetch the arguments
|
||||||
|
$reg_exp = array_fill(0,$action->getExpectedNoOfArguments(),"([\w:\/\.-]+)");
|
||||||
|
array_unshift($reg_exp, $function_name_regex_format);
|
||||||
|
|
||||||
|
if (!preg_match("/".implode(" ",$reg_exp)."/",$text,$matches))
|
||||||
|
return "[[ Action $function_name was passed with wrong number of arguemnts. Expected: ".$a->getExpectedNoOfArguments()." ]]";
|
||||||
|
|
||||||
|
$full_action_requested_string = $matches[0];
|
||||||
|
|
||||||
|
array_shift($matches); // Clip the first whole-string result
|
||||||
|
array_shift($matches); // Clip the function name
|
||||||
|
$arguments = $matches;
|
||||||
|
$actionResult = call_user_func_array($function_name, $arguments);
|
||||||
|
|
||||||
|
$text = str_replace($full_action_requested_string,$actionResult,$text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $text;
|
return $text;
|
||||||
|
|||||||
30
lib/actions/action.sample.php
Normal file
30
lib/actions/action.sample.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SAMPLE ACTION: Say Hello
|
||||||
|
* -------------------------
|
||||||
|
* Demonstrates how to register and implement a simple action.
|
||||||
|
*
|
||||||
|
* Call format:
|
||||||
|
* @sayHello Alice 30
|
||||||
|
*
|
||||||
|
* Expected output:
|
||||||
|
* "Hello Alice, you are 30 years old!"
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1. Register the action with expected argument types
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("sayHello")
|
||||||
|
->setArgumentsTypes(["name", "age"]) // name, age
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. Implement the function
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param int $age
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function sayHello(string $name, int $age): string {
|
||||||
|
return "Hello $name, you are $age years old!";
|
||||||
|
}
|
||||||
6
lib/actions/index.php
Normal file
6
lib/actions/index.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
foreach(scandir(__DIR__) as $filename) {
|
||||||
|
if ($filename == "." || $filename == "..") continue;
|
||||||
|
if (str_ends_with($filename, ".action.php")) include __DIR__."/$filename";
|
||||||
|
}
|
||||||
40
lib/actions/rss_first_title.action.php
Normal file
40
lib/actions/rss_first_title.action.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
registerAction((new Action())->setFunctionName("getFirstTitleFromRSSFeed")->setArgumentsTypes(["url"]));
|
||||||
|
|
||||||
|
function getFirstTitleFromRSSFeed(string $url) {
|
||||||
|
// Initialize cURL
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_USERAGENT => "PHP cURL RSS Reader"
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
|
||||||
|
if (curl_errno($ch)) die("cURL error: " . curl_error($ch));
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Parse as XML
|
||||||
|
$xml = @simplexml_load_string($response);
|
||||||
|
|
||||||
|
if (!$xml) die("Failed to parse XML.");
|
||||||
|
|
||||||
|
// Find first <title>
|
||||||
|
$firstTitle = '';
|
||||||
|
if (isset($xml->channel->item[0]->title)) {
|
||||||
|
// RSS 2.0 style
|
||||||
|
$firstTitle = (string)$xml->channel->item[0]->title;
|
||||||
|
} elseif (isset($xml->entry[0]->title)) {
|
||||||
|
// Atom style
|
||||||
|
$firstTitle = (string)$xml->entry[0]->title;
|
||||||
|
} elseif (isset($xml->title)) {
|
||||||
|
// fallback
|
||||||
|
$firstTitle = (string)$xml->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $firstTitle;
|
||||||
|
}
|
||||||
303
lib/actions/weather.action.php
Normal file
303
lib/actions/weather.action.php
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MODULE: Weather (Open-Meteo, no API key)
|
||||||
|
* ---------------------------------------
|
||||||
|
* Provides three actions:
|
||||||
|
* @weather_today Lisbon
|
||||||
|
* @weather_tomorrow Lisbon
|
||||||
|
* @weather_this_week Lisbon
|
||||||
|
*
|
||||||
|
* Uses Open-Meteo Geocoding + Forecast APIs (free, public).
|
||||||
|
* Returns human-readable summaries (temps °C, precip, wind + weather emoji).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 1) Register actions
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("weather_today")
|
||||||
|
->setArgumentsTypes(["string"]) // location
|
||||||
|
);
|
||||||
|
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("weather_tomorrow")
|
||||||
|
->setArgumentsTypes(["string"]) // location
|
||||||
|
);
|
||||||
|
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("weather_this_week")
|
||||||
|
->setArgumentsTypes(["string"]) // location
|
||||||
|
);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 2) Public action functions
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $place e.g. "Lisbon"
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function weather_today(string $place): string {
|
||||||
|
$ctx = wm_build_context($place);
|
||||||
|
if (isset($ctx['error'])) return $ctx['error'];
|
||||||
|
|
||||||
|
// index 0 = today
|
||||||
|
$i = 0;
|
||||||
|
$d = $ctx['daily'];
|
||||||
|
if (!isset($d['time'][$i])) return "No forecast found for today in {$ctx['name']}.";
|
||||||
|
|
||||||
|
$dateLabel = wm_pretty_date($d['time'][$i], $ctx['tz']);
|
||||||
|
return wm_format_day_summary("Today in {$ctx['name']}", $dateLabel, $d, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $place
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function weather_tomorrow(string $place): string {
|
||||||
|
$ctx = wm_build_context($place);
|
||||||
|
if (isset($ctx['error'])) return $ctx['error'];
|
||||||
|
|
||||||
|
// index 1 = tomorrow
|
||||||
|
$i = 1;
|
||||||
|
$d = $ctx['daily'];
|
||||||
|
if (!isset($d['time'][$i])) return "No forecast found for tomorrow in {$ctx['name']}.";
|
||||||
|
|
||||||
|
$dateLabel = wm_pretty_date($d['time'][$i], $ctx['tz']);
|
||||||
|
return wm_format_day_summary("Tomorrow in {$ctx['name']}", $dateLabel, $d, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $place
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function weather_this_week(string $place): string {
|
||||||
|
$ctx = wm_build_context($place);
|
||||||
|
if (isset($ctx['error'])) return $ctx['error'];
|
||||||
|
|
||||||
|
$d = $ctx['daily'];
|
||||||
|
if (empty($d['time'])) return "No weekly forecast found for {$ctx['name']}.";
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "This week in {$ctx['name']} ({$ctx['tz']}):";
|
||||||
|
|
||||||
|
$n = min(count($d['time']), 7);
|
||||||
|
for ($i = 0; $i < $n; $i++) {
|
||||||
|
$date = $d['time'][$i];
|
||||||
|
$label = wm_pretty_weekday($date, $ctx['tz']); // e.g., Mon 2025-09-15
|
||||||
|
$lines[] = "• " . wm_format_compact_line($label, $d, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
// 3) Internals
|
||||||
|
// ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve place -> lat/lon via Open-Meteo Geocoding and fetch a 7-day daily forecast.
|
||||||
|
* @param string $place
|
||||||
|
* @return array context with:
|
||||||
|
* name (City, Country), tz, daily => [keys matching Open-Meteo daily params]
|
||||||
|
* or ['error' => 'message']
|
||||||
|
*/
|
||||||
|
function wm_build_context(string $place): array {
|
||||||
|
$place = trim($place);
|
||||||
|
if ($place === '') return ['error' => "Please provide a location (e.g., 'Lisbon')."];
|
||||||
|
|
||||||
|
$geo = wm_http_json("https://geocoding-api.open-meteo.com/v1/search?name=" . urlencode($place) . "&count=1&language=en&format=json");
|
||||||
|
if (!$geo || empty($geo['results'][0])) {
|
||||||
|
return ['error' => "Could not find location for '$place'."];
|
||||||
|
}
|
||||||
|
|
||||||
|
$g = $geo['results'][0];
|
||||||
|
$lat = $g['latitude'];
|
||||||
|
$lon = $g['longitude'];
|
||||||
|
$displayName = $g['name'] . (isset($g['country']) ? ", " . $g['country'] : "");
|
||||||
|
|
||||||
|
// Daily parameters
|
||||||
|
$dailyParams = [
|
||||||
|
'weathercode',
|
||||||
|
'temperature_2m_max',
|
||||||
|
'temperature_2m_min',
|
||||||
|
'precipitation_sum',
|
||||||
|
'precipitation_probability_max',
|
||||||
|
'windspeed_10m_max'
|
||||||
|
];
|
||||||
|
|
||||||
|
$forecastUrl = "https://api.open-meteo.com/v1/forecast?"
|
||||||
|
. http_build_query([
|
||||||
|
'latitude' => $lat,
|
||||||
|
'longitude' => $lon,
|
||||||
|
'daily' => implode(',', $dailyParams),
|
||||||
|
'forecast_days' => 7,
|
||||||
|
'timezone' => 'auto'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fc = wm_http_json($forecastUrl);
|
||||||
|
if (!$fc || empty($fc['daily'])) {
|
||||||
|
return ['error' => "Could not fetch forecast for '$displayName'."];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tz = $fc['timezone'] ?? 'local';
|
||||||
|
return [
|
||||||
|
'name' => $displayName,
|
||||||
|
'tz' => $tz,
|
||||||
|
'daily' => $fc['daily'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple GET JSON helper with cURL
|
||||||
|
* @param string $url
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
function wm_http_json(string $url): ?array {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_USERAGENT => "WeatherModule/1.0 (+github.com/you)"
|
||||||
|
]);
|
||||||
|
$res = curl_exec($ch);
|
||||||
|
$err = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($res === false || !$res) return null;
|
||||||
|
$data = json_decode($res, true);
|
||||||
|
return is_array($data) ? $data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a single-day verbose summary.
|
||||||
|
*/
|
||||||
|
function wm_format_day_summary(string $title, string $dateLabel, array $d, int $i): string {
|
||||||
|
$code = $d['weathercode'][$i] ?? null;
|
||||||
|
$desc = wm_weathercode_text($code);
|
||||||
|
$icon = wm_weathercode_emoji($code);
|
||||||
|
|
||||||
|
$tmax = wm_num($d['temperature_2m_max'][$i] ?? null);
|
||||||
|
$tmin = wm_num($d['temperature_2m_min'][$i] ?? null);
|
||||||
|
$pp = wm_num($d['precipitation_probability_max'][$i] ?? null);
|
||||||
|
$pr = wm_num($d['precipitation_sum'][$i] ?? null);
|
||||||
|
$wmax = wm_num($d['windspeed_10m_max'][$i] ?? null);
|
||||||
|
|
||||||
|
$parts = [];
|
||||||
|
$parts[] = "$title — $dateLabel";
|
||||||
|
$parts[] = "$icon $desc";
|
||||||
|
if ($tmax !== null && $tmin !== null) $parts[] = "Temp: min {$tmin}°C / max {$tmax}°C";
|
||||||
|
if ($pp !== null) $parts[] = "Precip prob: {$pp}%";
|
||||||
|
if ($pr !== null) $parts[] = "Precip total: {$pr} mm";
|
||||||
|
if ($wmax !== null) $parts[] = "Wind up to: {$wmax} m/s";
|
||||||
|
|
||||||
|
return implode("\n", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a compact weekly line for a given day.
|
||||||
|
*/
|
||||||
|
function wm_format_compact_line(string $label, array $d, int $i): string {
|
||||||
|
$code = $d['weathercode'][$i] ?? null;
|
||||||
|
$icon = wm_weathercode_emoji($code);
|
||||||
|
|
||||||
|
$tmax = wm_num($d['temperature_2m_max'][$i] ?? null);
|
||||||
|
$tmin = wm_num($d['temperature_2m_min'][$i] ?? null);
|
||||||
|
$pp = wm_num($d['precipitation_probability_max'][$i] ?? null);
|
||||||
|
|
||||||
|
$temp = ($tmin !== null && $tmax !== null) ? "{$tmin}–{$tmax}°C" : "n/a";
|
||||||
|
$prob = ($pp !== null) ? "{$pp}%" : "n/a";
|
||||||
|
|
||||||
|
return sprintf("%s %s %s (rain prob %s)", $label, $icon, $temp, $prob);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weather code -> short text
|
||||||
|
* Ref: https://open-meteo.com/en/docs
|
||||||
|
*/
|
||||||
|
function wm_weathercode_text(?int $code): string {
|
||||||
|
$map = [
|
||||||
|
0 => "Clear sky",
|
||||||
|
1 => "Mainly clear",
|
||||||
|
2 => "Partly cloudy",
|
||||||
|
3 => "Overcast",
|
||||||
|
45 => "Fog",
|
||||||
|
48 => "Depositing rime fog",
|
||||||
|
51 => "Light drizzle",
|
||||||
|
53 => "Moderate drizzle",
|
||||||
|
55 => "Dense drizzle",
|
||||||
|
56 => "Freezing drizzle (light)",
|
||||||
|
57 => "Freezing drizzle (dense)",
|
||||||
|
61 => "Slight rain",
|
||||||
|
63 => "Moderate rain",
|
||||||
|
65 => "Heavy rain",
|
||||||
|
66 => "Freezing rain (light)",
|
||||||
|
67 => "Freezing rain (heavy)",
|
||||||
|
71 => "Slight snow fall",
|
||||||
|
73 => "Moderate snow fall",
|
||||||
|
75 => "Heavy snow fall",
|
||||||
|
77 => "Snow grains",
|
||||||
|
80 => "Rain showers (slight)",
|
||||||
|
81 => "Rain showers (moderate)",
|
||||||
|
82 => "Rain showers (violent)",
|
||||||
|
85 => "Snow showers (slight)",
|
||||||
|
86 => "Snow showers (heavy)",
|
||||||
|
95 => "Thunderstorm (slight/moderate)",
|
||||||
|
96 => "Thunderstorm with slight hail",
|
||||||
|
99 => "Thunderstorm with heavy hail",
|
||||||
|
];
|
||||||
|
return $map[$code] ?? "Unknown conditions";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weather code -> emoji
|
||||||
|
*/
|
||||||
|
function wm_weathercode_emoji(?int $code): string {
|
||||||
|
if ($code === null) return "🌡️";
|
||||||
|
if ($code === 0) return "☀️";
|
||||||
|
if (in_array($code, [1,2])) return "⛅";
|
||||||
|
if ($code === 3) return "☁️";
|
||||||
|
if (in_array($code, [45,48])) return "🌫️";
|
||||||
|
if (in_array($code, [51,53,55,61,63,65,80,81,82])) return "🌧️";
|
||||||
|
if (in_array($code, [66,67])) return "🌨️🧊";
|
||||||
|
if (in_array($code, [71,73,75,77,85,86])) return "❄️";
|
||||||
|
if (in_array($code, [95,96,99])) return "⛈️";
|
||||||
|
return "🌡️";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pretty date label (e.g., 2025-09-09 → Tue 9 Sep)
|
||||||
|
*/
|
||||||
|
function wm_pretty_date(string $isoDate, string $tz): string {
|
||||||
|
try {
|
||||||
|
$dt = new DateTime($isoDate, new DateTimeZone($tz ?: 'UTC'));
|
||||||
|
return $dt->format('D j M Y');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return $isoDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weekday + date (e.g., Tue 2025-09-09)
|
||||||
|
*/
|
||||||
|
function wm_pretty_weekday(string $isoDate, string $tz): string {
|
||||||
|
try {
|
||||||
|
$dt = new DateTime($isoDate, new DateTimeZone($tz ?: 'UTC'));
|
||||||
|
return $dt->format('D Y-m-d');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return $isoDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round number nicely or return null
|
||||||
|
*/
|
||||||
|
function wm_num($v): ?string {
|
||||||
|
if ($v === null) return null;
|
||||||
|
if ($v === '' || !is_numeric($v)) return null;
|
||||||
|
return (string)round((float)$v, 1);
|
||||||
|
}
|
||||||
131
lib/actions/wikipedia.action.php
Normal file
131
lib/actions/wikipedia.action.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ACTIONS: Wikipedia Integration
|
||||||
|
* ------------------------------
|
||||||
|
* @wikiSummary <Title> → returns the lead/summary paragraph
|
||||||
|
* @wikiFullArticle <Title> → returns the full article as plain text
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* @wikiSummary Finland
|
||||||
|
* @wikiFullArticle Bitcoin
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1) Register actions
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("wikiSummary")
|
||||||
|
->setArgumentsTypes(["string"]) // title
|
||||||
|
);
|
||||||
|
|
||||||
|
registerAction(
|
||||||
|
(new Action())
|
||||||
|
->setFunctionName("wikiFullArticle")
|
||||||
|
->setArgumentsTypes(["string"]) // title
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- Helpers ---------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic HTTP GET via cURL.
|
||||||
|
* @return array [int $httpCode, ?string $body, ?string $err]
|
||||||
|
*/
|
||||||
|
function http_get(string $url, int $timeout = 6): array {
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => $timeout,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 4,
|
||||||
|
CURLOPT_USERAGENT => "LLM-Action-Demo/1.0 (+https://example.com)"
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
$err = curl_error($ch) ?: null;
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
return [$code, $body, $err];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Safely pick the first (and only) page object from MediaWiki Action API */
|
||||||
|
function mw_first_page(array $json): ?array {
|
||||||
|
if (!isset($json['query']['pages']) || !is_array($json['query']['pages'])) return null;
|
||||||
|
foreach ($json['query']['pages'] as $page) {
|
||||||
|
return $page; // first element
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Actions ---------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $title Wikipedia article title
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function wikiSummary(string $title): string {
|
||||||
|
$title = str_replace("-"," ",$title);
|
||||||
|
$encoded = rawurlencode($title);
|
||||||
|
$url = "https://en.wikipedia.org/api/rest_v1/page/summary/{$encoded}";
|
||||||
|
|
||||||
|
[$code, $body, $err] = http_get($url);
|
||||||
|
if ($err) return "Error fetching summary for '{$title}': {$err}";
|
||||||
|
if ($code < 200 || $code >= 300 || !$body) return "HTTP {$code}: Failed to fetch summary for '{$title}'.";
|
||||||
|
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
if (isset($data['extract']) && is_string($data['extract']) && $data['extract'] !== '') {
|
||||||
|
return $data['extract'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common “not found” or disambiguation handling
|
||||||
|
if (!empty($data['type']) && $data['type'] === 'disambiguation') {
|
||||||
|
return "‘{$title}’ is a disambiguation page. Try a more specific title.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "No summary found for '{$title}'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns full article as plain text (sections + paragraphs).
|
||||||
|
* Uses MediaWiki Action API with extracts (plaintext).
|
||||||
|
*
|
||||||
|
* @param string $title
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function wikiFullArticle(string $title): string {
|
||||||
|
$title = str_replace("-"," ",$title);
|
||||||
|
$encoded = rawurlencode($title);
|
||||||
|
$url = "https://en.wikipedia.org/w/api.php"
|
||||||
|
. "?action=query"
|
||||||
|
. "&prop=extracts"
|
||||||
|
. "&explaintext=1"
|
||||||
|
. "&exsectionformat=plain"
|
||||||
|
. "&format=json"
|
||||||
|
. "&redirects=1"
|
||||||
|
. "&titles={$encoded}";
|
||||||
|
|
||||||
|
[$code, $body, $err] = http_get($url, 12);
|
||||||
|
if ($err) return "Error fetching article for '{$title}': {$err}";
|
||||||
|
if ($code < 200 || $code >= 300 || !$body) return "HTTP {$code}: Failed to fetch article for '{$title}'.";
|
||||||
|
|
||||||
|
$json = json_decode($body, true);
|
||||||
|
$page = mw_first_page($json);
|
||||||
|
|
||||||
|
if (!$page) {
|
||||||
|
return "No article found for '{$title}'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($page['missing'])) {
|
||||||
|
return "No article found for '{$title}'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($page['extract']) || trim($page['extract']) === '') {
|
||||||
|
return "Article exists but has no plain-text extract for '{$title}'.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally trim extremely long responses (LLM-friendly)
|
||||||
|
$maxChars = 40000; // adjust for your pipeline
|
||||||
|
$text = $page['extract'];
|
||||||
|
if (mb_strlen($text, 'UTF-8') > $maxChars) {
|
||||||
|
$text = mb_substr($text, 0, $maxChars, 'UTF-8') . "\n\n[Truncated]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
7
tests/01_test.php
Normal file
7
tests/01_test.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
// TEST 01: Simple query.
|
||||||
|
|
||||||
|
$out = LlamaCli("What is the capital of France?"); print_r($out);
|
||||||
|
|
||||||
|
|
||||||
11
tests/02_previous_background_info.php
Normal file
11
tests/02_previous_background_info.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
|
||||||
|
// TEST 02: Simple query with previous background conversation
|
||||||
|
|
||||||
|
$out = LlamaCli("Portugal?","",["previousConversation"=>[["role"=>"user","content"=>"What is the capital of France?"],["role"=>"assistant","content"=>"Paris."]]]);
|
||||||
|
print_r($out);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
8
tests/03_system_major_role.php
Normal file
8
tests/03_system_major_role.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
// TEST 03: Simple query with SYSTEM PRIMING message
|
||||||
|
|
||||||
|
$summary = LlamaCli(file_get_contents("test_input_data.3.txt"),"You are an executive assistant that will summarize english text in 1 paragraph");
|
||||||
|
$title = LlamaCli($summary[0],"You are an talented journalist that will produce a provocative headline title based on a summary of a text");
|
||||||
|
|
||||||
|
print_r([$summary,$title]);
|
||||||
40
tests/04_RecursiveQueries_ProduceLongContent.php
Normal file
40
tests/04_RecursiveQueries_ProduceLongContent.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
TEST 04: RECURSIVE QUERY, to produce long-content from short-one. LONG-WRITING. ( GOAL: Learning from model deep knowledge )
|
||||||
|
|
||||||
|
// -------- BOOK EXAMPLE ---------
|
||||||
|
|
||||||
|
// 0. CATEGORY
|
||||||
|
$category = "learning finnish";
|
||||||
|
$expert = "teacher";
|
||||||
|
$total_no_of_chapters = 3;
|
||||||
|
|
||||||
|
|
||||||
|
// 1. TITLE
|
||||||
|
$book_title = LlamaCli_raw("Write me a title for my new book about $category. Output title only, no options, no chat.","You are an expert $expert.");
|
||||||
|
|
||||||
|
|
||||||
|
// 2. CHAPTER TITLES
|
||||||
|
$sys_msg = "You are an expert $expert writing a book with the title $book_title with $total_no_of_chapters chapters";
|
||||||
|
$chapters = []; $conv_hist = [];
|
||||||
|
|
||||||
|
$msg = "Write me the title of my first chapter. Output title only, no options, no chat.";
|
||||||
|
$conv_hist[] = ["role"=>"user","content"=>$msg];
|
||||||
|
$conv_hist[] = ["role"=>"assistant","content"=>($chapters[] = LlamaCli_raw($msg,$sys_msg,["debug_level"=>0]))];
|
||||||
|
|
||||||
|
for($no=1; $no < $total_no_of_chapters; $no++) {
|
||||||
|
$msg = "Write me the title of chapter number $no.";
|
||||||
|
$conv_hist[] = ["role"=>"user","content"=>$msg];
|
||||||
|
$conv_hist[] = ["role"=>"assistant","content"=>($chapters[] = LlamaCli_raw($msg,$sys_msg,["previousConversation"=>$conv_hist, "debug_level"=>0]))];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. CHAPTER CONTENTS
|
||||||
|
$content = [];
|
||||||
|
foreach($chapters as $chapter_title)
|
||||||
|
$content[$chapter_title] = LlamaCli_raw(
|
||||||
|
"Write 2 paragraphs for a chapter titled $chapter_title in a book called $book_title. Output content only, no chat.",
|
||||||
|
"You are an expert $expert."
|
||||||
|
);
|
||||||
|
|
||||||
|
print_r([$book_title, $content]);
|
||||||
12
tests/05_ChainedQuery_ProduceShortContentFromLongContent.php
Normal file
12
tests/05_ChainedQuery_ProduceShortContentFromLongContent.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
// TEST 05: CHAINED QUERY, to parse long-content and produce short one. SUMMARIZATION. ( GOAL: )
|
||||||
|
|
||||||
|
$summaries = [];
|
||||||
|
$content = file_get_contents("test_input_data.2.txt");
|
||||||
|
$chunks = array_chunk(explode("\n",$content),80);
|
||||||
|
|
||||||
|
foreach($chunks as $chunk) {
|
||||||
|
$summaries[] = LlamaCli_raw(implode("\n",$chunk),"You are a legal executive assistant that will summarize a conversation in english between ex-husband and wife in 1 paragraph");
|
||||||
|
}
|
||||||
|
print_r($summaries);
|
||||||
12
tests/06_ResolveFetchingActions.php
Normal file
12
tests/06_ResolveFetchingActions.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php require_once __DIR__."/../LlamaCli.func.php";
|
||||||
|
|
||||||
|
// TEST 06: Resolve FETCHING ACTIONS
|
||||||
|
|
||||||
|
//echo LlamaCli_raw("translate this title to portuguese and finnish and quote the original in english: @getFirstTitleFromRSSFeed https://www.hs.fi/rss/tuoreimmat.xml","");
|
||||||
|
//echo LlamaCli_raw("translate this wiki summary to portuguse and show the result also in english @wikiFullArticle Bitcoin","");
|
||||||
|
|
||||||
|
// echo LlamaCli_raw("Give me a one paragraph description of what you know about Pablo Escobar","");
|
||||||
|
// echo LlamaCli_raw("did you know this about Pablo Escobar? @wikiSummary Pablo-Escobar \n\n\nlet me know your impressions.","");
|
||||||
|
|
||||||
|
echo LlamaCli_raw("Based on this forecasted weather for tomorrow @weather_tomorrow Lisbon give me a plan on what to do, since you know I'm on vacation","");
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user