Deepsake  - An AI Extension

Deepsake - An AI Extension

Powered by deepseek AI

·

7 min read

If you're in the tech world, you've probably heard about the Deepsake model. If not, you might be missing out! 😅

The Deepsake R1 model is open-source, which means we can run it locally and avoid sending sensitive data to the parent company. But why stop there? I've gone a step further and created a VS Code Extension to make interacting with the model even easier. Let’s dive into the details!

Introducing Deepsake: A VS Code Extension for Seamless Communication with the DeepSeek Model

(P.S.: I know, right?! The name might need some work, but we can work on that later!)

Pre-requisite Tools

Before we get started, ensure you have the following installed:

VS Code: The popular code editor.

Ollama: The API used to interact with the AI model.

Setting Up the VS Code Extension

To start building the extension, follow these steps:

1. Generate the Extension Boilerplate

To create a new VS Code extension, run the following command:

npx --package yo --package generator-code -- yo code

This will initialize the extension template for you. Just fill in the default values when prompted.

2. Update tsconfig.json

Now, replace the default tsconfig.json with the following configuration:

{
    "compilerOptions": {
        "module": "Node16",
        "target": "ES2022",
        "outDir": "out",
        "lib": [
            "DOM",
            "ES2022"
        ],
        "sourceMap": true,
        "rootDir": "src",
        "strict": true
    }
}

This will set up the TypeScript configuration for your extension.

3. Modify src/extension.ts

Now, let’s add the core functionality of the extension to the src/extension.ts file. Here’s the code that integrates with Ollama and the Deepseek model:

import ollama from 'ollama';
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
    console.log('Congratulations, your extension "deepsake" is now active!');

    const disposable = vscode.commands.registerCommand('deepsake.start', () => {
        const panel = vscode.window.createWebviewPanel(
            'deepsake',
            'Talk to DeepSeek',
            vscode.ViewColumn.One,
            {
                enableScripts: true,
                retainContextWhenHidden: true,
            }
        );

        panel.webview.html = getWebviewContent();

        let isStreaming = false;

        panel.webview.onDidReceiveMessage(
            async (message: any) => {
                switch (message.command) {
                    case 'chat':
                        if (isStreaming) {
                            panel.webview.postMessage({
                                command: 'receiveMessage',
                                text: 'Please wait for the current response to finish.',
                            });
                            return;
                        }

                        const prompt = message.text.trim();
                        if (!prompt) {
                            panel.webview.postMessage({
                                command: 'receiveMessage',
                                text: 'Please enter a message before sending.',
                            });
                            return;
                        }

                        isStreaming = true;

                        // Show typing indicator
                        panel.webview.postMessage({
                            command: 'typing',
                            isTyping: true,
                        });

                        try {
                            const deepResponse = await ollama.chat({
                                model: message.model || 'deepseek-r1:14b',
                                messages: [{ role: 'user', content: prompt }],
                                stream: true,
                            });

                            let messageBuffer = ''; // Buffer to collect all chunks
                            for await (const part of deepResponse) {
                                messageBuffer += part.message.content; // Collecting the parts
                            }

                            // Once all chunks are received, post the full message to the chat
                            panel.webview.postMessage({
                                command: 'receiveMessage',
                                text: messageBuffer,
                            });
                        } catch (error) {
                            console.error('Error communicating with Ollama:', error);
                            panel.webview.postMessage({
                                command: 'receiveMessage',
                                text: 'Error: Failed to communicate with Ollama.',
                            });
                        } finally {
                            isStreaming = false;
                            panel.webview.postMessage({
                                command: 'typing',
                                isTyping: false,
                            });
                        }
                        break;

                    case 'clearChat':
                        panel.webview.postMessage({
                            command: 'clearChat',
                        });
                        break;

                    case 'stopGeneration':
                        isStreaming = false;
                        panel.webview.postMessage({
                            command: 'receiveMessage',
                            text: 'Response generation stopped.',
                        });
                        break;
                }
            },
            undefined,
            context.subscriptions
        );
    });

    context.subscriptions.push(disposable);
}

export function deactivate() {}

function getWebviewContent(): string {
    return `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Talk to DeepSeek</title>
            <style>
                body {
                    font-family: Arial, sans-serif;
                    margin: 0;
                    padding: 0;
                    display: flex;
                    flex-direction: column;
                    height: 100vh;
                    background-color: var(--vscode-editor-background);
                    color: var(--vscode-editor-foreground);
                }
                header {
                    background-color: var(--vscode-titleBar-activeBackground);
                    color: var(--vscode-titleBar-activeForeground);
                    padding: 10px;
                    text-align: center;
                    font-size: 1.2em;
                    font-weight: bold;
                }
                #chat {
                    flex: 1;
                    padding: 10px;
                    overflow-y: auto;
                    border-bottom: 1px solid var(--vscode-editorWidget-border);
                }
                .message-container {
                    display: flex;
                    flex-direction: column;
                    margin-bottom: 10px;
                }
                .message {
                    padding: 8px 12px;
                    border-radius: 15px;
                    max-width: 80%;
                    position: relative;
                    margin-bottom: 4px;
                    font-size: 1em;
                    word-break: break-word;
                }
                .user-message {
                    background-color: #d0f0c0;
                    color: #000;
                    align-self: flex-end;
                }
                .bot-message {
                    background-color: #f0f0f0;
                    color: #000;
                    align-self: flex-start;
                }
                .timestamp {
                    font-size: 0.8em;
                    color: var(--vscode-descriptionForeground);
                    margin-top: 4px;
                }
                .copy-button {
                    position: absolute;
                    top: 5px;
                    right: 5px;
                    background: none;
                    border: none;
                    cursor: pointer;
                    color: var(--vscode-descriptionForeground);
                }
                .copy-button:hover {
                    color: var(--vscode-button-foreground);
                }
                #inputArea {
                    display: flex;
                    padding: 10px;
                    background-color: var(--vscode-editorWidget-background);
                    border-top: 1px solid var(--vscode-editorWidget-border);
                }
                #messageInput {
                    flex: 1;
                    padding: 8px;
                    border: 1px solid var(--vscode-input-border);
                    border-radius: 4px;
                    margin-right: 8px;
                    background-color: var(--vscode-input-background);
                    color: var(--vscode-input-foreground);
                }
                #sendButton {
                    padding: 8px 16px;
                    background-color: var(--vscode-button-background);
                    color: var(--vscode-button-foreground);
                    border: none;
                    border-radius: 4px;
                    cursor: pointer;
                }
                #sendButton:hover {
                    background-color: var(--vscode-button-hoverBackground);
                }
                #clearButton {
                    padding: 8px 16px;
                    background-color: var(--vscode-button-background);
                    color: var(--vscode-button-foreground);
                    border: none;
                    border-radius: 4px;
                    margin-top: 10px;
                    cursor: pointer;
                }
                #clearButton:hover {
                    background-color: var(--vscode-button-hoverBackground);
                }
                #stopButton {
                    padding: 8px 16px;
                    background-color: #ff4444;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    margin-top: 10px;
                    cursor: pointer;
                }
                #stopButton:hover {
                    background-color: #cc0000;
                }
                .typing-indicator {
                    display: none;
                    font-style: italic;
                    color: var(--vscode-descriptionForeground);
                    padding: 10px;
                }
                .confirmation {
                    font-size: 0.9em;
                    color: green;
                    padding: 5px;
                }
            </style>
        </head>
        <body>
            <header>Talk to DeepSeek</header>
            <div id="chat"></div>
            <div class="typing-indicator" id="typingIndicator">DeepSeek is typing...</div>
            <div id="inputArea">
                <input id="messageInput" type="text" placeholder="Type your message here..." />
                <button id="sendButton">Send</button>
            </div>
            <button id="clearButton">Clear Chat</button>
            <button id="stopButton">Stop Generation</button>

            <script>
                const vscode = acquireVsCodeApi();
                const chatDiv = document.getElementById('chat');
                const messageInput = document.getElementById('messageInput');
                const sendButton = document.getElementById('sendButton');
                const clearButton = document.getElementById('clearButton');
                const stopButton = document.getElementById('stopButton');
                const typingIndicator = document.getElementById('typingIndicator');

                // Send message on button click
                sendButton.addEventListener('click', sendMessage);

                // Send message on Enter key
                messageInput.addEventListener('keypress', (event) => {
                    if (event.key === 'Enter') {
                        sendMessage();
                    }
                });

                function sendMessage() {
                    const message = messageInput.value.trim();
                    if (!message) {
                        appendMessage('bot', 'Please enter a message before sending.');
                        return;
                    }

                    appendMessage('user', message);
                    vscode.postMessage({
                        command: 'chat',
                        text: message,
                        model: 'deepseek-r1:14b', // Default model
                    });
                    messageInput.value = '';
                }

                function appendMessage(sender, text) {
                    const messageContainer = document.createElement('div');
                    messageContainer.classList.add('message-container');

                    const messageElement = document.createElement('div');
                    messageElement.classList.add('message', sender + '-message');
                    messageElement.textContent = text;

                    const timestamp = document.createElement('div');
                    timestamp.classList.add('timestamp');
                    timestamp.textContent = new Date().toLocaleTimeString();

                    const copyButton = document.createElement('button');
                    copyButton.classList.add('copy-button');
                    copyButton.textContent = '📋';
                    copyButton.addEventListener('click', () => {
                        navigator.clipboard.writeText(text);
                        showConfirmation('Copied to clipboard!');
                    });

                    messageElement.appendChild(timestamp);
                    messageElement.appendChild(copyButton);
                    messageContainer.appendChild(messageElement);
                    chatDiv.appendChild(messageContainer);
                    chatDiv.scrollTop = chatDiv.scrollHeight; // Auto-scroll to the latest message
                }

                // Handle streaming response chunks
                window.addEventListener('message', event => {
                    const message = event.data;
                    if (message.command === 'receiveMessageChunk') {
                        appendMessage('bot', message.text);
                    } else if (message.command === 'receiveMessage') {
                        appendMessage('bot', message.text);
                    } else if (message.command === 'typing') {
                        typingIndicator.style.display = message.isTyping ? 'block' : 'none';
                    } else if (message.command === 'clearChat') {
                        chatDiv.innerHTML = '';
                        showConfirmation('Chat cleared!');
                    }
                });

                // Clear chat
                clearButton.addEventListener('click', () => {
                    vscode.postMessage({
                        command: 'clearChat',
                    });
                });
x
                // Stop generation
                stopButton.addEventListener('click', () => {
                    vscode.postMessage({
                        command: 'stopGeneration',
                    });
                });

                // Show confirmation message
                function showConfirmation(text) {
                    const confirmation = document.createElement('div');
                    confirmation.classList.add('confirmation');
                    confirmation.textContent = text;
                    document.body.appendChild(confirmation);
                    setTimeout(() => confirmation.remove(), 2000);
                }
            </script>
        </body>
        </html>
    `;
}

Installing and Setting Up Ollama

Before you can run the extension, you’ll need to install Ollama. Here’s how:

1. Download Ollama: Download and install Ollama for your PC from here.

2. Install the npm package:

npm install ollama

Once this is done, you’re ready to interact with the model.

  1. After copying the required model. Run the command while ollama is running in the background

How to Run the Extension

Now that you’ve set up everything, follow these steps to run the extension locally:

1. Open VS Code and Your Extension Project

• Open the directory where your extension’s code is stored in VS Code.

• Make sure you have tsconfig.json and extension.ts properly configured as mentioned earlier.

2. Install Dependencies

• Run the following command to install all necessary dependencies:

npm install

3. Run the Extension Locally

• Press F5 or click on the Run icon in the Activity Bar or type in and choose Debug : Select and Start Debugging

• This will open a new VS Code window (Extension Development Host) where you can try out your extension.

• In the new window, press Ctrl+Shift+P and run the >Deepsake: Start command.

4. Interact with the Model

• Type your message and send it to the model. You’ll see the bot’s response directly in the webview.

• You can clear the chat or stop generation by using the provided buttons.

Conclusion

With just a few simple steps, you can set up a local version of the Deepsake VS Code extension and chat directly with the DeepSeek model. This approach not only enhances your productivity but also ensures your data remains private and secure.

Get started now and enjoy seamless interactions with your AI assistant right inside VS Code!. Also this is an open source project to feel free to contribute here → Link