Skip to content

Commit e5ae5e8

Browse files
committed
feat(vscode): browser-local llm chat with self-hosted model files
1 parent 7bc7719 commit e5ae5e8

13 files changed

Lines changed: 809 additions & 27 deletions

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ dist
22
build
33
.react-router
44
packages/ide/vscode-web/
5+
packages/ide/models/

packages/ide/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vscode-web/
22
dist/
33
node_modules/
4+
models/

packages/ide/src/build-static.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ writeFileSync(
279279
{ key: "Access-Control-Allow-Private-Network", value: "true" },
280280
{ key: "Cross-Origin-Resource-Policy", value: "cross-origin" },
281281
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
282-
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
282+
{ key: "Cross-Origin-Embedder-Policy", value: "credentialless" },
283283
],
284284
},
285285
],

packages/ide/src/server.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ app.use((req, res, next) => {
155155
if (coi === "1") {
156156
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
157157
} else if (coi === "2") {
158-
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
158+
res.setHeader("Cross-Origin-Embedder-Policy", "credentialless");
159159
} else if (coi === "3" || coi === "") {
160160
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
161-
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
161+
res.setHeader("Cross-Origin-Embedder-Policy", "credentialless");
162162
}
163163
next();
164164
});
@@ -174,6 +174,19 @@ app.use("/static/devextensions", express.static(MODELSCRIPT_EXT_DIR, { dotfiles:
174174
// GitHub FS extension
175175
app.use("/static/extensions/github-fs", express.static(GITHUB_FS_EXT_DIR, { dotfiles: "allow" }));
176176

177+
// WebLLM model files (self-hosted to avoid COEP issues with external CDNs)
178+
// WebLLM constructs URLs as ${model}/resolve/main/${file} (HuggingFace pattern).
179+
// Strip /resolve/main/ so local files resolve correctly.
180+
const MODELS_DIR = resolve(__dirname, "..", "models");
181+
app.use(
182+
"/api/models",
183+
(req, _res, next) => {
184+
req.url = req.url.replace(/\/resolve\/main\//g, "/");
185+
next();
186+
},
187+
express.static(MODELS_DIR),
188+
);
189+
177190
// Favicon
178191
const morselFavicon = resolve(__dirname, "..", "..", "morsel", "public", "favicon.ico");
179192
app.get("/favicon.ico", (req, res) => {

packages/vscode/package.json

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,6 @@
3838
"watch": "webpack --watch"
3939
},
4040
"contributes": {
41-
"chatParticipants": [
42-
{
43-
"id": "modelscript.chat",
44-
"fullName": "ModelScript AI",
45-
"name": "modelscript",
46-
"description": "Modelica language assistant — ask about models, MSL components, simulation, and diagnostics",
47-
"isSticky": true
48-
}
49-
],
5041
"commands": [
5142
{
5243
"command": "modelscript.openDiagram",
@@ -76,6 +67,11 @@
7667
"command": "modelscript.autoLayout",
7768
"title": "ModelScript: Auto Layout",
7869
"icon": "$(wand)"
70+
},
71+
{
72+
"command": "modelscript.openChat",
73+
"title": "ModelScript: Open AI Chat",
74+
"icon": "$(comment-discussion)"
7975
}
8076
],
8177
"configuration": [
@@ -116,6 +112,13 @@
116112
"path": "./syntaxes/modelica.tmLanguage.json"
117113
}
118114
],
115+
"keybindings": [
116+
{
117+
"command": "modelscript.openChat",
118+
"key": "ctrl+alt+i",
119+
"mac": "cmd+alt+i"
120+
}
121+
],
119122
"languageModelTools": [
120123
{
121124
"name": "modelscript_flatten",
@@ -356,13 +359,5 @@
356359
"galleryBanner": {
357360
"color": "#0d1b2a",
358361
"theme": "dark"
359-
},
360-
"enabledApiProposals": [
361-
"chatParticipantAdditions",
362-
"chatParticipantPrivate",
363-
"chatProvider",
364-
"defaultChatParticipant",
365-
"languageModelSystem",
366-
"contribLanguageModelToolSets"
367-
]
362+
}
368363
}

packages/vscode/src/browserClientMain.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as vscode from "vscode";
22
import { Uri, commands, workspace } from "vscode";
33
import { LanguageClientOptions } from "vscode-languageclient";
44
import { LanguageClient } from "vscode-languageclient/browser";
5-
import { registerChatParticipant } from "./chatParticipant";
5+
import { ChatPanel } from "./chatPanel";
66
import { DiagramEditorProvider } from "./diagramEditorProvider";
77
import { LibraryTreeProvider } from "./libraryTreeProvider";
88
import { registerLLMProvider } from "./llmProvider";
@@ -146,10 +146,25 @@ export async function activate(context: vscode.ExtensionContext) {
146146
await client.start();
147147
console.log("ModelScript language server is ready");
148148

149-
// Register AI integration components
150-
registerLLMProvider(context);
151-
registerChatParticipant(context);
152-
registerMCPTools(context, client);
149+
// Register AI integration components (proposed APIs — may not be available in web builds)
150+
try {
151+
registerLLMProvider(context);
152+
} catch {
153+
/* proposed API not available */
154+
}
155+
// Note: registerChatParticipant requires a chatParticipants manifest entry — use the custom ChatPanel instead
156+
try {
157+
registerMCPTools(context, client);
158+
} catch {
159+
/* proposed API not available */
160+
}
161+
162+
// Custom chat panel (works without VS Code Chat API / Copilot)
163+
context.subscriptions.push(
164+
vscode.commands.registerCommand("modelscript.openChat", () => {
165+
if (client) ChatPanel.createOrShow(context.extensionUri, client);
166+
}),
167+
);
153168

154169
// Output channel for script execution
155170
const outputChannel = vscode.window.createOutputChannel("ModelScript Output");

0 commit comments

Comments
 (0)