mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-06 04:17:57 +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:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user