mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-29 22:02:05 +00:00
Add support for using alternative API endpoints (like Zhipu AI's GLM models) without affecting the user's global Claude Code settings. Configuration is done via AutoCoder's .env file. Changes: - Add API_ENV_VARS constant and pass through ClaudeAgentOptions.env parameter in client.py and all server service files (spec, expand, assistant sessions) - Add glm_mode to settings API response to indicate when GLM is configured - Add purple "GLM" badge in UI header when GLM mode is active - Update setup status to accept GLM credentials as valid authentication - Update .env.example with GLM configuration documentation - Update README.md with AutoCoder-scoped GLM setup instructions Supported environment variables: - ANTHROPIC_BASE_URL: Custom API endpoint (e.g., https://api.z.ai/api/anthropic) - ANTHROPIC_AUTH_TOKEN: API authentication token - API_TIMEOUT_MS: Request timeout in milliseconds - ANTHROPIC_DEFAULT_SONNET_MODEL: Model override for Sonnet - ANTHROPIC_DEFAULT_OPUS_MODEL: Model override for Opus - ANTHROPIC_DEFAULT_HAIKU_MODEL: Model override for Haiku This approach routes API requests through the alternative endpoint while keeping all Claude Code features (MCP servers, hooks, permissions) intact. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
275 lines
7.5 KiB
TypeScript
275 lines
7.5 KiB
TypeScript
/**
|
|
* React Query hooks for project data
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import * as api from '../lib/api'
|
|
import type { FeatureCreate, ModelsResponse, Settings, SettingsUpdate } from '../lib/types'
|
|
|
|
// ============================================================================
|
|
// Projects
|
|
// ============================================================================
|
|
|
|
export function useProjects() {
|
|
return useQuery({
|
|
queryKey: ['projects'],
|
|
queryFn: api.listProjects,
|
|
})
|
|
}
|
|
|
|
export function useProject(name: string | null) {
|
|
return useQuery({
|
|
queryKey: ['project', name],
|
|
queryFn: () => api.getProject(name!),
|
|
enabled: !!name,
|
|
})
|
|
}
|
|
|
|
export function useCreateProject() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: ({ name, path, specMethod }: { name: string; path: string; specMethod?: 'claude' | 'manual' }) =>
|
|
api.createProject(name, path, specMethod),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['projects'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteProject() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (name: string) => api.deleteProject(name),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['projects'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Features
|
|
// ============================================================================
|
|
|
|
export function useFeatures(projectName: string | null) {
|
|
return useQuery({
|
|
queryKey: ['features', projectName],
|
|
queryFn: () => api.listFeatures(projectName!),
|
|
enabled: !!projectName,
|
|
refetchInterval: 5000, // Refetch every 5 seconds for real-time updates
|
|
})
|
|
}
|
|
|
|
export function useCreateFeature(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (feature: FeatureCreate) => api.createFeature(projectName, feature),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['features', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteFeature(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (featureId: number) => api.deleteFeature(projectName, featureId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['features', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useSkipFeature(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (featureId: number) => api.skipFeature(projectName, featureId),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['features', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Agent
|
|
// ============================================================================
|
|
|
|
export function useAgentStatus(projectName: string | null) {
|
|
return useQuery({
|
|
queryKey: ['agent-status', projectName],
|
|
queryFn: () => api.getAgentStatus(projectName!),
|
|
enabled: !!projectName,
|
|
refetchInterval: 3000, // Poll every 3 seconds
|
|
})
|
|
}
|
|
|
|
export function useStartAgent(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (yoloMode: boolean = false) => api.startAgent(projectName, yoloMode),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useStopAgent(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: () => api.stopAgent(projectName),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function usePauseAgent(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: () => api.pauseAgent(projectName),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useResumeAgent(projectName: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: () => api.resumeAgent(projectName),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
|
|
},
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Setup
|
|
// ============================================================================
|
|
|
|
export function useSetupStatus() {
|
|
return useQuery({
|
|
queryKey: ['setup-status'],
|
|
queryFn: api.getSetupStatus,
|
|
staleTime: 60000, // Cache for 1 minute
|
|
})
|
|
}
|
|
|
|
export function useHealthCheck() {
|
|
return useQuery({
|
|
queryKey: ['health'],
|
|
queryFn: api.healthCheck,
|
|
retry: false,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Filesystem
|
|
// ============================================================================
|
|
|
|
export function useListDirectory(path?: string) {
|
|
return useQuery({
|
|
queryKey: ['filesystem', 'list', path],
|
|
queryFn: () => api.listDirectory(path),
|
|
})
|
|
}
|
|
|
|
export function useCreateDirectory() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (path: string) => api.createDirectory(path),
|
|
onSuccess: (_, path) => {
|
|
// Invalidate parent directory listing
|
|
const parentPath = path.split('/').slice(0, -1).join('/') || undefined
|
|
queryClient.invalidateQueries({ queryKey: ['filesystem', 'list', parentPath] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useValidatePath() {
|
|
return useMutation({
|
|
mutationFn: (path: string) => api.validatePath(path),
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// Settings
|
|
// ============================================================================
|
|
|
|
// Default models response for placeholder (until API responds)
|
|
const DEFAULT_MODELS: ModelsResponse = {
|
|
models: [
|
|
{ id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5' },
|
|
{ id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5' },
|
|
],
|
|
default: 'claude-opus-4-5-20251101',
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: Settings = {
|
|
yolo_mode: false,
|
|
model: 'claude-opus-4-5-20251101',
|
|
glm_mode: false,
|
|
}
|
|
|
|
export function useAvailableModels() {
|
|
return useQuery({
|
|
queryKey: ['available-models'],
|
|
queryFn: api.getAvailableModels,
|
|
staleTime: 300000, // Cache for 5 minutes - models don't change often
|
|
retry: 1,
|
|
placeholderData: DEFAULT_MODELS,
|
|
})
|
|
}
|
|
|
|
export function useSettings() {
|
|
return useQuery({
|
|
queryKey: ['settings'],
|
|
queryFn: api.getSettings,
|
|
staleTime: 60000, // Cache for 1 minute
|
|
retry: 1,
|
|
placeholderData: DEFAULT_SETTINGS,
|
|
})
|
|
}
|
|
|
|
export function useUpdateSettings() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: (settings: SettingsUpdate) => api.updateSettings(settings),
|
|
onMutate: async (newSettings) => {
|
|
// Cancel outgoing refetches
|
|
await queryClient.cancelQueries({ queryKey: ['settings'] })
|
|
|
|
// Snapshot previous value
|
|
const previous = queryClient.getQueryData<Settings>(['settings'])
|
|
|
|
// Optimistically update
|
|
queryClient.setQueryData<Settings>(['settings'], (old) => ({
|
|
...DEFAULT_SETTINGS,
|
|
...old,
|
|
...newSettings,
|
|
}))
|
|
|
|
return { previous }
|
|
},
|
|
onError: (_err, _newSettings, context) => {
|
|
// Rollback on error
|
|
if (context?.previous) {
|
|
queryClient.setQueryData(['settings'], context.previous)
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['settings'] })
|
|
},
|
|
})
|
|
}
|