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 */}
- {/* 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