picoclaw/web/frontend/src/components/agent/tools/web-search-general-settings.tsx
肆月 d2c0b69243
feat(web,api): provider selection and model form foundation (#2831)
* feat: improve model configuration workflows

Add model catalog browsing, provider registry with form validation,
model fetch/test dialogs, and enhanced model management UI.

- Add model catalog API and catalog-dialog component for browsing saved models
- Add provider-registry with auto-populated form fields per provider
- Add provider-combobox, fetch-models-dialog, test-model-dialog components
- Add model-validation for provider-aware model ID validation
- Add command and popover UI components
- Enhance edit-model-sheet with tool schema transform support
- Add anthropic to protocolMetaByName for correct default API base
- Apply NormalizeBaseURL to anthropic provider for consistent URL handling
- Add i18n keys for new model management features (en/zh)

* fix(web): prevent auto-fetch when API key is missing in fetch models dialog

When a provider requires an API key but none is set, the dialog now shows
the warning without triggering a doomed fetch attempt. Fetch is deferred
until the user provides a key.

* fix(web): add credential warning for catalog imports from remote providers

When importing models from a catalog entry whose provider requires an API
key, a yellow warning banner now informs users that credentials will need
to be configured after import.

* feat(web,api): test connection with real connectivity verification and unsaved form values

Add POST /api/models/test-inline endpoint that performs actual network
probes (GET /models) instead of just checking config. Frontend Test
Connection now uses current form values (not saved state) and is
available in both Add and Edit model flows.

* style(web): apply linter formatting across model config components

Normalize quote style, import ordering, and class name ordering as
reported by the project linter.

* fix(web,api): fix edit test connection false negative and gate fetch for unsupported providers

- handleTestInlineModel now accepts optional model_index to fall back to stored credentials when api_key is empty, fixing false negatives when testing edited models
- Add supportsFetch to provider registry and FETCHABLE_PROVIDER_KEYS derived set
- Gate Fetch Models button to only show for OpenAI-compatible and Ollama providers
- Add backend guard in handleFetchModels to reject unsupported providers with clear error

* fix: address review feedback on model config workflow

- Send explicit {} for empty extra_body/custom_headers fields so the
  backend clears stored values instead of preserving them
- Merge backend provider_options with frontend PROVIDERS registry so
  the provider picker reflects backend-supported providers and policy
  fields (create_allowed, default_auth_method, auth_method_locked)
- Render provider combobox popover inside the sheet scroll container
  to fix wheel events scrolling the sheet instead of the provider list

* feat(web,api): add provider selection, model form foundation, and validation

Split from PR #2752 (part 1 of 3).

Backend:
- CRUD model endpoints (list/add/update/delete/set-default)
- Provider metadata with default API bases and model provider options
- Model ID validation and normalization
- Anthropic default API base normalization

Frontend:
- Provider registry with metadata, labels, icons, and aliases
- Provider combobox with backend option merging
- Model field validation with provider-aware checks
- Redesigned add/edit model sheets with provider selection
- Dynamic imports for fetch/catalog/test dialogs (coming in PR2/PR3)
- i18n support for model configuration UI
2026-05-11 16:57:37 +08:00

136 lines
4.3 KiB
TypeScript

import type { ReactNode } from "react"
import { useTranslation } from "react-i18next"
import type { WebSearchConfigResponse } from "@/api/tools"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import type { WebSearchDraftUpdater } from "./types"
interface WebSearchGeneralSettingsProps {
draft: WebSearchConfigResponse
onUpdateDraft: WebSearchDraftUpdater
}
export function WebSearchGeneralSettings({
draft,
onUpdateDraft,
}: WebSearchGeneralSettingsProps) {
const { t } = useTranslation()
return (
<div className="space-y-4">
<h3 className="text-foreground/80 text-[13px] font-bold tracking-widest uppercase">
{t("pages.agent.tools.web_search.global_settings", "General")}
</h3>
<div className="bg-card border-border/40 divide-border/40 divide-y overflow-hidden rounded-2xl border shadow-sm">
<SettingRow
label={t("pages.agent.tools.web_search.provider", "Primary Provider")}
description={t(
"pages.agent.tools.web_search.provider_description",
"Select the default provider to use when the web search tool handles a request.",
)}
>
<Select
value={draft.provider}
onValueChange={(value) =>
onUpdateDraft((current) => ({
...current,
provider: value,
}))
}
>
<SelectTrigger className="bg-muted/40 hover:bg-muted/60 focus:ring-foreground/5 focus:border-border/80 w-full rounded-xl border-transparent shadow-none transition-all sm:w-64">
<SelectValue />
</SelectTrigger>
<SelectContent className="border-border/40 rounded-xl shadow-lg">
{draft.providers.map((provider) => (
<SelectItem
key={provider.id}
value={provider.id}
className="rounded-lg"
>
{provider.label}
</SelectItem>
))}
</SelectContent>
</Select>
</SettingRow>
<SettingRow
label={t("pages.agent.tools.web_search.proxy", "Proxy Configuration")}
description={t(
"pages.agent.tools.web_search.proxy_description",
"Optional global HTTP/S proxy for underlying web requests.",
)}
>
<Input
className="bg-muted/40 hover:bg-muted/60 focus-visible:bg-background focus-visible:border-border/80 focus-visible:ring-foreground/5 w-full rounded-xl border-transparent shadow-none transition-all duration-300 sm:w-64"
value={draft.proxy ?? ""}
onChange={(event) =>
onUpdateDraft((current) => ({
...current,
proxy: event.target.value,
}))
}
placeholder="http://127.0.0.1:7890"
/>
</SettingRow>
<SettingRow
label={t(
"pages.agent.tools.web_search.prefer_native",
"Prefer Native Search",
)}
description={t(
"pages.agent.tools.web_search.prefer_native_hint",
"When enabled, the model may use its built-in search capability instead of the configured provider list.",
)}
>
<Switch
checked={draft.prefer_native}
onCheckedChange={(checked) =>
onUpdateDraft((current) => ({
...current,
prefer_native: checked,
}))
}
className="data-[state=checked]:shadow-xs"
/>
</SettingRow>
</div>
</div>
)
}
function SettingRow({
label,
description,
children,
}: {
label: string
description: string
children: ReactNode
}) {
return (
<div className="hover:bg-muted/10 flex flex-col justify-between gap-4 p-5 transition-colors sm:flex-row sm:items-center">
<div className="w-full space-y-1 sm:max-w-md">
<label className="text-foreground/90 text-[15px] font-semibold tracking-tight">
{label}
</label>
<p className="text-muted-foreground/80 text-[13px] leading-relaxed">
{description}
</p>
</div>
{children}
</div>
)
}