Files
html/api/skills-explorer-api.php
opus 81ac42251b
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
AUTO-BACKUP 20260419-2150
2026-04-19 21:50:03 +02:00

230 lines
8.7 KiB
PHP

<?php
/**
* WEVIA Skills Explorer API · multi-source skills discovery
* Sources: /skills/ disk + Qdrant weval_skills + tool-registry + arena
* Opus Yacine · 19avr2026
*/
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
header('Access-Control-Allow-Origin: *');
$action = $_GET['action'] ?? 'sources';
$QDRANT = 'http://127.0.0.1:6333';
function qdrant_call($url, $method = 'GET', $data = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$r = curl_exec($ch);
curl_close($ch);
return json_decode($r ?: '{}', true);
}
$result = ['ok' => true, 'action' => $action, 'ts' => date('c')];
if ($action === 'sources') {
// Source 1: Disk /skills/ (with and without SKILL.md)
$all_dirs = glob('/var/www/html/skills/*', GLOB_ONLYDIR);
$disk_skills = [];
$big_catalogs = [];
foreach ($all_dirs as $dir) {
$name = basename($dir);
$skill_md = "$dir/SKILL.md";
$has_md = file_exists($skill_md);
$desc = '';
$sub_count = 0;
if ($has_md) {
$content = @file_get_contents($skill_md);
if ($content) {
foreach (explode("\n", $content) as $line) {
$line = trim($line);
if ($line && !str_starts_with($line, '#') && !str_starts_with($line, '---')) {
$desc = substr($line, 0, 200);
break;
}
}
}
$disk_skills[] = [
'name' => $name,
'type' => 'skill',
'desc' => $desc,
'path' => "/skills/$name/SKILL.md",
];
} else {
// Count sub-skills inside catalog folder
$sub_skills = glob("$dir/*", GLOB_ONLYDIR);
$sub_count = count($sub_skills);
$readme = @file_get_contents("$dir/README.md") ?: @file_get_contents("$dir/CATALOG.md");
$readme_desc = '';
if ($readme) {
foreach (explode("\n", $readme) as $line) {
$line = trim($line);
if ($line && !str_starts_with($line, '#')) {
$readme_desc = substr($line, 0, 200);
break;
}
}
}
$big_catalogs[] = [
'name' => $name,
'type' => 'catalog',
'sub_count' => $sub_count,
'desc' => $readme_desc ?: "Catalog avec $sub_count sub-items",
'path' => "/skills/$name/",
'samples' => array_slice(array_map('basename', $sub_skills), 0, 5),
];
}
}
// Source 2: Qdrant weval_skills
$qdrant_info = qdrant_call("$QDRANT/collections/weval_skills");
$qdrant_points = $qdrant_info['result']['points_count'] ?? 0;
// Source 3: Tools registry
$reg = @json_decode(@file_get_contents('/var/www/html/api/wevia-tool-registry.json'), true);
$tools_count = isset($reg['tools']) && is_array($reg['tools']) ? count($reg['tools']) : 0;
$tools_sample = [];
if ($tools_count) {
foreach (array_slice($reg['tools'], 0, 10) as $t) {
$tools_sample[] = ['name' => $t['name'] ?? $t['id'] ?? '?', 'desc' => substr($t['desc'] ?? $t['description'] ?? $t['kw'] ?? $t['cmd'] ?? '', 0, 150)];
}
}
// Source 4: Arena declared
$arena = @json_decode(@file_get_contents('/var/www/html/api/arena-intent-registry.json'), true);
$arena_skills = $arena['total_skills'] ?? 0;
$total = count($disk_skills) + $qdrant_points + $tools_count + $arena_skills;
$result['sources'] = [
'source_1_disk_skill_md' => [
'label' => '/skills/ dossiers avec SKILL.md · vitrine visible',
'count' => count($disk_skills),
'url' => '/skills/',
'items' => $disk_skills,
],
'source_2_disk_big_catalogs' => [
'label' => '/skills/ catalogs avec sub-skills · deer-flow/paperclip/etc',
'count' => count($big_catalogs),
'total_sub_items' => array_sum(array_column($big_catalogs, 'sub_count')),
'items' => $big_catalogs,
],
'source_3_qdrant_vectorized' => [
'label' => 'Qdrant weval_skills · vectorisés pour semantic search',
'count' => $qdrant_points,
'url' => '/api/skills-explorer-api.php?action=qdrant_sample',
'note' => 'Indexés depuis /opt/deer-flow/skills/ · searchable via embeddings',
],
'source_4_tools_registry' => [
'label' => 'wevia-tool-registry.json · tools callable',
'count' => $tools_count,
'url' => '/api/wevia-tool-registry.json',
'samples' => $tools_sample,
],
'source_5_arena_declared' => [
'label' => 'Arena registry · skills déclarés (intents associés)',
'count' => $arena_skills,
'url' => '/api/arena-intent-registry.json',
],
'TOTAL_ALL_SOURCES' => $total,
];
}
if ($action === 'qdrant_sample') {
$limit = (int)($_GET['limit'] ?? 50);
$limit = min(100, max(10, $limit));
$offset = $_GET['offset'] ?? null;
$payload = ['limit' => $limit, 'with_payload' => true];
if ($offset) $payload['offset'] = $offset;
$res = qdrant_call("$QDRANT/collections/weval_skills/points/scroll", 'POST', $payload);
$points = $res['result']['points'] ?? [];
$skills = [];
foreach ($points as $p) {
$pl = $p['payload'] ?? [];
$skills[] = [
'id' => $p['id'] ?? '?',
'name' => $pl['name'] ?? '?',
'desc' => substr($pl['desc'] ?? '', 0, 250),
'source' => $pl['source'] ?? '?',
'path' => $pl['path'] ?? '',
];
}
$result['qdrant_skills'] = $skills;
$result['count'] = count($skills);
$result['next_offset'] = $res['result']['next_page_offset'] ?? null;
}
if ($action === 'search' && !empty($_GET['q'])) {
$q = strtolower($_GET['q']);
// Scroll Qdrant with payload filter
$matches = [];
$offset = null;
$scanned = 0;
$max_scans = 5; // scroll 5 pages max
for ($i = 0; $i < $max_scans; $i++) {
$payload = ['limit' => 200, 'with_payload' => true];
if ($offset) $payload['offset'] = $offset;
$res = qdrant_call("$QDRANT/collections/weval_skills/points/scroll", 'POST', $payload);
$points = $res['result']['points'] ?? [];
$scanned += count($points);
foreach ($points as $p) {
$pl = $p['payload'] ?? [];
$name = strtolower($pl['name'] ?? '');
$desc = strtolower($pl['desc'] ?? '');
if (strpos($name, $q) !== false || strpos($desc, $q) !== false) {
$matches[] = [
'id' => $p['id'],
'name' => $pl['name'] ?? '?',
'desc' => substr($pl['desc'] ?? '', 0, 250),
'source' => $pl['source'] ?? '?',
'path' => $pl['path'] ?? '',
];
if (count($matches) >= 50) break 2;
}
}
$offset = $res['result']['next_page_offset'] ?? null;
if (!$offset) break;
}
$result['query'] = $q;
$result['scanned'] = $scanned;
$result['matches_count'] = count($matches);
$result['matches'] = $matches;
}
if ($action === 'tools') {
$reg = @json_decode(@file_get_contents('/var/www/html/api/wevia-tool-registry.json'), true);
$tools_list = [];
if (isset($reg['tools']) && is_array($reg['tools'])) {
$lim = (int)($_GET['limit'] ?? 0);
$off = (int)($_GET['offset'] ?? 0);
$all = $reg['tools'];
$sliced = $lim > 0 ? array_slice($all, $off, $lim) : $all;
foreach ($sliced as $t) {
$tools_list[] = [
'id' => $t['id'] ?? $t['name'] ?? '',
'name' => $t['name'] ?? $t['id'] ?? '',
'kw' => $t['kw'] ?? '',
'desc' => substr((string)($t['desc'] ?? $t['description'] ?? $t['kw'] ?? $t['cmd'] ?? ''), 0, 200),
'cmd' => isset($t['cmd']) ? substr((string)$t['cmd'], 0, 100) : '',
'category' => $t['category'] ?? $t['cat'] ?? 'general',
];
}
}
$result['total'] = isset($reg['tools']) ? count($reg['tools']) : 0;
$result['returned'] = count($tools_list);
$result['tools'] = $tools_list;
}
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);