fix(popup): allow Prompt selection with Built-in AI (#2094)

This commit is contained in:
ananaBMaster
2026-08-17 14:21:57 -07:00
committed by GitHub
parent f4efced764
commit aea6f604ed
5 changed files with 85 additions and 11 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@read-frog/extension": patch
---
fix(popup): allow Prompt selection with Built-in AI
@@ -8,13 +8,17 @@ import TranslatePromptSelector from "@/entrypoints/popup/components/translate-pr
import { PromptSelector as TranslationHubPromptSelector } from "@/entrypoints/translation-hub/components/prompt-selector"
import { DEFAULT_CONFIG } from "@/utils/constants/config"
const { providerAtom, selectedProvidersAtom, setTranslateMock, testState, translateAtom } =
const { providerRefAtom, selectedProvidersAtom, setTranslateMock, testState, translateAtom } =
vi.hoisted(() => ({
providerAtom: {},
providerRefAtom: {},
selectedProvidersAtom: {},
setTranslateMock: vi.fn<(value: Partial<Config["pageTranslation"]>) => Promise<void>>(),
testState: {
pageTranslation: null as Config["pageTranslation"] | null,
pageTranslationProviderRef: null as
| { kind: "local"; config: { provider: string } }
| { kind: "system"; id: string; name: string; modelTier: "normal" | "advance" }
| null,
},
translateAtom: {},
}))
@@ -25,7 +29,7 @@ vi.mock("jotai", () => ({
return [testState.pageTranslation, setTranslateMock]
},
useAtomValue: (atom: object) => {
if (atom === providerAtom) return { provider: "mock-llm" }
if (atom === providerRefAtom) return testState.pageTranslationProviderRef
if (atom === selectedProvidersAtom) return [{ provider: "mock-llm" }]
throw new Error("Unexpected atom")
},
@@ -36,7 +40,7 @@ vi.mock("@/utils/atoms/config", () => ({
}))
vi.mock("@/utils/atoms/provider", () => ({
featureProviderConfigAtom: () => providerAtom,
featureProviderRefAtom: () => providerRefAtom,
}))
vi.mock("@/entrypoints/translation-hub/atoms", () => ({
@@ -45,7 +49,7 @@ vi.mock("@/entrypoints/translation-hub/atoms", () => ({
vi.mock("@/types/config/provider", async (importOriginal) => ({
...(await importOriginal<typeof import("@/types/config/provider")>()),
isLLMProvider: () => true,
isLLMProvider: (provider: string) => provider === "mock-llm",
}))
vi.mock("@/components/help-tooltip", () => ({
@@ -116,6 +120,10 @@ function createTranslateConfig(): Config["pageTranslation"] {
describe("translation prompt selectors", () => {
beforeEach(() => {
testState.pageTranslation = createTranslateConfig()
testState.pageTranslationProviderRef = {
kind: "local",
config: { provider: "mock-llm" },
}
setTranslateMock.mockReset()
setTranslateMock.mockResolvedValue()
})
@@ -138,6 +146,35 @@ describe("translation prompt selectors", () => {
})
})
it("keeps prompt selection available for Built-in AI", () => {
testState.pageTranslationProviderRef = {
kind: "system",
id: "read-frog-free-ai",
name: "Built-in AI",
modelTier: "normal",
}
render(<TranslatePromptSelector />)
fireEvent.click(screen.getByRole("option", { name: "Custom" }))
expect(setTranslateMock).toHaveBeenCalledWith({
customPromptsConfig: {
...testState.pageTranslation!.customPromptsConfig,
promptId: "custom",
},
})
})
it("keeps prompt selection hidden for a local translation-only provider", () => {
testState.pageTranslationProviderRef = {
kind: "local",
config: { provider: "google-translate" },
}
render(<TranslatePromptSelector />)
expect(screen.queryByRole("combobox")).not.toBeInTheDocument()
})
it("shows the selected built-in and uses the same order in Translation Hub", () => {
testState.pageTranslation!.customPromptsConfig.promptId = "precision-rewrite"
render(<TranslationHubPromptSelector />)
@@ -11,15 +11,18 @@ import {
} from "@/components/ui/base-ui/select"
import { isLLMProvider } from "@/types/config/provider"
import { configFieldsAtomMap } from "@/utils/atoms/config"
import { featureProviderConfigAtom } from "@/utils/atoms/provider"
import { featureProviderRefAtom } from "@/utils/atoms/provider"
import { DEFAULT_TRANSLATE_PROMPT_ID } from "@/utils/constants/prompt"
import { i18n } from "@/utils/i18n"
export default function TranslatePromptSelector() {
const translateProviderConfig = useAtomValue(featureProviderConfigAtom("pageTranslation"))
const translateProviderRef = useAtomValue(featureProviderRefAtom("pageTranslation"))
const [translateConfig, setTranslateConfig] = useAtom(configFieldsAtomMap.pageTranslation)
if (!translateProviderConfig?.provider || !isLLMProvider(translateProviderConfig?.provider))
if (
!translateProviderRef ||
(translateProviderRef.kind === "local" && !isLLMProvider(translateProviderRef.config.provider))
)
return null
const customPromptsConfig = translateConfig.customPromptsConfig
+29 -1
View File
@@ -1,8 +1,12 @@
import type { PartialDeep } from "type-fest"
import type { ProviderConfig } from "@/types/config/provider"
import { createStore } from "jotai"
import { describe, expect, it } from "vitest"
import { configAtom } from "@/utils/atoms/config"
import { DEFAULT_CONFIG } from "@/utils/constants/config"
import { BUILT_IN_AI_PROVIDER_ID } from "@/utils/constants/provider-ids"
import { DEFAULT_PROVIDER_CONFIG } from "@/utils/constants/providers"
import { updateLLMProviderConfig, updateProviderConfig } from "../provider"
import { featureProviderRefAtom, updateLLMProviderConfig, updateProviderConfig } from "../provider"
type OpenAIProviderConfig = Extract<ProviderConfig, { provider: "openai" }>
type BedrockProviderConfig = Extract<ProviderConfig, { provider: "bedrock" }>
@@ -70,3 +74,27 @@ describe("provider config updates", () => {
).toThrow(/Invalid/)
})
})
describe("feature provider refs", () => {
it("resolves Built-in AI without requiring a persisted provider config row", () => {
const store = createStore()
const config = structuredClone(DEFAULT_CONFIG)
config.pageTranslation.providerId = BUILT_IN_AI_PROVIDER_ID
store.set(configAtom, config)
expect(store.get(featureProviderRefAtom("pageTranslation"))).toMatchObject({
kind: "system",
id: BUILT_IN_AI_PROVIDER_ID,
modelTier: "normal",
})
})
it("continues to resolve persisted providers as local refs", () => {
const store = createStore()
expect(store.get(featureProviderRefAtom("pageTranslation"))).toMatchObject({
kind: "local",
id: DEFAULT_CONFIG.pageTranslation.providerId,
})
})
})
+3 -2
View File
@@ -5,16 +5,17 @@ import { deepmerge } from "deepmerge-ts"
import { atom } from "jotai"
import { atomFamily } from "jotai-family"
import { llmProviderConfigItemSchema, providerConfigItemSchema } from "@/types/config/provider"
import { resolveProviderRefForCapability } from "@/utils/providers/provider-registry"
import { getProviderConfigById } from "../config/helpers"
import { FEATURE_PROVIDER_DEFS } from "../constants/feature-providers"
import { configAtom, configFieldsAtomMap } from "./config"
export const featureProviderConfigAtom = atomFamily((featureKey: FeatureKey) =>
export const featureProviderRefAtom = atomFamily((featureKey: FeatureKey) =>
atom((get) => {
const config = get(configAtom)
const def = FEATURE_PROVIDER_DEFS[featureKey]
const providerId = def.getProviderId(config)
return getProviderConfigById(config.providersConfig, providerId) ?? null
return resolveProviderRefForCapability(featureKey, config.providersConfig, providerId)
}),
)