diff --git a/CHANGELOG.md b/CHANGELOG.md index 670fab4155..4a7f215316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### ♻️ Code Quality +- **Provider-detail god-component decomposition — Phase 2 (helpers→lib)** ([#3501]): extracted the pure shared helpers — `ProviderMessageTranslator`/`LocalProviderMetadata` types, `providerText`/`providerCountText`/`readBooleanToggle`, and the provider base-URL + routing-tag/excluded-model parse/format block — into a new leaf `providers/[id]/providerPageHelpers.ts` (imports only `@/shared`, so the client and modals share them with no import cycle). `ProviderDetailPageClient.tsx`: 10,435 → 10,288 LOC. Unblocks extracting the heavier `AddApiKeyModal`/`EditConnectionModal` (which depend on these helpers) without cycling. The Phase 0 smoke test caught a missing transitive import (`isSelfHostedChatProvider`) at mount — now wired + locked by a new helpers unit test (12 assertions). Co-authored with @oyi77. + - **#3500 fully resolved** — Hard Rule #5 (no raw SQL in route handlers): all 13 internal offenders migrated to `src/lib/db/` modules across slices (call_logs, usage_history/daily_usage_summary, community_servers, usage_logs, semantic_cache, proxy_logs, skills UPDATE, db-backups). The gate's `KNOWN_RAW_SQL` set is renamed to `EXTERNAL_DB_ALLOWED` (with a back-compat alias) and now holds only the **2 external-DB reads** (`oauth/cursor/auto-import`, `oauth/kiro/auto-import`) — these open *another app's* SQLite to import credentials, so by design they cannot live in OmniRoute's `db/` domain. The gate still blocks any NEW raw SQL against OmniRoute's DB. - **chore(db-gate):** reclassify `KNOWN_UNEXPORTED` → `INTENTIONALLY_INTERNAL` in `scripts/check/check-db-rules.mjs` ([#3499]): a full audit of all 25 db modules confirmed each is consumed via direct/dynamic import per Hard Rule #2 ("Never barrel-import from localDb.ts"). The old framing labelled them as "debt", which was misleading — they are the correct pattern. The gate's blocking behaviour is unchanged (a NEW unexported module still fails); only the name, comments, and per-module justifications were updated to reflect audited truth. Four modules flagged `DEAD?` (`compressionScheduler`, `discovery`, `pluginMetrics`, `prompts`) have zero production importers and are documented as schema-reserved. A new regression-guard test (`tests/unit/check-db-rules-classification.test.ts`) asserts every non-dead module in the set has ≥1 real importer, so a future consumer removal surfaces as a test failure requiring explicit reclassification. - **refactor(db): move `call_logs` aggregations into `callLogStats` db module** ([#3500]): extracted raw SQL from three route handlers (`/api/provider-metrics`, `/api/search/stats`, `/api/v1/search/analytics`) into a new `src/lib/db/callLogStats.ts` domain module (`getProviderMetrics`, `getSearchProviderStats`, `getRecentSearchLogs`, `getSearchAggregateStats`, `getSearchProviderCounts`). First slice of #3500 (call_logs cluster). Behavior unchanged; the three routes are removed from `KNOWN_RAW_SQL` in the gate. Validated with TDD unit tests (6 assertions seeding an in-memory SQLite fixture). diff --git a/file-size-baseline.json b/file-size-baseline.json index 7d6cbb91eb..4a0d61c437 100644 --- a/file-size-baseline.json +++ b/file-size-baseline.json @@ -48,7 +48,7 @@ "src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2570, "src/app/(dashboard)/dashboard/health/page.tsx": 1091, "src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": 847, - "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 10435, + "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": 10288, "src/app/(dashboard)/dashboard/providers/components/onboarding/ProviderOnboardingWizard.tsx": 906, "src/app/(dashboard)/dashboard/providers/page.tsx": 1925, "src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1127, diff --git a/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx b/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx index e44a1d5b73..9b94660abe 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx @@ -95,12 +95,40 @@ import { getWebSessionCredentialRequirement, type WebSessionCredentialRequirement, } from "./webSessionCredentials"; -import { ImportCodexAuthModal, ApplyCodexAuthModal } from "./components/modals/ImportCodexAuthModal"; -import { ImportClaudeAuthModal, ApplyClaudeAuthModal } from "./components/modals/ImportClaudeAuthModal"; -import { ImportGeminiAuthModal, ApplyGeminiAuthModal } from "./components/modals/ImportGeminiAuthModal"; +import { + ImportCodexAuthModal, + ApplyCodexAuthModal, +} from "./components/modals/ImportCodexAuthModal"; +import { + ImportClaudeAuthModal, + ApplyClaudeAuthModal, +} from "./components/modals/ImportClaudeAuthModal"; +import { + ImportGeminiAuthModal, + ApplyGeminiAuthModal, +} from "./components/modals/ImportGeminiAuthModal"; import EditCompatibleNodeModal from "./components/modals/EditCompatibleNodeModal"; import { CC_COMPATIBLE_DEFAULT_CHAT_PATH } from "./providerDetailConstants"; +import { + CONFIGURABLE_BASE_URL_PROVIDERS, + DEFAULT_PROVIDER_BASE_URLS, + getLocalProviderMetadata, + isBaseUrlConfigurableProvider, + getProviderBaseUrlDefault, + getProviderBaseUrlHint, + getProviderBaseUrlPlaceholder, + isGlmProvider, + parseRoutingTagsInput, + parseExcludedModelsInput, + formatRoutingTagsInput, + formatExcludedModelsInput, + providerText, + providerCountText, + readBooleanToggle, + type ProviderMessageTranslator, + type LocalProviderMetadata, +} from "./providerPageHelpers"; type CompatByProtocolMap = Partial< Record< ModelCompatProtocolKey, @@ -135,11 +163,6 @@ type CompatModelRow = { }; type CompatModelMap = Map; -type LocalProviderMetadata = { - name?: string; - localDefault?: string; - [key: string]: unknown; -}; function buildCompatMap(rows: CompatModelRow[]): CompatModelMap { const m = new Map(); @@ -171,49 +194,6 @@ function isModelHidden( return false; } -type ProviderMessageTranslator = ((key: string, values?: Record) => string) & { - has?: (key: string) => boolean; -}; - -function providerText( - t: ProviderMessageTranslator, - key: string, - fallback: string, - values?: Record -): string { - if (typeof t.has === "function" && t.has(key)) { - return t(key, values); - } - if (values) { - return Object.entries(values).reduce( - (acc, [name, value]) => acc.replaceAll(`{${name}}`, String(value)), - fallback - ); - } - return fallback; -} - -function providerCountText( - t: ProviderMessageTranslator, - key: string, - count: number, - singularFallback: string, - pluralFallback: string -): string { - return providerText(t, key, count === 1 ? singularFallback : pluralFallback, { count }); -} - -function readBooleanToggle(value: unknown, fallback: boolean): boolean { - if (typeof value === "boolean") return value; - if (typeof value === "number") return value === 1; - if (typeof value === "string") { - const normalized = value.trim().toLowerCase(); - if (normalized === "1" || normalized === "true") return true; - if (normalized === "0" || normalized === "false") return false; - } - return fallback; -} - function getWebSessionCredentialLabel( t: ProviderMessageTranslator, requirement: WebSessionCredentialRequirement, @@ -1570,7 +1550,7 @@ export default function ProviderDetailPageClient() { source: normalizeModelCatalogSource(cm.source) === "imported" ? "imported" : "custom", })); const allModels = [...builtInModels, ...syncedExtras, ...customExtras]; - const deduped = new Map(); + const deduped = new Map(); for (const m of allModels) { if (m.id && !deduped.has(m.id)) deduped.set(m.id, m); } @@ -1673,10 +1653,9 @@ export default function ProviderDetailPageClient() { const handleDeleteAlias = useCallback( async (alias: string) => { try { - const res = await fetch( - `/api/models/alias?alias=${encodeURIComponent(alias)}`, - { method: "DELETE" } - ); + const res = await fetch(`/api/models/alias?alias=${encodeURIComponent(alias)}`, { + method: "DELETE", + }); if (res.ok) { await fetchAliases(); notify.success(t("deleteAliasSuccess", { alias })); @@ -2029,20 +2008,14 @@ export default function ProviderDetailPageClient() { } catch (e) { error++; } - setTestProgress((prev) => - prev ? { done: prev.done + 1, total: prev.total } : null - ); + setTestProgress((prev) => (prev ? { done: prev.done + 1, total: prev.total } : null)); }) ); } - notify.info( - providerText(t, "testAllResults", "{ok} ok, {error} error", { ok, error }) - ); + notify.info(providerText(t, "testAllResults", "{ok} ok, {error} error", { ok, error })); if (hiddenCount > 0) { - notify.info( - providerText(t, "testAllFailedHidden", "{count} hidden", { count: hiddenCount }) - ); + notify.info(providerText(t, "testAllFailedHidden", "{count} hidden", { count: hiddenCount })); } setTestingAll(false); setTestProgress(null); @@ -2677,8 +2650,7 @@ export default function ProviderDetailPageClient() { const handleDistributeProxies = async (tagFilter?: string) => { const targetConnections = tagFilter ? connections.filter( - (c: any) => - (c.providerSpecificData?.tag as string | undefined)?.trim() === tagFilter + (c: any) => (c.providerSpecificData?.tag as string | undefined)?.trim() === tagFilter ) : connections; if (targetConnections.length === 0) return; @@ -2687,9 +2659,7 @@ export default function ProviderDetailPageClient() { const proxiesRes = await fetch("/api/settings/proxies"); if (!proxiesRes.ok) throw new Error("Failed to fetch proxies"); const proxiesData = await proxiesRes.json(); - const savedProxies = (proxiesData?.items || []).filter( - (p: any) => p.status === "active" - ); + const savedProxies = (proxiesData?.items || []).filter((p: any) => p.status === "active"); if (savedProxies.length === 0) { notify.error("No saved proxies found. Add proxies in Settings → Proxy first."); return; @@ -3681,8 +3651,7 @@ export default function ProviderDetailPageClient() { const providerAliasEntries = useMemo( () => Object.entries(modelAliases).filter( - ([, model]) => - typeof model === "string" && model.startsWith(`${providerStorageAlias}/`) + ([, model]) => typeof model === "string" && model.startsWith(`${providerStorageAlias}/`) ), [modelAliases, providerStorageAlias] ); @@ -4786,15 +4755,24 @@ export default function ProviderDetailPageClient() { { value: "active", label: t("filterActive", "Active") }, { value: "error", label: t("filterError", "Error") }, { value: "banned", label: t("filterBanned", "Banned") }, - { value: "credits_exhausted", label: t("filterCreditsExhausted", "Credits Exhausted") }, + { + value: "credits_exhausted", + label: t("filterCreditsExhausted", "Credits Exhausted"), + }, ]; - const filtered = healthFilter === "all" - ? sorted - : sorted.filter((c) => { - if (healthFilter === "active") return isHealthy(c); - if (healthFilter === "error") return !isHealthy(c) && c.testStatus !== "banned" && c.testStatus !== "credits_exhausted"; - return c.testStatus === healthFilter; - }); + const filtered = + healthFilter === "all" + ? sorted + : sorted.filter((c) => { + if (healthFilter === "active") return isHealthy(c); + if (healthFilter === "error") + return ( + !isHealthy(c) && + c.testStatus !== "banned" && + c.testStatus !== "credits_exhausted" + ); + return c.testStatus === healthFilter; + }); const totalFilteredPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const clampedPage = Math.min(page, totalFilteredPages - 1); @@ -4823,36 +4801,38 @@ export default function ProviderDetailPageClient() { ); - const paginationBar = totalFilteredPages > 1 ? ( -
- - {pageStart + 1}–{Math.min(pageEnd, filtered.length)} / {filtered.length} - -
-
- - ) : null; + ) : null; if (!hasAnyTag) { const pageConnections = filtered.slice(pageStart, pageEnd); - const allSelected = pageConnections.length > 0 && pageConnections.every((c) => selectedIds.has(c.id)); + const allSelected = + pageConnections.length > 0 && pageConnections.every((c) => selectedIds.has(c.id)); const someSelected = pageConnections.some((c) => selectedIds.has(c.id)); return ( <> @@ -4913,113 +4893,117 @@ export default function ProviderDetailPageClient() { ) : ( pageConnections.map((conn, index) => ( - handleToggleSelectOne(conn.id)} - onMoveUp={() => handleSwapPriority(conn, sorted[index - 1])} - onMoveDown={() => handleSwapPriority(conn, sorted[index + 1])} - onToggleActive={(isActive) => - handleUpdateConnectionStatus(conn.id, isActive) - } - onToggleRateLimit={(enabled) => handleToggleRateLimit(conn.id, enabled)} - onToggleClaudeExtraUsage={(enabled) => - handleToggleClaudeExtraUsage(conn.id, enabled) - } - isCodex={providerId === "codex"} - isGeminiCli={providerId === "gemini-cli"} - isCcCompatible={isCcCompatible} - cliproxyapiEnabled={cpaProviderEnabled} - onToggleCliproxyapiMode={(enabled) => - handleToggleCliproxyapiMode(conn.id, enabled) - } - onToggleCodex5h={(enabled) => - handleToggleCodexLimit(conn.id, "use5h", enabled) - } - onToggleCodexWeekly={(enabled) => - handleToggleCodexLimit(conn.id, "useWeekly", enabled) - } - onRetest={() => handleRetestConnection(conn.id)} - isRetesting={retestingId === conn.id} - onEdit={() => { - setSelectedConnection(conn); - setShowEditModal(true); - }} - onDelete={() => handleDelete(conn.id)} - onReauth={ - conn.authType === "oauth" - ? () => gateConnectionFlow(() => setShowOAuthModal(true, conn)) - : undefined - } - onRefreshToken={ - conn.authType === "oauth" - ? () => handleRefreshToken(conn.id) - : undefined - } - isRefreshing={refreshingId === conn.id} - onApplyCodexAuthLocal={ - providerId === "codex" - ? () => setApplyCodexModalConnectionId(conn.id) - : undefined - } - isApplyingCodexAuthLocal={applyingCodexAuthId === conn.id} - onExportCodexAuthFile={ - providerId === "codex" - ? () => handleExportCodexAuthFile(conn.id) - : undefined - } - isExportingCodexAuthFile={exportingCodexAuthId === conn.id} - onApplyClaudeAuthLocal={ - providerId === "claude" - ? () => setApplyClaudeModalConnectionId(conn.id) - : undefined - } - isApplyingClaudeAuthLocal={applyingClaudeAuthId === conn.id} - onExportClaudeAuthFile={ - providerId === "claude" - ? () => handleExportClaudeAuthFile(conn.id) - : undefined - } - isExportingClaudeAuthFile={exportingClaudeAuthId === conn.id} - onApplyGeminiAuthLocal={ - providerId === "gemini-cli" - ? () => setApplyGeminiModalConnectionId(conn.id) - : undefined - } - isApplyingGeminiAuthLocal={applyingGeminiAuthId === conn.id} - onExportGeminiAuthFile={ - providerId === "gemini-cli" - ? () => handleExportGeminiAuthFile(conn.id) - : undefined - } - isExportingGeminiAuthFile={exportingGeminiAuthId === conn.id} - onProxy={() => - setProxyTarget({ - level: "key", - id: conn.id, - label: pickDisplayValue( - [conn.name, conn.email], - emailsVisible, - conn.id - ), - }) - } - hasProxy={!!connProxyMap[conn.id]?.proxy} - proxySource={connProxyMap[conn.id]?.level || null} - proxyHost={connProxyMap[conn.id]?.proxy?.host || null} - proxyEnabled={readBooleanToggle(conn.proxyEnabled, true)} - onToggleProxyEnabled={(enabled) => handleToggleProxyEnabled(conn.id, enabled)} - perKeyProxyEnabled={readBooleanToggle(conn.perKeyProxyEnabled, false)} - onTogglePerKeyProxyEnabled={(enabled) => handleTogglePerKeyProxyEnabled(conn.id, enabled)} - /> - ))) - } + handleToggleSelectOne(conn.id)} + onMoveUp={() => handleSwapPriority(conn, sorted[index - 1])} + onMoveDown={() => handleSwapPriority(conn, sorted[index + 1])} + onToggleActive={(isActive) => + handleUpdateConnectionStatus(conn.id, isActive) + } + onToggleRateLimit={(enabled) => handleToggleRateLimit(conn.id, enabled)} + onToggleClaudeExtraUsage={(enabled) => + handleToggleClaudeExtraUsage(conn.id, enabled) + } + isCodex={providerId === "codex"} + isGeminiCli={providerId === "gemini-cli"} + isCcCompatible={isCcCompatible} + cliproxyapiEnabled={cpaProviderEnabled} + onToggleCliproxyapiMode={(enabled) => + handleToggleCliproxyapiMode(conn.id, enabled) + } + onToggleCodex5h={(enabled) => + handleToggleCodexLimit(conn.id, "use5h", enabled) + } + onToggleCodexWeekly={(enabled) => + handleToggleCodexLimit(conn.id, "useWeekly", enabled) + } + onRetest={() => handleRetestConnection(conn.id)} + isRetesting={retestingId === conn.id} + onEdit={() => { + setSelectedConnection(conn); + setShowEditModal(true); + }} + onDelete={() => handleDelete(conn.id)} + onReauth={ + conn.authType === "oauth" + ? () => gateConnectionFlow(() => setShowOAuthModal(true, conn)) + : undefined + } + onRefreshToken={ + conn.authType === "oauth" + ? () => handleRefreshToken(conn.id) + : undefined + } + isRefreshing={refreshingId === conn.id} + onApplyCodexAuthLocal={ + providerId === "codex" + ? () => setApplyCodexModalConnectionId(conn.id) + : undefined + } + isApplyingCodexAuthLocal={applyingCodexAuthId === conn.id} + onExportCodexAuthFile={ + providerId === "codex" + ? () => handleExportCodexAuthFile(conn.id) + : undefined + } + isExportingCodexAuthFile={exportingCodexAuthId === conn.id} + onApplyClaudeAuthLocal={ + providerId === "claude" + ? () => setApplyClaudeModalConnectionId(conn.id) + : undefined + } + isApplyingClaudeAuthLocal={applyingClaudeAuthId === conn.id} + onExportClaudeAuthFile={ + providerId === "claude" + ? () => handleExportClaudeAuthFile(conn.id) + : undefined + } + isExportingClaudeAuthFile={exportingClaudeAuthId === conn.id} + onApplyGeminiAuthLocal={ + providerId === "gemini-cli" + ? () => setApplyGeminiModalConnectionId(conn.id) + : undefined + } + isApplyingGeminiAuthLocal={applyingGeminiAuthId === conn.id} + onExportGeminiAuthFile={ + providerId === "gemini-cli" + ? () => handleExportGeminiAuthFile(conn.id) + : undefined + } + isExportingGeminiAuthFile={exportingGeminiAuthId === conn.id} + onProxy={() => + setProxyTarget({ + level: "key", + id: conn.id, + label: pickDisplayValue( + [conn.name, conn.email], + emailsVisible, + conn.id + ), + }) + } + hasProxy={!!connProxyMap[conn.id]?.proxy} + proxySource={connProxyMap[conn.id]?.level || null} + proxyHost={connProxyMap[conn.id]?.proxy?.host || null} + proxyEnabled={readBooleanToggle(conn.proxyEnabled, true)} + onToggleProxyEnabled={(enabled) => + handleToggleProxyEnabled(conn.id, enabled) + } + perKeyProxyEnabled={readBooleanToggle(conn.perKeyProxyEnabled, false)} + onTogglePerKeyProxyEnabled={(enabled) => + handleTogglePerKeyProxyEnabled(conn.id, enabled) + } + /> + )) + )} {paginationBar} @@ -5226,9 +5210,16 @@ export default function ProviderDetailPageClient() { proxySource={connProxyMap[conn.id]?.level || null} proxyHost={connProxyMap[conn.id]?.proxy?.host || null} proxyEnabled={readBooleanToggle(conn.proxyEnabled, true)} - onToggleProxyEnabled={(enabled) => handleToggleProxyEnabled(conn.id, enabled)} - perKeyProxyEnabled={readBooleanToggle(conn.perKeyProxyEnabled, false)} - onTogglePerKeyProxyEnabled={(enabled) => handleTogglePerKeyProxyEnabled(conn.id, enabled)} + onToggleProxyEnabled={(enabled) => + handleToggleProxyEnabled(conn.id, enabled) + } + perKeyProxyEnabled={readBooleanToggle( + conn.perKeyProxyEnabled, + false + )} + onTogglePerKeyProxyEnabled={(enabled) => + handleTogglePerKeyProxyEnabled(conn.id, enabled) + } /> ))} @@ -5447,10 +5438,7 @@ export default function ProviderDetailPageClient() { /> )} {/* Codex CLI Guide Modal */} - setCodexCliGuideOpen(false)} - /> + setCodexCliGuideOpen(false)} /> {/* Codex Import Auth Modal */} {providerId === "codex" && importCodexModalOpen && (

Compartilhe este link com quem vai autenticar a conta do Codex. A pessoa abre a - página, faz o login da OpenAI no próprio navegador e a conexão é cadastrada aqui. - Uso único, expira em 15 minutos. + página, faz o login da OpenAI no próprio navegador e a conexão é cadastrada aqui. Uso + único, expira em 15 minutos.

{externalLinkLoading ? (

Gerando link…

@@ -5743,7 +5731,7 @@ export default function ProviderDetailPageClient() { {importProgress.logs.length > 0 && (
- {importProgress.logs.map((log, i) => ( + {importProgress.logs.map((log, i) => (

- - {onTestModel && ( - )} - {onToggleHidden && ( - - )} - effectiveModelNormalize(modelId, p)} - effectiveModelPreserveDeveloper={(p) => effectiveModelPreserveDeveloper(modelId, p)} - getUpstreamHeadersRecord={getUpstreamHeadersRecord} - onCompatPatch={(protocol, payload) => - saveModelCompatFlags(modelId, { compatByProtocol: { [protocol]: payload } }) - } - showDeveloperToggle={showDeveloperToggle} - compact - disabled={compatDisabled} - /> - {onDeleteAlias && ( - - )} + {onTestModel && ( + + )} + {onToggleHidden && ( + + )} + effectiveModelNormalize(modelId, p)} + effectiveModelPreserveDeveloper={(p) => effectiveModelPreserveDeveloper(modelId, p)} + getUpstreamHeadersRecord={getUpstreamHeadersRecord} + onCompatPatch={(protocol, payload) => + saveModelCompatFlags(modelId, { compatByProtocol: { [protocol]: payload } }) + } + showDeveloperToggle={showDeveloperToggle} + compact + disabled={compatDisabled} + /> + {onDeleteAlias && ( + + )}
@@ -8097,13 +8085,21 @@ function ConnectionRow({ |