mirror of
https://github.com/ggml-org/llama.vscode.git
synced 2026-05-07 01:15:23 +00:00
Compare commits
5 commits
copilot/ad
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79b90ef999 |
||
|
|
a544d93511 |
||
|
|
a73d9498ab |
||
|
|
29f6c9973b |
||
|
|
f98919badf |
4 changed files with 255 additions and 25 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"name": "llama-vscode",
|
||||
"displayName": "llama-vscode",
|
||||
"description": "Local LLM-assisted text completion using llama.cpp",
|
||||
"version": "0.0.45",
|
||||
"version": "0.0.47",
|
||||
"publisher": "ggml-org",
|
||||
"repository": "https://github.com/ggml-org/llama.vscode",
|
||||
"engines": {
|
||||
|
|
|
|||
31
src/lists.ts
31
src/lists.ts
|
|
@ -79,7 +79,36 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"endpoint": "http://127.0.0.1:8010"
|
||||
}
|
||||
]],
|
||||
[PREDEFINED_LISTS_KEYS.TOOLS, [
|
||||
[PREDEFINED_LISTS_KEYS.TOOLS,
|
||||
[
|
||||
{
|
||||
"name": "Qwen3.5-2B-GGUF:Q8_0 (LOCAL) (CPU)",
|
||||
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
|
||||
"endpoint": "http://localhost:8009",
|
||||
"aiModel": "",
|
||||
"isKeyRequired": false
|
||||
},
|
||||
{
|
||||
"name": "Qwen3.5-2B-GGUF:Q8_0 (LOCAL) (VRAM>3GB)",
|
||||
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q8_0 --jinja -ngl 99 -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
|
||||
"endpoint": "http://localhost:8009",
|
||||
"aiModel": "",
|
||||
"isKeyRequired": false
|
||||
},
|
||||
{
|
||||
"name": "Qwen3.5-4B-GGUF:Q8_0 (LOCAL) (VRAM>6GB)",
|
||||
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-4B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
|
||||
"endpoint": "http://localhost:8009",
|
||||
"aiModel": "",
|
||||
"isKeyRequired": false
|
||||
},
|
||||
{
|
||||
"name": "Qwen3.5-9B-GGUF:Q8_0 (LOCAL) (VRAM>12GB)",
|
||||
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-9B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
|
||||
"endpoint": "http://localhost:8009",
|
||||
"aiModel": "",
|
||||
"isKeyRequired": false
|
||||
},
|
||||
{
|
||||
"name": "OpenAI gpt-oss 20B (LOCAL) (> 19GB VRAM)",
|
||||
"localStartCommand": "llama-server -hf ggml-org/gpt-oss-20b-GGUF -c 0 --jinja --reasoning-format none -np 2 --port 8009",
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ export class OpenAiCompModelStrategy implements IAddStrategy {
|
|||
prompt: 'example: http://localhost:8080 or https://openrauter.ai/api'
|
||||
})??""
|
||||
isKeyRequired = await Utils.confirmAction(`Is API key required for this endpoint (${endpoint})?`, "");
|
||||
}
|
||||
}
|
||||
if (!endpoint){
|
||||
vscode.window.showWarningMessage("Endpoint is not provided!")
|
||||
return;
|
||||
}
|
||||
const providerModels: QuickPickItem[] = [];
|
||||
const models = await this.getModels(endpoint);
|
||||
const models = await this.getModels(endpoint, isKeyRequired);
|
||||
if (models.length == 0) {
|
||||
vscode.window.showInformationMessage("No models are found.")
|
||||
return
|
||||
|
|
@ -108,30 +108,50 @@ export class OpenAiCompModelStrategy implements IAddStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
private async getModels(endpoint: string): Promise<OpenAiCompModel[]> {
|
||||
let hfEndpoint = Utils.trimTrailingSlash(endpoint) +"/v1/models";
|
||||
private async getModels(endpoint: string, isKeyRequired: boolean): Promise<OpenAiCompModel[]> {
|
||||
const hfEndpoint = Utils.trimTrailingSlash(endpoint) + "/v1/models";
|
||||
|
||||
// Create a request configuration
|
||||
let requestConfig: any = {};
|
||||
|
||||
if (isKeyRequired) {
|
||||
// We get the saved key for this specific endpoint
|
||||
const apiKey = this.app.persistence.getApiKey(endpoint);
|
||||
if (apiKey) {
|
||||
requestConfig = {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let result = await axios.default.get(
|
||||
`${Utils.trimTrailingSlash(hfEndpoint)}`
|
||||
const result = await axios.default.get(
|
||||
`${Utils.trimTrailingSlash(hfEndpoint)}`,
|
||||
requestConfig
|
||||
);
|
||||
|
||||
let models: OpenAiCompModel[] = [];
|
||||
|
||||
let modelsList: OpenAiCompModel[] = []
|
||||
if (result && result.data && result.data.models) modelsList = result.data.models
|
||||
else if (result && result.data && result.data.data) modelsList = result.data.data
|
||||
if (modelsList.length > 0){
|
||||
for(let mdl of modelsList){
|
||||
models.push(mdl)
|
||||
let modelsList: OpenAiCompModel[] = [];
|
||||
|
||||
if (result && result.data && result.data.models) modelsList = result.data.models;
|
||||
else if (result && result.data && result.data.data) modelsList = result.data.data;
|
||||
|
||||
if (modelsList.length > 0) {
|
||||
for (let mdl of modelsList) {
|
||||
models.push(mdl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return models;
|
||||
} catch (error){
|
||||
vscode.window.showErrorMessage("Error getting provider models): " + error)
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage("Error getting provider models: " + error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sanitizeInput(input: string): string {
|
||||
return input ? input.trim() : '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,192 @@ export class TextEditor {
|
|||
vscode.commands.executeCommand('setContext', 'textEditSuggestionVisible', visible);
|
||||
}
|
||||
|
||||
private escapeWebviewAttr(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiline instructions (webview); resolves undefined if cancelled or closed.
|
||||
*/
|
||||
private showMultilineEditPrompt(): Promise<string | undefined> {
|
||||
const title =
|
||||
this.app.configuration.getUiText('How would you like to modify the selected text?') ??
|
||||
'How would you like to modify the selected text?';
|
||||
const placeholder =
|
||||
this.app.configuration.getUiText('Enter your instructions for editing the text...') ??
|
||||
'Enter your instructions for editing the text...';
|
||||
const submitLabel = this.app.configuration.getUiText('Submit') ?? 'Submit';
|
||||
const cancelLabel = this.app.configuration.getUiText('Cancel') ?? 'Cancel';
|
||||
const emptyHint =
|
||||
this.app.configuration.getUiText('Please enter editing instructions.') ??
|
||||
'Please enter editing instructions.';
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
'editWithAiMultilinePrompt',
|
||||
title,
|
||||
{ viewColumn: vscode.ViewColumn.Beside, preserveFocus: false },
|
||||
{ enableScripts: true }
|
||||
);
|
||||
|
||||
const finish = (value: string | undefined) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
resolve(value);
|
||||
panel.dispose();
|
||||
};
|
||||
|
||||
const cspSource = panel.webview.cspSource;
|
||||
panel.webview.html = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${cspSource} 'unsafe-inline'; script-src 'unsafe-inline' ${cspSource};">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
color: var(--vscode-foreground);
|
||||
background-color: var(--vscode-editor-background);
|
||||
}
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
textarea {
|
||||
flex: 1;
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: var(--vscode-editor-font-size);
|
||||
}
|
||||
textarea:focus {
|
||||
outline: 1px solid var(--vscode-focusBorder);
|
||||
}
|
||||
.actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
/* DOM order is Submit then Cancel (Tab: textarea → Submit → Cancel); flex order keeps Cancel left, Submit right. */
|
||||
.actions .secondary {
|
||||
order: 1;
|
||||
}
|
||||
.actions .primary {
|
||||
order: 2;
|
||||
}
|
||||
button {
|
||||
padding: 6px 14px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--vscode-font-size);
|
||||
}
|
||||
.primary {
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
}
|
||||
.primary:hover {
|
||||
background: var(--vscode-button-hoverBackground);
|
||||
}
|
||||
.secondary {
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
}
|
||||
.secondary:hover {
|
||||
background: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<label for="prompt">${this.escapeWebviewAttr(title)}</label>
|
||||
<textarea id="prompt" placeholder="${this.escapeWebviewAttr(placeholder)}" autofocus></textarea>
|
||||
<div class="actions">
|
||||
<button type="button" class="primary" id="submit">${this.escapeWebviewAttr(submitLabel)}</button>
|
||||
<button type="button" class="secondary" id="cancel">${this.escapeWebviewAttr(cancelLabel)}</button>
|
||||
</div>
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
const ta = document.getElementById('prompt');
|
||||
function focusPrompt() {
|
||||
if (!ta) {
|
||||
return;
|
||||
}
|
||||
ta.focus();
|
||||
const len = ta.value.length;
|
||||
ta.setSelectionRange(len, len);
|
||||
}
|
||||
window.addEventListener('load', focusPrompt);
|
||||
requestAnimationFrame(focusPrompt);
|
||||
setTimeout(focusPrompt, 0);
|
||||
setTimeout(focusPrompt, 100);
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (data && data.command === 'focusPrompt') {
|
||||
focusPrompt();
|
||||
}
|
||||
});
|
||||
document.getElementById('submit').addEventListener('click', () => {
|
||||
vscode.postMessage({ command: 'submit', text: ta.value });
|
||||
});
|
||||
document.getElementById('cancel').addEventListener('click', () => {
|
||||
vscode.postMessage({ command: 'cancel' });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const requestPromptFocus = () => {
|
||||
void panel.webview.postMessage({ command: 'focusPrompt' });
|
||||
};
|
||||
panel.onDidChangeViewState((e) => {
|
||||
if (e.webviewPanel.visible) {
|
||||
requestPromptFocus();
|
||||
}
|
||||
});
|
||||
requestPromptFocus();
|
||||
setTimeout(requestPromptFocus, 50);
|
||||
setTimeout(requestPromptFocus, 200);
|
||||
|
||||
panel.webview.onDidReceiveMessage((message) => {
|
||||
if (message.command === 'submit') {
|
||||
const text = typeof message.text === 'string' ? message.text : '';
|
||||
if (!text.trim()) {
|
||||
void vscode.window.showInformationMessage(emptyHint);
|
||||
return;
|
||||
}
|
||||
finish(text);
|
||||
} else if (message.command === 'cancel') {
|
||||
finish(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
panel.onDidDispose(() => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async showEditPrompt(editor: vscode.TextEditor) {
|
||||
let chatUrl = this.app.configuration.endpoint_chat
|
||||
if (!chatUrl) chatUrl = this.app.configuration.endpoint_tools;
|
||||
|
|
@ -64,12 +250,7 @@ export class TextEditor {
|
|||
const contextRange = new vscode.Range(startLine, 0, endLine, editor.document.lineAt(endLine).text.length);
|
||||
const context = editor.document.getText(contextRange);
|
||||
|
||||
// Create and show input box
|
||||
const prompt = await vscode.window.showInputBox({
|
||||
placeHolder: 'Enter your instructions for editing the text...',
|
||||
prompt: 'How would you like to modify the selected text?',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
const prompt = await this.showMultilineEditPrompt();
|
||||
|
||||
if (!prompt) {
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue