diff --git a/.git-credentials b/.git-credentials new file mode 100644 index 0000000..2b5e7d1 --- /dev/null +++ b/.git-credentials @@ -0,0 +1 @@ +https://x-access-token:ghs_b4NOitjlosRPPypJr3KupAZqrOXlxr4fq5Z9@github.com diff --git a/client/src/components/AgentCreateModal.tsx b/client/src/components/AgentCreateModal.tsx index 9c96183..8a3bf9e 100644 --- a/client/src/components/AgentCreateModal.tsx +++ b/client/src/components/AgentCreateModal.tsx @@ -28,11 +28,7 @@ const AGENT_ROLES = [ { value: "monitor", label: "Monitor - System monitoring" }, ]; -const PROVIDERS = [ - { value: "Ollama", label: "Ollama (Local/Cloud)" }, - { value: "OpenAI", label: "OpenAI (GPT)" }, - { value: "Anthropic", label: "Anthropic (Claude)" }, -]; +// Providers are loaded dynamically from server config — no hardcoded list export function AgentCreateModal({ open, onOpenChange, onSuccess }: AgentCreateModalProps) { const [formData, setFormData] = useState({ @@ -51,6 +47,11 @@ export function AgentCreateModal({ open, onOpenChange, onSuccess }: AgentCreateM const { data: modelsData, isLoading: modelsLoading } = trpc.ollama.models.useQuery(undefined, { staleTime: 60_000, }); + const { data: configData } = trpc.config.providers.useQuery(undefined, { + staleTime: 300_000, + }); + // Only providers configured on server + const connectedProviders = configData?.providers ?? []; const createMutation = trpc.agents.create.useMutation({ onSuccess: () => { @@ -157,11 +158,17 @@ export function AgentCreateModal({ open, onOpenChange, onSuccess }: AgentCreateM - {PROVIDERS.map((provider) => ( - - {provider.label} - - ))} + {connectedProviders.length > 0 + ? connectedProviders.map((p) => ( + + + {p.name} + ● connected + + + )) + : {formData.provider} + } diff --git a/client/src/components/AgentDetailModal.tsx b/client/src/components/AgentDetailModal.tsx index ebc033f..4067ff4 100644 --- a/client/src/components/AgentDetailModal.tsx +++ b/client/src/components/AgentDetailModal.tsx @@ -48,7 +48,6 @@ interface AgentDetailModalProps { onSave?: () => void; } -const PROVIDERS = ["Ollama", "OpenAI", "Anthropic", "Mistral", "Groq"]; const TOOL_OPTIONS = ["http_get", "http_post", "shell_exec", "file_read", "file_write", "docker_list", "docker_exec", "docker_logs", "browser_navigate"]; function toNum(v: string | number | null | undefined, fallback = 0): number { @@ -70,6 +69,11 @@ export function AgentDetailModal({ agent, open, onOpenChange, onSave }: AgentDet const { data: modelsData, isLoading: modelsLoading } = trpc.ollama.models.useQuery(undefined, { staleTime: 60_000, }); + const { data: configData } = trpc.config.providers.useQuery(undefined, { + staleTime: 300_000, + }); + // Only show providers that are actually configured on the server + const connectedProviders = configData?.providers ?? []; const { data: history = [] } = trpc.agents.history.useQuery( { id: agent?.id ?? 0, limit: 20 }, { enabled: !!agent && open } @@ -194,7 +198,17 @@ export function AgentDetailModal({ agent, open, onOpenChange, onSave }: AgentDet diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx index 7d5def7..10fbf52 100644 --- a/client/src/pages/Settings.tsx +++ b/client/src/pages/Settings.tsx @@ -53,11 +53,16 @@ export default function Settings() { // Реальные данные из Ollama API const healthQuery = trpc.ollama.health.useQuery(undefined, { - refetchInterval: 30_000, // Обновлять каждые 30 секунд + refetchInterval: 30_000, }); const modelsQuery = trpc.ollama.models.useQuery(undefined, { - refetchInterval: 60_000, // Обновлять каждые 60 секунд + refetchInterval: 60_000, }); + // Server-side provider configuration (API key masked) + const configQuery = trpc.config.providers.useQuery(undefined, { + staleTime: 300_000, + }); + const primaryProvider = configQuery.data?.providers?.[0]; const ollamaStatus = healthQuery.data?.connected ? "connected" : healthQuery.isLoading ? "unchecked" : "error"; const ollamaLatency = healthQuery.data?.latencyMs ?? 0; @@ -133,7 +138,7 @@ export default function Settings() { )}
- Ollama Cloud + {primaryProvider?.name ?? "Ollama Cloud"} LIVE @@ -156,12 +161,12 @@ export default function Settings() {
- {/* Base URL */} + {/* Base URL — from server config */}
@@ -182,15 +187,17 @@ export default function Settings() {
- {/* API Key */} + {/* API Key — masked, read from server env */}
- + {!primaryProvider?.hasKey && ( + + NO KEY — chat will fail + + )}
+ {!primaryProvider?.hasKey && ( +

+ Set OLLAMA_API_KEY in docker/.env and restart containers +

+ )}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ad0bf26..1836843 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -145,6 +145,9 @@ services: DATABASE_URL: "mysql://${MYSQL_USER:-goclaw}:${MYSQL_PASSWORD:-goClawPass123}@db:3306/${MYSQL_DATABASE:-goclaw}" GATEWAY_URL: "http://gateway:18789" JWT_SECRET: "${JWT_SECRET:-change-me-in-production}" + # ── LLM Provider (same as gateway, used by Node.js tRPC proxy) ────── + OLLAMA_BASE_URL: "${LLM_BASE_URL:-${OLLAMA_BASE_URL:-https://ollama.com/v1}}" + OLLAMA_API_KEY: "${LLM_API_KEY:-${OLLAMA_API_KEY:-}}" VITE_APP_ID: "${VITE_APP_ID:-}" OAUTH_SERVER_URL: "${OAUTH_SERVER_URL:-}" VITE_OAUTH_PORTAL_URL: "${VITE_OAUTH_PORTAL_URL:-}" diff --git a/server/routers.ts b/server/routers.ts index b392538..165a666 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -31,6 +31,46 @@ export const appRouter = router({ }), }), + /** + * System config — returns server-side LLM provider configuration. + * API keys are masked (never sent to frontend in full). + * Used by Settings page and AgentDetailModal to show real connected providers. + */ + config: router({ + providers: publicProcedure.query(async () => { + const { ENV } = await import("./_core/env"); + const baseUrl = ENV.ollamaBaseUrl || "https://ollama.com/v1"; + const apiKey = ENV.ollamaApiKey || ""; + const hasKey = apiKey.length > 0; + // Mask key: show first 8 chars + **** + const maskedKey = hasKey + ? `${apiKey.slice(0, 8)}${"*".repeat(Math.max(0, apiKey.length - 8))}` + : ""; + + // Determine provider name from base URL + let providerName = "Ollama Cloud"; + if (baseUrl.includes("openai.com")) providerName = "OpenAI"; + else if (baseUrl.includes("anthropic.com")) providerName = "Anthropic"; + else if (baseUrl.includes("groq.com")) providerName = "Groq"; + else if (baseUrl.includes("mistral.ai")) providerName = "Mistral"; + else if (baseUrl.includes("ollama.com")) providerName = "Ollama Cloud"; + else providerName = "Custom"; + + return { + providers: [ + { + id: "primary", + name: providerName, + baseUrl, + hasKey, + maskedKey, + isActive: true, + }, + ], + }; + }), + }), + /** * Ollama API — серверный прокси для безопасного доступа * Приоритет: Go Gateway → прямой Ollama