mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat(ui): add React Query hooks for data fetching
- Add useFeatures, useFeature, useAgentOutput for feature data - Add useGitHubIssues, useGitHubPRs, useGitHubValidations, useGitHubIssueComments - Add useClaudeUsage, useCodexUsage with polling intervals - Add useRunningAgents, useRunningAgentsCount - Add useWorktrees, useWorktreeInfo, useWorktreeStatus, useWorktreeDiffs - Add useGlobalSettings, useProjectSettings, useCredentials - Add useAvailableModels, useCodexModels, useOpencodeModels - Add useSessions, useSessionHistory, useSessionQueue - Add useIdeationPrompts, useIdeas - Add CLI status queries (claude, cursor, codex, opencode, github) - Add useCursorPermissionsQuery, useWorkspaceDirectories - Add usePipelineConfig, useSpecFile, useSpecRegenerationStatus Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
91
apps/ui/src/hooks/queries/index.ts
Normal file
91
apps/ui/src/hooks/queries/index.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Query Hooks Barrel Export
|
||||||
|
*
|
||||||
|
* Central export point for all React Query hooks.
|
||||||
|
* Import from this file for cleaner imports across the app.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { useFeatures, useGitHubIssues, useClaudeUsage } from '@/hooks/queries';
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Features
|
||||||
|
export { useFeatures, useFeature, useAgentOutput } from './use-features';
|
||||||
|
|
||||||
|
// GitHub
|
||||||
|
export {
|
||||||
|
useGitHubIssues,
|
||||||
|
useGitHubPRs,
|
||||||
|
useGitHubValidations,
|
||||||
|
useGitHubRemote,
|
||||||
|
useGitHubIssueComments,
|
||||||
|
} from './use-github';
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
export { useClaudeUsage, useCodexUsage } from './use-usage';
|
||||||
|
|
||||||
|
// Running Agents
|
||||||
|
export { useRunningAgents, useRunningAgentsCount } from './use-running-agents';
|
||||||
|
|
||||||
|
// Worktrees
|
||||||
|
export {
|
||||||
|
useWorktrees,
|
||||||
|
useWorktreeInfo,
|
||||||
|
useWorktreeStatus,
|
||||||
|
useWorktreeDiffs,
|
||||||
|
useWorktreeBranches,
|
||||||
|
useWorktreeInitScript,
|
||||||
|
useAvailableEditors,
|
||||||
|
} from './use-worktrees';
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
export {
|
||||||
|
useGlobalSettings,
|
||||||
|
useProjectSettings,
|
||||||
|
useSettingsStatus,
|
||||||
|
useCredentials,
|
||||||
|
useDiscoveredAgents,
|
||||||
|
} from './use-settings';
|
||||||
|
|
||||||
|
// Models
|
||||||
|
export {
|
||||||
|
useAvailableModels,
|
||||||
|
useCodexModels,
|
||||||
|
useOpencodeModels,
|
||||||
|
useOpencodeProviders,
|
||||||
|
useModelProviders,
|
||||||
|
} from './use-models';
|
||||||
|
|
||||||
|
// CLI Status
|
||||||
|
export {
|
||||||
|
useClaudeCliStatus,
|
||||||
|
useCursorCliStatus,
|
||||||
|
useCodexCliStatus,
|
||||||
|
useOpencodeCliStatus,
|
||||||
|
useGitHubCliStatus,
|
||||||
|
useApiKeysStatus,
|
||||||
|
usePlatformInfo,
|
||||||
|
} from './use-cli-status';
|
||||||
|
|
||||||
|
// Ideation
|
||||||
|
export { useIdeationPrompts, useIdeas, useIdea } from './use-ideation';
|
||||||
|
|
||||||
|
// Sessions
|
||||||
|
export { useSessions, useSessionHistory, useSessionQueue } from './use-sessions';
|
||||||
|
|
||||||
|
// Git
|
||||||
|
export { useGitDiffs } from './use-git';
|
||||||
|
|
||||||
|
// Pipeline
|
||||||
|
export { usePipelineConfig } from './use-pipeline';
|
||||||
|
|
||||||
|
// Spec
|
||||||
|
export { useSpecFile, useSpecRegenerationStatus } from './use-spec';
|
||||||
|
|
||||||
|
// Cursor Permissions
|
||||||
|
export { useCursorPermissionsQuery } from './use-cursor-permissions';
|
||||||
|
export type { CursorPermissionsData } from './use-cursor-permissions';
|
||||||
|
|
||||||
|
// Workspace
|
||||||
|
export { useWorkspaceDirectories } from './use-workspace';
|
||||||
147
apps/ui/src/hooks/queries/use-cli-status.ts
Normal file
147
apps/ui/src/hooks/queries/use-cli-status.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* CLI Status Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching CLI tool status (Claude, Cursor, Codex, etc.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Claude CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Claude CLI status
|
||||||
|
*/
|
||||||
|
export function useClaudeCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.claude(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getClaudeStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Claude status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Cursor CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Cursor CLI status
|
||||||
|
*/
|
||||||
|
export function useCursorCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.cursor(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getCursorStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Cursor status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Codex CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with Codex CLI status
|
||||||
|
*/
|
||||||
|
export function useCodexCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.codex(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getCodexStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Codex status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch OpenCode CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with OpenCode CLI status
|
||||||
|
*/
|
||||||
|
export function useOpencodeCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.opencode(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getOpencodeStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch OpenCode status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch GitHub CLI status
|
||||||
|
*
|
||||||
|
* @returns Query result with GitHub CLI status
|
||||||
|
*/
|
||||||
|
export function useGitHubCliStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.github(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getGhStatus();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch GitHub CLI status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch API keys status
|
||||||
|
*
|
||||||
|
* @returns Query result with API keys status
|
||||||
|
*/
|
||||||
|
export function useApiKeysStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.apiKeys(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getApiKeys();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch platform info
|
||||||
|
*
|
||||||
|
* @returns Query result with platform info
|
||||||
|
*/
|
||||||
|
export function usePlatformInfo() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cli.platform(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getPlatform();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error('Failed to fetch platform info');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: Infinity, // Platform info never changes
|
||||||
|
});
|
||||||
|
}
|
||||||
58
apps/ui/src/hooks/queries/use-cursor-permissions.ts
Normal file
58
apps/ui/src/hooks/queries/use-cursor-permissions.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Cursor Permissions Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching Cursor CLI permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { CursorPermissionProfile } from '@automaker/types';
|
||||||
|
|
||||||
|
export interface CursorPermissionsData {
|
||||||
|
activeProfile: CursorPermissionProfile | null;
|
||||||
|
effectivePermissions: { allow: string[]; deny: string[] } | null;
|
||||||
|
hasProjectConfig: boolean;
|
||||||
|
availableProfiles: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permissions: { allow: string[]; deny: string[] };
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Cursor permissions for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Optional path to the project
|
||||||
|
* @param enabled - Whether to enable the query
|
||||||
|
* @returns Query result with permissions data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: permissions, isLoading, refetch } = useCursorPermissions(projectPath);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCursorPermissionsQuery(projectPath?: string, enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.cursorPermissions.permissions(projectPath),
|
||||||
|
queryFn: async (): Promise<CursorPermissionsData> => {
|
||||||
|
const api = getHttpApiClient();
|
||||||
|
const result = await api.setup.getCursorPermissions(projectPath);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to load permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeProfile: result.activeProfile || null,
|
||||||
|
effectivePermissions: result.effectivePermissions || null,
|
||||||
|
hasProjectConfig: result.hasProjectConfig || false,
|
||||||
|
availableProfiles: result.availableProfiles || [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
127
apps/ui/src/hooks/queries/use-features.ts
Normal file
127
apps/ui/src/hooks/queries/use-features.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* Features Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching and managing features data.
|
||||||
|
* These hooks replace manual useState/useEffect patterns with
|
||||||
|
* automatic caching, deduplication, and background refetching.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { Feature } from '@/store/app-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all features for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with features array
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: features, isLoading, error } = useFeatures(currentProject?.path);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useFeatures(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.features.all(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<Feature[]> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.features?.getAll(projectPath);
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch features');
|
||||||
|
}
|
||||||
|
return (result.features ?? []) as Feature[];
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.FEATURES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseFeatureOptions {
|
||||||
|
enabled?: boolean;
|
||||||
|
/** Override polling interval (ms). Use false to disable polling. */
|
||||||
|
pollingInterval?: number | false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a single feature by ID
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param featureId - ID of the feature to fetch
|
||||||
|
* @param options - Query options including enabled and polling interval
|
||||||
|
* @returns Query result with single feature
|
||||||
|
*/
|
||||||
|
export function useFeature(
|
||||||
|
projectPath: string | undefined,
|
||||||
|
featureId: string | undefined,
|
||||||
|
options: UseFeatureOptions = {}
|
||||||
|
) {
|
||||||
|
const { enabled = true, pollingInterval } = options;
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.features.single(projectPath ?? '', featureId ?? ''),
|
||||||
|
queryFn: async (): Promise<Feature | null> => {
|
||||||
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.features?.get(projectPath, featureId);
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch feature');
|
||||||
|
}
|
||||||
|
return (result.feature as Feature) ?? null;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!featureId && enabled,
|
||||||
|
staleTime: STALE_TIMES.FEATURES,
|
||||||
|
refetchInterval: pollingInterval,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseAgentOutputOptions {
|
||||||
|
enabled?: boolean;
|
||||||
|
/** Override polling interval (ms). Use false to disable polling. */
|
||||||
|
pollingInterval?: number | false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch agent output for a feature
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param featureId - ID of the feature
|
||||||
|
* @param options - Query options including enabled and polling interval
|
||||||
|
* @returns Query result with agent output string
|
||||||
|
*/
|
||||||
|
export function useAgentOutput(
|
||||||
|
projectPath: string | undefined,
|
||||||
|
featureId: string | undefined,
|
||||||
|
options: UseAgentOutputOptions = {}
|
||||||
|
) {
|
||||||
|
const { enabled = true, pollingInterval } = options;
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.features.agentOutput(projectPath ?? '', featureId ?? ''),
|
||||||
|
queryFn: async (): Promise<string> => {
|
||||||
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.features?.getAgentOutput(projectPath, featureId);
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch agent output');
|
||||||
|
}
|
||||||
|
return result.content ?? '';
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!featureId && enabled,
|
||||||
|
staleTime: STALE_TIMES.AGENT_OUTPUT,
|
||||||
|
// Use provided polling interval or default behavior
|
||||||
|
refetchInterval:
|
||||||
|
pollingInterval !== undefined
|
||||||
|
? pollingInterval
|
||||||
|
: (query) => {
|
||||||
|
// Only poll if we have data and it's not empty (indicating active task)
|
||||||
|
if (query.state.data && query.state.data.length > 0) {
|
||||||
|
return 5000; // 5 seconds
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
37
apps/ui/src/hooks/queries/use-git.ts
Normal file
37
apps/ui/src/hooks/queries/use-git.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Git Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for git operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch git diffs for a project (main project, not worktree)
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param enabled - Whether to enable the query
|
||||||
|
* @returns Query result with files and diff content
|
||||||
|
*/
|
||||||
|
export function useGitDiffs(projectPath: string | undefined, enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.git.diffs(projectPath ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.git.getDiffs(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch diffs');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
files: result.files ?? [],
|
||||||
|
diff: result.diff ?? '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && enabled,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
184
apps/ui/src/hooks/queries/use-github.ts
Normal file
184
apps/ui/src/hooks/queries/use-github.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* GitHub Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching GitHub issues, PRs, and validations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { GitHubIssue, GitHubPR, GitHubComment, IssueValidation } from '@/lib/electron';
|
||||||
|
|
||||||
|
interface GitHubIssuesResult {
|
||||||
|
openIssues: GitHubIssue[];
|
||||||
|
closedIssues: GitHubIssue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubPRsResult {
|
||||||
|
openPRs: GitHubPR[];
|
||||||
|
mergedPRs: GitHubPR[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch GitHub issues for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with open and closed issues
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, isLoading } = useGitHubIssues(currentProject?.path);
|
||||||
|
* const { openIssues, closedIssues } = data ?? { openIssues: [], closedIssues: [] };
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useGitHubIssues(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.github.issues(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<GitHubIssuesResult> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.github.listIssues(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch issues');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
openIssues: result.openIssues ?? [],
|
||||||
|
closedIssues: result.closedIssues ?? [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.GITHUB,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch GitHub PRs for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with open and merged PRs
|
||||||
|
*/
|
||||||
|
export function useGitHubPRs(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.github.prs(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<GitHubPRsResult> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.github.listPRs(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch PRs');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
openPRs: result.openPRs ?? [],
|
||||||
|
mergedPRs: result.mergedPRs ?? [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.GITHUB,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch GitHub validations for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param issueNumber - Optional issue number to filter by
|
||||||
|
* @returns Query result with validations
|
||||||
|
*/
|
||||||
|
export function useGitHubValidations(projectPath: string | undefined, issueNumber?: number) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: issueNumber
|
||||||
|
? queryKeys.github.validation(projectPath ?? '', issueNumber)
|
||||||
|
: queryKeys.github.validations(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<IssueValidation[]> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.github.getValidations(projectPath, issueNumber);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch validations');
|
||||||
|
}
|
||||||
|
return result.validations ?? [];
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.GITHUB,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check GitHub remote for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with remote info
|
||||||
|
*/
|
||||||
|
export function useGitHubRemote(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.github.remote(projectPath ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.github.checkRemote(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to check remote');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hasRemote: result.hasRemote ?? false,
|
||||||
|
owner: result.owner,
|
||||||
|
repo: result.repo,
|
||||||
|
url: result.url,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.GITHUB,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch comments for a GitHub issue with pagination support
|
||||||
|
*
|
||||||
|
* Uses useInfiniteQuery for proper "load more" pagination.
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param issueNumber - Issue number
|
||||||
|
* @returns Infinite query result with comments and pagination helpers
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const {
|
||||||
|
* data,
|
||||||
|
* isLoading,
|
||||||
|
* isFetchingNextPage,
|
||||||
|
* hasNextPage,
|
||||||
|
* fetchNextPage,
|
||||||
|
* refetch,
|
||||||
|
* } = useGitHubIssueComments(projectPath, issueNumber);
|
||||||
|
*
|
||||||
|
* // Get all comments flattened
|
||||||
|
* const comments = data?.pages.flatMap(page => page.comments) ?? [];
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useGitHubIssueComments(
|
||||||
|
projectPath: string | undefined,
|
||||||
|
issueNumber: number | undefined
|
||||||
|
) {
|
||||||
|
return useInfiniteQuery({
|
||||||
|
queryKey: queryKeys.github.issueComments(projectPath ?? '', issueNumber ?? 0),
|
||||||
|
queryFn: async ({ pageParam }: { pageParam: string | undefined }) => {
|
||||||
|
if (!projectPath || !issueNumber) throw new Error('Missing project path or issue number');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.github.getIssueComments(projectPath, issueNumber, pageParam);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch comments');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
comments: (result.comments ?? []) as GitHubComment[],
|
||||||
|
totalCount: result.totalCount ?? 0,
|
||||||
|
hasNextPage: result.hasNextPage ?? false,
|
||||||
|
endCursor: result.endCursor as string | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
initialPageParam: undefined as string | undefined,
|
||||||
|
getNextPageParam: (lastPage) => (lastPage.hasNextPage ? lastPage.endCursor : undefined),
|
||||||
|
enabled: !!projectPath && !!issueNumber,
|
||||||
|
staleTime: STALE_TIMES.GITHUB,
|
||||||
|
});
|
||||||
|
}
|
||||||
86
apps/ui/src/hooks/queries/use-ideation.ts
Normal file
86
apps/ui/src/hooks/queries/use-ideation.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Ideation Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching ideation prompts and ideas.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch ideation prompts
|
||||||
|
*
|
||||||
|
* @returns Query result with prompts and categories
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, isLoading, error } = useIdeationPrompts();
|
||||||
|
* const { prompts, categories } = data ?? { prompts: [], categories: [] };
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useIdeationPrompts() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.ideation.prompts(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.ideation?.getPrompts();
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch prompts');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
prompts: result.prompts ?? [],
|
||||||
|
categories: result.categories ?? [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.SETTINGS, // Prompts rarely change
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch ideas for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with ideas array
|
||||||
|
*/
|
||||||
|
export function useIdeas(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.ideation.ideas(projectPath ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.ideation?.listIdeas(projectPath);
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch ideas');
|
||||||
|
}
|
||||||
|
return result.ideas ?? [];
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.FEATURES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a single idea by ID
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param ideaId - ID of the idea
|
||||||
|
* @returns Query result with single idea
|
||||||
|
*/
|
||||||
|
export function useIdea(projectPath: string | undefined, ideaId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.ideation.idea(projectPath ?? '', ideaId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath || !ideaId) throw new Error('Missing project path or idea ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.ideation?.getIdea(projectPath, ideaId);
|
||||||
|
if (!result?.success) {
|
||||||
|
throw new Error(result?.error || 'Failed to fetch idea');
|
||||||
|
}
|
||||||
|
return result.idea;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!ideaId,
|
||||||
|
staleTime: STALE_TIMES.FEATURES,
|
||||||
|
});
|
||||||
|
}
|
||||||
134
apps/ui/src/hooks/queries/use-models.ts
Normal file
134
apps/ui/src/hooks/queries/use-models.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* Models Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching available AI models.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
interface CodexModel {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
hasThinking: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: 'premium' | 'standard' | 'basic';
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpencodeModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
modelString: string;
|
||||||
|
provider: string;
|
||||||
|
description: string;
|
||||||
|
supportsTools: boolean;
|
||||||
|
supportsVision: boolean;
|
||||||
|
tier: string;
|
||||||
|
default?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch available models
|
||||||
|
*
|
||||||
|
* @returns Query result with available models
|
||||||
|
*/
|
||||||
|
export function useAvailableModels() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.models.available(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.model.getAvailable();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch available models');
|
||||||
|
}
|
||||||
|
return result.models ?? [];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.MODELS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Codex models
|
||||||
|
*
|
||||||
|
* @param refresh - Force refresh from server
|
||||||
|
* @returns Query result with Codex models
|
||||||
|
*/
|
||||||
|
export function useCodexModels(refresh = false) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.models.codex(),
|
||||||
|
queryFn: async (): Promise<CodexModel[]> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.codex.getModels(refresh);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch Codex models');
|
||||||
|
}
|
||||||
|
return (result.models ?? []) as CodexModel[];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.MODELS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch OpenCode models
|
||||||
|
*
|
||||||
|
* @param refresh - Force refresh from server
|
||||||
|
* @returns Query result with OpenCode models
|
||||||
|
*/
|
||||||
|
export function useOpencodeModels(refresh = false) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.models.opencode(),
|
||||||
|
queryFn: async (): Promise<OpencodeModel[]> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getOpencodeModels(refresh);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch OpenCode models');
|
||||||
|
}
|
||||||
|
return (result.models ?? []) as OpencodeModel[];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.MODELS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch OpenCode providers
|
||||||
|
*
|
||||||
|
* @returns Query result with OpenCode providers
|
||||||
|
*/
|
||||||
|
export function useOpencodeProviders() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.models.opencodeProviders(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.setup.getOpencodeProviders();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch OpenCode providers');
|
||||||
|
}
|
||||||
|
return result.providers ?? [];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.MODELS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch model providers status
|
||||||
|
*
|
||||||
|
* @returns Query result with provider status
|
||||||
|
*/
|
||||||
|
export function useModelProviders() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.models.providers(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.model.checkProviders();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch providers');
|
||||||
|
}
|
||||||
|
return result.providers ?? {};
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.MODELS,
|
||||||
|
});
|
||||||
|
}
|
||||||
39
apps/ui/src/hooks/queries/use-pipeline.ts
Normal file
39
apps/ui/src/hooks/queries/use-pipeline.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Pipeline Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching pipeline configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { PipelineConfig } from '@/store/app-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch pipeline config for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with pipeline config
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: pipelineConfig, isLoading } = usePipelineConfig(currentProject?.path);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function usePipelineConfig(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.pipeline.config(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<PipelineConfig | null> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getHttpApiClient();
|
||||||
|
const result = await api.pipeline.getConfig(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch pipeline config');
|
||||||
|
}
|
||||||
|
return result.config ?? null;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
61
apps/ui/src/hooks/queries/use-running-agents.ts
Normal file
61
apps/ui/src/hooks/queries/use-running-agents.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Running Agents Query Hook
|
||||||
|
*
|
||||||
|
* React Query hook for fetching currently running agents.
|
||||||
|
* This data is invalidated by WebSocket events when agents start/stop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI, type RunningAgent } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
interface RunningAgentsResult {
|
||||||
|
agents: RunningAgent[];
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all currently running agents
|
||||||
|
*
|
||||||
|
* @returns Query result with running agents and total count
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, isLoading } = useRunningAgents();
|
||||||
|
* const { agents, count } = data ?? { agents: [], count: 0 };
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useRunningAgents() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.runningAgents.all(),
|
||||||
|
queryFn: async (): Promise<RunningAgentsResult> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.runningAgents.getAll();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch running agents');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
agents: result.runningAgents ?? [],
|
||||||
|
count: result.totalCount ?? 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.RUNNING_AGENTS,
|
||||||
|
// Note: Don't use refetchInterval here - rely on WebSocket invalidation
|
||||||
|
// for real-time updates instead of polling
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get running agents count
|
||||||
|
* This is a selector that derives count from the main query
|
||||||
|
*
|
||||||
|
* @returns Query result with just the count
|
||||||
|
*/
|
||||||
|
export function useRunningAgentsCount() {
|
||||||
|
const query = useRunningAgents();
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
data: query.data?.count ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
86
apps/ui/src/hooks/queries/use-sessions.ts
Normal file
86
apps/ui/src/hooks/queries/use-sessions.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Sessions Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching session data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { SessionListItem } from '@/types/electron';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all sessions
|
||||||
|
*
|
||||||
|
* @param includeArchived - Whether to include archived sessions
|
||||||
|
* @returns Query result with sessions array
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: sessions, isLoading } = useSessions(false);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useSessions(includeArchived = false) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.sessions.all(includeArchived),
|
||||||
|
queryFn: async (): Promise<SessionListItem[]> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.sessions.list(includeArchived);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch sessions');
|
||||||
|
}
|
||||||
|
return result.sessions ?? [];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.SESSIONS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch session history
|
||||||
|
*
|
||||||
|
* @param sessionId - ID of the session
|
||||||
|
* @returns Query result with session messages
|
||||||
|
*/
|
||||||
|
export function useSessionHistory(sessionId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.sessions.history(sessionId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!sessionId) throw new Error('No session ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.agent.getHistory(sessionId);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch session history');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
messages: result.messages ?? [],
|
||||||
|
isRunning: result.isRunning ?? false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!sessionId,
|
||||||
|
staleTime: STALE_TIMES.FEATURES, // Session history changes during conversations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch session message queue
|
||||||
|
*
|
||||||
|
* @param sessionId - ID of the session
|
||||||
|
* @returns Query result with queued messages
|
||||||
|
*/
|
||||||
|
export function useSessionQueue(sessionId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.sessions.queue(sessionId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!sessionId) throw new Error('No session ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.agent.queueList(sessionId);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch queue');
|
||||||
|
}
|
||||||
|
return result.queue ?? [];
|
||||||
|
},
|
||||||
|
enabled: !!sessionId,
|
||||||
|
staleTime: STALE_TIMES.RUNNING_AGENTS, // Queue changes frequently during use
|
||||||
|
});
|
||||||
|
}
|
||||||
122
apps/ui/src/hooks/queries/use-settings.ts
Normal file
122
apps/ui/src/hooks/queries/use-settings.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* Settings Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching global and project settings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { GlobalSettings, ProjectSettings } from '@automaker/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch global settings
|
||||||
|
*
|
||||||
|
* @returns Query result with global settings
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: settings, isLoading } = useGlobalSettings();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useGlobalSettings() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.settings.global(),
|
||||||
|
queryFn: async (): Promise<GlobalSettings> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.settings.getGlobal();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch global settings');
|
||||||
|
}
|
||||||
|
return result.settings as GlobalSettings;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch project-specific settings
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with project settings
|
||||||
|
*/
|
||||||
|
export function useProjectSettings(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.settings.project(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<ProjectSettings> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.settings.getProject(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch project settings');
|
||||||
|
}
|
||||||
|
return result.settings as ProjectSettings;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch settings status (migration status, etc.)
|
||||||
|
*
|
||||||
|
* @returns Query result with settings status
|
||||||
|
*/
|
||||||
|
export function useSettingsStatus() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.settings.status(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.settings.getStatus();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch credentials status (masked API keys)
|
||||||
|
*
|
||||||
|
* @returns Query result with credentials info
|
||||||
|
*/
|
||||||
|
export function useCredentials() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.settings.credentials(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.settings.getCredentials();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch credentials');
|
||||||
|
}
|
||||||
|
return result.credentials;
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover agents for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param sources - Sources to search ('user' | 'project')
|
||||||
|
* @returns Query result with discovered agents
|
||||||
|
*/
|
||||||
|
export function useDiscoveredAgents(
|
||||||
|
projectPath: string | undefined,
|
||||||
|
sources?: Array<'user' | 'project'>
|
||||||
|
) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.settings.agents(projectPath ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.settings.discoverAgents(projectPath, sources);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to discover agents');
|
||||||
|
}
|
||||||
|
return result.agents ?? [];
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
103
apps/ui/src/hooks/queries/use-spec.ts
Normal file
103
apps/ui/src/hooks/queries/use-spec.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Spec Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching spec file content and regeneration status.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
interface SpecFileResult {
|
||||||
|
content: string;
|
||||||
|
exists: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpecRegenerationStatusResult {
|
||||||
|
isRunning: boolean;
|
||||||
|
currentPhase?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch spec file content for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with spec content and existence flag
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, isLoading } = useSpecFile(currentProject?.path);
|
||||||
|
* if (data?.exists) {
|
||||||
|
* console.log(data.content);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useSpecFile(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.spec.file(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<SpecFileResult> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.readFile(`${projectPath}/.automaker/app_spec.txt`);
|
||||||
|
|
||||||
|
if (result.success && result.content) {
|
||||||
|
return {
|
||||||
|
content: result.content,
|
||||||
|
exists: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
exists: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check spec regeneration status for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param enabled - Whether to enable the query (useful during regeneration)
|
||||||
|
* @returns Query result with regeneration status
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data } = useSpecRegenerationStatus(projectPath, isRegenerating);
|
||||||
|
* if (data?.isRunning) {
|
||||||
|
* // Show loading indicator
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useSpecRegenerationStatus(projectPath: string | undefined, enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.specRegeneration.status(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<SpecRegenerationStatusResult> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
|
||||||
|
const api = getElectronAPI();
|
||||||
|
if (!api.specRegeneration) {
|
||||||
|
return { isRunning: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await api.specRegeneration.status(projectPath);
|
||||||
|
|
||||||
|
if (status.success) {
|
||||||
|
return {
|
||||||
|
isRunning: status.isRunning ?? false,
|
||||||
|
currentPhase: status.currentPhase,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isRunning: false };
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && enabled,
|
||||||
|
staleTime: 5000, // Check every 5 seconds when active
|
||||||
|
refetchInterval: enabled ? 5000 : false,
|
||||||
|
});
|
||||||
|
}
|
||||||
77
apps/ui/src/hooks/queries/use-usage.ts
Normal file
77
apps/ui/src/hooks/queries/use-usage.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Usage Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching Claude and Codex API usage data.
|
||||||
|
* These hooks include automatic polling for real-time usage updates.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
import type { ClaudeUsage, CodexUsage } from '@/store/app-store';
|
||||||
|
|
||||||
|
/** Polling interval for usage data (60 seconds) */
|
||||||
|
const USAGE_POLLING_INTERVAL = 60 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Claude API usage data
|
||||||
|
*
|
||||||
|
* @param enabled - Whether the query should run (default: true)
|
||||||
|
* @returns Query result with Claude usage data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: usage, isLoading } = useClaudeUsage(isPopoverOpen);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useClaudeUsage(enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.usage.claude(),
|
||||||
|
queryFn: async (): Promise<ClaudeUsage> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.claude.getUsage();
|
||||||
|
// Check if result is an error response
|
||||||
|
if ('error' in result) {
|
||||||
|
throw new Error(result.message || result.error);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
staleTime: STALE_TIMES.USAGE,
|
||||||
|
refetchInterval: enabled ? USAGE_POLLING_INTERVAL : false,
|
||||||
|
// Keep previous data while refetching
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch Codex API usage data
|
||||||
|
*
|
||||||
|
* @param enabled - Whether the query should run (default: true)
|
||||||
|
* @returns Query result with Codex usage data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: usage, isLoading } = useCodexUsage(isPopoverOpen);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCodexUsage(enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.usage.codex(),
|
||||||
|
queryFn: async (): Promise<CodexUsage> => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.codex.getUsage();
|
||||||
|
// Check if result is an error response
|
||||||
|
if ('error' in result) {
|
||||||
|
throw new Error(result.message || result.error);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
staleTime: STALE_TIMES.USAGE,
|
||||||
|
refetchInterval: enabled ? USAGE_POLLING_INTERVAL : false,
|
||||||
|
// Keep previous data while refetching
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
}
|
||||||
42
apps/ui/src/hooks/queries/use-workspace.ts
Normal file
42
apps/ui/src/hooks/queries/use-workspace.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Workspace Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for workspace operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
interface WorkspaceDirectory {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch workspace directories
|
||||||
|
*
|
||||||
|
* @param enabled - Whether to enable the query
|
||||||
|
* @returns Query result with directories
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data: directories, isLoading, error } = useWorkspaceDirectories(open);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useWorkspaceDirectories(enabled = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.workspace.directories(),
|
||||||
|
queryFn: async (): Promise<WorkspaceDirectory[]> => {
|
||||||
|
const api = getHttpApiClient();
|
||||||
|
const result = await api.workspace.getDirectories();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to load directories');
|
||||||
|
}
|
||||||
|
return result.directories ?? [];
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
252
apps/ui/src/hooks/queries/use-worktrees.ts
Normal file
252
apps/ui/src/hooks/queries/use-worktrees.ts
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* Worktrees Query Hooks
|
||||||
|
*
|
||||||
|
* React Query hooks for fetching worktree data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getElectronAPI } from '@/lib/electron';
|
||||||
|
import { queryKeys } from '@/lib/query-keys';
|
||||||
|
import { STALE_TIMES } from '@/lib/query-client';
|
||||||
|
|
||||||
|
interface WorktreeInfo {
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
isMain: boolean;
|
||||||
|
hasChanges?: boolean;
|
||||||
|
changedFilesCount?: number;
|
||||||
|
featureId?: string;
|
||||||
|
linkedToBranch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemovedWorktree {
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorktreesResult {
|
||||||
|
worktrees: WorktreeInfo[];
|
||||||
|
removedWorktrees: RemovedWorktree[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all worktrees for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param includeDetails - Whether to include detailed info (default: true)
|
||||||
|
* @returns Query result with worktrees array and removed worktrees
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const { data, isLoading, refetch } = useWorktrees(currentProject?.path);
|
||||||
|
* const worktrees = data?.worktrees ?? [];
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useWorktrees(projectPath: string | undefined, includeDetails = true) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.all(projectPath ?? ''),
|
||||||
|
queryFn: async (): Promise<WorktreesResult> => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.listAll(projectPath, includeDetails);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch worktrees');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
worktrees: result.worktrees ?? [],
|
||||||
|
removedWorktrees: result.removedWorktrees ?? [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch worktree info for a specific feature
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param featureId - ID of the feature
|
||||||
|
* @returns Query result with worktree info
|
||||||
|
*/
|
||||||
|
export function useWorktreeInfo(projectPath: string | undefined, featureId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.single(projectPath ?? '', featureId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.getInfo(projectPath, featureId);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch worktree info');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!featureId,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch worktree status for a specific feature
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param featureId - ID of the feature
|
||||||
|
* @returns Query result with worktree status
|
||||||
|
*/
|
||||||
|
export function useWorktreeStatus(projectPath: string | undefined, featureId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.status(projectPath ?? '', featureId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.getStatus(projectPath, featureId);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch worktree status');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!featureId,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch worktree diffs for a specific feature
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @param featureId - ID of the feature
|
||||||
|
* @returns Query result with files and diff content
|
||||||
|
*/
|
||||||
|
export function useWorktreeDiffs(projectPath: string | undefined, featureId: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.diffs(projectPath ?? '', featureId ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath || !featureId) throw new Error('Missing project path or feature ID');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.getDiffs(projectPath, featureId);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch diffs');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
files: result.files ?? [],
|
||||||
|
diff: result.diff ?? '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath && !!featureId,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BranchInfo {
|
||||||
|
name: string;
|
||||||
|
isCurrent: boolean;
|
||||||
|
isRemote?: boolean;
|
||||||
|
lastCommit?: string;
|
||||||
|
upstream?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BranchesResult {
|
||||||
|
branches: BranchInfo[];
|
||||||
|
aheadCount: number;
|
||||||
|
behindCount: number;
|
||||||
|
isGitRepo: boolean;
|
||||||
|
hasCommits: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch available branches for a worktree
|
||||||
|
*
|
||||||
|
* @param worktreePath - Path to the worktree
|
||||||
|
* @param includeRemote - Whether to include remote branches
|
||||||
|
* @returns Query result with branches, ahead/behind counts, and git repo status
|
||||||
|
*/
|
||||||
|
export function useWorktreeBranches(worktreePath: string | undefined, includeRemote = false) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.branches(worktreePath ?? ''),
|
||||||
|
queryFn: async (): Promise<BranchesResult> => {
|
||||||
|
if (!worktreePath) throw new Error('No worktree path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.listBranches(worktreePath, includeRemote);
|
||||||
|
|
||||||
|
// Handle special git status codes
|
||||||
|
if (result.code === 'NOT_GIT_REPO') {
|
||||||
|
return {
|
||||||
|
branches: [],
|
||||||
|
aheadCount: 0,
|
||||||
|
behindCount: 0,
|
||||||
|
isGitRepo: false,
|
||||||
|
hasCommits: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (result.code === 'NO_COMMITS') {
|
||||||
|
return {
|
||||||
|
branches: [],
|
||||||
|
aheadCount: 0,
|
||||||
|
behindCount: 0,
|
||||||
|
isGitRepo: true,
|
||||||
|
hasCommits: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch branches');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
branches: result.result?.branches ?? [],
|
||||||
|
aheadCount: result.result?.aheadCount ?? 0,
|
||||||
|
behindCount: result.result?.behindCount ?? 0,
|
||||||
|
isGitRepo: true,
|
||||||
|
hasCommits: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!worktreePath,
|
||||||
|
staleTime: STALE_TIMES.WORKTREES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch init script for a project
|
||||||
|
*
|
||||||
|
* @param projectPath - Path to the project
|
||||||
|
* @returns Query result with init script content
|
||||||
|
*/
|
||||||
|
export function useWorktreeInitScript(projectPath: string | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.initScript(projectPath ?? ''),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!projectPath) throw new Error('No project path');
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.getInitScript(projectPath);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch init script');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
exists: result.exists ?? false,
|
||||||
|
content: result.content ?? '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!projectPath,
|
||||||
|
staleTime: STALE_TIMES.SETTINGS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch available editors
|
||||||
|
*
|
||||||
|
* @returns Query result with available editors
|
||||||
|
*/
|
||||||
|
export function useAvailableEditors() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: queryKeys.worktrees.editors(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const api = getElectronAPI();
|
||||||
|
const result = await api.worktree.getAvailableEditors();
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to fetch editors');
|
||||||
|
}
|
||||||
|
return result.editors ?? [];
|
||||||
|
},
|
||||||
|
staleTime: STALE_TIMES.CLI_STATUS,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user