mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-16 06:05:27 +02:00
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:
co-authored by
Robert Pickett
Cursor
parent
9c5a4054f6
commit
cfbeabda1e
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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.'
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user