/
GreyCat
/
openhuman
Обзор
Документация
Войти
/
GreyCat
/
openhuman
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
app/src/components/intelligence/ConnectionPathTab.tsx
131 строка
5 KB
Steven Enamakel
feat(theme): runtime theming — semantic tokens, Theme Studio, presets (#4130)
26 июн 2026, 00:33
Не верифицирован
26 июн 2026, 00:33
a49f2ce
Код
Авторство
О чём код?
/** * Connection Path tab (container). Loads the graph once (entities for the * pickers + relations), then runs the pure synchronous path engine on the * selected endpoints via useMemo — so tracing is instant with no extra * round-trips. Read-only. Delegates rendering to <ConnectionPathPanel>. */ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useT } from '../../lib/i18n/I18nContext'; import { findConnectionPath } from '../../lib/memory/connectionPath'; import { type GraphData, loadGraph, loadNamespaces } from '../../services/api/connectionPathApi'; import ConnectionPathPanel from './ConnectionPathPanel'; const EMPTY_GRAPH: GraphData = { entities: [], relations: [] }; const ENTITY_LIST_ID = 'connection-path-entities'; const ConnectionPathTab = () => { const { t } = useT(); const [graph, setGraph] = useState<GraphData>(EMPTY_GRAPH); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const [namespaces, setNamespaces] = useState<string[]>([]); const [namespace, setNamespace] = useState(''); const [source, setSource] = useState(''); const [target, setTarget] = useState(''); // Monotonic token: ignore a graph response if a newer load has since started. const latestRequestId = useRef(0); const load = useCallback(async (ns: string) => { const requestId = (latestRequestId.current += 1); setLoading(true); setError(null); try { const next = await loadGraph(ns || undefined); if (requestId !== latestRequestId.current) return; setGraph(next); } catch (err) { if (requestId !== latestRequestId.current) return; setGraph(EMPTY_GRAPH); setError(err instanceof Error ? err.message : String(err)); } finally { if (requestId === latestRequestId.current) setLoading(false); } }, []); useEffect(() => { loadNamespaces() .then(setNamespaces) .catch(() => setNamespaces([])); // Intentional fetch-on-mount. `loading` initializes to true and `error` to // null, so load()'s synchronous setState is a no-op on the first render and // triggers no cascading re-render. // eslint-disable-next-line react-hooks/set-state-in-effect void load(''); }, [load]); const handleNamespace = (next: string): void => { setNamespace(next); setSource(''); setTarget(''); void load(next); }; const trimmedSource = source.trim(); const trimmedTarget = target.trim(); const result = useMemo(() => { if (!trimmedSource || !trimmedTarget) return null; return findConnectionPath(graph.relations, trimmedSource, trimmedTarget); }, [graph.relations, trimmedSource, trimmedTarget]); return ( <div className="space-y-4"> {namespaces.length > 0 && ( <label className="flex items-center gap-2 text-xs text-content-secondary"> {t('connectionPath.namespaceLabel')} <select value={namespace} onChange={e => handleNamespace(e.target.value)} className="rounded-lg border border-line bg-surface px-2 py-1 text-sm text-content"> <option value="">{t('connectionPath.namespaceAll')}</option> {namespaces.map(ns => ( <option key={ns} value={ns}> {ns} </option> ))} </select> </label> )} <div className="flex flex-wrap gap-3"> <label className="flex flex-1 min-w-[10rem] flex-col gap-1 text-xs text-content-secondary"> {t('connectionPath.sourceLabel')} <input type="text" list={ENTITY_LIST_ID} value={source} onChange={e => setSource(e.target.value)} placeholder={t('connectionPath.sourcePlaceholder')} className="rounded-lg border border-line bg-surface px-2 py-1.5 text-sm text-content" /> </label> <label className="flex flex-1 min-w-[10rem] flex-col gap-1 text-xs text-content-secondary"> {t('connectionPath.targetLabel')} <input type="text" list={ENTITY_LIST_ID} value={target} onChange={e => setTarget(e.target.value)} placeholder={t('connectionPath.targetPlaceholder')} className="rounded-lg border border-line bg-surface px-2 py-1.5 text-sm text-content" /> </label> <datalist id={ENTITY_LIST_ID}> {graph.entities.map(entity => ( <option key={entity} value={entity} /> ))} </datalist> </div> <ConnectionPathPanel result={result} hasGraph={graph.entities.length > 0} loading={loading} error={error} onRetry={() => void load(namespace)} /> </div> ); }; export default ConnectionPathTab;