From 0c8a185df7b086ee0e178262bd3c481b2a886b0a Mon Sep 17 00:00:00 2001 From: Piyu-Pika Date: Tue, 29 Oct 2024 18:05:07 +0530 Subject: [PATCH] Gemini added --- extensions/void/package-lock.json | 20 +++++ extensions/void/package.json | 3 + extensions/void/src/common/sendLLMMessage.ts | 75 +++++++++++++++++-- .../src/webviews/common/contextForConfig.tsx | 17 ++++- 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index 62449b32..4d7d38aa 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "void", "version": "0.0.1", + "dependencies": { + "@google/generative-ai": "^0.21.0" + }, "devDependencies": { "@anthropic-ai/sdk": "^0.29.2", "@eslint/js": "^9.9.1", @@ -737,6 +740,15 @@ "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": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -6013,6 +6025,14 @@ "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": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index 16553535..0a0d3995 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -151,5 +151,8 @@ "typescript": "5.5.4", "typescript-eslint": "^8.3.0", "uuid": "^10.0.0" + }, + "dependencies": { + "@google/generative-ai": "^0.21.0" } } diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index c3feea76..a6746009 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,6 +1,7 @@ import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; import { Ollama } from 'ollama/browser' +import { GoogleGenerativeAI } from '@google/generative-ai'; import { VoidConfig } from '../webviews/common/contextForConfig' export type AbortRef = { current: (() => void) | null } @@ -37,8 +38,6 @@ type SendLLMMessageFnTypeExternal = (params: { abortRef: AbortRef, }) => void - - const parseMaxTokensStr = (maxTokensStr: string) => { // 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) @@ -103,8 +102,72 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi 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 const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => { @@ -178,7 +241,6 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal }; - // Ollama export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => { @@ -220,8 +282,6 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, }; - - // Greptile // https://docs.greptile.com/api-reference/query // https://docs.greptile.com/quickstart#sample-response-streamed @@ -236,7 +296,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin didAbort = true } - fetch('https://api.greptile.com/v2/query', { method: 'POST', headers: { @@ -291,8 +350,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin } - - export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => { if (!voidConfig) return; @@ -310,6 +367,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }); case 'greptile': return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }); + case 'gemini': + return sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }); default: onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`) } diff --git a/extensions/void/src/webviews/common/contextForConfig.tsx b/extensions/void/src/webviews/common/contextForConfig.tsx index b535d588..5d8dc1a5 100644 --- a/extensions/void/src/webviews/common/contextForConfig.tsx +++ b/extensions/void/src/webviews/common/contextForConfig.tsx @@ -25,7 +25,8 @@ export const configFields = [ 'ollama', 'openRouter', 'openAICompatible', - 'azure' + 'azure', + 'gemini' ] 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 } -