mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add Kodu provider
This commit is contained in:
@@ -10,7 +10,6 @@ import WelcomeView from "./components/WelcomeView"
|
||||
import { vscode } from "./utils/vscode"
|
||||
import HistoryView from "./components/HistoryView"
|
||||
import { HistoryItem } from "../../src/shared/HistoryItem"
|
||||
import { MaestroUser } from "../../src/shared/maestro"
|
||||
|
||||
/*
|
||||
The contents of webviews however are created when the webview becomes visible and destroyed when the webview is moved into the background. Any state inside the webview will be lost when the webview is moved to a background tab.
|
||||
@@ -31,7 +30,7 @@ const App: React.FC = () => {
|
||||
const [claudeMessages, setClaudeMessages] = useState<ClaudeMessage[]>([])
|
||||
const [taskHistory, setTaskHistory] = useState<HistoryItem[]>([])
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false)
|
||||
const [maestroUser, setMaestroUser] = useState<MaestroUser | undefined>(undefined)
|
||||
const [koduCredits, setKoduCredits] = useState<number | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
vscode.postMessage({ type: "webviewDidLaunch" })
|
||||
@@ -45,7 +44,8 @@ const App: React.FC = () => {
|
||||
const hasKey =
|
||||
message.state!.apiConfiguration?.apiKey !== undefined ||
|
||||
message.state!.apiConfiguration?.openRouterApiKey !== undefined ||
|
||||
message.state!.apiConfiguration?.awsAccessKey !== undefined
|
||||
message.state!.apiConfiguration?.awsAccessKey !== undefined ||
|
||||
message.state!.apiConfiguration?.koduApiKey !== undefined
|
||||
setShowWelcome(!hasKey)
|
||||
setApiConfiguration(message.state!.apiConfiguration)
|
||||
setMaxRequestsPerTask(
|
||||
@@ -55,12 +55,12 @@ const App: React.FC = () => {
|
||||
setVscodeThemeName(message.state!.themeName)
|
||||
setClaudeMessages(message.state!.claudeMessages)
|
||||
setTaskHistory(message.state!.taskHistory)
|
||||
setKoduCredits(message.state!.koduCredits)
|
||||
// don't update showAnnouncement to false if shouldShowAnnouncement is false
|
||||
if (message.state!.shouldShowAnnouncement) {
|
||||
setShowAnnouncement(true)
|
||||
vscode.postMessage({ type: "didShowAnnouncement" })
|
||||
}
|
||||
setMaestroUser(message.state!.maestroUser)
|
||||
setDidHydrateState(true)
|
||||
break
|
||||
case "action":
|
||||
@@ -103,8 +103,8 @@ const App: React.FC = () => {
|
||||
<SettingsView
|
||||
version={version}
|
||||
apiConfiguration={apiConfiguration}
|
||||
maestroUser={maestroUser}
|
||||
setApiConfiguration={setApiConfiguration}
|
||||
koduCredits={koduCredits}
|
||||
maxRequestsPerTask={maxRequestsPerTask}
|
||||
setMaxRequestsPerTask={setMaxRequestsPerTask}
|
||||
customInstructions={customInstructions}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
VSCodeOption,
|
||||
VSCodeTextField,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useMemo } from "react"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
ApiConfiguration,
|
||||
ApiModelId,
|
||||
@@ -14,27 +14,31 @@ import {
|
||||
anthropicModels,
|
||||
bedrockDefaultModelId,
|
||||
bedrockModels,
|
||||
maestroDefaultModelId,
|
||||
maestroModels,
|
||||
koduDefaultModelId,
|
||||
koduModels,
|
||||
openRouterDefaultModelId,
|
||||
openRouterModels,
|
||||
} from "../../../src/shared/api"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { MaestroUser } from "../../../src/shared/maestro"
|
||||
import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "../../../src/shared/ExtensionMessage"
|
||||
|
||||
interface ApiOptionsProps {
|
||||
showModelOptions: boolean
|
||||
apiConfiguration?: ApiConfiguration
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
maestroUser?: MaestroUser
|
||||
koduCredits?: number
|
||||
apiErrorMessage?: string
|
||||
}
|
||||
|
||||
const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
showModelOptions,
|
||||
apiConfiguration,
|
||||
setApiConfiguration,
|
||||
maestroUser,
|
||||
koduCredits,
|
||||
apiErrorMessage,
|
||||
}) => {
|
||||
const [didFetchKoduCredits, setDidFetchKoduCredits] = useState(false)
|
||||
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
|
||||
setApiConfiguration((prev) => ({ ...prev, [field]: event.target.value }))
|
||||
}
|
||||
@@ -75,6 +79,27 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProvider === "kodu" && apiConfiguration?.koduApiKey) {
|
||||
setDidFetchKoduCredits(false)
|
||||
vscode.postMessage({ type: "fetchKoduCredits" })
|
||||
}
|
||||
}, [selectedProvider, apiConfiguration?.koduApiKey])
|
||||
|
||||
const handleMessage = useCallback((e: MessageEvent) => {
|
||||
const message: ExtensionMessage = e.data
|
||||
switch (message.type) {
|
||||
case "action":
|
||||
switch (message.action) {
|
||||
case "koduCreditsFetched":
|
||||
setDidFetchKoduCredits(true)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}, [])
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
<div className="dropdown-container">
|
||||
@@ -82,10 +107,10 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
<span style={{ fontWeight: 500 }}>API Provider</span>
|
||||
</label>
|
||||
<VSCodeDropdown id="api-provider" value={selectedProvider} onChange={handleInputChange("apiProvider")}>
|
||||
<VSCodeOption value="kodu">Kodu</VSCodeOption>
|
||||
<VSCodeOption value="anthropic">Anthropic</VSCodeOption>
|
||||
<VSCodeOption value="bedrock">AWS Bedrock</VSCodeOption>
|
||||
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
|
||||
<VSCodeOption value="maestro">Maestro</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
|
||||
@@ -139,42 +164,54 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedProvider === "maestro" && (
|
||||
{selectedProvider === "kodu" && (
|
||||
<>
|
||||
{maestroUser ? (
|
||||
{apiConfiguration?.koduApiKey !== undefined ? (
|
||||
<div>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: "var(--vscode-testing-iconPassed)",
|
||||
}}>
|
||||
<i
|
||||
className={`codicon codicon-check`}
|
||||
style={{
|
||||
marginRight: 4,
|
||||
marginBottom: 1,
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
display: "inline-block",
|
||||
verticalAlign: "bottom",
|
||||
}}></i>
|
||||
Signed in as {maestroUser.email}
|
||||
</span>
|
||||
<div style={{ margin: "4px 0px 2px 0px" }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => vscode.postMessage({ type: "didClickMaestroSignOut" })}>
|
||||
Sign out
|
||||
</VSCodeButton>
|
||||
<div style={{ marginBottom: 5, marginTop: 3 }}>
|
||||
<span style={{ color: "var(--vscode-descriptionForeground)" }}>
|
||||
Signed in as {apiConfiguration?.koduEmail || "Unknown"}
|
||||
</span>{" "}
|
||||
<VSCodeLink
|
||||
style={{ display: "inline" }}
|
||||
onClick={() => vscode.postMessage({ type: "didClickKoduSignOut" })}>
|
||||
(sign out?)
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
<div style={{ marginBottom: 7 }}>
|
||||
Credits remaining:{" "}
|
||||
<span style={{ fontWeight: 500, opacity: didFetchKoduCredits ? 1 : 0.6 }}>
|
||||
{formatPrice(koduCredits || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={() => vscode.postMessage({ type: "didClickKoduAddCredits" })}
|
||||
style={{
|
||||
width: "fit-content",
|
||||
}}>
|
||||
Add Credits
|
||||
</VSCodeButton>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "7px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Kodu is recommended for its high rate limits and access to the latest features like
|
||||
prompt caching.
|
||||
<VSCodeLink href="https://kodu.ai/" style={{ display: "inline", fontSize: "12px" }}>
|
||||
Learn more about Kodu here.
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ margin: "4px 0px" }}>
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={() => vscode.postMessage({ type: "didClickMaestroSignIn" })}>
|
||||
Sign in to Maestro
|
||||
onClick={() => vscode.postMessage({ type: "didClickKoduSignIn" })}>
|
||||
Sign in to Kodu
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
<p
|
||||
@@ -183,7 +220,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
marginTop: 5,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
This will open your browser to sign in to Maestro. You will be redirected back to the
|
||||
This will open your browser to sign in to Kodu. You will be redirected back to the
|
||||
extension after signing in.
|
||||
</p>
|
||||
</div>
|
||||
@@ -256,6 +293,17 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-10px 0 4px 0",
|
||||
fontSize: 12,
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{showModelOptions && (
|
||||
<>
|
||||
<div className="dropdown-container">
|
||||
@@ -265,7 +313,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
{selectedProvider === "anthropic" && createDropdown(anthropicModels)}
|
||||
{selectedProvider === "openrouter" && createDropdown(openRouterModels)}
|
||||
{selectedProvider === "bedrock" && createDropdown(bedrockModels)}
|
||||
{selectedProvider === "maestro" && createDropdown(maestroModels)}
|
||||
{selectedProvider === "kodu" && createDropdown(koduModels)}
|
||||
</div>
|
||||
|
||||
<ModelInfoView modelInfo={selectedModelInfo} />
|
||||
@@ -275,16 +323,16 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const ModelInfoView = ({ modelInfo }: { modelInfo: ModelInfo }) => {
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(price)
|
||||
}
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
const ModelInfoView = ({ modelInfo }: { modelInfo: ModelInfo }) => {
|
||||
return (
|
||||
<p style={{ fontSize: "12px", marginTop: "2px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
<ModelInfoSupportsItem
|
||||
@@ -369,8 +417,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration) {
|
||||
return getProviderData(openRouterModels, openRouterDefaultModelId)
|
||||
case "bedrock":
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId)
|
||||
case "maestro":
|
||||
return getProviderData(maestroModels, maestroDefaultModelId)
|
||||
case "kodu":
|
||||
return getProviderData(koduModels, koduDefaultModelId)
|
||||
default:
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import { ApiConfiguration } from "../../../src/shared/api"
|
||||
import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utils/validate"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
import { MaestroUser } from "../../../src/shared/maestro"
|
||||
|
||||
type SettingsViewProps = {
|
||||
version: string
|
||||
apiConfiguration?: ApiConfiguration
|
||||
maestroUser?: MaestroUser
|
||||
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
|
||||
koduCredits?: number
|
||||
maxRequestsPerTask: string
|
||||
setMaxRequestsPerTask: React.Dispatch<React.SetStateAction<string>>
|
||||
customInstructions: string
|
||||
@@ -21,8 +20,8 @@ type SettingsViewProps = {
|
||||
const SettingsView = ({
|
||||
version,
|
||||
apiConfiguration,
|
||||
maestroUser,
|
||||
setApiConfiguration,
|
||||
koduCredits,
|
||||
maxRequestsPerTask,
|
||||
setMaxRequestsPerTask,
|
||||
customInstructions,
|
||||
@@ -96,20 +95,11 @@ const SettingsView = ({
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<ApiOptions
|
||||
apiConfiguration={apiConfiguration}
|
||||
maestroUser={maestroUser}
|
||||
setApiConfiguration={setApiConfiguration}
|
||||
showModelOptions={true}
|
||||
koduCredits={koduCredits}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
/>
|
||||
{apiErrorMessage && (
|
||||
<p
|
||||
style={{
|
||||
margin: "-5px 0 12px 0",
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-errorForeground)",
|
||||
}}>
|
||||
{apiErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
|
||||
@@ -18,10 +18,10 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
|
||||
return "You must provide a valid API key or choose a different provider."
|
||||
}
|
||||
break
|
||||
case "maestro":
|
||||
// if (!apiConfiguration.maestroApiKey) {
|
||||
// return "You must provide a valid API key or choose a different provider."
|
||||
// }
|
||||
case "kodu":
|
||||
if (!apiConfiguration.koduApiKey) {
|
||||
return "You must sign in to Kodu to use it as an API provider."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user