Skip to content

Commit 4aff012

Browse files
authored
Merge pull request #667 from RooVetGit/cte/vite-hmr
Enable HMR for the webview, complements of vite
2 parents a5dac3f + 9736795 commit 4aff012

File tree

6 files changed

+547
-67
lines changed

6 files changed

+547
-67
lines changed

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"connor4312.esbuild-problem-matchers",
77
"ms-vscode.extension-test-runner",
88
"csstools.postcss",
9-
"bradlc.vscode-tailwindcss"
9+
"bradlc.vscode-tailwindcss",
10+
"tobermory.es6-string-html"
1011
]
1112
}

src/core/webview/ClineProvider.ts

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
269269
enableScripts: true,
270270
localResourceRoots: [this.context.extensionUri],
271271
}
272-
webviewView.webview.html = this.getHtmlContent(webviewView.webview)
272+
273+
webviewView.webview.html =
274+
this.context.extensionMode === vscode.ExtensionMode.Production
275+
? this.getHtmlContent(webviewView.webview)
276+
: this.getHMRHtmlContent(webviewView.webview)
273277

274278
// Sets up an event listener to listen for messages passed from the webview view context
275279
// and executes code based on the message that is recieved
@@ -393,6 +397,64 @@ export class ClineProvider implements vscode.WebviewViewProvider {
393397
await this.view?.webview.postMessage(message)
394398
}
395399

400+
private getHMRHtmlContent(webview: vscode.Webview): string {
401+
const nonce = getNonce()
402+
403+
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
404+
const codiconsUri = getUri(webview, this.context.extensionUri, [
405+
"node_modules",
406+
"@vscode",
407+
"codicons",
408+
"dist",
409+
"codicon.css",
410+
])
411+
412+
const file = "src/index.tsx"
413+
const localPort = "5173"
414+
const localServerUrl = `localhost:${localPort}`
415+
const scriptUri = `http://${localServerUrl}/${file}`
416+
417+
const reactRefreshHash = "sha256-YmMpkm5ow6h+lfI3ZRp0uys+EUCt6FOyLkJERkfVnTY="
418+
419+
const reactRefresh = /*html*/ `
420+
<script sha256="${reactRefreshHash}" nonce="${nonce}" type="module">
421+
import RefreshRuntime from "http://localhost:${localPort}/@react-refresh"
422+
RefreshRuntime.injectIntoGlobalHook(window)
423+
window.$RefreshReg$ = () => {}
424+
window.$RefreshSig$ = () => (type) => type
425+
window.__vite_plugin_react_preamble_installed__ = true
426+
</script>
427+
`
428+
429+
const csp = [
430+
"default-src 'none'",
431+
`font-src ${webview.cspSource}`,
432+
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
433+
`img-src ${webview.cspSource} data:`,
434+
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} '${reactRefreshHash}' 'nonce-${nonce}'`,
435+
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
436+
]
437+
438+
return /*html*/ `
439+
<!DOCTYPE html>
440+
<html lang="en">
441+
<head>
442+
<meta charset="utf-8">
443+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
444+
<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
445+
<link rel="stylesheet" type="text/css" href="${stylesUri}">
446+
<link href="${codiconsUri}" rel="stylesheet" />
447+
<title>Roo Code</title>
448+
</head>
449+
<body>
450+
<div id="root"></div>
451+
${reactRefresh}
452+
<script type="module" src="${scriptUri}"></script>
453+
</body>
454+
</html>
455+
`
456+
}
457+
396458
/**
397459
* Defines and returns the HTML that should be rendered within the webview panel.
398460
*
@@ -548,7 +610,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
548610
}
549611
}
550612

551-
let currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string
613+
const currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string
552614

553615
if (currentConfigName) {
554616
if (!(await this.configManager.hasConfig(currentConfigName))) {
@@ -1124,7 +1186,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11241186
if (message.text && message.apiConfiguration) {
11251187
try {
11261188
await this.configManager.saveConfig(message.text, message.apiConfiguration)
1127-
let listApiConfig = await this.configManager.listConfig()
1189+
const listApiConfig = await this.configManager.listConfig()
11281190

11291191
await Promise.all([
11301192
this.updateGlobalState("listApiConfigMeta", listApiConfig),
@@ -1149,7 +1211,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11491211
await this.configManager.saveConfig(newName, message.apiConfiguration)
11501212
await this.configManager.deleteConfig(oldName)
11511213

1152-
let listApiConfig = await this.configManager.listConfig()
1214+
const listApiConfig = await this.configManager.listConfig()
11531215
const config = listApiConfig?.find((c) => c.name === newName)
11541216

11551217
// Update listApiConfigMeta first to ensure UI has latest data
@@ -1207,7 +1269,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12071269
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
12081270

12091271
// If this was the current config, switch to first available
1210-
let currentApiConfigName = await this.getGlobalState("currentApiConfigName")
1272+
const currentApiConfigName = await this.getGlobalState("currentApiConfigName")
12111273
if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
12121274
const apiConfig = await this.configManager.loadConfig(listApiConfig[0].name)
12131275
await Promise.all([
@@ -1227,7 +1289,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12271289
break
12281290
case "getListApiConfiguration":
12291291
try {
1230-
let listApiConfig = await this.configManager.listConfig()
1292+
const listApiConfig = await this.configManager.listConfig()
12311293
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
12321294
this.postMessageToWebview({ type: "listApiConfig", listApiConfig })
12331295
} catch (error) {
@@ -1267,7 +1329,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12671329
this.outputChannel.appendLine(
12681330
`Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
12691331
)
1270-
vscode.window.showErrorMessage(`Failed to update server timeout`)
1332+
vscode.window.showErrorMessage("Failed to update server timeout")
12711333
}
12721334
}
12731335
break
@@ -1620,7 +1682,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
16201682
async refreshGlamaModels() {
16211683
const glamaModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.glamaModels)
16221684

1623-
let models: Record<string, ModelInfo> = {}
1685+
const models: Record<string, ModelInfo> = {}
16241686
try {
16251687
const response = await axios.get("https://glama.ai/api/gateway/v1/models")
16261688
/*
@@ -1710,7 +1772,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
17101772
GlobalFileNames.openRouterModels,
17111773
)
17121774

1713-
let models: Record<string, ModelInfo> = {}
1775+
const models: Record<string, ModelInfo> = {}
17141776
try {
17151777
const response = await axios.get("https://openrouter.ai/api/v1/models")
17161778
/*

0 commit comments

Comments
 (0)