Merge pull request #1 from covespace/chat_ui

Implement chat UI in VSCode extension with improved styling
This commit is contained in:
boob.yang 2023-04-21 18:01:54 +08:00 committed by GitHub
commit 7d7cf673e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 3078 additions and 195 deletions

View File

@ -1,14 +1,71 @@
# Visual Studio Code Extension for DevChat
# devchat README
See https://github.com/covespace/devchat.
This is the README for your extension "devchat". After writing up a brief description, we recommend including the following sections.
## Features
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
For example if there is an image subfolder under your extension project workspace:
\!\[feature X\]\(images/feature-x.png\)
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
## Requirements
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
## Extension Settings
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
For example:
This extension contributes the following settings:
* `myExtension.enable`: Enable/disable this extension.
* `myExtension.thing`: Set to `blah` to do something.
## Known Issues
Calling out known issues can help limit users opening duplicate issues against your extension.
## Release Notes
Users appreciate release notes as you update your extension.
### 1.0.0
Initial release of ...
### 1.0.1
Fixed issue #.
### 1.1.0
Added features X, Y, and Z.
---
## Following extension guidelines
Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
## Working with Markdown
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
## For more information
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
**Enjoy!**

BIN
media/bot-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

90
media/chatPanel.css Normal file
View File

@ -0,0 +1,90 @@
:root {
--vscode-background: var(--vscode-editor-background);
--vscode-foreground: var(--vscode-editor-foreground);
--vscode-border: var(--vscode-editorWidget-border);
--vscode-button-bg: var(--vscode-button-background);
--vscode-button-fg: var(--vscode-button-foreground);
--vscode-button-hover-bg: var(--vscode-button-hoverBackground);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--vscode-background);
color: var(--vscode-foreground);
margin: 0;
padding: 0;
}
#chat-container {
display: flex;
flex-direction: column;
height: 100vh;
}
#messages-container {
flex-grow: 1;
overflow-y: auto;
padding: 10px;
}
.message {
display: flex;
align-items: center;
margin: 20px 0;
}
.sender-icon {
width: 24px;
height: 24px;
margin-right: 40px;
}
.message-text {
flex-grow: 1;
padding: 5px 10px; /* Add some padding to the message text */
border-radius: 4px;
}
.user-message .message-text {
background-color: #007acc; /* Different background color for user messages */
color: white;
}
.bot-message .message-text {
background-color: #f1f1f1; /* Different background color for bot messages */
color: black;
}
.action-icon {
width: 24px;
height: 24px;
margin-left: 10px;
}
#input-container {
display: flex;
padding: 10px;
border-top: 1px solid #ccc;
}
#message-input {
flex-grow: 1;
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px;
outline: none;
}
#send-button {
margin-left: 10px;
background-color: var(--vscode-button-bg);
color: var(--vscode-button-fg);
border: 1px solid var(--vscode-border);
border-radius: 4px;
padding: 5px 10px;
cursor: pointer;
}
#send-button:hover {
background-color: var(--vscode-button-hover-bg);
}

20
media/chatPanel.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... meta tags, title, etc. ... -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/all.min.css" />
<link rel="stylesheet" href="<vscode-resource:/chatPanel.css>" />
</head>
<body>
<div id="chat-container">
<div id="messages-container">
<!-- Chat messages will be added here dynamically -->
</div>
<div id="input-container">
<input id="message-input" type="text" placeholder="Type your message..." />
<button id="send-button">Send</button>
</div>
</div>
<script src="<vscode-resource:/chatPanel.js>"></script>
</body>
</html>

57
media/chatPanel.js Normal file
View File

@ -0,0 +1,57 @@
(function () {
const vscode = acquireVsCodeApi();
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
const messagesContainer = document.getElementById('messages-container');
function addMessageToUI(message, senderIconClass, actionIconClass, messageType) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', messageType);
const senderIcon = document.createElement('i');
senderIcon.className = senderIconClass;
const messageText = document.createElement('span');
messageText.classList.add('message-text');
messageText.textContent = message;
const actionIcon = document.createElement('i');
actionIcon.className = actionIconClass;
messageElement.appendChild(senderIcon);
messageElement.appendChild(messageText);
messageElement.appendChild(actionIcon);
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function processMessage(message) {
// Process the message and get the bot's response
// For an echo bot, return the same message
return message;
}
sendButton.addEventListener('click', () => {
const message = messageInput.value;
if (message) {
const userIconClass = 'fas fa-user-circle';
const botIconClass = 'fas fa-robot';
const actionIconClass = 'fas fa-check-circle';
addMessageToUI(message, userIconClass, actionIconClass, 'user-message');
messageInput.value = '';
const botResponse = processMessage(message);
addMessageToUI(botResponse, botIconClass, actionIconClass, 'bot-message');
}
});
messageInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
sendButton.click();
}
});
})();

BIN
media/check-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
media/user-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

2826
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,8 @@
{
"name": "devchat",
"displayName": "DevChat",
"description": "Write prompts to create code!",
"version": "0.0.2",
"publisher": "merico",
"displayName": "devchat",
"description": "devchat",
"version": "0.0.1",
"engines": {
"vscode": "^1.77.0"
},
@ -13,10 +12,18 @@
"activationEvents": [],
"main": "./dist/extension.js",
"contributes": {
"views": {
"explorer": [
{
"id": "chatPanel",
"name": "Chat with Bot"
}
]
},
"commands": [
{
"command": "devchat.prompt",
"title": "DevChat: Prompt"
"command": "devchat.openChatPanel",
"title": "Open Chat Panel"
}
]
},
@ -32,26 +39,22 @@
"test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/vscode": "^1.77.0",
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "16.x",
"@types/vscode": "^1.77.0",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"@vscode/test-electron": "^2.3.0",
"eslint": "^8.36.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"typescript": "^4.9.5",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1",
"@vscode/test-electron": "^2.3.0"
"webpack-cli": "^5.0.1"
},
"repository": {
"type": "git",
"url": "https://github.com/covespace/devchat-vscode.git"
},
"bugs": {
"url": "https://github.com/covespace/devchat-vscode/issues"
"dependencies": {
"nonce": "^1.0.4"
}
}

70
src/chatPanel.ts Normal file
View File

@ -0,0 +1,70 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
export default class ChatPanel {
public static currentPanel: ChatPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
// If the panel already exists, show it in the target column
if (ChatPanel.currentPanel) {
ChatPanel.currentPanel._panel.reveal(column);
return;
}
// Create a new panel
const panel = vscode.window.createWebviewPanel(
'chatPanel',
'Chat with Bot',
column || vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true
}
);
// Create a new ChatPanel instance and set it as the current panel
ChatPanel.currentPanel = new ChatPanel(panel, extensionUri);
}
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
// ... initialize the chat panel ...
this._panel = panel;
// Set the webview options
this._panel.webview.options = {
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')]
};
// Set the webview content
this._panel.webview.html = this._getHtmlContent(extensionUri);
// Handle webview events and dispose of the panel when closed
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
}
private _getHtmlContent(extensionUri: vscode.Uri): string {
const htmlPath = vscode.Uri.joinPath(extensionUri, 'media', 'chatPanel.html');
const htmlContent = fs.readFileSync(htmlPath.fsPath, 'utf8');
// Replace the resource placeholder with the correct resource URI
return htmlContent.replace(/<vscode-resource:(\/.+?)>/g, (_, resourcePath) => {
const resourceUri = vscode.Uri.joinPath(extensionUri, 'media', resourcePath);
return this._panel.webview.asWebviewUri(resourceUri).toString();
});
}
public dispose() {
// ... dispose the panel and clean up resources ...
}
// ... other helper methods ...
}

View File

@ -1,25 +1,18 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
const vscode = require('vscode');
const ChatPanel = require('./chatPanel').default;
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
function activate(context: { extensionUri: any; subscriptions: any[]; }) {
let disposable = vscode.commands.registerCommand('devchat.openChatPanel', function () {
ChatPanel.createOrShow(context.extensionUri);
});
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "devchat" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('devchat.prompt', () => {
// Display a message box to the user
vscode.window.showInformationMessage('DevChat is under development!');
});
context.subscriptions.push(disposable);
context.subscriptions.push(disposable);
}
exports.activate = activate;
// This method is called when your extension is deactivated
export function deactivate() {}
function processMessage(message: any) {
// For an echo bot, return the same message
return message;
}

22
src/test/runTest.js Normal file
View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const test_electron_1 = require("@vscode/test-electron");
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './suite/index');
// Download VS Code, unzip it and run the integration test
await (0, test_electron_1.runTests)({ extensionDevelopmentPath, extensionTestsPath });
}
catch (err) {
console.error('Failed to run tests', err);
process.exit(1);
}
}
main();
//# sourceMappingURL=runTest.js.map

1
src/test/runTest.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"runTest.js","sourceRoot":"","sources":["runTest.ts"],"names":[],"mappings":";;AAAA,6BAA6B;AAE7B,yDAAiD;AAEjD,KAAK,UAAU,IAAI;IAClB,IAAI;QACH,4DAA4D;QAC5D,yCAAyC;QACzC,MAAM,wBAAwB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,0BAA0B;QAC1B,iCAAiC;QACjC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAEpE,0DAA0D;QAC1D,MAAM,IAAA,wBAAQ,EAAC,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,CAAC,CAAC;KACjE;IAAC,OAAO,GAAG,EAAE;QACb,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KAChB;AACF,CAAC;AAED,IAAI,EAAE,CAAC"}

View File

@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
const vscode = require("vscode");
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});
//# sourceMappingURL=extension.test.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extension.test.js","sourceRoot":"","sources":["extension.test.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AAEjC,0DAA0D;AAC1D,8CAA8C;AAC9C,iCAAiC;AACjC,kDAAkD;AAElD,KAAK,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAClC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,kBAAkB,CAAC,CAAC;IAEzD,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}

40
src/test/suite/index.js Normal file
View File

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const path = require("path");
const Mocha = require("mocha");
const glob = require("glob");
function run() {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
}
else {
c();
}
});
}
catch (err) {
console.error(err);
e(err);
}
});
});
}
exports.run = run;
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAAA,6BAA6B;AAC7B,+BAA+B;AAC/B,6BAA6B;AAE7B,SAAgB,GAAG;IAClB,wBAAwB;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACvB,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI;KACX,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAEhD,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC3B,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACxD,IAAI,GAAG,EAAE;gBACR,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;aACd;YAED,8BAA8B;YAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAE9D,IAAI;gBACH,qBAAqB;gBACrB,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;oBACpB,IAAI,QAAQ,GAAG,CAAC,EAAE;wBACjB,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,QAAQ,gBAAgB,CAAC,CAAC,CAAC;qBAC1C;yBAAM;wBACN,CAAC,EAAE,CAAC;qBACJ;gBACF,CAAC,CAAC,CAAC;aACH;YAAC,OAAO,GAAG,EAAE;gBACb,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC,CAAC,GAAG,CAAC,CAAC;aACP;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAjCD,kBAiCC"}

View File

@ -5,6 +5,7 @@
"lib": [
"ES2020"
],
"outDir": "./dist",
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */