-
Notifications
You must be signed in to change notification settings - Fork 213
feat(webview): add OpenAI Codex speed selector #1076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
69e5851
1977341
3558eed
0c7b0f9
4ca5f3e
bcb5e4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| .vscode-light { | ||
| color-scheme: light; | ||
| --vscode-foreground: #3b3b3b; | ||
| --vscode-disabledForeground: #61616180; | ||
| --vscode-descriptionForeground: #717171; | ||
| --vscode-errorForeground: #a1260d; | ||
| --vscode-focusBorder: #0090f1; | ||
| --vscode-editor-foreground: #333333; | ||
| --vscode-editor-background: #ffffff; | ||
| --vscode-button-foreground: #ffffff; | ||
| --vscode-button-background: #007acc; | ||
| --vscode-button-hoverBackground: #0062a3; | ||
| --vscode-dropdown-foreground: #3b3b3b; | ||
| --vscode-dropdown-background: #ffffff; | ||
| --vscode-dropdown-border: #cecece; | ||
| --vscode-input-foreground: #3b3b3b; | ||
| --vscode-input-background: #ffffff; | ||
| --vscode-input-border: #cecece; | ||
| --vscode-list-hoverForeground: #3b3b3b; | ||
| --vscode-list-hoverBackground: #e8e8e8; | ||
| --vscode-list-focusBackground: #d6ebff; | ||
| --vscode-list-activeSelectionBackground: #0060c0; | ||
| --vscode-list-activeSelectionForeground: #ffffff; | ||
| --vscode-toolbar-hoverBackground: #e8e8e8; | ||
| --vscode-widget-border: #d4d4d4; | ||
| --vscode-widget-shadow: #00000029; | ||
| --vscode-menu-foreground: #3b3b3b; | ||
| --vscode-menu-background: #ffffff; | ||
| --vscode-editorHoverWidget-foreground: #3b3b3b; | ||
| --vscode-editorHoverWidget-background: #f8f8f8; | ||
| --vscode-editorHoverWidget-border: #c8c8c8; | ||
| --vscode-scrollbarSlider-background: #64646466; | ||
| --vscode-scrollbarSlider-hoverBackground: #64646499; | ||
| --vscode-scrollbarSlider-activeBackground: #00000099; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import React from "react" | ||
|
|
||
| import { | ||
| OpenAiCodexServiceTier, | ||
| type OpenAiCodexServiceTier as OpenAiCodexServiceTierValue, | ||
| } from "@roo-code/types/model" | ||
|
|
||
| import { useAppTranslation } from "@src/i18n/TranslationContext" | ||
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, StandardTooltip } from "@src/components/ui" | ||
|
|
||
| interface OpenAICodexSpeedSelectorProps { | ||
| value?: OpenAiCodexServiceTierValue | ||
| onValueChange: (value: OpenAiCodexServiceTierValue) => void | ||
| } | ||
|
|
||
| export const OpenAICodexSpeedSelector: React.FC<OpenAICodexSpeedSelectorProps> = ({ value, onValueChange }) => { | ||
| const { t } = useAppTranslation() | ||
| const selectId = React.useId() | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-1" data-testid="openai-codex-service-tier"> | ||
| <div className="flex items-center gap-1"> | ||
| <label htmlFor={selectId} className="block font-medium"> | ||
| {t("settings:openAiCodexSpeed.label")} | ||
| </label> | ||
| <StandardTooltip content={t("settings:openAiCodexSpeed.tooltip")}> | ||
| <i className="codicon codicon-info text-vscode-descriptionForeground text-xs" /> | ||
| </StandardTooltip> | ||
| </div> | ||
| <Select value={value ?? OpenAiCodexServiceTier.Default} onValueChange={onValueChange}> | ||
| <SelectTrigger id={selectId} className="w-full"> | ||
| <SelectValue placeholder={t("settings:common.select")} /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value={OpenAiCodexServiceTier.Default}> | ||
| {t("settings:openAiCodexSpeed.standard")} | ||
| </SelectItem> | ||
| <SelectItem value={OpenAiCodexServiceTier.Priority}> | ||
| {t("settings:openAiCodexSpeed.fast")} | ||
| </SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import React from "react" | ||
|
|
||
| import { | ||
| OPEN_AI_CODEX_SERVICE_TIER_KEY, | ||
| OpenAiCodexServiceTier, | ||
| providerIdentifiers, | ||
| type ProviderSettings, | ||
| } from "@roo-code/types" | ||
|
|
||
| import { fireEvent, render, screen } from "@/utils/test-utils" | ||
| import { vscode } from "@src/utils/vscode" | ||
|
|
||
| import { OpenAICodex } from "../OpenAICodex" | ||
|
|
||
| vi.mock("@src/i18n/TranslationContext", () => ({ | ||
| useAppTranslation: () => ({ | ||
| t: (key: string) => | ||
| ({ | ||
| "settings:openAiCodexSpeed.label": "Speed", | ||
| "settings:openAiCodexSpeed.tooltip": | ||
| "Fast uses Codex priority processing for about 1.5x speed and consumes more subscription quota.", | ||
| "settings:openAiCodexSpeed.standard": "Standard", | ||
| "settings:openAiCodexSpeed.fast": "Fast (1.5x speed, increased usage)", | ||
| })[key] ?? key, | ||
| }), | ||
| })) | ||
|
|
||
| vi.mock("@src/components/ui", () => ({ | ||
| Button: ({ children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) => ( | ||
| <button {...props}>{children}</button> | ||
| ), | ||
| Select: ({ children, value, onValueChange }: any) => ( | ||
| <select aria-label="Speed" value={value} onChange={(event) => onValueChange(event.target.value)}> | ||
| {children} | ||
| </select> | ||
| ), | ||
| SelectContent: ({ children }: any) => <>{children}</>, | ||
| SelectItem: ({ children, value }: any) => <option value={value}>{children}</option>, | ||
| SelectTrigger: ({ children }: any) => <>{children}</>, | ||
| SelectValue: () => null, | ||
| StandardTooltip: ({ children, content }: any) => <span title={content}>{children}</span>, | ||
| })) | ||
|
|
||
| vi.mock("../../ModelPicker", () => ({ | ||
| ModelPicker: () => <div data-testid="model-picker" />, | ||
| })) | ||
|
|
||
| vi.mock("../OpenAICodexRateLimitDashboard", () => ({ | ||
| OpenAICodexRateLimitDashboard: () => null, | ||
| })) | ||
|
|
||
| vi.mock("@src/utils/vscode", () => ({ | ||
| vscode: { postMessage: vi.fn() }, | ||
| })) | ||
|
|
||
| describe("OpenAICodex speed selector", () => { | ||
| const renderSelector = (apiConfiguration: ProviderSettings, setApiConfigurationField = vi.fn()) => { | ||
| render(<OpenAICodex apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />) | ||
| return { setApiConfigurationField, selector: screen.getByRole("combobox", { name: "Speed" }) } | ||
| } | ||
|
|
||
| it("defaults to Standard and clearly explains the Fast quota trade-off", () => { | ||
| const { selector } = renderSelector({ apiProvider: providerIdentifiers.openaiCodex }) | ||
|
|
||
| expect(selector).toHaveValue(OpenAiCodexServiceTier.Default) | ||
| expect(screen.getByRole("option", { name: "Standard" })).toBeInTheDocument() | ||
| expect(screen.getByRole("option", { name: "Fast (1.5x speed, increased usage)" })).toBeInTheDocument() | ||
| expect( | ||
| screen.getByTitle( | ||
| "Fast uses Codex priority processing for about 1.5x speed and consumes more subscription quota.", | ||
| ), | ||
| ).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it("selects Fast from a saved preference and persists changes through the settings callback", () => { | ||
| const { selector, setApiConfigurationField } = renderSelector({ | ||
| apiProvider: providerIdentifiers.openaiCodex, | ||
| [OPEN_AI_CODEX_SERVICE_TIER_KEY]: OpenAiCodexServiceTier.Priority, | ||
| }) | ||
| const postMessage = vi.mocked(vscode.postMessage) | ||
|
|
||
| expect(selector).toHaveValue(OpenAiCodexServiceTier.Priority) | ||
|
|
||
| fireEvent.change(selector, { target: { value: OpenAiCodexServiceTier.Default } }) | ||
| expect(setApiConfigurationField).toHaveBeenLastCalledWith( | ||
| OPEN_AI_CODEX_SERVICE_TIER_KEY, | ||
| OpenAiCodexServiceTier.Default, | ||
| ) | ||
|
|
||
| fireEvent.change(selector, { target: { value: OpenAiCodexServiceTier.Priority } }) | ||
| expect(setApiConfigurationField).toHaveBeenLastCalledWith( | ||
| OPEN_AI_CODEX_SERVICE_TIER_KEY, | ||
| OpenAiCodexServiceTier.Priority, | ||
| ) | ||
| expect(postMessage).not.toHaveBeenCalled() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* v8 ignore file -- Playwright component fixture is covered by the visual test. */ | ||
| import React from "react" | ||
|
|
||
| import { OpenAiCodexServiceTier } from "@roo-code/types/model" | ||
|
|
||
| import { TranslationContext } from "@src/i18n/TranslationContext" | ||
| import { TooltipProvider } from "@src/components/ui/tooltip" | ||
| import { OpenAICodexSpeedSelector } from "../OpenAICodexSpeedSelector" | ||
|
|
||
| const translations: Record<string, string> = { | ||
| "settings:common.select": "Select", | ||
| "settings:openAiCodexSpeed.label": "Speed", | ||
| "settings:openAiCodexSpeed.tooltip": | ||
| "Fast uses Codex priority processing for about 1.5x speed and consumes more subscription quota.", | ||
| "settings:openAiCodexSpeed.standard": "Standard", | ||
| "settings:openAiCodexSpeed.fast": "Fast (1.5x speed, increased usage)", | ||
| } | ||
|
|
||
| export const OpenAICodexFixture = () => ( | ||
| <TranslationContext.Provider | ||
| value={{ | ||
| t: (key) => translations[key] ?? key, | ||
| i18n: null as unknown as typeof import("../../../../i18n/setup").default, | ||
| }}> | ||
| <TooltipProvider> | ||
| <div className="flex w-[480px] flex-col gap-4 bg-vscode-editor-background p-4 text-vscode-foreground"> | ||
| <OpenAICodexSpeedSelector value={OpenAiCodexServiceTier.Default} onValueChange={() => {}} /> | ||
| <OpenAICodexSpeedSelector value={OpenAiCodexServiceTier.Priority} onValueChange={() => {}} /> | ||
| </div> | ||
| </TooltipProvider> | ||
| </TranslationContext.Provider> | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import React from "react" | ||
|
|
||
| import { expect, test } from "../../../../../playwright/coverage-fixture" | ||
| import { OpenAICodexFixture } from "./OpenAICodex.visual.fixture" | ||
|
|
||
| const themes = [ | ||
| { | ||
| name: "dark", | ||
| bodyClass: "vscode-dark", | ||
| themeId: "Default Dark Modern", | ||
| editorBackground: "#1e1e1e", | ||
| dropdownBackground: "#3c3c3c", | ||
| triggerBackground: "rgb(60, 60, 60)", | ||
| }, | ||
| { | ||
| name: "light", | ||
| bodyClass: "vscode-light", | ||
| themeId: "Default Light Modern", | ||
| editorBackground: "#ffffff", | ||
| dropdownBackground: "#ffffff", | ||
| triggerBackground: "rgb(255, 255, 255)", | ||
| }, | ||
| ] as const | ||
|
|
||
| for (const theme of themes) { | ||
| test(`renders both OpenAI Codex speeds in the VS Code ${theme.name} theme`, async ({ mount }) => { | ||
| const component = await mount(<OpenAICodexFixture />) | ||
| const selectors = component.getByTestId("openai-codex-service-tier") | ||
| const comboboxes = component.getByRole("combobox", { name: "Speed" }) | ||
| const selector = selectors.first() | ||
|
|
||
| await expect(selectors).toHaveCount(2) | ||
| await expect(comboboxes).toHaveCount(2) | ||
| await selector.evaluate((element, { bodyClass, themeId }) => { | ||
| const { document } = element.ownerDocument.defaultView! | ||
|
|
||
| document.documentElement.className = bodyClass | ||
| document.body.className = bodyClass | ||
| document.body.dataset.vscodeThemeId = themeId | ||
| }, theme) | ||
| await expect | ||
| .poll(() => | ||
| selector.evaluate((element) => { | ||
| const body = element.ownerDocument.body | ||
| const styles = getComputedStyle(body) | ||
| const trigger = element.querySelector("button")! | ||
|
|
||
| return { | ||
| documentClass: element.ownerDocument.documentElement.className, | ||
| bodyClass: body.className, | ||
| editorBackground: styles.getPropertyValue("--vscode-editor-background").trim(), | ||
| dropdownBackground: styles.getPropertyValue("--vscode-dropdown-background").trim(), | ||
| triggerBackground: getComputedStyle(trigger).backgroundColor, | ||
| } | ||
| }), | ||
| ) | ||
| .toEqual({ | ||
| documentClass: theme.bodyClass, | ||
| bodyClass: theme.bodyClass, | ||
| editorBackground: theme.editorBackground, | ||
| dropdownBackground: theme.dropdownBackground, | ||
| triggerBackground: theme.triggerBackground, | ||
| }) | ||
|
|
||
| await component.evaluate(async () => { | ||
| await document.fonts.ready | ||
| await new Promise<void>((resolve) => requestAnimationFrame(() => resolve())) | ||
| }) | ||
|
|
||
| await expect(component).toHaveScreenshot(`openai-codex-speed-selector-states-${theme.name}.png`) | ||
| }) | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.