Files
openhuman/src/hooks/useIntelligenceStats.ts
T
ad9d001261 Feat/e2e appium mac2 (#116)
* Replace Playwright with Appium mac2 + WebDriverIO for E2E testing and add unit tests

- Remove Playwright config and old e2e/ directory
- Add WebDriverIO + Appium mac2 driver for true native macOS app testing
- Add wdio.conf.ts with platform-aware app path detection
- Add scripts/e2e.sh to manage Appium lifecycle under Node 24
- Add E2E smoke, navigation, and Tauri integration specs
- Add tsconfig.e2e.json for E2E test TypeScript config
- Add unit tests for components, store slices, selectors, services, and utils
- Add test infrastructure (setup.ts, MSW handlers, test-utils)
- Update vitest.config.ts with coverage thresholds and setup
- Update package.json scripts and dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update skills submodule to latest

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add complete E2E login flow test with mock server and onboarding walkthrough

Implements end-to-end testing of the full deep link auth → onboarding → home
flow using Appium mac2, WebDriverIO, and a local HTTP/WebSocket mock server.

Key additions:
- Mock server (port 18473) with all API routes + Socket.IO/Engine.IO WebSocket
  handler to prevent Rust SocketManager crashes
- Deep link helpers to trigger alphahuman:// URLs via macOS `open` command
- Element helpers using XPath selectors (not iOS predicates, which crash on
  mac2 with non-string attributes) and W3C pointer actions for clicking
  WKWebView content (standard element.click() doesn't trigger DOM handlers)
- Login flow spec: 11 tests covering auth deep link, API call verification,
  all 4 onboarding steps, and home page arrival
- Dedicated e2e-build.sh script with cargo clean + mock URL injection
- Cache cleanup and mock URL verification in e2e.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Harden E2E tests and fix config issues from PR review

- e2e.sh: fail fast when dist bundle is missing instead of silently skipping
- setup.ts: add React type import for PersistGate mock annotation
- app-helpers: throw descriptive error on waitForAppReady timeout
- element-helpers: XPath quote escaping via concat() for special chars
- login-flow: fail explicitly when invite code step retry doesn't advance
- smoke.spec: replace no-op assertion with real session ID checks
- vite/vitest configs: remove unused @alphahuman/skill-types alias and
  dead path/fileURLToPath imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add TypeScript types to E2E element helpers and configure ESLint for E2E files

Remove blanket eslint-disable/ts-nocheck from element-helpers.ts, add explicit
TypeScript types with ChainablePromiseElement import, and add ESLint config block
for test/e2e/ files using tsconfig.e2e.json with WebDriverIO/Mocha globals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CI submodule checkout by adding XGH_TOKEN_READ secret

The skills submodule is a private repo that requires authentication.
The build and package-android workflows were missing the token that
package-and-publish already had.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix CI: install skills deps, format with Prettier, lower coverage thresholds

- build.yml: add skills dependency install step and remove redundant
  frontend build (tauri build already runs build:app via beforeBuildCommand)
- Run Prettier --write on all 32 files with formatting drift
- Lower vitest coverage thresholds to match actual coverage (~20%)
  until test coverage improves organically

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove stray globals.d.ts that failed Prettier check

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix ESLint parsing for wdio.conf.ts and type remaining E2E helpers

- Add wdio.conf.ts to ESLint E2E config block so TS parser covers it
- Remove blanket eslint-disable/ts-nocheck from deep-link-helpers.ts
  and app-helpers.ts, add explicit TypeScript types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix unnecessary regex escape in useSettingsNavigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Ghost Scripter <ghostscripter@zerolend.xyz>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:10:06 +05:30

110 lines
3.2 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core';
import { useCallback, useEffect, useState } from 'react';
import { apiClient } from '../services/apiClient';
import type { AIStatus } from '../store/aiSlice';
import { useAppSelector } from '../store/hooks';
interface SessionEntry {
sessionId: string;
updatedAt: number;
inputTokens: number;
outputTokens: number;
totalTokens: number;
compactionCount: number;
memoryFlushAt?: number;
}
interface SessionStats {
total: number;
totalTokens: number;
compactions: number;
memoryFlushes: number;
}
export interface IntelligenceStats {
sessions: SessionStats | null;
memoryFiles: number | null;
entities: Record<string, number> | null;
entityError: boolean;
aiStatus: AIStatus;
isLoading: boolean;
refetch: () => void;
}
const ENTITY_TYPES = ['contact', 'chat', 'message', 'wallet', 'token', 'transaction'];
export function useIntelligenceStats(): IntelligenceStats {
const aiStatus = useAppSelector(state => state.ai.status);
const [sessions, setSessions] = useState<SessionStats | null>(null);
const [memoryFiles, setMemoryFiles] = useState<number | null>(null);
const [entities, setEntities] = useState<Record<string, number> | null>(null);
const [entityError, setEntityError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const fetchStats = useCallback(async () => {
setIsLoading(true);
// Fetch local stats (Tauri invoke)
try {
const index = await invoke<Record<string, SessionEntry>>('ai_sessions_load_index');
const entries = Object.values(index);
setSessions({
total: entries.length,
totalTokens: entries.reduce((sum, e) => sum + (e.totalTokens || 0), 0),
compactions: entries.reduce((sum, e) => sum + (e.compactionCount || 0), 0),
memoryFlushes: entries.filter(e => e.memoryFlushAt).length,
});
} catch {
setSessions(null);
}
try {
const files = await invoke<string[]>('ai_list_memory_files', { relativeDir: 'memory' });
setMemoryFiles(files.length);
} catch {
setMemoryFiles(null);
}
// Fetch entity counts from backend API (graceful degradation)
try {
const counts: Record<string, number> = {};
const results = await Promise.allSettled(
ENTITY_TYPES.map(async type => {
const resp = await apiClient.get<{ count?: number; total?: number; data?: unknown[] }>(
`/api/entity-graph/entities?type=${type}&limit=1`
);
return { type, count: resp.count ?? resp.total ?? (resp.data ? resp.data.length : 0) };
})
);
let anySuccess = false;
for (const result of results) {
if (result.status === 'fulfilled') {
counts[result.value.type] = result.value.count;
anySuccess = true;
}
}
if (anySuccess) {
setEntities(counts);
setEntityError(false);
} else {
setEntities(null);
setEntityError(true);
}
} catch {
setEntities(null);
setEntityError(true);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchStats();
}, [fetchStats, aiStatus]);
return { sessions, memoryFiles, entities, entityError, aiStatus, isLoading, refetch: fetchStats };
}