SurfSense/surfsense_web/components/settings/model-connections/default-connect-form.tsx
Thibault Jaigu 2ff7ea4cb6 feat: add Requesty as a model provider
Add Requesty (https://requesty.ai), an OpenAI-compatible LLM router, as a
model provider by mirroring the existing OpenRouter integration.

Backend:
- app/services/requesty_model_normalizer.py: normalizes Requesty's /v1/models
  catalogue, mapping its flat capability booleans (supports_tool_calling/
  supports_vision/supports_image_generation) and context_window field onto the
  shared normalized shape (Requesty differs from OpenRouter's architecture +
  supported_parameters + context_length layout)
- provider_registry.py: Requesty ProviderSpec (OpenAI-compatible, base URL
  https://router.requesty.ai/v1, REQUESTY_API_KEY bearer auth)
- model_connection_service.py: key verification + live model discovery
- quality_score.py: Requesty score entry
- unit tests mirroring the OpenRouter normalizer coverage

Frontend:
- Requesty provider icon + registration, metadata entry, and base-url hint

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
2026-07-13 09:42:30 +01:00

67 lines
1.9 KiB
TypeScript

import { useEffect, useState } from "react";
import { ApiBaseUrlField, ApiKeyField } from "./connect-fields";
import type { ProviderConnectFormProps } from "./provider-metadata";
const OPTIONAL_API_KEY_PROVIDERS = new Set(["ollama_chat", "lm_studio", "openai_compatible"]);
function baseUrlHint(provider: string) {
if (provider === "ollama_chat" || provider === "lm_studio") {
return "For local servers, use host.docker.internal instead of localhost.";
}
if (provider === "openai_compatible") {
return "Enter the full endpoint URL.";
}
if (
provider === "openai" ||
provider === "anthropic" ||
provider === "openrouter" ||
provider === "requesty"
) {
return "Override only if you route through a proxy or gateway.";
}
return undefined;
}
/**
* Connect form for OpenAI-compatible / native key providers (OpenAI, Anthropic,
* OpenRouter, OpenAI-Compatible, LM Studio, Ollama, …). The base URL is
* prefilled from the provider default.
*/
export function DefaultConnectForm({
provider,
defaultBaseUrl,
baseUrlRequired,
onDraftChange,
}: ProviderConnectFormProps) {
const [baseUrl, setBaseUrl] = useState(defaultBaseUrl);
const [apiKey, setApiKey] = useState("");
const isApiKeyOptional = OPTIONAL_API_KEY_PROVIDERS.has(provider);
const hint = baseUrlHint(provider);
const apiKeyValue = apiKey.trim();
const canSubmit =
!(baseUrlRequired && !baseUrl.trim()) && (isApiKeyOptional || Boolean(apiKeyValue));
useEffect(() => {
onDraftChange(
{ base_url: baseUrl || null, api_key: apiKeyValue || null, extra: {} },
canSubmit
);
}, [apiKeyValue, baseUrl, canSubmit, onDraftChange]);
return (
<div className="flex flex-col gap-4">
<ApiBaseUrlField
value={baseUrl}
onChange={setBaseUrl}
placeholder={defaultBaseUrl}
hint={hint}
/>
<ApiKeyField
value={apiKey}
onChange={setApiKey}
label={isApiKeyOptional ? "API Key (optional)" : "API Key"}
placeholder="Enter your API key"
/>
</div>
);
}