Sfoglia il codice sorgente

Refactor Kodu links

Saoud Rizwan 1 anno fa
parent
commit
7fa7589ed0

+ 4 - 16
src/api/kodu.ts

@@ -1,23 +1,11 @@
 import { Anthropic } from "@anthropic-ai/sdk"
 import { Anthropic } from "@anthropic-ai/sdk"
+import axios from "axios"
 import { ApiHandler, withoutImageData } from "."
 import { ApiHandler, withoutImageData } from "."
 import { ApiHandlerOptions, koduDefaultModelId, KoduModelId, koduModels, ModelInfo } from "../shared/api"
 import { ApiHandlerOptions, koduDefaultModelId, KoduModelId, koduModels, ModelInfo } from "../shared/api"
-import axios from "axios"
-import * as vscode from "vscode"
-
-const KODU_BASE_URL = "https://claude-dev.com"
-
-export function didClickKoduSignIn() {
-	const loginUrl = `${KODU_BASE_URL}/auth/login?redirectTo=${vscode.env.uriScheme}://saoudrizwan.claude-dev&ext=1`
-	vscode.env.openExternal(vscode.Uri.parse(loginUrl))
-}
-
-export function didClickKoduAddCredits() {
-	const addCreditsUrl = `${KODU_BASE_URL}/user/addCredits?redirectTo=${vscode.env.uriScheme}://saoudrizwan.claude-dev&ext=1`
-	vscode.env.openExternal(vscode.Uri.parse(addCreditsUrl))
-}
+import { getKoduCreditsUrl, getKoduInferenceUrl } from "../shared/kodu"
 
 
 export async function fetchKoduCredits({ apiKey }: { apiKey: string }) {
 export async function fetchKoduCredits({ apiKey }: { apiKey: string }) {
-	const response = await axios.get(`${KODU_BASE_URL}/api/credits`, {
+	const response = await axios.get(getKoduCreditsUrl(), {
 		headers: {
 		headers: {
 			"x-api-key": apiKey,
 			"x-api-key": apiKey,
 		},
 		},
@@ -89,7 +77,7 @@ export class KoduHandler implements ApiHandler {
 					tool_choice: { type: "auto" },
 					tool_choice: { type: "auto" },
 				}
 				}
 		}
 		}
-		const response = await axios.post(`${KODU_BASE_URL}/api/inference`, requestBody, {
+		const response = await axios.post(getKoduInferenceUrl(), requestBody, {
 			headers: {
 			headers: {
 				"x-api-key": this.options.koduApiKey,
 				"x-api-key": this.options.koduApiKey,
 			},
 			},

+ 2 - 7
src/providers/ClaudeDevProvider.ts

@@ -8,7 +8,7 @@ import { downloadTask, getNonce, getUri, selectImages } from "../utils"
 import * as path from "path"
 import * as path from "path"
 import fs from "fs/promises"
 import fs from "fs/promises"
 import { HistoryItem } from "../shared/HistoryItem"
 import { HistoryItem } from "../shared/HistoryItem"
-import { didClickKoduAddCredits, didClickKoduSignIn, fetchKoduCredits } from "../api/kodu"
+import { fetchKoduCredits } from "../api/kodu"
 
 
 /*
 /*
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -358,15 +358,9 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
 					case "exportTaskWithId":
 					case "exportTaskWithId":
 						this.exportTaskWithId(message.text!)
 						this.exportTaskWithId(message.text!)
 						break
 						break
-					case "didClickKoduSignIn":
-						didClickKoduSignIn()
-						break
 					case "didClickKoduSignOut":
 					case "didClickKoduSignOut":
 						await this.signOutKodu()
 						await this.signOutKodu()
 						break
 						break
-					case "didClickKoduAddCredits":
-						didClickKoduAddCredits()
-						break
 					case "fetchKoduCredits":
 					case "fetchKoduCredits":
 						const koduApiKey = await this.getSecret("koduApiKey")
 						const koduApiKey = await this.getSecret("koduApiKey")
 						if (koduApiKey) {
 						if (koduApiKey) {
@@ -515,6 +509,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
 			customInstructions,
 			customInstructions,
 			alwaysAllowReadOnly,
 			alwaysAllowReadOnly,
 			themeName: vscode.workspace.getConfiguration("workbench").get<string>("colorTheme"),
 			themeName: vscode.workspace.getConfiguration("workbench").get<string>("colorTheme"),
+			uriScheme: vscode.env.uriScheme,
 			claudeMessages: this.claudeDev?.claudeMessages || [],
 			claudeMessages: this.claudeDev?.claudeMessages || [],
 			taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
 			taskHistory: (taskHistory || []).filter((item) => item.ts && item.task).sort((a, b) => b.ts - a.ts),
 			shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
 			shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -25,6 +25,7 @@ export interface ExtensionState {
 	customInstructions?: string
 	customInstructions?: string
 	alwaysAllowReadOnly?: boolean
 	alwaysAllowReadOnly?: boolean
 	themeName?: string
 	themeName?: string
+	uriScheme?: string
 	claudeMessages: ClaudeMessage[]
 	claudeMessages: ClaudeMessage[]
 	taskHistory: HistoryItem[]
 	taskHistory: HistoryItem[]
 	shouldShowAnnouncement: boolean
 	shouldShowAnnouncement: boolean

+ 0 - 2
src/shared/WebviewMessage.ts

@@ -16,9 +16,7 @@ export interface WebviewMessage {
 		| "showTaskWithId"
 		| "showTaskWithId"
 		| "deleteTaskWithId"
 		| "deleteTaskWithId"
 		| "exportTaskWithId"
 		| "exportTaskWithId"
-		| "didClickKoduSignIn"
 		| "didClickKoduSignOut"
 		| "didClickKoduSignOut"
-		| "didClickKoduAddCredits"
 		| "fetchKoduCredits"
 		| "fetchKoduCredits"
 	text?: string
 	text?: string
 	askResponse?: ClaudeAskResponse
 	askResponse?: ClaudeAskResponse

+ 17 - 0
src/shared/kodu.ts

@@ -0,0 +1,17 @@
+const KODU_BASE_URL = "https://claude-dev.com"
+
+export function getKoduSignInUrl(uriScheme?: string) {
+	return `${KODU_BASE_URL}/auth/login?redirectTo=${uriScheme}://saoudrizwan.claude-dev&ext=1`
+}
+
+export function getKoduAddCreditsUrl(uriScheme?: string) {
+	return `${KODU_BASE_URL}/user/addCredits?redirectTo=${uriScheme}://saoudrizwan.claude-dev&ext=1`
+}
+
+export function getKoduCreditsUrl() {
+	return `${KODU_BASE_URL}/api/credits`
+}
+
+export function getKoduInferenceUrl() {
+	return `${KODU_BASE_URL}/api/inference`
+}

+ 9 - 1
webview-ui/src/App.tsx

@@ -28,6 +28,7 @@ const App: React.FC = () => {
 	const [customInstructions, setCustomInstructions] = useState<string>("")
 	const [customInstructions, setCustomInstructions] = useState<string>("")
 	const [alwaysAllowReadOnly, setAlwaysAllowReadOnly] = useState<boolean>(false)
 	const [alwaysAllowReadOnly, setAlwaysAllowReadOnly] = useState<boolean>(false)
 	const [vscodeThemeName, setVscodeThemeName] = useState<string | undefined>(undefined)
 	const [vscodeThemeName, setVscodeThemeName] = useState<string | undefined>(undefined)
+	const [vscodeUriScheme, setVscodeUriScheme] = useState<string | undefined>(undefined)
 	const [claudeMessages, setClaudeMessages] = useState<ClaudeMessage[]>([])
 	const [claudeMessages, setClaudeMessages] = useState<ClaudeMessage[]>([])
 	const [taskHistory, setTaskHistory] = useState<HistoryItem[]>([])
 	const [taskHistory, setTaskHistory] = useState<HistoryItem[]>([])
 	const [showAnnouncement, setShowAnnouncement] = useState(false)
 	const [showAnnouncement, setShowAnnouncement] = useState(false)
@@ -62,6 +63,7 @@ const App: React.FC = () => {
 					setCustomInstructions(message.state!.customInstructions || "")
 					setCustomInstructions(message.state!.customInstructions || "")
 					setAlwaysAllowReadOnly(message.state!.alwaysAllowReadOnly || false)
 					setAlwaysAllowReadOnly(message.state!.alwaysAllowReadOnly || false)
 					setVscodeThemeName(message.state!.themeName)
 					setVscodeThemeName(message.state!.themeName)
+					setVscodeUriScheme(message.state!.uriScheme)
 					setClaudeMessages(message.state!.claudeMessages)
 					setClaudeMessages(message.state!.claudeMessages)
 					setTaskHistory(message.state!.taskHistory)
 					setTaskHistory(message.state!.taskHistory)
 					setKoduCredits(message.state!.koduCredits)
 					setKoduCredits(message.state!.koduCredits)
@@ -121,7 +123,11 @@ const App: React.FC = () => {
 	return (
 	return (
 		<>
 		<>
 			{showWelcome ? (
 			{showWelcome ? (
-				<WelcomeView apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
+				<WelcomeView
+					apiConfiguration={apiConfiguration}
+					setApiConfiguration={setApiConfiguration}
+					vscodeUriScheme={vscodeUriScheme}
+				/>
 			) : (
 			) : (
 				<>
 				<>
 					{showSettings && (
 					{showSettings && (
@@ -137,6 +143,7 @@ const App: React.FC = () => {
 							alwaysAllowReadOnly={alwaysAllowReadOnly}
 							alwaysAllowReadOnly={alwaysAllowReadOnly}
 							setAlwaysAllowReadOnly={setAlwaysAllowReadOnly}
 							setAlwaysAllowReadOnly={setAlwaysAllowReadOnly}
 							onDone={() => setShowSettings(false)}
 							onDone={() => setShowSettings(false)}
+							vscodeUriScheme={vscodeUriScheme}
 						/>
 						/>
 					)}
 					)}
 					{showHistory && <HistoryView taskHistory={taskHistory} onDone={() => setShowHistory(false)} />}
 					{showHistory && <HistoryView taskHistory={taskHistory} onDone={() => setShowHistory(false)} />}
@@ -159,6 +166,7 @@ const App: React.FC = () => {
 							setShowAnnouncement(false)
 							setShowAnnouncement(false)
 						}}
 						}}
 						apiConfiguration={apiConfiguration}
 						apiConfiguration={apiConfiguration}
+						vscodeUriScheme={vscodeUriScheme}
 					/>
 					/>
 				</>
 				</>
 			)}
 			)}

+ 8 - 6
webview-ui/src/components/Announcement.tsx

@@ -1,16 +1,18 @@
 import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
-import { vscode } from "../utils/vscode"
 import { ApiConfiguration } from "../../../src/shared/api"
 import { ApiConfiguration } from "../../../src/shared/api"
+import { getKoduSignInUrl } from "../../../src/shared/kodu"
+import VSCodeButtonLink from "./VSCodeButtonLink"
 
 
 interface AnnouncementProps {
 interface AnnouncementProps {
 	version: string
 	version: string
 	hideAnnouncement: () => void
 	hideAnnouncement: () => void
 	apiConfiguration?: ApiConfiguration
 	apiConfiguration?: ApiConfiguration
+	vscodeUriScheme?: string
 }
 }
 /*
 /*
 You must update the latestAnnouncementId in ClaudeDevProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
 You must update the latestAnnouncementId in ClaudeDevProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
 */
 */
-const Announcement = ({ version, hideAnnouncement, apiConfiguration }: AnnouncementProps) => {
+const Announcement = ({ version, hideAnnouncement, apiConfiguration, vscodeUriScheme }: AnnouncementProps) => {
 	return (
 	return (
 		<div
 		<div
 			style={{
 			style={{
@@ -33,23 +35,23 @@ const Announcement = ({ version, hideAnnouncement, apiConfiguration }: Announcem
 			<ul style={{ margin: "0 0 8px", paddingLeft: "20px" }}>
 			<ul style={{ margin: "0 0 8px", paddingLeft: "20px" }}>
 				<li>
 				<li>
 					Excited to announce that{" "}
 					Excited to announce that{" "}
-					<VSCodeLink href="https://claude-dev.com" style={{ display: "inline" }}>
+					<VSCodeLink href={getKoduSignInUrl(vscodeUriScheme)} style={{ display: "inline" }}>
 						Kodu
 						Kodu
 					</VSCodeLink>{" "}
 					</VSCodeLink>{" "}
 					is offering $10 free credits to help new users get the most out of Claude Dev with high rate limits
 					is offering $10 free credits to help new users get the most out of Claude Dev with high rate limits
 					and prompt caching! Stay tuned for some exciting updates like easier billing and deploying live
 					and prompt caching! Stay tuned for some exciting updates like easier billing and deploying live
 					websites.
 					websites.
 					{apiConfiguration?.koduApiKey === undefined && (
 					{apiConfiguration?.koduApiKey === undefined && (
-						<VSCodeButton
+						<VSCodeButtonLink
 							appearance="secondary"
 							appearance="secondary"
-							onClick={() => vscode.postMessage({ type: "didClickKoduSignIn" })}
+							href={getKoduSignInUrl(vscodeUriScheme)}
 							style={{
 							style={{
 								transform: "scale(0.8)",
 								transform: "scale(0.8)",
 								transformOrigin: "left center",
 								transformOrigin: "left center",
 								margin: "4px 0 2px 0",
 								margin: "4px 0 2px 0",
 							}}>
 							}}>
 							Claim $10 Free Credits
 							Claim $10 Free Credits
-						</VSCodeButton>
+						</VSCodeButtonLink>
 					)}
 					)}
 				</li>
 				</li>
 				<li>
 				<li>

+ 13 - 18
webview-ui/src/components/ApiOptions.tsx

@@ -1,11 +1,6 @@
-import {
-	VSCodeButton,
-	VSCodeDropdown,
-	VSCodeLink,
-	VSCodeOption,
-	VSCodeTextField,
-} from "@vscode/webview-ui-toolkit/react"
+import { VSCodeDropdown, VSCodeLink, VSCodeOption, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
 import React, { useCallback, useEffect, useMemo, useState } from "react"
 import React, { useCallback, useEffect, useMemo, useState } from "react"
+import { useEvent } from "react-use"
 import {
 import {
 	ApiConfiguration,
 	ApiConfiguration,
 	ApiModelId,
 	ApiModelId,
@@ -19,9 +14,10 @@ import {
 	openRouterDefaultModelId,
 	openRouterDefaultModelId,
 	openRouterModels,
 	openRouterModels,
 } from "../../../src/shared/api"
 } from "../../../src/shared/api"
-import { vscode } from "../utils/vscode"
-import { useEvent } from "react-use"
 import { ExtensionMessage } from "../../../src/shared/ExtensionMessage"
 import { ExtensionMessage } from "../../../src/shared/ExtensionMessage"
+import { getKoduAddCreditsUrl, getKoduSignInUrl } from "../../../src/shared/kodu"
+import { vscode } from "../utils/vscode"
+import VSCodeButtonLink from "./VSCodeButtonLink"
 
 
 interface ApiOptionsProps {
 interface ApiOptionsProps {
 	showModelOptions: boolean
 	showModelOptions: boolean
@@ -29,6 +25,7 @@ interface ApiOptionsProps {
 	setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
 	setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
 	koduCredits?: number
 	koduCredits?: number
 	apiErrorMessage?: string
 	apiErrorMessage?: string
+	vscodeUriScheme?: string
 }
 }
 
 
 const ApiOptions: React.FC<ApiOptionsProps> = ({
 const ApiOptions: React.FC<ApiOptionsProps> = ({
@@ -37,6 +34,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
 	setApiConfiguration,
 	setApiConfiguration,
 	koduCredits,
 	koduCredits,
 	apiErrorMessage,
 	apiErrorMessage,
+	vscodeUriScheme,
 }) => {
 }) => {
 	const [didFetchKoduCredits, setDidFetchKoduCredits] = useState(false)
 	const [didFetchKoduCredits, setDidFetchKoduCredits] = useState(false)
 	const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
 	const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
@@ -184,22 +182,19 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
 									{formatPrice(koduCredits || 0)}
 									{formatPrice(koduCredits || 0)}
 								</span>
 								</span>
 							</div>
 							</div>
-							<VSCodeButton
-								appearance="primary"
-								onClick={() => vscode.postMessage({ type: "didClickKoduAddCredits" })}
+							<VSCodeButtonLink
+								href={getKoduAddCreditsUrl(vscodeUriScheme)}
 								style={{
 								style={{
 									width: "fit-content",
 									width: "fit-content",
 								}}>
 								}}>
 								Add Credits
 								Add Credits
-							</VSCodeButton>
+							</VSCodeButtonLink>
 						</>
 						</>
 					) : (
 					) : (
 						<div style={{ margin: "4px 0px" }}>
 						<div style={{ margin: "4px 0px" }}>
-							<VSCodeButton
-								appearance="primary"
-								onClick={() => vscode.postMessage({ type: "didClickKoduSignIn" })}>
+							<VSCodeButtonLink href={getKoduSignInUrl(vscodeUriScheme)}>
 								Sign in to Kodu
 								Sign in to Kodu
-							</VSCodeButton>
+							</VSCodeButtonLink>
 						</div>
 						</div>
 					)}
 					)}
 					<p
 					<p
@@ -210,7 +205,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
 						}}>
 						}}>
 						Kodu is recommended for its high rate limits and access to the latest features like prompt
 						Kodu is recommended for its high rate limits and access to the latest features like prompt
 						caching.
 						caching.
-						<VSCodeLink href="https://claude-dev.com" style={{ display: "inline", fontSize: "12px" }}>
+						<VSCodeLink href="https://kodu.ai" style={{ display: "inline", fontSize: "12px" }}>
 							Learn more about Kodu here.
 							Learn more about Kodu here.
 						</VSCodeLink>
 						</VSCodeLink>
 					</p>
 					</p>

+ 3 - 0
webview-ui/src/components/ChatView.tsx

@@ -30,6 +30,7 @@ interface ChatViewProps {
 	hideAnnouncement: () => void
 	hideAnnouncement: () => void
 	showHistoryView: () => void
 	showHistoryView: () => void
 	apiConfiguration?: ApiConfiguration
 	apiConfiguration?: ApiConfiguration
+	vscodeUriScheme?: string
 }
 }
 
 
 const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
 const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
@@ -46,6 +47,7 @@ const ChatView = ({
 	hideAnnouncement,
 	hideAnnouncement,
 	showHistoryView,
 	showHistoryView,
 	apiConfiguration,
 	apiConfiguration,
+	vscodeUriScheme,
 }: ChatViewProps) => {
 }: ChatViewProps) => {
 	//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
 	//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
 	const task = messages.length > 0 ? messages[0] : undefined // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see ClaudeDev.abort)
 	const task = messages.length > 0 ? messages[0] : undefined // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see ClaudeDev.abort)
@@ -490,6 +492,7 @@ const ChatView = ({
 							version={version}
 							version={version}
 							hideAnnouncement={hideAnnouncement}
 							hideAnnouncement={hideAnnouncement}
 							apiConfiguration={apiConfiguration}
 							apiConfiguration={apiConfiguration}
+							vscodeUriScheme={vscodeUriScheme}
 						/>
 						/>
 					)}
 					)}
 					<div style={{ padding: "0 20px", flexGrow: taskHistory.length > 0 ? undefined : 1 }}>
 					<div style={{ padding: "0 20px", flexGrow: taskHistory.length > 0 ? undefined : 1 }}>

+ 30 - 19
webview-ui/src/components/SettingsView.tsx

@@ -10,6 +10,7 @@ import { ApiConfiguration } from "../../../src/shared/api"
 import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utils/validate"
 import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utils/validate"
 import { vscode } from "../utils/vscode"
 import { vscode } from "../utils/vscode"
 import ApiOptions from "./ApiOptions"
 import ApiOptions from "./ApiOptions"
+import { getKoduSignInUrl } from "../../../src/shared/kodu"
 
 
 type SettingsViewProps = {
 type SettingsViewProps = {
 	version: string
 	version: string
@@ -23,6 +24,7 @@ type SettingsViewProps = {
 	onDone: () => void
 	onDone: () => void
 	alwaysAllowReadOnly: boolean
 	alwaysAllowReadOnly: boolean
 	setAlwaysAllowReadOnly: React.Dispatch<React.SetStateAction<boolean>>
 	setAlwaysAllowReadOnly: React.Dispatch<React.SetStateAction<boolean>>
+	vscodeUriScheme?: string
 }
 }
 
 
 const SettingsView = ({
 const SettingsView = ({
@@ -37,6 +39,7 @@ const SettingsView = ({
 	onDone,
 	onDone,
 	alwaysAllowReadOnly,
 	alwaysAllowReadOnly,
 	setAlwaysAllowReadOnly,
 	setAlwaysAllowReadOnly,
+	vscodeUriScheme,
 }: SettingsViewProps) => {
 }: SettingsViewProps) => {
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [maxRequestsErrorMessage, setMaxRequestsErrorMessage] = useState<string | undefined>(undefined)
 	const [maxRequestsErrorMessage, setMaxRequestsErrorMessage] = useState<string | undefined>(undefined)
@@ -104,27 +107,34 @@ const SettingsView = ({
 			<div
 			<div
 				style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
 				style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
 				{apiConfiguration?.koduApiKey === undefined && (
 				{apiConfiguration?.koduApiKey === undefined && (
-					<div
+					<a
+						href={getKoduSignInUrl(vscodeUriScheme)}
 						style={{
 						style={{
-							display: "flex",
-							alignItems: "center",
-							backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
-							color: "var(--vscode-textLink-foreground)",
-							padding: "6px 8px",
-							borderRadius: "3px",
-							margin: "0 0 8px 0px",
-							fontSize: "12px",
-							cursor: "pointer",
-						}}
-						onClick={() => vscode.postMessage({ type: "didClickKoduSignIn" })}>
-						<i
-							className="codicon codicon-info"
+							textDecoration: "none",
+							color: "inherit",
+							outline: "none",
+						}}>
+						<div
 							style={{
 							style={{
-								marginRight: 6,
-								fontSize: 16,
-							}}></i>
-						<span>Claim $10 free credits from Kodu</span>
-					</div>
+								display: "flex",
+								alignItems: "center",
+								backgroundColor: "var(--vscode-editor-inactiveSelectionBackground)",
+								color: "var(--vscode-textLink-foreground)",
+								padding: "6px 8px",
+								borderRadius: "3px",
+								margin: "0 0 8px 0px",
+								fontSize: "12px",
+								cursor: "pointer",
+							}}>
+							<i
+								className="codicon codicon-info"
+								style={{
+									marginRight: 6,
+									fontSize: 16,
+								}}></i>
+							<span>Claim $10 free credits from Kodu</span>
+						</div>
+					</a>
 				)}
 				)}
 				<div style={{ marginBottom: 5 }}>
 				<div style={{ marginBottom: 5 }}>
 					<ApiOptions
 					<ApiOptions
@@ -133,6 +143,7 @@ const SettingsView = ({
 						showModelOptions={true}
 						showModelOptions={true}
 						koduCredits={koduCredits}
 						koduCredits={koduCredits}
 						apiErrorMessage={apiErrorMessage}
 						apiErrorMessage={apiErrorMessage}
+						vscodeUriScheme={vscodeUriScheme}
 					/>
 					/>
 				</div>
 				</div>
 
 

+ 23 - 0
webview-ui/src/components/VSCodeButtonLink.tsx

@@ -0,0 +1,23 @@
+import React from "react"
+import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
+
+interface VSCodeButtonLinkProps {
+	href: string
+	children: React.ReactNode
+	[key: string]: any
+}
+
+const VSCodeButtonLink: React.FC<VSCodeButtonLinkProps> = ({ href, children, ...props }) => {
+	return (
+		<a
+			href={href}
+			style={{
+				textDecoration: "none",
+				color: "inherit",
+			}}>
+			<VSCodeButton {...props}>{children}</VSCodeButton>
+		</a>
+	)
+}
+
+export default VSCodeButtonLink

+ 5 - 2
webview-ui/src/components/WelcomeView.tsx

@@ -4,13 +4,15 @@ import { ApiConfiguration } from "../../../src/shared/api"
 import { validateApiConfiguration } from "../utils/validate"
 import { validateApiConfiguration } from "../utils/validate"
 import { vscode } from "../utils/vscode"
 import { vscode } from "../utils/vscode"
 import ApiOptions from "./ApiOptions"
 import ApiOptions from "./ApiOptions"
+import { getKoduSignInUrl } from "../../../src/shared/kodu"
 
 
 interface WelcomeViewProps {
 interface WelcomeViewProps {
 	apiConfiguration?: ApiConfiguration
 	apiConfiguration?: ApiConfiguration
 	setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
 	setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
+	vscodeUriScheme?: string
 }
 }
 
 
-const WelcomeView: React.FC<WelcomeViewProps> = ({ apiConfiguration, setApiConfiguration }) => {
+const WelcomeView: React.FC<WelcomeViewProps> = ({ apiConfiguration, setApiConfiguration, vscodeUriScheme }) => {
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 
 
 	const disableLetsGoButton = apiErrorMessage != null
 	const disableLetsGoButton = apiErrorMessage != null
@@ -59,7 +61,7 @@ const WelcomeView: React.FC<WelcomeViewProps> = ({ apiConfiguration, setApiConfi
 					}}></i>
 					}}></i>
 				<span>
 				<span>
 					Explore Claude's capabilities with $10 free credits from{" "}
 					Explore Claude's capabilities with $10 free credits from{" "}
-					<VSCodeLink href="https://claude-dev.com" style={{ display: "inline" }}>
+					<VSCodeLink href={getKoduSignInUrl(vscodeUriScheme)} style={{ display: "inline" }}>
 						Kodu
 						Kodu
 					</VSCodeLink>
 					</VSCodeLink>
 				</span>
 				</span>
@@ -70,6 +72,7 @@ const WelcomeView: React.FC<WelcomeViewProps> = ({ apiConfiguration, setApiConfi
 					apiConfiguration={apiConfiguration}
 					apiConfiguration={apiConfiguration}
 					setApiConfiguration={setApiConfiguration}
 					setApiConfiguration={setApiConfiguration}
 					showModelOptions={false}
 					showModelOptions={false}
+					vscodeUriScheme={vscodeUriScheme}
 				/>
 				/>
 				{apiConfiguration?.apiProvider !== "kodu" && (
 				{apiConfiguration?.apiProvider !== "kodu" && (
 					<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
 					<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>