mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
* 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>
110 lines
3.2 KiB
TypeScript
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 };
|
|
}
|