CrossRelay stage 0+1: Go gateway with Anthropic<->Chat Completions relay, Tauri shell, bilingual management UI

- Go gateway (stdlib only): /v1/messages non-stream + SSE, /v1/messages/count_tokens,
  management API /api/* (providers CRUD, apply/restore, settings, backups, logs)
- Protocol conversion: Anthropic Messages <-> Chat Completions with a streaming
  state machine keyed by content block index (text, tool_use, parallel calls)
- ~/.claude/settings.json guarded read/write: timestamped backups, managed env
  keys only, one-click restore
- Tauri 2 shell: spawns the gateway sidecar, parses the random port from stdout,
  stdin pipe watchdog prevents orphans, kills the child on exit
- React frontend: Dashboard/Providers/Settings, zh-CN + en-US, TanStack Query
- Tests: Go table-driven + httptest integration (all green), vitest + RTL
This commit is contained in:
kate522 committed 2026-10-18 17:19:13 +08:00
1 parent 8ee5e98ea3
commit f8acf31ebc
71 files changed
+15727 -1

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api, type ProviderInput } from "./api";
export const queryKeys = {
status: ["status"] as const,
providers: ["providers"] as const,
logs: ["logs"] as const,
backups: ["backups"] as const,
settings: ["settings"] as const,
};
export function useStatus() {
return useQuery({ queryKey: queryKeys.status, queryFn: api.getStatus, refetchInterval: 5000 });
}
export function useProviders() {
return useQuery({ queryKey: queryKeys.providers, queryFn: api.listProviders });
}
export function useLogs() {
return useQuery({ queryKey: queryKeys.logs, queryFn: api.listLogs, refetchInterval: 5000 });
}
export function useBackups() {
return useQuery({ queryKey: queryKeys.backups, queryFn: api.listBackups });
}
export function useSettings() {
return useQuery({ queryKey: queryKeys.settings, queryFn: api.getSettings });
}
function invalidateAll(queryClient: ReturnType<typeof useQueryClient>) {
return Promise.all([
queryClient.invalidateQueries({ queryKey: queryKeys.providers }),
queryClient.invalidateQueries({ queryKey: queryKeys.status }),
queryClient.invalidateQueries({ queryKey: queryKeys.backups }),
queryClient.invalidateQueries({ queryKey: queryKeys.settings }),
]);
}
export function useProviderMutations() {
const queryClient = useQueryClient();
const create = useMutation({
mutationFn: (input: ProviderInput) => api.createProvider(input),
onSuccess: () => invalidateAll(queryClient),
});
const update = useMutation({
mutationFn: ({ id, input }: { id: string; input: ProviderInput }) => api.updateProvider(id, input),
onSuccess: () => invalidateAll(queryClient),
});
const remove = useMutation({
mutationFn: (id: string) => api.deleteProvider(id),
onSuccess: () => invalidateAll(queryClient),
});
const apply = useMutation({
mutationFn: (id: string) => api.applyProvider(id),
onSuccess: () => invalidateAll(queryClient),
});
return { create, update, remove, apply };
}
export function useRestore() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => api.restore(),
onSuccess: () => invalidateAll(queryClient),
});
}
export function useSettingsMutation() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (patch: Parameters<typeof api.updateSettings>[0]) => api.updateSettings(patch),
onSuccess: () => invalidateAll(queryClient),
});
}