Feat/gt analytics openclaw (#392)

* feat(telegram): auto-translate OSINT channel posts to English

Cherry-picked from @Bobpick PR #391 (telegram-only slice): server-side translation during fetch, SHOW ORIGINAL toggle in TelegramOsintPopup, and on-demand /api/telegram-feed?lang=.

Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(gt): experimental Derived OSINT analytics with lean-node safeguards

Cherry-picked from @Bobpick PR #391 (GT + OpenClaw slice): Bayesian strategic-risk engine, map overlay, OpenClaw commands, and telegram_rhetoric watchdog. Off by default (GT_ANALYTICS_ENABLED=false, gt_risk layer false). 1 vCPU nodes get cgroup detection, UI warning on layer toggle, and lean profile that skips scheduled ingest/Louvain unless GT_ANALYTICS_ACK_LOW_CPU=true. Backtest HUD removed from dashboard (OpenClaw/API regression only).

Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shadowbroker
2026-06-16 17:05:46 -06:00
committed by GitHub
co-authored by Robert Pickett Cursor
parent 9c5a4054f6
commit cfbeabda1e
69 changed files with 8101 additions and 77 deletions
+121
View File
@@ -0,0 +1,121 @@
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
export interface FloatingPanelPosition {
x: number;
y: number;
}
interface StoredFloatingPanelState {
position?: FloatingPanelPosition;
isMinimized?: boolean;
}
interface UseFloatingPanelOptions {
defaultPosition?: FloatingPanelPosition;
minVisible?: number;
}
export function useFloatingPanel(
storageKey: string,
{ defaultPosition = { x: 24, y: 380 }, minVisible = 48 }: UseFloatingPanelOptions = {},
) {
const [position, setPosition] = useState<FloatingPanelPosition>(defaultPosition);
const [isMinimized, setIsMinimized] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const dragStartRef = useRef({ x: 0, y: 0, posX: 0, posY: 0 });
const hydratedRef = useRef(false);
useEffect(() => {
try {
const raw = localStorage.getItem(storageKey);
if (!raw) return;
const parsed = JSON.parse(raw) as StoredFloatingPanelState;
if (
parsed.position &&
Number.isFinite(parsed.position.x) &&
Number.isFinite(parsed.position.y)
) {
setPosition(parsed.position);
}
if (typeof parsed.isMinimized === 'boolean') {
setIsMinimized(parsed.isMinimized);
}
} catch {
/* non-fatal */
} finally {
hydratedRef.current = true;
}
}, [storageKey]);
useEffect(() => {
if (!hydratedRef.current) return;
try {
localStorage.setItem(
storageKey,
JSON.stringify({ position, isMinimized } satisfies StoredFloatingPanelState),
);
} catch {
/* non-fatal */
}
}, [storageKey, position, isMinimized]);
const clampPosition = useCallback(
(next: FloatingPanelPosition): FloatingPanelPosition => {
const maxX = Math.max(0, window.innerWidth - minVisible);
const maxY = Math.max(0, window.innerHeight - minVisible);
return {
x: Math.min(Math.max(0, next.x), maxX),
y: Math.min(Math.max(0, next.y), maxY),
};
},
[minVisible],
);
const onDragStart = useCallback(
(event: React.MouseEvent) => {
event.preventDefault();
setIsDragging(true);
dragStartRef.current = {
x: event.clientX,
y: event.clientY,
posX: position.x,
posY: position.y,
};
},
[position.x, position.y],
);
useEffect(() => {
if (!isDragging) return undefined;
const handleMove = (event: MouseEvent) => {
const dx = event.clientX - dragStartRef.current.x;
const dy = event.clientY - dragStartRef.current.y;
setPosition(
clampPosition({
x: dragStartRef.current.posX + dx,
y: dragStartRef.current.posY + dy,
}),
);
};
const handleUp = () => setIsDragging(false);
window.addEventListener('mousemove', handleMove);
window.addEventListener('mouseup', handleUp);
return () => {
window.removeEventListener('mousemove', handleMove);
window.removeEventListener('mouseup', handleUp);
};
}, [isDragging, clampPosition]);
return {
position,
isMinimized,
setIsMinimized,
isDragging,
onDragStart,
};
}
+58
View File
@@ -0,0 +1,58 @@
import { useEffect, useState } from 'react';
import type { GtDossier } from '@/types/dashboard';
import { API_BASE } from '@/lib/api';
export function useGtDossier(
lat: number | undefined,
lng: number | undefined,
countryName?: string,
enabled = true,
) {
const [gtDossier, setGtDossier] = useState<GtDossier | null>(null);
const [gtDossierLoading, setGtDossierLoading] = useState(false);
useEffect(() => {
if (!enabled || lat == null || lng == null) {
setGtDossier(null);
setGtDossierLoading(false);
return;
}
let cancelled = false;
const regions = [
`${lat.toFixed(2)},${lng.toFixed(2)}`,
countryName?.trim().toLowerCase(),
].filter((value): value is string => Boolean(value));
const load = async () => {
setGtDossierLoading(true);
let best: GtDossier | null = null;
for (const region of regions) {
try {
const response = await fetch(
`${API_BASE}/api/analytics/dossier/${encodeURIComponent(region)}`,
);
if (!response.ok) continue;
const payload = (await response.json()) as GtDossier;
if (!payload.enabled) continue;
if (!best || (payload.current_risk ?? 0) > (best.current_risk ?? 0)) {
best = { ...payload, region };
}
} catch {
// GT analytics optional — ignore fetch errors
}
}
if (!cancelled) {
setGtDossier(best);
setGtDossierLoading(false);
}
};
void load();
return () => {
cancelled = true;
};
}, [lat, lng, countryName, enabled]);
return { gtDossier, gtDossierLoading };
}
+60
View File
@@ -0,0 +1,60 @@
'use client';
import { useEffect, useState } from 'react';
import { API_BASE } from '@/lib/api';
export interface RuntimeGtAnalytics {
enabled?: boolean;
operational?: boolean;
profile?: string;
lean_node?: boolean;
recommended?: boolean;
warning?: string | null;
experimental?: boolean;
}
export interface RuntimeProfile {
profile?: string;
cpu_limit?: number | null;
memory_limit_mb?: number | null;
gt_analytics?: RuntimeGtAnalytics;
}
export function useRuntimeProfile(): RuntimeProfile | null {
const [runtime, setRuntime] = useState<RuntimeProfile | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
const res = await fetch(`${API_BASE}/api/health`, { cache: 'no-store' });
if (!res.ok || cancelled) return;
const body = await res.json();
if (!cancelled && body?.runtime) {
setRuntime(body.runtime as RuntimeProfile);
}
} catch {
/* health unavailable during boot */
}
};
void load();
const timer = window.setInterval(load, 60_000);
return () => {
cancelled = true;
window.clearInterval(timer);
};
}, []);
return runtime;
}
export function gtLeanLayerWarning(runtime: RuntimeProfile | null): string | null {
const gt = runtime?.gt_analytics;
if (!gt?.lean_node) return null;
return (
gt.warning ||
'This node is capped at 1 vCPU. Enabling Strategic Risk (Derived OSINT) may slow OSINT fetches.'
);
}