auto-sync-0335
This commit is contained in:
@@ -80,11 +80,11 @@
|
||||
"workerIndex": 0,
|
||||
"parallelIndex": 0,
|
||||
"status": "passed",
|
||||
"duration": 20618,
|
||||
"duration": 20510,
|
||||
"errors": [],
|
||||
"stdout": [
|
||||
{
|
||||
"text": "Mermaid found · 1 divs · 1 SVG rendered · badges: [\"♻️ KB Reused (1 uses)\",\"flowchart\",\"2ms\"]\n"
|
||||
"text": "Mermaid found · 1 divs · 1 SVG rendered · badges: [\"♻️ KB Reused (2 uses)\",\"flowchart\",\"3ms\"]\n"
|
||||
},
|
||||
{
|
||||
"text": "Total: 1.5s · mermaid found: true\n"
|
||||
@@ -95,7 +95,7 @@
|
||||
],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2026-04-22T01:24:19.355Z",
|
||||
"startTime": "2026-04-22T01:33:41.411Z",
|
||||
"annotations": [],
|
||||
"attachments": [
|
||||
{
|
||||
@@ -124,8 +124,8 @@
|
||||
],
|
||||
"errors": [],
|
||||
"stats": {
|
||||
"startTime": "2026-04-22T01:24:18.728Z",
|
||||
"duration": 21481.236,
|
||||
"startTime": "2026-04-22T01:33:40.350Z",
|
||||
"duration": 21970.469,
|
||||
"expected": 1,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 80 KiB |
Binary file not shown.
51
api/ambre-scan-230b.php
Normal file
51
api/ambre-scan-230b.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
chdir("/var/www/html");
|
||||
$out["recent_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -5"))));
|
||||
$out["latest_commit"] = trim(@shell_exec("git log -1 --oneline 2>&1"));
|
||||
|
||||
// Check my wave-229 tools + wave-230 state
|
||||
$reg = @file_get_contents("/var/www/html/api/wevia-tool-registry.json");
|
||||
$data = @json_decode($reg, true);
|
||||
if ($data) {
|
||||
$out["wave_229_tools"] = array_map(function($t){return $t["id"];},
|
||||
array_filter($data["tools"] ?? [], function($t){return ($t["wave"] ?? 0) == 229;}));
|
||||
$out["total_tools"] = count($data["tools"] ?? []);
|
||||
}
|
||||
|
||||
// Ethica state
|
||||
$ethica = [];
|
||||
$ethica["ecm_py_exists"] = file_exists("/var/www/html/ethica/ecm.py") || file_exists("/opt/ethica/ecm.py") || file_exists("/var/www/weval/ecm.py");
|
||||
$ethica["find_ecm"] = trim(@shell_exec("find /var/www /opt -name 'ecm.py' 2>/dev/null | head -5"));
|
||||
$ethica["consent_urls"] = [
|
||||
"consent.wevup.app" => @shell_exec("curl -sI --max-time 3 https://consent.wevup.app/ 2>&1 | head -1"),
|
||||
];
|
||||
$out["ethica"] = $ethica;
|
||||
|
||||
// Mermaid V10 state in wevia.html
|
||||
$w = @file_get_contents("/var/www/html/wevia.html");
|
||||
$out["wevia"] = [
|
||||
"size" => strlen($w),
|
||||
"v10_mermaid" => strpos($w, "AMBRE-V10-MERMAID") !== false,
|
||||
"v10_css_minheight" => strpos($w, "min-height:200px") !== false,
|
||||
];
|
||||
|
||||
// Check i18n helpers in wevia.html
|
||||
$out["i18n"] = [
|
||||
"detectLang" => strpos($w, "function detectLang") !== false,
|
||||
"lang_var" => strpos($w, "var lang =") !== false,
|
||||
];
|
||||
|
||||
// Mermaid KB state
|
||||
$mkb = @file_get_contents("/var/www/html/generated/mermaid-learn-kb.json");
|
||||
if ($mkb) {
|
||||
$kb_data = @json_decode($mkb, true);
|
||||
$out["mermaid_kb_entries"] = count($kb_data ?: []);
|
||||
}
|
||||
|
||||
// Load current
|
||||
$out["load"] = trim(shell_exec("uptime"));
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
78
api/ambre-v10-render.php
Normal file
78
api/ambre-v10-render.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// The V10 block uses class="mermaid" which triggers the problematic CSS
|
||||
// Keep class="mermaid" (so mermaid.run picks it up) BUT add an override style in-line
|
||||
// The issue is font-size:0 !important - we can't override easily
|
||||
// Better: after mermaid.run, the data-processed=true attribute is set, which UNSETS font-size:0
|
||||
// So the issue must be that mermaid.run() isn't triggering or the SVG has issues
|
||||
|
||||
// Let me use a different approach: use mermaid.render() directly to get SVG as string, insert directly
|
||||
|
||||
$old = "var uniqId = \"mmd-\" + Date.now();
|
||||
var inlineBlock = \"<div style=\\\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\\\">\" +
|
||||
\"<div style=\\\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\\\">📊 \" + topic + \"</div>\" +
|
||||
\"<div class=\\\"mermaid\\\" id=\\\"\" + uniqId + \"\\\" style=\\\"text-align:center;min-height:200px;font-size:14px;color:#333\\\">\" + mcode + \"</div>\"";
|
||||
|
||||
$new = "var uniqId = \"mmd-\" + Date.now();
|
||||
// Pre-render SVG via mermaid.render() to avoid CSS font-size:0 !important issue
|
||||
var inlineBlock = \"<div style=\\\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\\\">\" +
|
||||
\"<div style=\\\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\\\">📊 \" + topic + \"</div>\" +
|
||||
\"<div id=\\\"\" + uniqId + \"\\\" style=\\\"text-align:center;min-height:150px;padding:10px;background:#fff;border-radius:8px\\\">Rendu en cours...</div>\"";
|
||||
|
||||
if (strpos($c, $old) === false) {
|
||||
echo json_encode(["error"=>"V10 pattern not found for CSS fix"]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old, $new, $c);
|
||||
|
||||
// And update the render call to use mermaid.render(id, code) API
|
||||
$old_render = "setTimeout(function(){
|
||||
try {
|
||||
if (window.mermaid && typeof window.mermaid.run === \"function\") {
|
||||
window.mermaid.run({ nodes: [document.getElementById(uniqId)] });
|
||||
}
|
||||
} catch(e) { console.warn(\"mermaid render fail\", e); }
|
||||
}, 300);";
|
||||
|
||||
$new_render = "setTimeout(function(){
|
||||
try {
|
||||
var target = document.getElementById(uniqId);
|
||||
if (!target) return;
|
||||
if (window.mermaid && typeof window.mermaid.render === \"function\") {
|
||||
// Use render() to get SVG string directly
|
||||
window.mermaid.render(\"svg-\" + uniqId, mcode).then(function(result) {
|
||||
if (result && result.svg) {
|
||||
target.innerHTML = result.svg;
|
||||
target.style.minHeight = \"auto\";
|
||||
}
|
||||
}).catch(function(err){
|
||||
console.warn(\"mermaid.render error\", err);
|
||||
target.innerHTML = \"<pre style=\\\"font-size:11px;color:#b00;padding:10px;background:#fee;border-radius:6px\\\">Erreur rendu: \" + String(err).substring(0, 200) + \"</pre>\";
|
||||
});
|
||||
} else if (window.mermaid && typeof window.mermaid.init === \"function\") {
|
||||
target.className = \"mermaid\";
|
||||
target.textContent = mcode;
|
||||
window.mermaid.init(undefined, target);
|
||||
}
|
||||
} catch(e) { console.warn(\"mermaid render fail\", e); }
|
||||
}, 500);";
|
||||
|
||||
if (strpos($c, $old_render) === false) {
|
||||
echo json_encode(["error"=>"render pattern not found", "orig_changed" => strlen($c) != $orig]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old_render, $new_render, $c);
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-render";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
|
||||
echo json_encode([
|
||||
"delta" => strlen($c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// WAVE 232 v5 · Twitter snscrape OSS + 5 Mastodon instances + task PATCH + SSE
|
||||
// WAVE 233 v6 · Ask WEVIA + 5 Reddit subs + lead linking + CSV export
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
set_time_limit(25);
|
||||
@@ -31,83 +31,152 @@ function multi_fetch($urls, $timeout=7) {
|
||||
return $out;
|
||||
}
|
||||
|
||||
// === POST create_task ===
|
||||
function link_lead($opportunity) {
|
||||
// WAVE 233: match opportunity string to weval_leads.id via slug
|
||||
if (empty($opportunity)) return null;
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=2');
|
||||
if (!$pg) return null;
|
||||
$opp_lower = strtolower($opportunity);
|
||||
$r = @pg_query($pg, 'SELECT id, slug, company, mql_score FROM weval_leads ORDER BY mql_score DESC LIMIT 50');
|
||||
$match = null;
|
||||
if ($r) while ($row = pg_fetch_assoc($r)) {
|
||||
$co = strtolower($row['company'] ?? '');
|
||||
$sl = strtolower($row['slug'] ?? '');
|
||||
$co_first = explode(' ', $co)[0] ?? '';
|
||||
if ($sl && strpos($opp_lower, $sl) !== false) { $match = $row; break; }
|
||||
if ($co_first && strlen($co_first) > 3 && strpos($opp_lower, $co_first) !== false) { $match = $row; break; }
|
||||
}
|
||||
pg_close($pg);
|
||||
return $match;
|
||||
}
|
||||
|
||||
// === POST create_task with lead linking (WAVE 233) ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'create_task') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { http_response_code(500); echo json_encode(['error'=>'no pg']); exit; }
|
||||
$q = "INSERT INTO weval_tasks (title, source, source_ref, category, opportunity, tools_used, first_steps, kpi, estimated_mad, inspired_by, status, wave) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING id, created_at";
|
||||
|
||||
// WAVE 233: Auto-link lead
|
||||
$lead_match = link_lead($body['opportunity'] ?? '');
|
||||
$lead_id = $lead_match ? (int)$lead_match['id'] : null;
|
||||
|
||||
$q = "INSERT INTO weval_tasks (title, source, source_ref, category, opportunity, tools_used, first_steps, kpi, estimated_mad, inspired_by, status, wave, lead_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING id, created_at";
|
||||
$r = @pg_query_params($pg, $q, [
|
||||
$body['title']??'?', $body['source']??'advisor-wave232', $body['source_ref']??'',
|
||||
$body['title']??'?', $body['source']??'advisor-wave233', $body['source_ref']??'',
|
||||
$body['category']??'conversion', $body['opportunity']??'',
|
||||
is_array($body['tools_used']??null)?implode('|',$body['tools_used']):($body['tools_used']??''),
|
||||
is_array($body['first_steps']??null)?implode("\n- ",$body['first_steps']):($body['first_steps']??''),
|
||||
$body['kpi']??'', (int)($body['estimated_mad']??0), $body['inspired_by']??'', 'proposed', 232
|
||||
$body['kpi']??'', (int)($body['estimated_mad']??0), $body['inspired_by']??'', 'proposed', 233, $lead_id
|
||||
]);
|
||||
if ($r) { $row = pg_fetch_assoc($r); pg_close($pg); echo json_encode(['ok'=>true, 'task_id'=>(int)$row['id'], 'created_at'=>$row['created_at']]); }
|
||||
else { pg_close($pg); http_response_code(500); echo json_encode(['error'=>'insert failed', 'details'=>pg_last_error()]); }
|
||||
if ($r) {
|
||||
$row = pg_fetch_assoc($r);
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'task_id'=>(int)$row['id'], 'created_at'=>$row['created_at'], 'lead_linked'=>$lead_match]);
|
||||
} else { pg_close($pg); http_response_code(500); echo json_encode(['error'=>'insert failed', 'details'=>pg_last_error()]); }
|
||||
exit;
|
||||
}
|
||||
|
||||
// === PATCH update_task_status ===
|
||||
// === PATCH update_status ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'update_status') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$task_id = (int)($body['task_id'] ?? 0);
|
||||
$new_status = $body['status'] ?? '';
|
||||
$allowed = ['proposed', 'in_progress', 'done', 'cancelled', 'blocked'];
|
||||
if (!$task_id || !in_array($new_status, $allowed)) {
|
||||
http_response_code(400); echo json_encode(['error'=>'invalid', 'allowed'=>$allowed]); exit;
|
||||
}
|
||||
$allowed = ['proposed','in_progress','done','cancelled','blocked'];
|
||||
if (!$task_id || !in_array($new_status, $allowed)) { http_response_code(400); echo json_encode(['error'=>'invalid']); exit; }
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { http_response_code(500); echo json_encode(['error'=>'no pg']); exit; }
|
||||
$r = @pg_query_params($pg, 'UPDATE weval_tasks SET status=$1 WHERE id=$2 RETURNING id, status', [$new_status, $task_id]);
|
||||
if ($r && ($row = pg_fetch_assoc($r))) { pg_close($pg); echo json_encode(['ok'=>true, 'task_id'=>(int)$row['id'], 'new_status'=>$row['status']]); }
|
||||
else { pg_close($pg); http_response_code(404); echo json_encode(['error'=>'task not found']); }
|
||||
else { pg_close($pg); http_response_code(404); echo json_encode(['error'=>'not found']); }
|
||||
exit;
|
||||
}
|
||||
|
||||
// === GET list_tasks ===
|
||||
// === GET list_tasks with lead JOIN (WAVE 233) ===
|
||||
if (($_GET['action'] ?? '') === 'list_tasks') {
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { echo json_encode(['error'=>'no pg', 'tasks'=>[]]); exit; }
|
||||
$r = @pg_query($pg, 'SELECT * FROM weval_tasks ORDER BY created_at DESC LIMIT 20');
|
||||
// LEFT JOIN with weval_leads
|
||||
$r = @pg_query($pg, '
|
||||
SELECT t.*, l.slug AS lead_slug, l.company AS lead_company, l.mql_score AS lead_mql, l.sql_qualified AS lead_sql
|
||||
FROM weval_tasks t
|
||||
LEFT JOIN weval_leads l ON t.lead_id = l.id
|
||||
ORDER BY t.created_at DESC LIMIT 20
|
||||
');
|
||||
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
|
||||
$agg = []; foreach ($tasks as $t) { $s = $t['status']??'?'; $agg[$s] = ($agg[$s]??0)+1; }
|
||||
$linked = count(array_filter($tasks, function($t){return !empty($t['lead_id']);}));
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'count'=>count($tasks), 'by_status'=>$agg, 'tasks'=>$tasks]);
|
||||
echo json_encode(['ok'=>true, 'count'=>count($tasks), 'by_status'=>$agg, 'linked_count'=>$linked, 'tasks'=>$tasks]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === SSE streaming endpoint ===
|
||||
// === GET export_tasks_csv (WAVE 233) ===
|
||||
if (($_GET['action'] ?? '') === 'export_csv') {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="weval_tasks_' . date('Ymd') . '.csv"');
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { echo "error,no pg\n"; exit; }
|
||||
$r = @pg_query($pg, 'SELECT t.id, t.title, t.status, t.opportunity, t.estimated_mad, t.tools_used, t.kpi, t.created_at, l.company AS linked_lead, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id = l.id ORDER BY t.created_at DESC');
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, ['id','title','status','opportunity','estimated_mad','tools_used','kpi','created_at','linked_lead','lead_mql']);
|
||||
while ($row = pg_fetch_assoc($r)) fputcsv($out, $row);
|
||||
fclose($out);
|
||||
pg_close($pg);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === POST ask_wevia (WAVE 233) ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'ask_wevia') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$idea = $body['idea'] ?? [];
|
||||
$q = "Contextualisé par cette idea de conversion:\n" .
|
||||
"Titre: " . ($idea['title'] ?? '?') . "\n" .
|
||||
"Opportunité: " . ($idea['opportunity'] ?? '?') . "\n" .
|
||||
"Tools: " . (is_array($idea['tools_used']??null) ? implode(', ', $idea['tools_used']) : ($idea['tools_used'] ?? '?')) . "\n" .
|
||||
"KPI: " . ($idea['kpi'] ?? '?') . "\n" .
|
||||
"MAD est: " . ($idea['estimated_mad'] ?? 0) . "\n" .
|
||||
"Détaille un plan exécutable 14j step-by-step avec multi-agents WEVAL.";
|
||||
|
||||
// Query WEVIA Master via saas-chat.php
|
||||
$ch = curl_init('http://127.0.0.1/api/saas-chat.php');
|
||||
$payload = json_encode(['message' => $q]);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>30, CURLOPT_HTTPHEADER=>['Content-Type: application/json'], CURLOPT_POSTFIELDS=>$payload]);
|
||||
$r = curl_exec($ch); $c = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
|
||||
if ($c === 200) {
|
||||
echo json_encode(['ok'=>true, 'wevia_response'=>json_decode($r, true) ?: $r, 'query'=>$q]);
|
||||
} else {
|
||||
// Fallback: direct LLM query
|
||||
$secrets = load_secrets();
|
||||
$payload2 = json_encode(['model'=>'mistral-small-latest','messages'=>[['role'=>'system','content'=>'Tu es WEVIA Master, assistant multi-agents WEVAL Consulting. Réponds en FR compact.'],['role'=>'user','content'=>$q]],'max_tokens'=>1500,'temperature'=>0.3]);
|
||||
$ch = curl_init('https://api.mistral.ai/v1/chat/completions');
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>25, CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.($secrets['MISTRAL_KEY']??'')], CURLOPT_POSTFIELDS=>$payload2]);
|
||||
$rf = curl_exec($ch); $cf = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
$text = '';
|
||||
if ($cf === 200) { $d = json_decode($rf, true); $text = $d['choices'][0]['message']['content'] ?? ''; }
|
||||
echo json_encode(['ok'=>(bool)$text, 'fallback'=>'Mistral direct', 'wevia_response'=>$text, 'query'=>$q]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// === SSE stream ===
|
||||
if (($_GET['action'] ?? '') === 'stream') {
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('X-Accel-Buffering: no');
|
||||
@ob_end_flush();
|
||||
$send = function($event, $data) {
|
||||
echo "event: $event\n";
|
||||
echo "data: " . json_encode($data) . "\n\n";
|
||||
@ob_flush(); flush();
|
||||
};
|
||||
$send('hello', ['wave'=>232, 'msg'=>'SSE social stream live', 'ts'=>date('c')]);
|
||||
// Stream channels one by one
|
||||
$channels = [
|
||||
'linkedin' => 'http://127.0.0.1/api/linkedin-posts.php',
|
||||
'hackernews' => 'https://hn.algolia.com/api/v1/search?query=' . urlencode('SaaS conversion') . '&tags=story&hitsPerPage=5',
|
||||
'reddit' => 'https://old.reddit.com/r/SaaS/.rss?limit=5',
|
||||
];
|
||||
$send = function($event, $data) { echo "event: $event\n"; echo "data: " . json_encode($data) . "\n\n"; @ob_flush(); flush(); };
|
||||
$send('hello', ['wave'=>233, 'msg'=>'SSE social stream live', 'ts'=>date('c')]);
|
||||
$channels = ['linkedin'=>'http://127.0.0.1/api/linkedin-posts.php', 'hackernews'=>'https://hn.algolia.com/api/v1/search?query='.urlencode('SaaS conversion').'&tags=story&hitsPerPage=5', 'reddit'=>'https://old.reddit.com/r/SaaS/.rss?limit=5'];
|
||||
foreach ($channels as $name => $url) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5, CURLOPT_USERAGENT=>'weval-bot']);
|
||||
$raw = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5, CURLOPT_USERAGENT=>'weval-bot']);
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
$count = 0; $top = '';
|
||||
if ($name === 'linkedin') { $d = @json_decode($raw, true); if (isset($d['posts'])) { $count = count($d['posts']); $top = $d['posts'][0]['title'] ?? ''; } }
|
||||
elseif ($name === 'hackernews') { $d = @json_decode($raw, true); if (isset($d['hits'])) { $count = count($d['hits']); $top = $d['hits'][0]['title'] ?? ''; } }
|
||||
elseif ($name === 'reddit') { $xml = @simplexml_load_string($raw); if ($xml) { $entries = $xml->entry??[]; $count = count($entries); $top = $count ? (string)$entries[0]->title : ''; } }
|
||||
elseif ($name === 'reddit') { $xml = @simplexml_load_string($raw); if ($xml) { $entries = $xml->entry ?? []; $count = count($entries); $top = $count ? (string)$entries[0]->title : ''; } }
|
||||
$send('channel', ['name'=>$name, 'count'=>$count, 'top'=>$top, 'ts'=>date('c')]);
|
||||
}
|
||||
// Tasks count
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if ($pg) {
|
||||
$r = @pg_query($pg, 'SELECT status, COUNT(*) AS n FROM weval_tasks GROUP BY status');
|
||||
@@ -119,29 +188,26 @@ if (($_GET['action'] ?? '') === 'stream') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// === Default: aggregation with snscrape Twitter + 5 Mastodon ===
|
||||
// === Default aggregation (Reddit 5 subs WAVE 233) ===
|
||||
$topics = $_GET['topics'] ?? 'B2B SaaS conversion,LinkedIn outbound,pharma digital';
|
||||
$topic_list = array_slice(array_map('trim', explode(',', $topics)), 0, 3);
|
||||
$with_scout = ($_GET['scout'] ?? '') === '1';
|
||||
$with_twitter = ($_GET['twitter'] ?? '1') === '1'; // default ON
|
||||
|
||||
$signals = [
|
||||
'ts' => date('c'), 'wave' => 232, 'version' => 'social-signals-hub-v5',
|
||||
'topics' => $topic_list, 'channels' => [], 'aggregated_ideas' => [],
|
||||
];
|
||||
$signals = ['ts'=>date('c'), 'wave'=>233, 'version'=>'social-signals-hub-v6', 'topics'=>$topic_list, 'channels'=>[], 'aggregated_ideas'=>[]];
|
||||
|
||||
// Parallel fetch
|
||||
$urls = [];
|
||||
$urls['linkedin'] = 'http://127.0.0.1/api/linkedin-posts.php';
|
||||
foreach (['SaaS conversion', 'B2B sales outbound'] as $i => $q) {
|
||||
$urls['hn_'.$i] = 'https://hn.algolia.com/api/v1/search?query=' . urlencode($q) . '&tags=story&hitsPerPage=6';
|
||||
}
|
||||
foreach (['SaaS', 'Entrepreneur', 'B2BSales'] as $i => $s) {
|
||||
$urls['rd_'.$i] = 'https://old.reddit.com/r/' . $s . '/.rss?limit=5';
|
||||
// WAVE 233: 5 Reddit subs (was 3)
|
||||
$reddit_subs = ['SaaS', 'Entrepreneur', 'B2BSales', 'startups', 'marketing'];
|
||||
foreach ($reddit_subs as $i => $s) {
|
||||
$urls['rd_'.$i] = 'https://old.reddit.com/r/' . $s . '/.rss?limit=4';
|
||||
}
|
||||
$urls['hn_yt'] = 'https://hn.algolia.com/api/v1/search?query=' . urlencode('youtube.com SaaS') . '&tags=story&hitsPerPage=10';
|
||||
// Mastodon 5 instances
|
||||
$mast_hosts = ['mastodon.social', 'mstdn.social', 'fosstodon.org', 'hachyderm.io', 'techhub.social'];
|
||||
// Mastodon 5 instances (kept from wave 232)
|
||||
$mast_hosts = ['mastodon.social','mstdn.social','fosstodon.org','hachyderm.io','techhub.social'];
|
||||
foreach ($mast_hosts as $i => $h) {
|
||||
$urls['ma_'.$i] = 'https://' . $h . '/api/v2/search?q=' . urlencode($topic_list[0] ?? 'SaaS') . '&type=statuses&limit=3';
|
||||
}
|
||||
@@ -161,7 +227,7 @@ if (!empty($results['linkedin'])) {
|
||||
$ln['count'] = count($ln['items']);
|
||||
$signals['channels']['linkedin'] = $ln;
|
||||
|
||||
// HackerNews
|
||||
// HN
|
||||
$hn = ['channel'=>'hackernews','source'=>'Algolia API','items'=>[]];
|
||||
foreach ([0,1] as $i) {
|
||||
if (empty($results['hn_'.$i])) continue;
|
||||
@@ -175,22 +241,22 @@ $hn['items'] = array_slice($hn['items'], 0, 10);
|
||||
$hn['count'] = count($hn['items']);
|
||||
$signals['channels']['hackernews'] = $hn;
|
||||
|
||||
// Reddit RSS
|
||||
$rd = ['channel'=>'reddit','source'=>'old.reddit RSS','items'=>[]];
|
||||
foreach ([0,1,2] as $i) {
|
||||
// Reddit 5 subs (WAVE 233)
|
||||
$rd = ['channel'=>'reddit','source'=>'5 subs RSS (SaaS+Entr+B2B+startups+marketing)','items'=>[]];
|
||||
foreach (range(0,4) as $i) {
|
||||
if (empty($results['rd_'.$i])) continue;
|
||||
$xml = @simplexml_load_string($results['rd_'.$i]);
|
||||
if (!$xml) continue;
|
||||
$sub = ['SaaS','Entrepreneur','B2BSales'][$i];
|
||||
$sub = $reddit_subs[$i];
|
||||
foreach ($xml->entry ?? [] as $entry) {
|
||||
$rd['items'][] = ['title'=>substr((string)$entry->title,0,140),'subreddit'=>'r/'.$sub,'url'=>(string)$entry->link['href'],'date'=>substr((string)$entry->updated,0,10)];
|
||||
}
|
||||
}
|
||||
$rd['items'] = array_slice($rd['items'], 0, 15);
|
||||
$rd['items'] = array_slice($rd['items'], 0, 20);
|
||||
$rd['count'] = count($rd['items']);
|
||||
$signals['channels']['reddit'] = $rd;
|
||||
|
||||
// YouTube via HN-filtered
|
||||
// YouTube
|
||||
$yt = ['channel'=>'youtube','source'=>'HackerNews YT-filtered','items'=>[]];
|
||||
if (!empty($results['hn_yt'])) {
|
||||
$hd = @json_decode($results['hn_yt'], true);
|
||||
@@ -206,67 +272,31 @@ $yt['items'] = array_slice($yt['items'], 0, 8);
|
||||
$yt['count'] = count($yt['items']);
|
||||
$signals['channels']['youtube'] = $yt;
|
||||
|
||||
// Twitter via snscrape OSS
|
||||
$tw = ['channel'=>'twitter','source'=>'snscrape (OSS)','items'=>[]];
|
||||
if ($with_twitter) {
|
||||
$tw_query = $topic_list[0] ?? 'SaaS';
|
||||
$tw_cmd = '/opt/oss/pandas-ai/venv/bin/snscrape --jsonl --max-results 6 twitter-search ' . escapeshellarg($tw_query) . ' 2>/dev/null';
|
||||
$tw_raw = @shell_exec('timeout 8 ' . $tw_cmd);
|
||||
if ($tw_raw) {
|
||||
$lines = explode("\n", trim($tw_raw));
|
||||
foreach ($lines as $line) {
|
||||
$l = @json_decode($line, true);
|
||||
if (!$l) continue;
|
||||
$tw['items'][] = [
|
||||
'title' => substr($l['rawContent'] ?? $l['content'] ?? '', 0, 180),
|
||||
'user' => '@' . ($l['user']['username'] ?? '?'),
|
||||
'url' => $l['url'] ?? '',
|
||||
'likes' => (int)($l['likeCount'] ?? 0),
|
||||
'retweets' => (int)($l['retweetCount'] ?? 0),
|
||||
'date' => substr($l['date'] ?? '', 0, 10),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$tw['items'] = array_slice($tw['items'], 0, 8);
|
||||
$tw['count'] = count($tw['items']);
|
||||
$signals['channels']['twitter'] = $tw;
|
||||
|
||||
// Mastodon 5 instances merged
|
||||
$ma = ['channel'=>'mastodon','source'=>'5 instances (social/mstdn/fosstodon/hachyderm/techhub)','items'=>[]];
|
||||
// Mastodon
|
||||
$ma = ['channel'=>'mastodon','source'=>'5 instances merged','items'=>[]];
|
||||
foreach (range(0,4) as $i) {
|
||||
if (empty($results['ma_'.$i])) continue;
|
||||
$md = @json_decode($results['ma_'.$i], true);
|
||||
foreach (($md['statuses'] ?? []) as $s) {
|
||||
$content = trim(strip_tags($s['content'] ?? ''));
|
||||
if (strlen($content) > 20) {
|
||||
$ma['items'][] = [
|
||||
'title' => substr($content, 0, 180),
|
||||
'url' => $s['url'] ?? '',
|
||||
'user' => '@' . ($s['account']['acct'] ?? '?'),
|
||||
'instance' => $mast_hosts[$i] ?? '?',
|
||||
'favorites' => (int)($s['favourites_count'] ?? 0),
|
||||
'reblogs' => (int)($s['reblogs_count'] ?? 0),
|
||||
'date' => substr($s['created_at'] ?? '', 0, 10),
|
||||
];
|
||||
$ma['items'][] = ['title'=>substr($content,0,180),'url'=>$s['url']??'','user'=>'@'.($s['account']['acct']??'?'),'instance'=>$mast_hosts[$i]??'?','favorites'=>(int)($s['favourites_count']??0)];
|
||||
}
|
||||
}
|
||||
}
|
||||
usort($ma['items'], function($a,$b){return ($b['favorites']??0)-($a['favorites']??0);});
|
||||
$ma['items'] = array_slice($ma['items'], 0, 10);
|
||||
$ma['count'] = count($ma['items']);
|
||||
$signals['channels']['mastodon'] = $ma;
|
||||
|
||||
// Dark Scout async
|
||||
// Dark Scout opt-in
|
||||
if ($with_scout) {
|
||||
$ds_ch = curl_init('http://127.0.0.1/api/v83-dark-scout-enriched.php?q=' . urlencode($topic_list[0] ?? 'SaaS'));
|
||||
curl_setopt_array($ds_ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>12]);
|
||||
$ds_raw = curl_exec($ds_ch);
|
||||
curl_close($ds_ch);
|
||||
$sc = ['channel'=>'dark_scout','source'=>'google+bing+ddg','items'=>[]];
|
||||
$ds_raw = curl_exec($ds_ch); curl_close($ds_ch);
|
||||
$sc = ['channel'=>'dark_scout','source'=>'multi-engine','items'=>[]];
|
||||
if ($ds_raw) {
|
||||
$sd = @json_decode($ds_raw, true);
|
||||
foreach (array_slice(($sd['results']??[]), 0, 6) as $r) {
|
||||
foreach (array_slice(($sd['results']??[]),0,6) as $r) {
|
||||
$sc['items'][] = ['title'=>substr($r['title']??'',0,140),'snippet'=>substr($r['snippet']??'',0,150),'url'=>$r['url']??'','category'=>$r['category']??''];
|
||||
}
|
||||
}
|
||||
@@ -279,14 +309,14 @@ foreach ($signals['channels'] as $c) foreach ($c['items'] as $i) if (!empty($i['
|
||||
$signals['aggregated_ideas'] = array_slice(array_unique($all), 0, 30);
|
||||
$signals['total_items'] = array_sum(array_map(function($c){return $c['count']??0;}, $signals['channels']));
|
||||
|
||||
// LLM cascade
|
||||
// LLM
|
||||
if (($_GET['llm'] ?? '') === '1') {
|
||||
$secrets = load_secrets();
|
||||
$weval_ctx = "WEVAL Consulting (Casablanca/Paris · SAP Ecosystem Partner).\nLive: 48 leads Paperclip, Vistex MQL 95 (450K MAD), Ethica MQL 100 (200K MAD), Huawei MQL 90.\nProducts: SAP, API HCP Maghreb 157K HCPs, Pharma Cloud, WEVAL SaaS Freemium.\nTools: WEVIA Master 269 tools, Dark Scout, WePredict, WEVADS Brain 9 winners, Blade AI, DocuSeal 3050, pandasai+Ollama.\nPipeline 2.9M MAD.";
|
||||
$weval_ctx = "WEVAL Consulting: 48 leads Paperclip · Vistex MQL95 450K MAD · Ethica MQL100 200K MAD · Huawei MQL90. Products: SAP, API HCP Maghreb 157K HCPs, Pharma Cloud, WEVAL SaaS Freemium. Tools: WEVIA Master 269 tools, Dark Scout, WePredict, WEVADS Brain, Blade AI, DocuSeal. Pipeline 2.9M MAD.";
|
||||
$summary = "";
|
||||
foreach ($signals['channels'] as $k => $c) { $summary .= "- $k ({$c['count']}): ".substr($c['items'][0]['title']??'(none)',0,60)."\n"; }
|
||||
foreach ($signals['channels'] as $k => $c) { $summary .= "- $k ({$c['count']}): " . substr($c['items'][0]['title']??'(none)', 0, 60) . "\n"; }
|
||||
$headlines = implode("\n - ", array_slice($signals['aggregated_ideas'], 0, 15));
|
||||
$prompt = "$weval_ctx\n\nSignals from 7 channels:\n$summary\nTop headlines:\n - $headlines\n\nProvide 5 CONCRETE conversion ideas for WEVAL MENA market. Each: opp, tools, 14d exec, KPI, MAD est, inspired_by.\nJSON: {ideas:[{rank:N, title:str, channel:str, opportunity:str, tools_used:[str], first_steps:[str,str,str], kpi:str, estimated_mad:N, inspired_by:str}]}";
|
||||
$prompt = "$weval_ctx\n\nSignals 7 channels:\n$summary\nHeadlines:\n - $headlines\n\n5 CONCRETE conversion ideas for WEVAL MENA. Each: opp, tools, 14d exec, KPI, MAD, inspired_by.\nJSON: {ideas:[{rank:N,title:str,channel:str,opportunity:str,tools_used:[str],first_steps:[str,str,str],kpi:str,estimated_mad:N,inspired_by:str}]}";
|
||||
$payload = json_encode(['model'=>'llama-3.3-70b','messages'=>[['role'=>'user','content'=>$prompt]],'max_tokens'=>2200,'temperature'=>0.4]);
|
||||
$provs = [
|
||||
['url'=>'https://api.cerebras.ai/v1/chat/completions','key'=>$secrets['CEREBRAS_API_KEY']??'','name'=>'Cerebras'],
|
||||
@@ -297,7 +327,7 @@ if (($_GET['llm'] ?? '') === '1') {
|
||||
if (empty($p['key'])) continue;
|
||||
$pp = isset($p['override']) ? preg_replace('/"model":"[^"]+"/','"model":"'.$p['override'].'"',$payload,1) : $payload;
|
||||
$ch = curl_init($p['url']);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>20, CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['key']], CURLOPT_POSTFIELDS=>$pp]);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['key']],CURLOPT_POSTFIELDS=>$pp]);
|
||||
$r = curl_exec($ch); $c = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
if ($c === 200) {
|
||||
$d = json_decode($r, true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-22T01:30:29+00:00",
|
||||
"ts": "2026-04-22T01:34:42+00:00",
|
||||
"summary": {
|
||||
"total_categories": 8,
|
||||
"total_kpis": 64,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"context": "Customer journey retail e-commerce physical store loyalty",
|
||||
"code": "flowchart LR\n A[Découverte] --> B[Recherche]\n B --> C[App Mobile]\n C --> D[Click & Collect]\n D --> E[Magasin]\n E --> F[Fidélité]",
|
||||
"created_at": "2026-04-22T01:06:12+00:00",
|
||||
"use_count": 3
|
||||
"use_count": 4
|
||||
},
|
||||
{
|
||||
"id": "39559de03fd9",
|
||||
@@ -15,7 +15,7 @@
|
||||
"context": "Architecture cascade LLM multi-provider sovereign",
|
||||
"code": "flowchart TD\n U[Utilisateur] --> R[Routeur]\n R --> C[Cerebras]\n R --> G[Groq]\n R --> S[SambaNova]\n C --> O[Orchestrateur]\n G --> O\n S --> O\n O --> U",
|
||||
"created_at": "2026-04-22T01:06:12+00:00",
|
||||
"use_count": 2
|
||||
"use_count": 3
|
||||
},
|
||||
{
|
||||
"id": "bf87b2067bbd",
|
||||
|
||||
Reference in New Issue
Block a user