fix(image-recognition): keep the vision key on its own ref; add clear-key
Image recognition and the chat model shared DEEPSEEK_API_KEY: the image-recognition bundle defaulted apiKeyEnv to the model key, so saving one overwrote the other. Point the bundle at IMAGE_RECOGNITION_API_KEY and guard both the provider and the settings card against a stale model ref, so vision never reads or writes the chat key. Also add a clear-key button, default the model to qwen3-vl-flash on the DashScope compatible-mode endpoint, and send file images as base64 with a normalized base URL. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -116,9 +116,11 @@ describe('ui-settings-plugins apply', () => {
|
||||
|
||||
// A key written on another surface changes no settings section, so this
|
||||
// event is the only thing that reaches the card.
|
||||
ctx.remote.$dispatch('credentials/updated', ['DEEPSEEK_API_KEY'])
|
||||
ctx.remote.$dispatch('credentials/updated', ['IMAGE_RECOGNITION_API_KEY'])
|
||||
|
||||
await vi.waitFor(() => { expect(describeCredentials).toHaveBeenCalledTimes(2) })
|
||||
// Only the image-recognition card watches this ref now (web search uses
|
||||
// DEEPSEEK_API_KEY), so one re-read fires.
|
||||
await vi.waitFor(() => { expect(describeCredentials).toHaveBeenCalledTimes(1) })
|
||||
})
|
||||
|
||||
it('ignores a credential change for a reference no card watches', async () => {
|
||||
|
||||
@@ -15,17 +15,18 @@ afterEach(cleanup)
|
||||
|
||||
const t = (key: string): string => key
|
||||
|
||||
function renderCard() {
|
||||
function renderCard(value: ImageRecognitionSettings = {}) {
|
||||
const host = stubSettingsScope<ImageRecognitionSettings>()
|
||||
const credentials = {
|
||||
describe: vi.fn(() => Promise.resolve({
|
||||
rpcId: 'c' as never,
|
||||
result: { ok: true as const, value: { credentials: { DEEPSEEK_API_KEY: { configured: false, writable: true } } } },
|
||||
result: { ok: true as const, value: { credentials: { IMAGE_RECOGNITION_API_KEY: { configured: false, writable: true } } } },
|
||||
})),
|
||||
set: vi.fn(),
|
||||
unset: vi.fn(),
|
||||
}
|
||||
const controller = new ImageRecognitionCardController(host.scope, credentials as never)
|
||||
host.publish({ status: 'ready', writable: true, value: {}, user: {} })
|
||||
const controller = new ImageRecognitionCardController(host.scope, { credentials })
|
||||
host.publish({ status: 'ready', writable: true, value, user: {} })
|
||||
const face = controller.inject()
|
||||
render(
|
||||
<ImageRecognitionCard
|
||||
@@ -35,6 +36,7 @@ function renderCard() {
|
||||
discard={face.discard}
|
||||
edit={face.edit}
|
||||
resetField={face.resetField}
|
||||
clearApiKey={face.clearApiKey}
|
||||
useSessions={() => [] as never}
|
||||
useWorkspaces={() => [] as never}
|
||||
/>,
|
||||
@@ -52,4 +54,16 @@ describe('ImageRecognitionCard', () => {
|
||||
expect(screen.getByLabelText('imageRecognitionBaseUrl')).toBeTruthy()
|
||||
expect(screen.getByLabelText('imageRecognitionModel')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('clears the configured credential through the clear-key button', () => {
|
||||
const { credentials } = renderCard()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'imageRecognitionClearKey' }))
|
||||
expect(credentials.unset).toHaveBeenCalledWith({ ref: 'IMAGE_RECOGNITION_API_KEY' })
|
||||
})
|
||||
|
||||
it('ignores a stale model apiKeyEnv so the image-recognition key never overwrites the chat key', () => {
|
||||
const { credentials } = renderCard({ apiKeyEnv: 'DEEPSEEK_API_KEY' })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'imageRecognitionClearKey' }))
|
||||
expect(credentials.unset).toHaveBeenCalledWith({ ref: 'IMAGE_RECOGNITION_API_KEY' })
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user