diff --git a/.env.example b/.env.example index 7d7ca1e..deb8da8 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ CRX_PUBLIC_KEY="longCRXPublicKeyToEnsureTheExtensionIdStaysTheSameandOauth2.0WorksNicely" OAUTH_CLIENT_ID=xxx.apps.googleusercontent.com -PUBLIC_EXTENSION_OS_API_ENDPOINT=https://localhost:3000/v1/chat/completions \ No newline at end of file +PUBLIC_EXTENSION_OS_API_ENDPOINT=https://localhost:3000/v1/chat/completions +E2E_TEST_GROQ_KEY="yourapikey:thisisusedforE2ETestingOnly" diff --git a/README.md b/README.md index 74c990b..75e973a 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,10 @@ Move it somewhere else ASAP: # Changelog +### 0.0.24 + +- Adding the ability to specify a custom URL + ### 0.0.23 - Adding the uninstall hook to understand what can we improve. diff --git a/contents/core.tsx b/contents/core.tsx index 2705f28..4514b34 100644 --- a/contents/core.tsx +++ b/contents/core.tsx @@ -123,7 +123,7 @@ const PlasmoOverlay = () => { wordBreak: "break-word", display: successDivVisibe ? "block" : "none" }}> - + {/* Loading box */} diff --git a/lib/openAITypeCall.ts b/lib/openAITypeCall.ts index 96da53d..d0af544 100644 --- a/lib/openAITypeCall.ts +++ b/lib/openAITypeCall.ts @@ -3,19 +3,25 @@ // ------------------------------------------------------------------------------------ import { Storage } from "@plasmohq/storage"; -import { useUserInfo } from "./providers/UserInfoContext"; import { getOrCreateClientUUID } from "./clientUUID"; import { insertStatisticsRow } from "./anonymousTracking"; // Function to map vendor names to their respective API endpoints -function vendorToEndpoint(vendor: string): string { +async function vendorToEndpoint(vendor: string): Promise { + const storage = new Storage(); + + if (vendor === "localhost") { + const customUrl = await storage.get("llmCustomEndpoint"); + return customUrl; + } + const endpoints: { [key: string]: string } = { "extension | OS": process.env.PLASMO_PUBLIC_EXTENSION_OS_API_ENDPOINT, openai: "https://api.openai.com/v1/chat/completions", groq: "https://api.groq.com/openai/v1/chat/completions", together: "https://api.together.xyz/v1/chat/completions", - localhost: "http://localhost:11434/v1/chat/completions", }; + return endpoints[vendor] || endpoints["groq"]; } @@ -72,7 +78,7 @@ export async function callOpenAIReturn( const openAIModel = overrideModel || storedModel; const vendor = overrideProvider || storedVendor; const apiKey = llmKeys ? llmKeys[vendor] : ""; - const openAIEndpoint = vendorToEndpoint(vendor); + const openAIEndpoint = await vendorToEndpoint(vendor); const headers = new Headers({ "Content-Type": "application/json", diff --git a/options/LlmSettings.tsx b/options/LlmSettings.tsx index 64f591a..a4235e8 100644 --- a/options/LlmSettings.tsx +++ b/options/LlmSettings.tsx @@ -174,6 +174,7 @@ export default function LlmSettings({ debugInfo }: { debugInfo: string }) { const [llmModel, setLlmModel] = useStorage("llmModel", "llama-3.1-70b-versatile") const [llmProvider, setLlmProvider] = useStorage("llmProvider", "extension | OS") const [llmKeys, setLlmKeys] = useStorage("llmKeys", {}) + const [llmCustomEndpoint, setLlmCustomEndpoint] = useStorage("llmCustomEndpoint", (v) => v === undefined ? "http://localhost:11434/v1/chat/completions" : v) const hasRun = useRef(false); // Add this line @@ -226,78 +227,96 @@ export default function LlmSettings({ debugInfo }: { debugInfo: string }) { )} -
-
- -
- +
+
+
+ +
+ - {llmProvider === "extension | OS" && } + {llmProvider === "extension | OS" && } - {!llmProvider && ( - <> - - Instructions: Choose a provider from the list on your left.
The selected provider will be set as the default. - - )} + {!llmProvider && ( + <> + + Instructions: Choose a provider from the list on your left.
The selected provider will be set as the default. + + )} +
-
-
- {providersData.providers.map( - (provider) => - llmProvider === provider.name && ( -
-
- - {provider.models.length > 0 && provider.name !== "localhost" ? ( - <> - - - ) : ( - setLlmModel(e.target.value)} - placeholder="Enter LLM model name" - className="border border-input rounded-md p-2 w-full" - /> - )} + {providersData.providers.map( + (provider) => + llmProvider === provider.name && ( +
+
+ + {provider.models.length > 0 && provider.name !== "localhost" ? ( + <> + + + ) : ( + setLlmModel(e.target.value)} + placeholder="Enter LLM model name" + className="border border-input rounded-md p-2 w-full" + /> + )} +
-
- ) - )} -
-
+ ) + )} + {providersData.providers.map( + (provider) => + llmProvider === provider.name && provider.name === "localhost" && ( +
+
+ + { + setLlmCustomEndpoint(e.target.value)} + placeholder="Enter LLM model name" + className="border border-input rounded-md p-2 w-full" + /> + } +
+
+ ) + )} {providersData.providers.map( (provider) => llmProvider === provider.name && ( @@ -307,7 +326,7 @@ export default function LlmSettings({ debugInfo }: { debugInfo: string }) { handleKeyChange(llmProvider, e.target.value)} diff --git a/package.json b/package.json index c767581..8651f79 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "extension-os", "displayName": "Extension-OS: Your AI Partner", - "version": "0.0.23", + "version": "0.0.24", "description": "AI at Your Fingertips, Anytime, Anywhere.", "author": "acubeddu87@gmail.com", "scripts": { diff --git a/playwright.config.ts b/playwright.config.ts index caa0510..edc302b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,8 +4,9 @@ import { defineConfig, devices } from "@playwright/test"; * Read environment variables from file. * https://github.com/motdotla/dotenv */ -// import dotenv from 'dotenv'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); +import dotenv from "dotenv"; +import path from "path"; +dotenv.config({ path: path.resolve(__dirname, ".env.development") }); /** * See https://playwright.dev/docs/test-configuration. diff --git a/tests/llmExecution.spec.ts b/tests/llmExecution.spec.ts new file mode 100644 index 0000000..66c3e54 --- /dev/null +++ b/tests/llmExecution.spec.ts @@ -0,0 +1,60 @@ +import { test, expect } from "./fixtures"; +const groqKey = process.env.E2E_TEST_GROQ_KEY; + +test("be able to use groq and succesfully execute a query", async ({ + page, +}) => { + await page.waitForTimeout(300); + const pageTitle = await page.title(); + await expect(pageTitle).toBe("Extension-OS: Your AI Partner"); + await page.click("#llm-provider"); + await page.click('div[role="option"] >> text="Groq"'); + await expect(page.locator("#llm-model > span")).toHaveText( + "llama-3.1-70b-versatile" + ); + + const llmKeyInput = await page.locator("#llm-key"); // Changed from getById to locator + await llmKeyInput.fill(groqKey); + + await page.goto("https://www.york.ac.uk/teaching/cws/wws/webpage1.html"); + const pageTitle2 = await page.title(); + await expect(pageTitle2).toBe("webpage1"); + await page.mouse.move(100, 100); // Move the mouse to the starting position (x: 100, y: 100) + await page.mouse.down(); // Press the mouse button down to start selecting + await page.mouse.move(200, 120); // Move the mouse to the end position (x: 300, y: 300) to select text + await page.mouse.up(); // Release the mouse button to complete the selection + + const options = await page.getByRole("option"); + const optionsCount = await options.count(); + //Must be 7 as you have to count the +2 (separator + Setup Your Own Prompt) + // expect(optionsCount).toBe(7); + await page.click('role=option[name="❗Grammar Fixer"]'); + await page.waitForSelector("#success", { state: "visible" }); +}); + +test("be able to use default localhost and succesfully execute a query", async ({ + page, +}) => { + await page.waitForTimeout(300); + const pageTitle = await page.title(); + await expect(pageTitle).toBe("Extension-OS: Your AI Partner"); + await page.click("#llm-provider"); + await page.click('div[role="option"] >> text="Localhost"'); + const modelText = await page.locator("#llm-model").inputValue(); // Retrieve the text from the input + await expect(modelText).toBe("llama3"); // + + await page.goto("https://www.york.ac.uk/teaching/cws/wws/webpage1.html"); + const pageTitle2 = await page.title(); + await expect(pageTitle2).toBe("webpage1"); + await page.mouse.move(100, 100); // Move the mouse to the starting position (x: 100, y: 100) + await page.mouse.down(); // Press the mouse button down to start selecting + await page.mouse.move(200, 120); // Move the mouse to the end position (x: 300, y: 300) to select text + await page.mouse.up(); // Release the mouse button to complete the selection + + const options = await page.getByRole("option"); + const optionsCount = await options.count(); + //Must be 7 as you have to count the +2 (separator + Setup Your Own Prompt) + // expect(optionsCount).toBe(7); + await page.click('role=option[name="❗Grammar Fixer"]'); + await page.waitForSelector("#success", { state: "visible" }); +});