Skip to content

Commit 071f32e

Browse files
committed
feat: gemini api key input component
1 parent 842cfad commit 071f32e

16 files changed

Lines changed: 148 additions & 1096 deletions

File tree

package-lock.json

Lines changed: 21 additions & 996 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"dependencies": {
33
"@google/generative-ai": "^0.24.1",
4-
"@huggingface/transformers": "^3.6.3",
54
"dom-accessibility-api": "^0.7.0",
65
"marked": "^15.0.7",
76
"notivue": "^2.4.5",

src/background/services/llm.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { GenerativeModel, GoogleGenerativeAI } from "@google/generative-ai"
22

3+
const MODEL = "gemini-2.5-flash"
34
export default class Llmservice {
45
private genAI: GoogleGenerativeAI
56
private textModel: GenerativeModel
@@ -8,13 +9,22 @@ export default class Llmservice {
89
constructor(apiKey: string) {
910
this.genAI = new GoogleGenerativeAI(apiKey)
1011
// For text-only input, use the gemini-pro model
11-
this.textModel = this.genAI.getGenerativeModel({
12-
model: "gemini-2.5-flash",
13-
})
12+
this.textModel = this.genAI.getGenerativeModel({model: MODEL})
1413
// For JSON output, we'll use the same model and guide it with the prompt
15-
this.jsonModel = this.genAI.getGenerativeModel({
16-
model: "gemini-2.5-flash",
17-
})
14+
this.jsonModel = this.genAI.getGenerativeModel({model: MODEL})
15+
}
16+
17+
static async validateApiKey(apiKey: string): Promise<boolean> {
18+
try {
19+
const genAI = new GoogleGenerativeAI(apiKey)
20+
const model = genAI.getGenerativeModel({ model: MODEL })
21+
// Make a simple, low-cost call to validate the key
22+
await model.generateContent("ping !")
23+
return true
24+
} catch (error) {
25+
console.error("API Key validation failed:", error)
26+
return false
27+
}
1828
}
1929

2030
/**
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import { useSettingsStore } from "@/stores/settings.store"
3+
4+
const settingsStore = useSettingsStore()
5+
6+
</script>
7+
8+
<template>
9+
<div class="flex flex-col text-sm rounded-lg px-1 mt-4 gap-y-1">
10+
<div class="mb-2 md:mb-0">
11+
<div class="flex items-center">
12+
<div>Gemini API Key</div>
13+
<div
14+
class="tooltip tooltip-right ml-1"
15+
data-tip="This is required for the extension to autofill using AI"
16+
>
17+
<i-ph-info class="w-4 h-4 text-gray-400 cursor-pointer" />
18+
</div>
19+
</div>
20+
</div>
21+
<div>
22+
<input
23+
type="text"
24+
class="input input-bordered input-sm max-w-md"
25+
:value="settingsStore.llmApiKey"
26+
@input="settingsStore.validateAndSetLlmApiKey(($event.target as HTMLInputElement).value)"
27+
/>
28+
</div>
29+
<div
30+
v-if="settingsStore.llmApiKeyIsValid === false"
31+
class="text-error text-xs mt-1"
32+
>
33+
API key is invalid :(
34+
</div>
35+
<div
36+
v-if="settingsStore.llmApiKeyIsValid"
37+
class="text-success text-xs mt-1"
38+
>
39+
Valid key, awesome :)
40+
</div>
41+
<div
42+
v-if="!settingsStore.llmApiKeyIsValid"
43+
class="text-xs text-gray-500"
44+
>
45+
Go to
46+
<a href="https://aistudio.google.com/apikey"> https://aistudio.google.com/apikey </a>
47+
and click on 'Create API Key'
48+
</div>
49+
</div>
50+
</template>

src/content-script/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,6 @@ async function fillInputInForm(
211211
}
212212
const isRoleGroup = isRoleGroupNode(element)
213213

214-
console.log({
215-
isRoleGroup,
216-
element,
217-
})
218-
219214
if (isRoleGroup && typeof item.value === "string") {
220215
const options = item.value.split("|")
221216
applyGroupNodeAnswer(element, options)

src/stores/settings.store.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Settings } from "@/types/common"
22
import { defineStore } from "pinia"
3+
import LlmService from "@/background/services/llm"
4+
import { debounce } from "@/utils/common"
35

46
interface Tokens {
57
google?: string
@@ -37,6 +39,7 @@ export const useSettingsStore = defineStore("settings", () => {
3739
email: "",
3840
isTosAgreed: false,
3941
llmApiKey: "",
42+
llmApiKeyIsValid: null
4043
},
4144
)
4245

@@ -68,8 +71,6 @@ export const useSettingsStore = defineStore("settings", () => {
6871
tokens.value.google = token
6972
}
7073

71-
72-
7374
function clearTokens() {
7475
tokens.value = {}
7576
}
@@ -78,9 +79,20 @@ export const useSettingsStore = defineStore("settings", () => {
7879
settings.value.isTosAgreed = true
7980
}
8081

81-
function setLlmApiKey(apiKey: string) {
82+
const validateAndSetLlmApiKey = debounce(async (apiKey: string) => {
83+
if (!apiKey) {
84+
settings.value.llmApiKey = ""
85+
settings.value.llmApiKeyIsValid = null
86+
return
87+
}
8288
settings.value.llmApiKey = apiKey
83-
}
89+
const isValid = await LlmService.validateApiKey(apiKey)
90+
if (isValid) {
91+
settings.value.llmApiKeyIsValid = true
92+
} else {
93+
settings.value.llmApiKeyIsValid = false
94+
}
95+
}, 300)
8496

8597
function setDummyValuesForLocalDev() {
8698
tokens.value = {
@@ -102,7 +114,7 @@ export const useSettingsStore = defineStore("settings", () => {
102114
setGoogleToken,
103115
clearTokens,
104116
tosAgreed,
105-
setLlmApiKey,
117+
validateAndSetLlmApiKey,
106118
setDummyValuesForLocalDev,
107119
displayActionIcon: computed(() => settings.value.displayActionIcon),
108120
activeProfileId: computed(() => settings.value.activeProfileId),
@@ -116,6 +128,7 @@ export const useSettingsStore = defineStore("settings", () => {
116128
googleToken: computed(() => tokens.value.google),
117129
isTosAgreed: computed(() => settings.value.isTosAgreed),
118130
llmApiKey: computed(() => settings.value.llmApiKey),
131+
llmApiKeyIsValid: computed(() => settings.value.llmApiKeyIsValid),
119132
profiles: computed(() => settings.value.profiles),
120133
}
121134
})

src/types/.eslintrc-auto-import.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@
377377
"YT_PLAYLIST_LINK": true,
378378
"isDevelopmentEnv": true,
379379
"useProfilesStore": true,
380-
"shortDetailsDynamicStore": true
380+
"shortDetailsDynamicStore": true,
381+
"debounce": true
381382
}
382383
}

src/types/auto-imports.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ declare global {
3939
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
4040
const customRef: typeof import('vue')['customRef']
4141
const customersRef: typeof import('../utils/firebase')['customersRef']
42+
const debounce: typeof import('../utils/common')['debounce']
4243
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
4344
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
4445
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
@@ -411,6 +412,7 @@ declare module 'vue' {
411412
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
412413
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
413414
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
415+
readonly debounce: UnwrapRef<typeof import('../utils/common')['debounce']>
414416
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
415417
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
416418
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>

src/types/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export interface Settings {
7474
email: string
7575
isTosAgreed: boolean
7676
llmApiKey: string
77+
llmApiKeyIsValid: boolean | null
7778
}
7879

7980
export enum DETAIL_TYPES {

src/types/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ declare module 'vue' {
1111
AppFooter: typeof import('./../components/AppFooter.vue')['default']
1212
AppHeader: typeof import('./../components/AppHeader.vue')['default']
1313
DisplayError: typeof import('./../components/state/DisplayError.vue')['default']
14+
GeminiApiKeyInput: typeof import('./../components/GeminiApiKeyInput.vue')['default']
1415
ICarbonLogoGithub: typeof import('~icons/carbon/logo-github')['default']
1516
ILucideDelete: typeof import('~icons/lucide/delete')['default']
1617
IPhArrowLeft: typeof import('~icons/ph/arrow-left')['default']

0 commit comments

Comments
 (0)