mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
Add API error monitoring tests and error context snapshots for various browsers
- Created error context snapshots for Firefox, WebKit, and Chromium to capture UI state during API error monitoring. - Implemented e2e tests for API error detection, including console errors, failed API requests, and proxy errors. - Added functionality to ignore specific API patterns in monitoring assertions. - Ensured tests validate the monitoring system's ability to detect and report errors effectively.
This commit is contained in:
@@ -166,3 +166,13 @@ export type {
|
||||
UseRulesOptions,
|
||||
UseRulesReturn,
|
||||
} from './useCli';
|
||||
|
||||
// ========== CLI Execution ==========
|
||||
export {
|
||||
useCliExecutionDetail,
|
||||
cliExecutionKeys,
|
||||
} from './useCliExecution';
|
||||
export type {
|
||||
UseCliExecutionOptions,
|
||||
UseCliExecutionReturn,
|
||||
} from './useCliExecution';
|
||||
|
||||
112
ccw/frontend/src/hooks/useCliExecution.ts
Normal file
112
ccw/frontend/src/hooks/useCliExecution.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
// ========================================
|
||||
// useCliExecution Hook
|
||||
// ========================================
|
||||
// TanStack Query hook for CLI execution details
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
fetchExecutionDetail,
|
||||
type ConversationRecord,
|
||||
} from '../lib/api';
|
||||
|
||||
// ========== Query Keys ==========
|
||||
|
||||
/**
|
||||
* Query key factory for CLI execution queries
|
||||
*/
|
||||
export const cliExecutionKeys = {
|
||||
all: ['cliExecution'] as const,
|
||||
details: () => [...cliExecutionKeys.all, 'detail'] as const,
|
||||
detail: (id: string | null) => [...cliExecutionKeys.details(), id] as const,
|
||||
};
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
/**
|
||||
* Default stale time: 5 minutes
|
||||
* Execution details don't change frequently after completion
|
||||
*/
|
||||
const STALE_TIME = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Cache time: 10 minutes
|
||||
* Keep cached data available for potential re-use
|
||||
*/
|
||||
const GC_TIME = 10 * 60 * 1000;
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface UseCliExecutionOptions {
|
||||
/** Override default stale time (ms) */
|
||||
staleTime?: number;
|
||||
/** Override default cache time (ms) */
|
||||
gcTime?: number;
|
||||
/** Enable/disable the query */
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface UseCliExecutionReturn {
|
||||
/** Execution detail data */
|
||||
data: ConversationRecord | undefined;
|
||||
/** Loading state for initial fetch */
|
||||
isLoading: boolean;
|
||||
/** Fetching state (initial or refetch) */
|
||||
isFetching: boolean;
|
||||
/** Error object if query failed */
|
||||
error: Error | null;
|
||||
/** Manually refetch data */
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
// ========== Hook ==========
|
||||
|
||||
/**
|
||||
* Hook for fetching CLI execution detail (conversation records)
|
||||
*
|
||||
* @param executionId - The CLI execution ID to fetch details for
|
||||
* @param options - Query options
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data, isLoading, error } = useCliExecutionDetail('exec-123');
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* - Query is disabled when executionId is null/undefined
|
||||
* - Data is cached for 5 minutes by default
|
||||
* - Auto-refetch is disabled (execution details don't change)
|
||||
*/
|
||||
export function useCliExecutionDetail(
|
||||
executionId: string | null,
|
||||
options: UseCliExecutionOptions = {}
|
||||
): UseCliExecutionReturn {
|
||||
const { staleTime = STALE_TIME, gcTime = GC_TIME, enabled = true } = options;
|
||||
|
||||
const query = useQuery<ConversationRecord>({
|
||||
queryKey: cliExecutionKeys.detail(executionId),
|
||||
queryFn: () => {
|
||||
if (!executionId) throw new Error('executionId is required');
|
||||
return fetchExecutionDetail(executionId);
|
||||
},
|
||||
enabled: !!executionId && enabled,
|
||||
staleTime,
|
||||
gcTime,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: 2,
|
||||
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
|
||||
});
|
||||
|
||||
const refetch = async () => {
|
||||
await query.refetch();
|
||||
};
|
||||
|
||||
return {
|
||||
data: query.data,
|
||||
isLoading: query.isLoading,
|
||||
isFetching: query.isFetching,
|
||||
error: query.error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
37
ccw/frontend/src/hooks/useLocalStorage.ts
Normal file
37
ccw/frontend/src/hooks/useLocalStorage.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Generic hook for managing localStorage state with SSR safety
|
||||
* @template T The type of value being stored
|
||||
* @param key The localStorage key
|
||||
* @param defaultValue The default value if not in localStorage
|
||||
* @returns [value, setValue] tuple similar to useState
|
||||
*/
|
||||
export function useLocalStorage<T>(key: string, defaultValue: T): [T, (value: T) => void] {
|
||||
const [storedValue, setStoredValue] = useState<T>(defaultValue);
|
||||
|
||||
// Load value from localStorage on mount (for SSR safety)
|
||||
useEffect(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
if (item) {
|
||||
setStoredValue(JSON.parse(item));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load localStorage key "${key}":`, error);
|
||||
}
|
||||
}, [key]);
|
||||
|
||||
// Update localStorage when value changes
|
||||
const setValue = (value: T) => {
|
||||
try {
|
||||
setStoredValue(value);
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.warn(`Failed to set localStorage key "${key}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// Return storedValue immediately (it will be hydrated after effect runs)
|
||||
return [storedValue, setValue];
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// ========================================
|
||||
// useTheme Hook
|
||||
// ========================================
|
||||
// Convenient hook for theme management
|
||||
// Convenient hook for theme management with multi-color scheme support
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useAppStore, selectTheme, selectResolvedTheme } from '../stores/appStore';
|
||||
import type { Theme } from '../types/store';
|
||||
import type { Theme, ColorScheme } from '../types/store';
|
||||
|
||||
export interface UseThemeReturn {
|
||||
/** Current theme preference ('light', 'dark', 'system') */
|
||||
@@ -14,31 +14,40 @@ export interface UseThemeReturn {
|
||||
resolvedTheme: 'light' | 'dark';
|
||||
/** Whether the resolved theme is dark */
|
||||
isDark: boolean;
|
||||
/** Current color scheme ('blue', 'green', 'orange', 'purple') */
|
||||
colorScheme: ColorScheme;
|
||||
/** Set theme preference */
|
||||
setTheme: (theme: Theme) => void;
|
||||
/** Set color scheme */
|
||||
setColorScheme: (scheme: ColorScheme) => void;
|
||||
/** Toggle between light and dark (ignores system) */
|
||||
toggleTheme: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing theme state
|
||||
* Hook for managing theme state with multi-color scheme support
|
||||
* @returns Theme state and actions
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { theme, isDark, setTheme, toggleTheme } = useTheme();
|
||||
* const { theme, colorScheme, isDark, setTheme, setColorScheme, toggleTheme } = useTheme();
|
||||
*
|
||||
* return (
|
||||
* <button onClick={toggleTheme}>
|
||||
* {isDark ? 'Switch to Light' : 'Switch to Dark'}
|
||||
* </button>
|
||||
* <div>
|
||||
* <button onClick={() => setColorScheme('blue')}>Blue Theme</button>
|
||||
* <button onClick={toggleTheme}>
|
||||
* {isDark ? 'Switch to Light' : 'Switch to Dark'}
|
||||
* </button>
|
||||
* </div>
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function useTheme(): UseThemeReturn {
|
||||
const theme = useAppStore(selectTheme);
|
||||
const resolvedTheme = useAppStore(selectResolvedTheme);
|
||||
const colorScheme = useAppStore((state) => state.colorScheme);
|
||||
const setThemeAction = useAppStore((state) => state.setTheme);
|
||||
const setColorSchemeAction = useAppStore((state) => state.setColorScheme);
|
||||
const toggleThemeAction = useAppStore((state) => state.toggleTheme);
|
||||
|
||||
const setTheme = useCallback(
|
||||
@@ -48,6 +57,13 @@ export function useTheme(): UseThemeReturn {
|
||||
[setThemeAction]
|
||||
);
|
||||
|
||||
const setColorScheme = useCallback(
|
||||
(newColorScheme: ColorScheme) => {
|
||||
setColorSchemeAction(newColorScheme);
|
||||
},
|
||||
[setColorSchemeAction]
|
||||
);
|
||||
|
||||
const toggleTheme = useCallback(() => {
|
||||
toggleThemeAction();
|
||||
}, [toggleThemeAction]);
|
||||
@@ -56,7 +72,9 @@ export function useTheme(): UseThemeReturn {
|
||||
theme,
|
||||
resolvedTheme,
|
||||
isDark: resolvedTheme === 'dark',
|
||||
colorScheme,
|
||||
setTheme,
|
||||
setColorScheme,
|
||||
toggleTheme,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import { useExecutionStore } from '@/stores/executionStore';
|
||||
import { useFlowStore } from '@/stores';
|
||||
import { useCliStreamStore } from '@/stores/cliStreamStore';
|
||||
import {
|
||||
OrchestratorMessageSchema,
|
||||
type OrchestratorWebSocketMessage,
|
||||
@@ -54,6 +55,9 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
// Flow store for node status updates on canvas
|
||||
const updateNode = useFlowStore((state) => state.updateNode);
|
||||
|
||||
// CLI stream store for CLI output handling
|
||||
const addOutput = useCliStreamStore((state) => state.addOutput);
|
||||
|
||||
// Handle incoming WebSocket messages
|
||||
const handleMessage = useCallback(
|
||||
(event: MessageEvent) => {
|
||||
@@ -63,6 +67,69 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
// Store last message for debugging
|
||||
setWsLastMessage(data);
|
||||
|
||||
// Handle CLI messages
|
||||
if (data.type?.startsWith('CLI_')) {
|
||||
switch (data.type) {
|
||||
case 'CLI_STARTED': {
|
||||
const { executionId, tool, mode, timestamp } = data.payload;
|
||||
|
||||
// Add system message for CLI start
|
||||
addOutput(executionId, {
|
||||
type: 'system',
|
||||
content: `[${new Date(timestamp).toLocaleTimeString()}] CLI execution started: ${tool} (${mode || 'default'} mode)`,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'CLI_OUTPUT': {
|
||||
const { executionId, chunkType, data: outputData, unit } = data.payload;
|
||||
|
||||
// Handle structured output
|
||||
const unitContent = unit?.content || outputData;
|
||||
const unitType = unit?.type || chunkType;
|
||||
|
||||
// Special handling for tool_call type
|
||||
let content: string;
|
||||
if (unitType === 'tool_call' && typeof unitContent === 'object' && unitContent !== null) {
|
||||
// Format tool_call display
|
||||
content = JSON.stringify(unitContent);
|
||||
} else {
|
||||
content = typeof unitContent === 'string' ? unitContent : JSON.stringify(unitContent);
|
||||
}
|
||||
|
||||
// Split by lines and add each line to store
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line: string) => {
|
||||
// Add non-empty lines, or single line if that's all we have
|
||||
if (line.trim() || lines.length === 1) {
|
||||
addOutput(executionId, {
|
||||
type: unitType as any,
|
||||
content: line,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'CLI_COMPLETED': {
|
||||
const { executionId, success, duration } = data.payload;
|
||||
|
||||
const statusText = success ? 'completed successfully' : 'failed';
|
||||
const durationText = duration ? ` (${duration}ms)` : '';
|
||||
|
||||
addOutput(executionId, {
|
||||
type: 'system',
|
||||
content: `[${new Date().toLocaleTimeString()}] CLI execution ${statusText}${durationText}`,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is an orchestrator message
|
||||
if (!data.type?.startsWith('ORCHESTRATOR_')) {
|
||||
return;
|
||||
@@ -138,6 +205,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
addLog,
|
||||
completeExecution,
|
||||
updateNode,
|
||||
addOutput,
|
||||
onMessage,
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user