extension.ts•12.5 kB
import * as vscode from 'vscode';
import axios from 'axios';
// MemOS API配置
const MEMOS_API_BASE = 'http://localhost:7788';
interface MemoryItem {
id: string;
content: string;
memory_type: string;
tags: string[];
metadata: any;
created_at: string;
}
interface SearchResponse {
success: boolean;
message: string;
data: {
query: string;
results: MemoryItem[];
total_count: number;
processing_time: number;
};
}
interface AddResponse {
success: boolean;
message: string;
data: {
content_preview: string;
memory_type: string;
tags: string[];
processing_time: number;
};
}
export function activate(context: vscode.ExtensionContext) {
console.log('MemOS VS Code Extension is now active!');
// 创建WebView Provider
const provider = new MemoryWebviewProvider(context.extensionUri);
// 注册WebView Provider
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('memoryView', provider)
);
// 注册命令
const searchCommand = vscode.commands.registerCommand('memos.searchMemory', async () => {
const query = await vscode.window.showInputBox({
prompt: 'Enter search query for MemOS',
placeHolder: 'e.g., Python function, error handling...'
});
if (query) {
await searchMemory(query);
}
});
const addSnippetCommand = vscode.commands.registerCommand('memos.addCodeSnippet', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No active editor found');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage('No text selected');
return;
}
await addCodeSnippet(selectedText, editor.document.languageId);
});
const showPanelCommand = vscode.commands.registerCommand('memos.showMemoryPanel', () => {
provider.show();
});
context.subscriptions.push(searchCommand, addSnippetCommand, showPanelCommand);
// 注册Hover Provider
const hoverProvider = vscode.languages.registerHoverProvider('*', {
async provideHover(document, position, token) {
const wordRange = document.getWordRangeAtPosition(position);
if (!wordRange) {
return;
}
const word = document.getText(wordRange);
try {
const response = await axios.get(`${MEMOS_API_BASE}/mem/search`, {
params: { q: `error ${word}` },
timeout: 2000
});
if (response.data.success && response.data.data.results.length > 0) {
const result = response.data.data.results[0];
const hoverText = new vscode.MarkdownString();
hoverText.appendMarkdown(`**MemOS Memory**: ${result.content.substring(0, 200)}...`);
hoverText.appendMarkdown(`\n\n*Tags: ${result.tags.join(', ')}*`);
return new vscode.Hover(hoverText);
}
} catch (error) {
// 静默失败,不显示错误
console.log('MemOS hover failed:', error);
}
return undefined;
}
});
context.subscriptions.push(hoverProvider);
}
async function searchMemory(query: string) {
try {
const response = await axios.post<SearchResponse>(`${MEMOS_API_BASE}/mem/search`, {
query: query,
memory_type: 'code_snippet_mem',
limit: 10
});
if (response.data.success) {
const results = response.data.data.results;
if (results.length === 0) {
vscode.window.showInformationMessage('No memories found for your query');
return;
}
const items = results.map(result => ({
label: result.content.substring(0, 60) + '...',
description: result.tags.join(', '),
detail: result.memory_type,
result: result
}));
const selected = await vscode.window.showQuickPick(items, {
placeHolder: 'Select a memory to view details'
});
if (selected) {
showMemoryDetails(selected.result);
}
} else {
vscode.window.showErrorMessage(`Search failed: ${response.data.message}`);
}
} catch (error) {
vscode.window.showErrorMessage(`Failed to search memories: ${error}`);
}
}
async function addCodeSnippet(code: string, language: string) {
try {
const tags = [language, 'code-snippet', 'vscode'];
const metadata = {
language: language,
source: 'vscode-extension',
timestamp: new Date().toISOString()
};
const response = await axios.post<AddResponse>(`${MEMOS_API_BASE}/mem/add`, {
text: code,
memory_type: 'code_snippet_mem',
tags: tags,
metadata: metadata
});
if (response.data.success) {
vscode.window.showInformationMessage('Code snippet added to MemOS successfully!');
} else {
vscode.window.showErrorMessage(`Failed to add code snippet: ${response.data.message}`);
}
} catch (error) {
vscode.window.showErrorMessage(`Failed to add code snippet: ${error}`);
}
}
function showMemoryDetails(memory: MemoryItem) {
const panel = vscode.window.createWebviewPanel(
'memoryDetails',
'Memory Details',
vscode.ViewColumn.One,
{
enableScripts: true
}
);
panel.webview.html = getMemoryDetailsHtml(memory);
}
function getMemoryDetailsHtml(memory: MemoryItem): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Details</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.memory-content { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 10px 0; }
.tags { margin: 10px 0; }
.tag { background: #007acc; color: white; padding: 2px 8px; border-radius: 3px; margin-right: 5px; }
.metadata { background: #f0f0f0; padding: 10px; border-radius: 5px; margin: 10px 0; }
</style>
</head>
<body>
<h2>Memory Details</h2>
<div class="memory-content">
<pre>${memory.content}</pre>
</div>
<div class="tags">
<strong>Tags:</strong>
${memory.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
<div class="metadata">
<strong>Type:</strong> ${memory.memory_type}<br>
<strong>Created:</strong> ${memory.created_at}<br>
<strong>ID:</strong> ${memory.id}
</div>
</body>
</html>
`;
}
class MemoryWebviewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'memoryView';
private _view?: vscode.WebviewView;
constructor(private readonly _extensionUri: vscode.Uri) {}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case 'search':
await this.handleSearch(data.query);
break;
}
});
}
public show() {
if (this._view) {
this._view.show?.(true);
}
}
private async handleSearch(query: string) {
try {
const response = await axios.post<SearchResponse>(`${MEMOS_API_BASE}/mem/search`, {
query: query,
limit: 5
});
if (this._view) {
this._view.webview.postMessage({
type: 'searchResults',
results: response.data.data.results
});
}
} catch (error) {
if (this._view) {
this._view.webview.postMessage({
type: 'error',
message: `Search failed: ${error}`
});
}
}
}
private _getHtmlForWebview(webview: vscode.Webview) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MemOS Search</title>
<style>
body { font-family: Arial, sans-serif; padding: 10px; }
input { width: 100%; padding: 8px; margin: 5px 0; border: 1px solid #ccc; border-radius: 3px; }
button { width: 100%; padding: 8px; background: #007acc; color: white; border: none; border-radius: 3px; cursor: pointer; }
button:hover { background: #005a9e; }
.result { border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 3px; }
.result-content { font-family: monospace; background: #f5f5f5; padding: 5px; margin: 5px 0; }
.result-tags { font-size: 0.8em; color: #666; }
</style>
</head>
<body>
<h3>MemOS Memory Search</h3>
<input type="text" id="searchInput" placeholder="Enter search query..." />
<button onclick="search()">Search</button>
<div id="results"></div>
<script>
const vscode = acquireVsCodeApi();
function search() {
const query = document.getElementById('searchInput').value;
if (query.trim()) {
vscode.postMessage({
type: 'search',
query: query
});
}
}
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
search();
}
});
window.addEventListener('message', event => {
const message = event.data;
const resultsDiv = document.getElementById('results');
switch (message.type) {
case 'searchResults':
resultsDiv.innerHTML = '';
if (message.results.length === 0) {
resultsDiv.innerHTML = '<p>No results found</p>';
} else {
message.results.forEach(result => {
const resultDiv = document.createElement('div');
resultDiv.className = 'result';
resultDiv.innerHTML = \`
<div class="result-content">\${result.content.substring(0, 100)}...</div>
<div class="result-tags">Tags: \${result.tags.join(', ')}</div>
<div class="result-tags">Type: \${result.memory_type}</div>
\`;
resultsDiv.appendChild(resultDiv);
});
}
break;
case 'error':
resultsDiv.innerHTML = \`<p style="color: red;">Error: \${message.message}</p>\`;
break;
}
});
</script>
</body>
</html>
`;
}
}
export function deactivate() {}