mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Gemini added
This commit is contained in:
parent
0f0719c596
commit
0c8a185df7
4 changed files with 105 additions and 10 deletions
20
extensions/void/package-lock.json
generated
20
extensions/void/package-lock.json
generated
|
|
@ -7,6 +7,9 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "void",
|
"name": "void",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@google/generative-ai": "^0.21.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.29.2",
|
"@anthropic-ai/sdk": "^0.29.2",
|
||||||
"@eslint/js": "^9.9.1",
|
"@eslint/js": "^9.9.1",
|
||||||
|
|
@ -737,6 +740,15 @@
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@google/generative-ai": {
|
||||||
|
"version": "0.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz",
|
||||||
|
"integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||||
|
|
@ -6013,6 +6025,14 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/monaco-editor": {
|
||||||
|
"version": "0.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz",
|
||||||
|
"integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -151,5 +151,8 @@
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.5.4",
|
||||||
"typescript-eslint": "^8.3.0",
|
"typescript-eslint": "^8.3.0",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@google/generative-ai": "^0.21.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import Anthropic from '@anthropic-ai/sdk';
|
import Anthropic from '@anthropic-ai/sdk';
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { Ollama } from 'ollama/browser'
|
import { Ollama } from 'ollama/browser'
|
||||||
|
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||||
import { VoidConfig } from '../webviews/common/contextForConfig'
|
import { VoidConfig } from '../webviews/common/contextForConfig'
|
||||||
|
|
||||||
export type AbortRef = { current: (() => void) | null }
|
export type AbortRef = { current: (() => void) | null }
|
||||||
|
|
@ -37,8 +38,6 @@ type SendLLMMessageFnTypeExternal = (params: {
|
||||||
abortRef: AbortRef,
|
abortRef: AbortRef,
|
||||||
}) => void
|
}) => void
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const parseMaxTokensStr = (maxTokensStr: string) => {
|
const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||||
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||||
let int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
let int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||||
|
|
@ -103,8 +102,72 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
|
||||||
return { abort }
|
return { abort }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Gemini
|
||||||
|
const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
let didAbort = false
|
||||||
|
let fullText = ''
|
||||||
|
|
||||||
|
abortRef.current = () => {
|
||||||
|
didAbort = true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const genAI = new GoogleGenerativeAI(voidConfig.gemini.apikey);
|
||||||
|
// Force the model to be exactly what's configured
|
||||||
|
const modelName = voidConfig.gemini.model;
|
||||||
|
const model = genAI.getGenerativeModel({ model: modelName });
|
||||||
|
|
||||||
|
// Combine system messages with the first user message
|
||||||
|
let systemContent = messages
|
||||||
|
.filter(msg => msg.role === 'system')
|
||||||
|
.map(msg => msg.content)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
// Filter out system messages and modify first user message if needed
|
||||||
|
let geminiMessages = messages.filter(msg => msg.role !== 'system');
|
||||||
|
if (systemContent && geminiMessages.length > 0 && geminiMessages[0].role === 'user') {
|
||||||
|
geminiMessages[0] = {
|
||||||
|
...geminiMessages[0],
|
||||||
|
content: `${systemContent}\n\n${geminiMessages[0].content}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert remaining messages to Gemini format
|
||||||
|
const history = geminiMessages.map(msg => ({
|
||||||
|
role: msg.role === 'assistant' ? 'model' : msg.role,
|
||||||
|
parts: [{ text: msg.content }]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const chat = model.startChat({
|
||||||
|
history: history.slice(0, -1), // Exclude last message
|
||||||
|
generationConfig: {
|
||||||
|
maxOutputTokens: parseInt(voidConfig.default.maxTokens)
|
||||||
|
// Removed model from generationConfig since it's not a valid property
|
||||||
|
// Model is already set when creating the model instance above
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastMessage = messages[messages.length - 1].content;
|
||||||
|
const result = await chat.sendMessageStream(lastMessage);
|
||||||
|
|
||||||
|
for await (const chunk of result.stream) {
|
||||||
|
if (didAbort) return;
|
||||||
|
const newText = chunk.text();
|
||||||
|
fullText += newText;
|
||||||
|
onText(newText, fullText);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinalMessage(fullText);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error && error.message?.includes('API key')) {
|
||||||
|
onError('Invalid API key.');
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
onError(error.message || 'Error connecting to Gemini');
|
||||||
|
} else {
|
||||||
|
onError('Error connecting to Gemini');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// OpenAI, OpenRouter, OpenAICompatible
|
// OpenAI, OpenRouter, OpenAICompatible
|
||||||
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
@ -178,7 +241,6 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Ollama
|
// Ollama
|
||||||
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
|
|
@ -220,8 +282,6 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Greptile
|
// Greptile
|
||||||
// https://docs.greptile.com/api-reference/query
|
// https://docs.greptile.com/api-reference/query
|
||||||
// https://docs.greptile.com/quickstart#sample-response-streamed
|
// https://docs.greptile.com/quickstart#sample-response-streamed
|
||||||
|
|
@ -236,7 +296,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||||
didAbort = true
|
didAbort = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fetch('https://api.greptile.com/v2/query', {
|
fetch('https://api.greptile.com/v2/query', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -291,8 +350,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
if (!voidConfig) return;
|
if (!voidConfig) return;
|
||||||
|
|
||||||
|
|
@ -310,6 +367,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText,
|
||||||
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||||
case 'greptile':
|
case 'greptile':
|
||||||
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||||
|
case 'gemini':
|
||||||
|
return sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||||
default:
|
default:
|
||||||
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
|
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ export const configFields = [
|
||||||
'ollama',
|
'ollama',
|
||||||
'openRouter',
|
'openRouter',
|
||||||
'openAICompatible',
|
'openAICompatible',
|
||||||
'azure'
|
'azure',
|
||||||
|
'gemini'
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -164,6 +165,19 @@ const voidConfigInfo: Record<
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
},
|
},
|
||||||
|
gemini: {
|
||||||
|
apikey: configString('Google API key.', ''),
|
||||||
|
model: configEnum(
|
||||||
|
'Gemini model to use.',
|
||||||
|
'gemini-1.5-flash',
|
||||||
|
[
|
||||||
|
"gemini-1.5-flash",
|
||||||
|
"gemini-1.5-pro",
|
||||||
|
"gemini-1.5-flash-8b",
|
||||||
|
"gemini-1.0-pro"
|
||||||
|
] as const
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -273,4 +287,3 @@ export function useVoidConfig(): ConfigValueType {
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue